diff --git a/db/migrations/0095_add_object_observations.sql b/db/migrations/0095_add_object_observations.sql new file mode 100644 index 000000000..9c9cb7d01 --- /dev/null +++ b/db/migrations/0095_add_object_observations.sql @@ -0,0 +1,186 @@ +-- INT_01 durable object observations, inference-frame evidence assets, the +-- monotonic change log behind the history cursor, and transactional +-- publication staging for the event relay. Normative contract: +-- docs/integrations/OBSERVATIONS_CONTRACT.md (sections 3-5, 7, 13, 14). +-- +-- Every observation references an asset row (contract D5); observations with +-- no media reference an 'unavailable' asset. Reason values stay open text so a +-- new reason is a minor contract change, but the reason must be NULL exactly +-- when the state is pending or ready. Provisional budget bounds (section 13) +-- are enforced by the API, not by CHECKs, so tuning them needs no rebuild. + +-- migrate:up + +CREATE TABLE observation_assets ( + uuid TEXT PRIMARY KEY, + camera_uuid TEXT NOT NULL, + state TEXT NOT NULL CHECK (state IN ('pending', 'ready', 'unavailable', 'expired')), + reason TEXT CHECK (reason IS NULL OR length(reason) BETWEEN 1 AND 32), + origin TEXT CHECK (origin IS NULL OR origin IN ('inference_input', 'submitted_image')), + media_type TEXT, + path TEXT, + bytes INTEGER CHECK (bytes IS NULL OR bytes >= 0), + width INTEGER CHECK (width IS NULL OR width > 0), + height INTEGER CHECK (height IS NULL OR height > 0), + captured_at_ms INTEGER, + transform_json TEXT, + protected INTEGER NOT NULL DEFAULT 0 CHECK (protected IN (0, 1)), + expires_at_ms INTEGER, + created_at_ms INTEGER NOT NULL, + updated_at_ms INTEGER NOT NULL, + CHECK ((state IN ('pending', 'ready')) = (reason IS NULL)) +); + +-- Reconciliation scans by state; retention walks one camera's ready assets +-- oldest first. +CREATE INDEX idx_observation_assets_state_camera +ON observation_assets(state, camera_uuid, created_at_ms); + +CREATE TABLE object_observations ( + uuid TEXT PRIMARY KEY, + camera_uuid TEXT NOT NULL, + stream_name_at_capture TEXT NOT NULL, + batch_id TEXT NOT NULL, + object_index INTEGER NOT NULL CHECK (object_index BETWEEN 0 AND 19), + observed_at_ms INTEGER NOT NULL, + created_at_ms INTEGER NOT NULL, + engine_key TEXT NOT NULL, + engine_type TEXT NOT NULL CHECK (engine_type IN ('object', 'api', 'onvif', 'external')), + model TEXT, + model_version TEXT, + label TEXT NOT NULL CHECK (length(label) BETWEEN 1 AND 31), + confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0), + box_x REAL, + box_y REAL, + box_width REAL, + box_height REAL, + track_id TEXT, + track_namespace TEXT, + track_session TEXT, + recording_id INTEGER REFERENCES recordings(id) ON DELETE SET NULL, + asset_uuid TEXT REFERENCES observation_assets(uuid), + revision INTEGER NOT NULL DEFAULT 1 CHECK (revision >= 1), + UNIQUE(batch_id, object_index), + -- Normalized xywh relative to the stored evidence image; all or nothing. + -- The 1e-6 slack absorbs rounding to 6 decimals. IS NOT NULL is explicit + -- because a CHECK that evaluates to NULL passes. + CHECK ((box_x IS NULL AND box_y IS NULL AND box_width IS NULL AND box_height IS NULL) + OR (box_x IS NOT NULL AND box_y IS NOT NULL + AND box_width IS NOT NULL AND box_height IS NOT NULL + AND box_x >= 0.0 AND box_y >= 0.0 + AND box_width > 0.0 AND box_height > 0.0 + AND box_x + box_width <= 1.000001 AND box_y + box_height <= 1.000001)), + CHECK ((track_id IS NULL AND track_namespace IS NULL AND track_session IS NULL) + OR (length(track_id) BETWEEN 1 AND 64 + AND track_namespace IS NOT NULL AND track_session IS NOT NULL)) +); + +CREATE INDEX idx_object_observations_camera_time +ON object_observations(camera_uuid, observed_at_ms); + +CREATE INDEX idx_object_observations_label_time +ON object_observations(label, observed_at_ms); + +CREATE INDEX idx_object_observations_created +ON object_observations(created_at_ms); + +-- Asset transitions bump every referencing observation; refcount is derived. +CREATE INDEX idx_object_observations_asset +ON object_observations(asset_uuid); + +-- Keeps the ON DELETE SET NULL action cheap on every recording delete. +CREATE INDEX idx_object_observations_recording +ON object_observations(recording_id); + +-- seq is the history cursor: monotonic persistence order, never reused. +CREATE TABLE observation_changes ( + seq INTEGER PRIMARY KEY AUTOINCREMENT, + observation_uuid TEXT NOT NULL REFERENCES object_observations(uuid) ON DELETE CASCADE, + revision INTEGER NOT NULL CHECK (revision >= 1), + cause TEXT NOT NULL CHECK (cause IN ('created', 'evidence', 'classification')), + evidence_state TEXT NOT NULL CHECK (evidence_state IN ('pending', 'ready', 'unavailable', 'expired')), + evidence_reason TEXT CHECK (evidence_reason IS NULL OR length(evidence_reason) BETWEEN 1 AND 32), + created_at_ms INTEGER NOT NULL, + UNIQUE(observation_uuid, revision), + CHECK ((evidence_state IN ('pending', 'ready')) = (evidence_reason IS NULL)), + CHECK ((cause = 'created') = (revision = 1)) +); + +CREATE TABLE observation_publications ( + change_seq INTEGER PRIMARY KEY REFERENCES observation_changes(seq) ON DELETE CASCADE, + message_id TEXT NOT NULL UNIQUE, + state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ( + 'pending', 'relayed', 'relayed_no_route', 'expired', 'failed' + )), + attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0), + last_error TEXT, + updated_at_ms INTEGER NOT NULL +); + +-- The relay reads pending rows in seq order. +CREATE INDEX idx_observation_publications_pending +ON observation_publications(change_seq) WHERE state = 'pending'; + +-- One row. epoch_uuid identifies this DB generation for cursors (contract +-- D9); oldest_seq is the lowest seq that may still be retained (prune +-- boundary, initially 1). Kept out of system_settings so it can be written +-- inside observation transactions. +CREATE TABLE observation_meta ( + id INTEGER PRIMARY KEY CHECK (id = 1), + epoch_uuid TEXT NOT NULL CHECK (length(epoch_uuid) = 36), + oldest_seq INTEGER NOT NULL DEFAULT 1 CHECK (oldest_seq >= 1) +); + +INSERT INTO observation_meta (id, epoch_uuid, oldest_seq) +VALUES ( + 1, + lower( + hex(randomblob(4)) || '-' || + hex(randomblob(2)) || '-4' || + substr(hex(randomblob(2)), 2) || '-' || + substr('89ab', (abs(random()) % 4) + 1, 1) || + substr(hex(randomblob(2)), 2) || '-' || + hex(randomblob(6)) + ), + 1 +); + +-- Per-camera evidence settings (contract section 13). Evidence is off by +-- default; an empty label list means every object label. +ALTER TABLE streams ADD COLUMN observation_evidence_enabled INTEGER NOT NULL DEFAULT 0 + CHECK (observation_evidence_enabled IN (0, 1)); +ALTER TABLE streams ADD COLUMN observation_labels TEXT NOT NULL DEFAULT ''; +ALTER TABLE streams ADD COLUMN observation_max_age_days INTEGER NOT NULL DEFAULT 7 + CHECK (observation_max_age_days > 0); +ALTER TABLE streams ADD COLUMN observation_max_bytes INTEGER NOT NULL DEFAULT 2147483648 + CHECK (observation_max_bytes > 0); + +INSERT OR IGNORE INTO system_settings (key, value) +VALUES ('observation_history_retention_days', '30'); + +INSERT OR IGNORE INTO system_settings (key, value) +VALUES ('observation_global_queue_bytes', '33554432'); + +-- migrate:down + +-- Drops all observation history and evidence metadata; take a backup first. +-- Evidence files under /observations are left on disk. +DELETE FROM system_settings WHERE key IN ( + 'observation_history_retention_days', 'observation_global_queue_bytes' +); +ALTER TABLE streams DROP COLUMN observation_max_bytes; +ALTER TABLE streams DROP COLUMN observation_max_age_days; +ALTER TABLE streams DROP COLUMN observation_labels; +ALTER TABLE streams DROP COLUMN observation_evidence_enabled; +DROP TABLE IF EXISTS observation_meta; +DROP INDEX IF EXISTS idx_observation_publications_pending; +DROP TABLE IF EXISTS observation_publications; +DROP TABLE IF EXISTS observation_changes; +DROP INDEX IF EXISTS idx_object_observations_recording; +DROP INDEX IF EXISTS idx_object_observations_asset; +DROP INDEX IF EXISTS idx_object_observations_created; +DROP INDEX IF EXISTS idx_object_observations_label_time; +DROP INDEX IF EXISTS idx_object_observations_camera_time; +DROP TABLE IF EXISTS object_observations; +DROP INDEX IF EXISTS idx_observation_assets_state_camera; +DROP TABLE IF EXISTS observation_assets; diff --git a/db/migrations/0096_add_observation_classifications.sql b/db/migrations/0096_add_observation_classifications.sql new file mode 100644 index 000000000..c6593eec6 --- /dev/null +++ b/db/migrations/0096_add_observation_classifications.sql @@ -0,0 +1,54 @@ +-- INT_01 P2 classification annotations on object observations and the +-- observations.annotate authorization action. Normative contract: +-- docs/integrations/OBSERVATIONS_CONTRACT.md (sections 10, 12.1). +-- +-- One row per (observation, "/") key. Write authority +-- is pinned to writer_user_id, stored when the key is created (D3): user ids +-- are never reused, so a later user with the same username cannot overwrite. +-- There is no foreign key to users, so annotations survive user deletion and +-- become read-only. Times are epoch milliseconds. The original detector +-- fields on object_observations are never modified by an annotation. + +-- migrate:up + +CREATE TABLE observation_classifications ( + observation_uuid TEXT NOT NULL REFERENCES object_observations(uuid) ON DELETE CASCADE, + key TEXT NOT NULL CHECK (length(key) BETWEEN 3 AND 65), + writer_namespace TEXT NOT NULL CHECK (length(writer_namespace) BETWEEN 1 AND 32), + writer_user_id INTEGER NOT NULL CHECK (writer_user_id > 0), + classifier TEXT NOT NULL CHECK (length(classifier) BETWEEN 1 AND 64), + model_version TEXT CHECK (model_version IS NULL OR length(model_version) BETWEEN 1 AND 64), + label TEXT NOT NULL CHECK (length(CAST(label AS BLOB)) BETWEEN 1 AND 128), + taxonomy_id TEXT CHECK (taxonomy_id IS NULL OR length(taxonomy_id) BETWEEN 1 AND 64), + confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0), + classified_at_ms INTEGER NOT NULL CHECK (classified_at_ms >= 0), + input_asset_uuid TEXT NOT NULL CHECK (length(input_asset_uuid) = 36), + input_revision INTEGER NOT NULL CHECK (input_revision >= 1), + kind TEXT NOT NULL CHECK (kind IN ('automated', 'owner_confirmed')), + annotation_revision INTEGER NOT NULL DEFAULT 1 CHECK (annotation_revision >= 1), + created_at_ms INTEGER NOT NULL, + updated_at_ms INTEGER NOT NULL, + PRIMARY KEY (observation_uuid, key), + -- The key's namespace segment is the writer's namespace. + CHECK (substr(key, 1, length(writer_namespace) + 1) = writer_namespace || '/') +); + +-- New persisted action-mask positions are appended; existing positions never +-- move. Built-in roles fail closed: only Administrator receives the action. +INSERT INTO authz_actions + (action_key, category, description, camera_scoped, destructive, sort_order, bit_index) +VALUES + ('observations.annotate', 'Integrations', + 'Annotate detection observations with classifier results', 1, 0, 200, 19); + +INSERT INTO authz_role_actions (role_uuid, action_key) +SELECT '00000000-0000-4000-8000-000000000001', action_key +FROM authz_actions +WHERE action_key = 'observations.annotate'; + +-- migrate:down + +-- Drops all classification annotations; take a backup first. +DELETE FROM authz_role_actions WHERE action_key = 'observations.annotate'; +DELETE FROM authz_actions WHERE action_key = 'observations.annotate'; +DROP TABLE IF EXISTS observation_classifications; diff --git a/docs/DETECTION_ENGINES.md b/docs/DETECTION_ENGINES.md index 7811b4787..7588e9e6a 100644 --- a/docs/DETECTION_ENGINES.md +++ b/docs/DETECTION_ENGINES.md @@ -90,3 +90,44 @@ stream's legacy detection model empty); see the runtime boundaries below. - Engine lifecycle still follows the stream's existing `detection_based_recording`/schedule lifecycle. This release does not create a separate always-on analytics-only coordinator or UI editor. + +## Observation evidence (INT_01) + +A camera can keep durable observations for integrations such as YA-WAMF: one +row per detected object, with its label, confidence and box, plus the image +the detector ran on. It is **off by default**. Turn it on per camera with +`observation_evidence_enabled`, and bound it with `observation_labels`, +`observation_max_age_days` and `observation_max_bytes` on the stream API +(`camera.configure`), or in the camera's **Observation evidence for +integrations** settings. Changes apply without a restart. Defaults and bounds +are in the +[observations contract](integrations/OBSERVATIONS_CONTRACT.md#13-budgets-and-configuration). + +What each engine contributes: + +| Engine | Observations | Evidence image | +| --- | --- | --- | +| `object` (SOD) | one per eligible object | the decoded detection frame; transform `resize` to the network size | +| `object` (TFLite on LiteRT) | one per eligible object | the decoded detection frame; transform `letterbox` | +| `api` (single HTTP detector) | one per eligible object that passes zones, object filters and the threshold | the exact JPEG submitted to the detector; transform `none`, or a server-declared letterbox | +| `onvif` | one per classified camera event (not plain `motion`) | none: `unavailable(no_matching_media)`, no box | +| `motion` | none | none | +| Additional `api` engines in a multi-engine set | none (they are isolated and do not run) | none | + +- The image is the low-resolution inference input, not a recording frame. + Boxes are normalized to it, and the `transform` records how it maps to the + source and inference sizes + ([contract §4.4](integrations/OBSERVATIONS_CONTRACT.md#44-transform)). +- In a multi-engine set, each local object engine submits its own batch, so + each observation carries a single engine's provenance. Objects from one + frame share one image. +- Detection threads never block on evidence. A frame is copied into a bounded + queue and encoded by one worker thread; when the queue or a budget is full + the observation is recorded as `unavailable` with a reason (`queue_full`, + `budget_exhausted`) instead. If even the drop-record ring is full, the drop + is only counted (`untracked` in the diagnostics). +- Detection rows and their MQTT events are unchanged. The recording timeline + links a detection to the observation from the same camera, label and second + (±1 s) to show its classification. + +The deployment recipe is [YA-WAMF with lightNVR](integrations/YA_WAMF.md). diff --git a/docs/EVENT_CONTRACT.md b/docs/EVENT_CONTRACT.md index 88fb310aa..19ed00912 100644 --- a/docs/EVENT_CONTRACT.md +++ b/docs/EVENT_CONTRACT.md @@ -47,6 +47,7 @@ Every event uses a CloudEvents-inspired JSON envelope: | Type | Severity | Sensitivity | Default expiry | Expected rate | Media policy | | --- | --- | --- | ---: | --- | --- | | `io.lightnvr.detection.object.v1` | info | operational | 1 hour | high | reference allowed | +| `io.lightnvr.detection.observation_changed.v1` | info | operational | 1 hour | high | reference allowed | | `io.lightnvr.recognition.license_plate.v1` | info | restricted | 1 hour | high | forbidden | | `io.lightnvr.camera.offline.v1` | warning | operational | 1 day | low | forbidden | | `io.lightnvr.camera.recovered.v1` | info | operational | 1 day | low | forbidden | @@ -62,6 +63,34 @@ Expiry is internal delivery metadata and is intentionally not part of the JSON payload. MQTT 5 delivery may carry it as a message-expiry property; the durable outbox uses the same value. +### Observation change events (INT_01) + +`io.lightnvr.detection.observation_changed.v1` (subject `camera/`) is +defined by the +[observations contract](integrations/OBSERVATIONS_CONTRACT.md#6-change-event-iolightnvrdetectionobservation_changedv1) +and registered in `src/core/event_envelope.c`. Unlike the facts above, each +message carries a deterministic UUIDv5 `id` per observation revision and its +`time` is the change row's creation time truncated to seconds, so a replay +is byte-identical. + +- The publication relay (`src/core/observation_relay.c`) enqueues staged + changes in persistence order through `event_producer_enqueue_durable()`, + the same router → outbox path as other durable producers. Route + suppression applies; a suppressed or unrouted change is recorded as + `relayed_no_route`, never deleted. +- The type holds at most 2,000 undelivered outbox rows (contract D8). Above + that the relay pauses and leaves changes pending, so a broker outage cannot + crowd out operational warnings. +- Delivery is at least once. Consumers deduplicate on `source` + `id`, and + the + [observation history API](integrations/OBSERVATIONS_CONTRACT.md#7-history-and-cursor-get-apiobservations) + is the durable recovery path. +- Observations are recorded only for cameras with observation evidence turned + on (off by default). Setup for a consumer is described in the + [YA-WAMF deployment recipe](integrations/YA_WAMF.md). + +Existing detection topics are unchanged. + ## Data schemas Additional fields are allowed unless they violate the privacy rules above. @@ -96,6 +125,38 @@ metadata; when present, all four coordinates are required, normalized to `0.0–1.0`, and width and height must be greater than zero. `track_id`, `zone_id`, and an authorization-aware `snapshot_url` are optional. +### `io.lightnvr.detection.observation_changed.v1` + +```json +{ + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 2, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.481Z", + "change_cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "asset_id": "88888888-8888-4888-8888-888888888888", + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" +} +``` + +One message per observation revision. `change_cause` is `created`, +`evidence` or `classification`. `evidence_state` and `evidence_reason` are the +values at that revision, and `snapshot_url` is present only when the state is +`ready`. `camera_uuid` must equal the subject's UUID. Objects detected in the +same frame are separate observations that share `asset_id`. The payload is a +trigger: the box, transform and classifications come from +`GET /api/observations/{id}`. The full field rules are in +[contract §6.3](integrations/OBSERVATIONS_CONTRACT.md#63-data-payload), and +the OpenAPI schema is `ObservationChangeEvent`. + +Event routes match this type by type and camera scope only. A route +`predicate` (`labels_any`, `zone_ids_any`, `min_confidence`) applies only to +`io.lightnvr.detection.object.v1`, so a route with a predicate never matches +observation events. + ### `io.lightnvr.recognition.license_plate.v1` ```json diff --git a/docs/MQTT_INTEGRATION.md b/docs/MQTT_INTEGRATION.md index 7e25a526f..663bd2c6a 100644 --- a/docs/MQTT_INTEGRATION.md +++ b/docs/MQTT_INTEGRATION.md @@ -206,6 +206,45 @@ active even when the existing `[mqtt]` default destination is disabled. See } ``` +### Observation change events (INT_01) + +Cameras with observation evidence turned on (off by default; see +[Detection engines](DETECTION_ENGINES.md#observation-evidence-int_01)) also +publish `io.lightnvr.detection.observation_changed.v1` on: + +`{topic_prefix}/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}` + +Each message announces a new revision of one durable observation: it was +created, its evidence image became ready, unavailable or expired, or a +classification annotation changed. The data is a small trigger (`observation_id`, +`revision`, `change_cause`, `evidence_state` and `snapshot_url` when the image +is ready); consumers read the full observation from +`GET /api/observations/{id}`. The payload rules are in the +[observations contract](integrations/OBSERVATIONS_CONTRACT.md#6-change-event-iolightnvrdetectionobservation_changedv1). + +- The envelope `id` is a deterministic UUIDv5 of the source, observation and + revision, so a redelivery repeats the same `id`. Delivery is at least once; + deduplicate on `source + id`. +- Changes are staged in SQLite with the observation itself and relayed to the + durable outbox in order. A broker outage delays them; it does not lose them + until the one-hour delivery expiry. Missed changes stay available through + the cursor API, `GET /api/observations`. +- **Routes.** With no event routes stored, these events are published like + every other normalized event. Once routes exist, add one that lists + `io.lightnvr.detection.observation_changed.v1` **without** a detection + predicate. `labels_any`, `zone_ids_any` and `min_confidence` only match + `io.lightnvr.detection.object.v1`, so a route with a predicate never matches + observation events; those changes are recorded as `relayed_no_route` and are + not published. Avoid debounce, cooldown and grouping on this route: they are + keyed per camera and would drop revisions. +- The observation type may hold at most 2,000 undelivered outbox rows, so a + long outage cannot crowd out other events. +- The legacy `{topic_prefix}/detections/{stream_name}`, snapshot and Home + Assistant topics are unchanged and carry no observation data. + +For a complete consumer setup (scoped token, routes, detector, retention and +diagnostics), see the [YA-WAMF deployment recipe](integrations/YA_WAMF.md). + ## Legacy Detection Format The existing topic and payload remain available during migration for Home diff --git a/docs/api/contracts/coverage.json b/docs/api/contracts/coverage.json index 30d3bdc10..36e8dddac 100644 --- a/docs/api/contracts/coverage.json +++ b/docs/api/contracts/coverage.json @@ -3,7 +3,7 @@ "source_revision": "8de25fa08522e6c4f6ce677a44940991d1855209", "partial": true, "notes": [ - "Reconciled four-PR review stack: 241 literal native registrations, 232 OpenAPI operations, eight explicitly documented media-protocol surfaces and one internal lease callback. No unassigned residual registrations.", + "Reconciled four-PR review stack: 246 literal native registrations, 237 OpenAPI operations, eight explicitly documented media-protocol surfaces and one internal lease callback. No unassigned residual registrations.", "OpenAPI coverage includes JSON, binary downloads and other explicitly described responses. The eight media-protocol registrations remain outside the projected OpenAPI document; partial=true preserves that distinction.", "Discovery is descriptive. Every request must independently satisfy current principal, token, action, resource and deployment checks. Inventory coverage never grants access.", "Historical slices and their counts are retained in execution receipts, not repeated as current coverage. The original baseline had 57 accepted operations out of 240 registrations.", @@ -14,6 +14,7 @@ "F8 events: 13 operations; owned MQTT packet, retry and suppression evidence is separate from JSON acknowledgments.", "F9/F10: 15 stream lifecycle JSON operations, 22 ONVIF/device operations and eight explicit protocol dispositions. Owned media/SOAP evidence does not qualify physical cameras or multicast discovery.", "F11 evidence: 23 operations; captured UUID is authoritative where present, legacy identity fallback is explicitly bounded, and media/image/CSV bytes have independent evidence.", + "INT_01 observations: 5 operations (getIntegrationCapabilities, listObservations, getObservation, getObservationSnapshot, putObservationClassification) registered as authenticated 501 not_implemented stubs (task C2); L8 and L14 replace them. Refresh line numbers and sha256 with node scripts/contracts/coverage.mjs.", "See docs/internal/plans/engineering-hardening-four-pr-plan.md and docs/api/contracts/*-evidence.md for accepted behavior, exact validation and remaining limitations." ], "operations": [ @@ -22,7 +23,7 @@ "route_pattern": "/api/setup/status", "handler": "handle_get_setup_status", "source": "src/web/libuv_api_handlers.c", - "line": 99, + "line": 100, "coverage": "system-settings", "classification": "public-bootstrap", "operation_id": "getSetupStatus", @@ -33,7 +34,7 @@ "route_pattern": "/api/setup/status", "handler": "handle_post_setup_complete", "source": "src/web/libuv_api_handlers.c", - "line": 100, + "line": 101, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "updateSetupStatus", @@ -44,7 +45,7 @@ "route_pattern": "/api/health", "handler": "handle_get_health", "source": "src/web/libuv_api_handlers.c", - "line": 103, + "line": 104, "coverage": "system-settings", "classification": "public-bootstrap", "operation_id": "getStreamHealth", @@ -55,7 +56,7 @@ "route_pattern": "/api/health/hls", "handler": "handle_get_hls_health", "source": "src/web/libuv_api_handlers.c", - "line": 104, + "line": 105, "coverage": "system-settings", "classification": "public-bootstrap", "operation_id": "getHlsHealth", @@ -66,7 +67,7 @@ "route_pattern": "/api/metrics", "handler": "handle_get_metrics", "source": "src/web/libuv_api_handlers.c", - "line": 107, + "line": 108, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getMetrics", @@ -77,7 +78,7 @@ "route_pattern": "/api/telemetry/player", "handler": "handle_post_player_telemetry", "source": "src/web/libuv_api_handlers.c", - "line": 108, + "line": 109, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "recordPlayerTelemetry", @@ -88,7 +89,7 @@ "route_pattern": "/api/motion/trigger", "handler": "handle_post_motion_trigger", "source": "src/web/libuv_api_handlers.c", - "line": 111, + "line": 112, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "triggerMotion", @@ -99,7 +100,7 @@ "route_pattern": "/api/streams", "handler": "handle_get_streams", "source": "src/web/libuv_api_handlers.c", - "line": 114, + "line": 115, "coverage": "pilot", "classification": "protected-review-per-route", "operation_id": "listStreams", @@ -111,7 +112,7 @@ "route_pattern": "/api/streams", "handler": "handle_post_stream", "source": "src/web/libuv_api_handlers.c", - "line": 115, + "line": 116, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "createStream", @@ -123,7 +124,7 @@ "route_pattern": "/api/streams/test", "handler": "handle_test_stream", "source": "src/web/libuv_api_handlers.c", - "line": 116, + "line": 117, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "probeStream", @@ -135,7 +136,7 @@ "route_pattern": "/api/locations", "handler": "handle_get_locations", "source": "src/web/libuv_api_handlers.c", - "line": 119, + "line": 120, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "listFleetLocations", @@ -147,7 +148,7 @@ "route_pattern": "/api/locations", "handler": "handle_post_location", "source": "src/web/libuv_api_handlers.c", - "line": 120, + "line": 121, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "createFleetLocation", @@ -159,7 +160,7 @@ "route_pattern": "/api/locations/#", "handler": "handle_get_location", "source": "src/web/libuv_api_handlers.c", - "line": 121, + "line": 122, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "getFleetLocation", @@ -171,7 +172,7 @@ "route_pattern": "/api/locations/#", "handler": "handle_put_location", "source": "src/web/libuv_api_handlers.c", - "line": 122, + "line": 123, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "updateFleetLocation", @@ -183,7 +184,7 @@ "route_pattern": "/api/locations/#", "handler": "handle_delete_location", "source": "src/web/libuv_api_handlers.c", - "line": 123, + "line": 124, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteFleetLocation", @@ -195,7 +196,7 @@ "route_pattern": "/api/cameras/#/location", "handler": "handle_put_camera_location", "source": "src/web/libuv_api_handlers.c", - "line": 124, + "line": 125, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "setCameraLocation", @@ -207,7 +208,7 @@ "route_pattern": "/api/live/plans", "handler": "handle_get_operator_floor_plans", "source": "src/web/libuv_api_handlers.c", - "line": 128, + "line": 129, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "listFloorPlans", @@ -219,7 +220,7 @@ "route_pattern": "/api/live/plans", "handler": "handle_post_operator_floor_plan", "source": "src/web/libuv_api_handlers.c", - "line": 130, + "line": 131, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "createFloorPlan", @@ -231,7 +232,7 @@ "route_pattern": "/api/live/plans/#/background", "handler": "handle_get_operator_floor_plan_background", "source": "src/web/libuv_api_handlers.c", - "line": 133, + "line": 134, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "getFloorPlanBackground", @@ -243,7 +244,7 @@ "route_pattern": "/api/live/plans/#/background", "handler": "handle_put_operator_floor_plan_background", "source": "src/web/libuv_api_handlers.c", - "line": 135, + "line": 136, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "setFloorPlanBackground", @@ -255,7 +256,7 @@ "route_pattern": "/api/live/plans/#/background", "handler": "handle_delete_operator_floor_plan_background", "source": "src/web/libuv_api_handlers.c", - "line": 137, + "line": 138, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteFloorPlanBackground", @@ -267,7 +268,7 @@ "route_pattern": "/api/live/plans/#", "handler": "handle_get_operator_floor_plan", "source": "src/web/libuv_api_handlers.c", - "line": 140, + "line": 141, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "getFloorPlan", @@ -279,7 +280,7 @@ "route_pattern": "/api/live/plans/#", "handler": "handle_put_operator_floor_plan", "source": "src/web/libuv_api_handlers.c", - "line": 142, + "line": 143, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "updateFloorPlan", @@ -291,7 +292,7 @@ "route_pattern": "/api/live/plans/#", "handler": "handle_delete_operator_floor_plan", "source": "src/web/libuv_api_handlers.c", - "line": 144, + "line": 145, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteFloorPlan", @@ -303,7 +304,7 @@ "route_pattern": "/api/camera-tags", "handler": "handle_get_camera_tags", "source": "src/web/libuv_api_handlers.c", - "line": 147, + "line": 148, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "listFleetTags", @@ -315,7 +316,7 @@ "route_pattern": "/api/camera-tags", "handler": "handle_post_camera_tag", "source": "src/web/libuv_api_handlers.c", - "line": 149, + "line": 150, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "createFleetTag", @@ -327,7 +328,7 @@ "route_pattern": "/api/camera-tags/#/merge", "handler": "handle_post_camera_tag_merge", "source": "src/web/libuv_api_handlers.c", - "line": 151, + "line": 152, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "mergeFleetTag", @@ -339,7 +340,7 @@ "route_pattern": "/api/camera-tags/#", "handler": "handle_get_camera_tag", "source": "src/web/libuv_api_handlers.c", - "line": 153, + "line": 154, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "getFleetTag", @@ -351,7 +352,7 @@ "route_pattern": "/api/camera-tags/#", "handler": "handle_put_camera_tag", "source": "src/web/libuv_api_handlers.c", - "line": 155, + "line": 156, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "updateFleetTag", @@ -363,7 +364,7 @@ "route_pattern": "/api/camera-tags/#", "handler": "handle_delete_camera_tag", "source": "src/web/libuv_api_handlers.c", - "line": 157, + "line": 158, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteFleetTag", @@ -375,7 +376,7 @@ "route_pattern": "/api/cameras/#/tags", "handler": "handle_get_camera_tag_assignments", "source": "src/web/libuv_api_handlers.c", - "line": 159, + "line": 160, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "getCameraTagAssignments", @@ -387,7 +388,7 @@ "route_pattern": "/api/cameras/#/tags", "handler": "handle_put_camera_tag_assignments", "source": "src/web/libuv_api_handlers.c", - "line": 161, + "line": 162, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "setCameraTags", @@ -399,7 +400,7 @@ "route_pattern": "/api/fleet/cameras/query", "handler": "handle_post_fleet_camera_query", "source": "src/web/libuv_api_handlers.c", - "line": 165, + "line": 166, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "queryFleetCameras", @@ -411,7 +412,7 @@ "route_pattern": "/api/fleet/selectors/preview", "handler": "handle_post_fleet_selector_preview", "source": "src/web/libuv_api_handlers.c", - "line": 167, + "line": 168, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "previewFleetSelector", @@ -423,7 +424,7 @@ "route_pattern": "/api/fleet/views", "handler": "handle_get_fleet_saved_views", "source": "src/web/libuv_api_handlers.c", - "line": 169, + "line": 170, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "listFleetViews", @@ -435,7 +436,7 @@ "route_pattern": "/api/fleet/views", "handler": "handle_post_fleet_saved_view", "source": "src/web/libuv_api_handlers.c", - "line": 171, + "line": 172, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "createFleetView", @@ -447,7 +448,7 @@ "route_pattern": "/api/fleet/views/#", "handler": "handle_get_fleet_saved_view", "source": "src/web/libuv_api_handlers.c", - "line": 173, + "line": 174, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "getFleetView", @@ -459,7 +460,7 @@ "route_pattern": "/api/fleet/views/#", "handler": "handle_put_fleet_saved_view", "source": "src/web/libuv_api_handlers.c", - "line": 175, + "line": 176, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "updateFleetView", @@ -471,7 +472,7 @@ "route_pattern": "/api/fleet/views/#", "handler": "handle_delete_fleet_saved_view", "source": "src/web/libuv_api_handlers.c", - "line": 177, + "line": 178, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteFleetView", @@ -483,7 +484,7 @@ "route_pattern": "/api/live/layouts", "handler": "handle_get_live_layouts", "source": "src/web/libuv_api_handlers.c", - "line": 181, + "line": 182, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "listLiveLayouts", @@ -495,7 +496,7 @@ "route_pattern": "/api/live/layouts", "handler": "handle_post_live_layout", "source": "src/web/libuv_api_handlers.c", - "line": 183, + "line": 184, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "createLiveLayout", @@ -507,7 +508,7 @@ "route_pattern": "/api/live/layouts/#", "handler": "handle_put_live_layout", "source": "src/web/libuv_api_handlers.c", - "line": 185, + "line": 186, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "updateLiveLayout", @@ -519,7 +520,7 @@ "route_pattern": "/api/live/layouts/#", "handler": "handle_delete_live_layout", "source": "src/web/libuv_api_handlers.c", - "line": 187, + "line": 188, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteLiveLayout", @@ -531,7 +532,7 @@ "route_pattern": "/api/camera-favorites", "handler": "handle_get_camera_favorites", "source": "src/web/libuv_api_handlers.c", - "line": 191, + "line": 192, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "listCameraFavorites", @@ -543,7 +544,7 @@ "route_pattern": "/api/camera-favorites/#", "handler": "handle_put_camera_favorite", "source": "src/web/libuv_api_handlers.c", - "line": 193, + "line": 194, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "addCameraFavorite", @@ -555,7 +556,7 @@ "route_pattern": "/api/camera-favorites/#", "handler": "handle_delete_camera_favorite", "source": "src/web/libuv_api_handlers.c", - "line": 195, + "line": 196, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteCameraFavorite", @@ -567,7 +568,7 @@ "route_pattern": "/api/camera-collections", "handler": "handle_get_camera_collections", "source": "src/web/libuv_api_handlers.c", - "line": 199, + "line": 200, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "listCameraCollections", @@ -579,7 +580,7 @@ "route_pattern": "/api/camera-collections", "handler": "handle_post_camera_collection", "source": "src/web/libuv_api_handlers.c", - "line": 201, + "line": 202, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "createCameraCollection", @@ -591,7 +592,7 @@ "route_pattern": "/api/camera-collections/#/members", "handler": "handle_get_camera_collection_members", "source": "src/web/libuv_api_handlers.c", - "line": 203, + "line": 204, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "getCameraCollectionMembers", @@ -603,7 +604,7 @@ "route_pattern": "/api/camera-collections/#/members", "handler": "handle_put_camera_collection_members", "source": "src/web/libuv_api_handlers.c", - "line": 205, + "line": 206, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "setCameraCollectionMembers", @@ -615,7 +616,7 @@ "route_pattern": "/api/camera-collections/#/preview", "handler": "handle_post_camera_collection_preview", "source": "src/web/libuv_api_handlers.c", - "line": 207, + "line": 208, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "previewCameraCollection", @@ -627,7 +628,7 @@ "route_pattern": "/api/camera-collections/#", "handler": "handle_get_camera_collection", "source": "src/web/libuv_api_handlers.c", - "line": 209, + "line": 210, "coverage": "fleet-read", "classification": "protected-review-per-route", "operation_id": "getCameraCollection", @@ -639,7 +640,7 @@ "route_pattern": "/api/camera-collections/#", "handler": "handle_put_camera_collection", "source": "src/web/libuv_api_handlers.c", - "line": 211, + "line": 212, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "updateCameraCollection", @@ -651,7 +652,7 @@ "route_pattern": "/api/camera-collections/#", "handler": "handle_delete_camera_collection", "source": "src/web/libuv_api_handlers.c", - "line": 213, + "line": 214, "coverage": "fleet-writes", "classification": "protected-review-per-route", "operation_id": "deleteCameraCollection", @@ -663,7 +664,7 @@ "route_pattern": "/api/streams/#/recording", "handler": "handle_get_stream_recording", "source": "src/web/libuv_api_handlers.c", - "line": 217, + "line": 218, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "getStreamRecording", @@ -675,7 +676,7 @@ "route_pattern": "/api/streams/#/recording", "handler": "handle_post_stream_recording", "source": "src/web/libuv_api_handlers.c", - "line": 219, + "line": 220, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "controlStreamRecording", @@ -687,7 +688,7 @@ "route_pattern": "/api/streams/#/zones", "handler": "handle_get_zones", "source": "src/web/libuv_api_handlers.c", - "line": 223, + "line": 224, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "getStreamZones", @@ -699,7 +700,7 @@ "route_pattern": "/api/streams/#/zones", "handler": "handle_post_zones", "source": "src/web/libuv_api_handlers.c", - "line": 224, + "line": 225, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "saveStreamZones", @@ -711,7 +712,7 @@ "route_pattern": "/api/streams/#/zones", "handler": "handle_delete_zones", "source": "src/web/libuv_api_handlers.c", - "line": 225, + "line": 226, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "deleteStreamZones", @@ -723,7 +724,7 @@ "route_pattern": "/api/streams/#/retention", "handler": "handle_get_stream_retention", "source": "src/web/libuv_api_handlers.c", - "line": 228, + "line": 229, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "getStreamRetention", @@ -735,7 +736,7 @@ "route_pattern": "/api/streams/#/retention", "handler": "handle_put_stream_retention", "source": "src/web/libuv_api_handlers.c", - "line": 229, + "line": 230, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "setStreamRetention", @@ -747,7 +748,7 @@ "route_pattern": "/api/streams/#/detection-engines", "handler": "handle_get_detection_engines", "source": "src/web/libuv_api_handlers.c", - "line": 230, + "line": 231, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "getStreamDetectionEngines", @@ -759,7 +760,7 @@ "route_pattern": "/api/streams/#/detection-engines", "handler": "handle_put_detection_engines", "source": "src/web/libuv_api_handlers.c", - "line": 232, + "line": 233, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "setStreamDetectionEngines", @@ -771,7 +772,7 @@ "route_pattern": "/api/streams/#/refresh", "handler": "handle_post_stream_refresh", "source": "src/web/libuv_api_handlers.c", - "line": 236, + "line": 237, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "refreshStream", @@ -783,7 +784,7 @@ "route_pattern": "/api/streams/#/ptz/capabilities", "handler": "handle_ptz_capabilities", "source": "src/web/libuv_api_handlers.c", - "line": 239, + "line": 240, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getPtzCapabilities", @@ -795,7 +796,7 @@ "route_pattern": "/api/streams/#/ptz/presets", "handler": "handle_ptz_get_presets", "source": "src/web/libuv_api_handlers.c", - "line": 240, + "line": 241, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getPtzPresets", @@ -807,7 +808,7 @@ "route_pattern": "/api/streams/#/ptz/move", "handler": "handle_ptz_move", "source": "src/web/libuv_api_handlers.c", - "line": 241, + "line": 242, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "movePtz", @@ -819,7 +820,7 @@ "route_pattern": "/api/streams/#/ptz/stop", "handler": "handle_ptz_stop", "source": "src/web/libuv_api_handlers.c", - "line": 242, + "line": 243, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "stopPtz", @@ -831,7 +832,7 @@ "route_pattern": "/api/streams/#/ptz/absolute", "handler": "handle_ptz_absolute", "source": "src/web/libuv_api_handlers.c", - "line": 243, + "line": 244, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "absolutePtz", @@ -843,7 +844,7 @@ "route_pattern": "/api/streams/#/ptz/relative", "handler": "handle_ptz_relative", "source": "src/web/libuv_api_handlers.c", - "line": 244, + "line": 245, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "relativePtz", @@ -855,7 +856,7 @@ "route_pattern": "/api/streams/#/ptz/home", "handler": "handle_ptz_home", "source": "src/web/libuv_api_handlers.c", - "line": 245, + "line": 246, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "homePtz", @@ -867,7 +868,7 @@ "route_pattern": "/api/streams/#/ptz/set-home", "handler": "handle_ptz_set_home", "source": "src/web/libuv_api_handlers.c", - "line": 246, + "line": 247, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "setPtzHome", @@ -879,7 +880,7 @@ "route_pattern": "/api/streams/#/ptz/goto-preset", "handler": "handle_ptz_goto_preset", "source": "src/web/libuv_api_handlers.c", - "line": 247, + "line": 248, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "gotoPtzPreset", @@ -891,7 +892,7 @@ "route_pattern": "/api/streams/#/ptz/preset", "handler": "handle_ptz_set_preset", "source": "src/web/libuv_api_handlers.c", - "line": 248, + "line": 249, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "setPtzPreset", @@ -903,7 +904,7 @@ "route_pattern": "/api/streams/#/imaging/settings", "handler": "handle_imaging_get_settings", "source": "src/web/libuv_api_handlers.c", - "line": 251, + "line": 252, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getImagingSettings", @@ -915,7 +916,7 @@ "route_pattern": "/api/streams/#/imaging/settings", "handler": "handle_imaging_put_settings", "source": "src/web/libuv_api_handlers.c", - "line": 252, + "line": 253, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "setImagingSettings", @@ -927,7 +928,7 @@ "route_pattern": "/api/streams/#/imaging/options", "handler": "handle_imaging_get_options", "source": "src/web/libuv_api_handlers.c", - "line": 253, + "line": 254, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getImagingOptions", @@ -939,7 +940,7 @@ "route_pattern": "/api/streams/#/daynight", "handler": "handle_daynight_get", "source": "src/web/libuv_api_handlers.c", - "line": 254, + "line": 255, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getDaynight", @@ -951,7 +952,7 @@ "route_pattern": "/api/streams/#/daynight", "handler": "handle_daynight_put", "source": "src/web/libuv_api_handlers.c", - "line": 255, + "line": 256, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "setDaynight", @@ -963,7 +964,7 @@ "route_pattern": "/api/streams/#/full", "handler": "handle_get_stream_full", "source": "src/web/libuv_api_handlers.c", - "line": 258, + "line": 259, "coverage": "pilot", "classification": "protected-review-per-route", "operation_id": "getStreamFull", @@ -975,7 +976,7 @@ "route_pattern": "/api/streams/#", "handler": "handle_get_stream", "source": "src/web/libuv_api_handlers.c", - "line": 259, + "line": 260, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "getStream", @@ -987,7 +988,7 @@ "route_pattern": "/api/streams/#", "handler": "handle_put_stream", "source": "src/web/libuv_api_handlers.c", - "line": 260, + "line": 261, "coverage": "pilot", "classification": "protected-review-per-route", "operation_id": "updateStream", @@ -999,7 +1000,7 @@ "route_pattern": "/api/streams/#", "handler": "handle_delete_stream", "source": "src/web/libuv_api_handlers.c", - "line": 261, + "line": 262, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "deleteStream", @@ -1011,7 +1012,7 @@ "route_pattern": "/api/settings", "handler": "handle_get_settings", "source": "src/web/libuv_api_handlers.c", - "line": 264, + "line": 265, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getSystemSettings", @@ -1022,7 +1023,7 @@ "route_pattern": "/api/client-config", "handler": "handle_get_client_config", "source": "src/web/libuv_api_handlers.c", - "line": 265, + "line": 266, "coverage": "identity-token", "classification": "authenticated-bootstrap", "operation_id": "getClientConfig", @@ -1033,7 +1034,7 @@ "route_pattern": "/api/settings", "handler": "handle_post_settings", "source": "src/web/libuv_api_handlers.c", - "line": 267, + "line": 268, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "updateSystemSettings", @@ -1044,7 +1045,7 @@ "route_pattern": "/api/settings/go2rtc/validate", "handler": "handle_post_settings_go2rtc_validate", "source": "src/web/libuv_api_handlers.c", - "line": 268, + "line": 269, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "validateGo2rtcConfiguration", @@ -1055,7 +1056,7 @@ "route_pattern": "/api/ui/workspaces", "handler": "handle_get_ui_workspaces", "source": "src/web/libuv_api_handlers.c", - "line": 270, + "line": 271, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getWorkspaces", @@ -1066,7 +1067,7 @@ "route_pattern": "/api/ui/workspaces", "handler": "handle_put_ui_workspaces", "source": "src/web/libuv_api_handlers.c", - "line": 272, + "line": 273, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "updateWorkspaces", @@ -1077,7 +1078,7 @@ "route_pattern": "/api/ice-servers", "handler": "handle_get_ice_servers", "source": "src/web/libuv_api_handlers.c", - "line": 276, + "line": 277, "coverage": "stream-lifecycle", "classification": "protected-review-per-route", "operation_id": "getIceServers", @@ -1089,7 +1090,7 @@ "route_pattern": "/api/system", "handler": "handle_get_system_info", "source": "src/web/libuv_api_handlers.c", - "line": 279, + "line": 280, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getSystemInformationLegacy", @@ -1100,7 +1101,7 @@ "route_pattern": "/api/system/info", "handler": "handle_get_system_info", "source": "src/web/libuv_api_handlers.c", - "line": 280, + "line": 281, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getSystemInformation", @@ -1111,7 +1112,7 @@ "route_pattern": "/api/system/health/incidents", "handler": "handle_get_system_health_incidents", "source": "src/web/libuv_api_handlers.c", - "line": 281, + "line": 282, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getHostHealthIncidents", @@ -1122,7 +1123,7 @@ "route_pattern": "/api/system/health", "handler": "handle_get_system_health", "source": "src/web/libuv_api_handlers.c", - "line": 283, + "line": 284, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getHostHealth", @@ -1133,7 +1134,7 @@ "route_pattern": "/api/system/logs", "handler": "handle_get_system_logs", "source": "src/web/libuv_api_handlers.c", - "line": 285, + "line": 286, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getSystemLogs", @@ -1144,7 +1145,7 @@ "route_pattern": "/api/system/restart", "handler": "handle_post_system_restart", "source": "src/web/libuv_api_handlers.c", - "line": 286, + "line": 287, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "restartSystem", @@ -1155,7 +1156,7 @@ "route_pattern": "/api/system/shutdown", "handler": "handle_post_system_shutdown", "source": "src/web/libuv_api_handlers.c", - "line": 287, + "line": 288, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "shutdownSystem", @@ -1166,7 +1167,7 @@ "route_pattern": "/api/system/logs/clear", "handler": "handle_post_system_logs_clear", "source": "src/web/libuv_api_handlers.c", - "line": 288, + "line": 289, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "clearSystemLogs", @@ -1177,7 +1178,7 @@ "route_pattern": "/api/system/backup", "handler": "handle_post_system_backup", "source": "src/web/libuv_api_handlers.c", - "line": 289, + "line": 290, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "createSystemBackup", @@ -1188,7 +1189,7 @@ "route_pattern": "/api/system/status", "handler": "handle_get_system_status", "source": "src/web/libuv_api_handlers.c", - "line": 290, + "line": 291, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getSystemStatus", @@ -1199,7 +1200,7 @@ "route_pattern": "/api/system/go2rtc/effective-config", "handler": "handle_get_system_go2rtc_effective_config", "source": "src/web/libuv_api_handlers.c", - "line": 291, + "line": 292, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getGo2rtcEffectiveConfiguration", @@ -1210,7 +1211,7 @@ "route_pattern": "/api/system/go2rtc/override-status", "handler": "handle_get_system_go2rtc_override_status", "source": "src/web/libuv_api_handlers.c", - "line": 293, + "line": 294, "coverage": "system-settings", "classification": "protected-review-per-route", "operation_id": "getGo2rtcOverrideDiagnostics", @@ -1221,7 +1222,7 @@ "route_pattern": "/api/detection/results/#", "handler": "handle_get_detection_results", "source": "src/web/libuv_api_handlers.c", - "line": 297, + "line": 298, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "getEvidenceDetections", @@ -1233,7 +1234,7 @@ "route_pattern": "/api/detection/models", "handler": "handle_get_detection_models", "source": "src/web/libuv_api_handlers.c", - "line": 298, + "line": 299, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "listEvidenceModels", @@ -1245,7 +1246,7 @@ "route_pattern": "/api/lpr/search", "handler": "handle_post_lpr_search", "source": "src/web/libuv_api_handlers.c", - "line": 299, + "line": 300, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "searchEvidenceLpr", @@ -1257,7 +1258,7 @@ "route_pattern": "/api/lpr/export", "handler": "handle_post_lpr_export", "source": "src/web/libuv_api_handlers.c", - "line": 300, + "line": 301, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "exportEvidenceLpr", @@ -1269,7 +1270,7 @@ "route_pattern": "/api/lpr/reads/#", "handler": "handle_delete_lpr_read", "source": "src/web/libuv_api_handlers.c", - "line": 301, + "line": 302, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "deleteEvidenceLpr", @@ -1281,19 +1282,79 @@ "route_pattern": "/api/snapshots/#/#", "handler": "handle_get_detection_snapshot", "source": "src/web/libuv_api_handlers.c", - "line": 303, + "line": 304, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "getEvidenceSnapshot", "eligibility": "camera", "canonical_path": "/api/snapshots/{camera}/{filename}" }, + { + "method": "GET", + "route_pattern": "/api/integrations/capabilities", + "handler": "handle_get_integration_capabilities", + "source": "src/web/libuv_api_handlers.c", + "line": 308, + "coverage": "observations", + "classification": "protected-review-per-route", + "operation_id": "getIntegrationCapabilities", + "eligibility": "authenticated", + "canonical_path": "/api/integrations/capabilities" + }, + { + "method": "GET", + "route_pattern": "/api/observations", + "handler": "handle_get_observations", + "source": "src/web/libuv_api_handlers.c", + "line": 310, + "coverage": "observations", + "classification": "protected-review-per-route", + "operation_id": "listObservations", + "eligibility": "camera", + "canonical_path": "/api/observations" + }, + { + "method": "GET", + "route_pattern": "/api/observations/#/snapshot", + "handler": "handle_get_observation_snapshot", + "source": "src/web/libuv_api_handlers.c", + "line": 311, + "coverage": "observations", + "classification": "protected-review-per-route", + "operation_id": "getObservationSnapshot", + "eligibility": "camera", + "canonical_path": "/api/observations/{id}/snapshot" + }, + { + "method": "PUT", + "route_pattern": "/api/observations/#/classifications/#/#", + "handler": "handle_put_observation_classification", + "source": "src/web/libuv_api_handlers.c", + "line": 313, + "coverage": "observations", + "classification": "protected-review-per-route", + "operation_id": "putObservationClassification", + "eligibility": "authenticated", + "canonical_path": "/api/observations/{id}/classifications/{namespace}/{name}" + }, + { + "method": "GET", + "route_pattern": "/api/observations/#", + "handler": "handle_get_observation", + "source": "src/web/libuv_api_handlers.c", + "line": 315, + "coverage": "observations", + "classification": "protected-review-per-route", + "operation_id": "getObservation", + "eligibility": "camera", + "canonical_path": "/api/observations/{id}" + }, { "method": "GET", "route_pattern": "/api/storage/health", "handler": "handle_get_storage_health", "source": "src/web/libuv_api_handlers.c", - "line": 306, + "line": 318, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStorageHealth", @@ -1305,7 +1366,7 @@ "route_pattern": "/api/storage/cleanup", "handler": "handle_post_storage_cleanup", "source": "src/web/libuv_api_handlers.c", - "line": 307, + "line": 319, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "triggerStorageCleanup", @@ -1317,7 +1378,7 @@ "route_pattern": "/api/auth/login/config", "handler": "handle_auth_login_config", "source": "src/web/libuv_api_handlers.c", - "line": 310, + "line": 322, "coverage": "identity-token", "classification": "public-bootstrap", "operation_id": "getLoginConfig", @@ -1328,7 +1389,7 @@ "route_pattern": "/api/auth/login", "handler": "handle_auth_login", "source": "src/web/libuv_api_handlers.c", - "line": 311, + "line": 323, "coverage": "identity-token", "classification": "public-bootstrap", "operation_id": "login", @@ -1339,7 +1400,7 @@ "route_pattern": "/api/auth/logout", "handler": "handle_auth_logout", "source": "src/web/libuv_api_handlers.c", - "line": 312, + "line": 324, "coverage": "identity-token", "classification": "public-bootstrap", "operation_id": "logout", @@ -1350,7 +1411,7 @@ "route_pattern": "/api/auth/verify", "handler": "handle_auth_verify", "source": "src/web/libuv_api_handlers.c", - "line": 313, + "line": 325, "coverage": "identity-token", "classification": "identity-self-service", "operation_id": "verifyIdentity", @@ -1361,7 +1422,7 @@ "route_pattern": "/api/auth/sessions", "handler": "handle_auth_sessions_list", "source": "src/web/libuv_api_handlers.c", - "line": 314, + "line": 326, "coverage": "identity-lifecycle", "classification": "identity-self-service", "operation_id": "listAuthSessions", @@ -1373,7 +1434,7 @@ "route_pattern": "/api/auth/sessions/#", "handler": "handle_auth_sessions_delete", "source": "src/web/libuv_api_handlers.c", - "line": 315, + "line": 327, "coverage": "identity-lifecycle", "classification": "identity-self-service", "operation_id": "revokeAuthSession", @@ -1385,7 +1446,7 @@ "route_pattern": "/api/auth/trusted-devices", "handler": "handle_auth_trusted_devices_list", "source": "src/web/libuv_api_handlers.c", - "line": 316, + "line": 328, "coverage": "identity-lifecycle", "classification": "identity-self-service", "operation_id": "listTrustedDevices", @@ -1397,7 +1458,7 @@ "route_pattern": "/api/auth/trusted-devices/#", "handler": "handle_auth_trusted_devices_delete", "source": "src/web/libuv_api_handlers.c", - "line": 317, + "line": 329, "coverage": "identity-lifecycle", "classification": "identity-self-service", "operation_id": "revokeTrustedDevice", @@ -1409,7 +1470,7 @@ "route_pattern": "/logout", "handler": "handle_auth_logout", "source": "src/web/libuv_api_handlers.c", - "line": 318, + "line": 330, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "legacyLogout", @@ -1421,7 +1482,7 @@ "route_pattern": "/api/auth/users", "handler": "handle_users_list", "source": "src/web/libuv_api_handlers.c", - "line": 321, + "line": 333, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "listAdminUsers", @@ -1433,7 +1494,7 @@ "route_pattern": "/api/auth/users", "handler": "handle_users_create", "source": "src/web/libuv_api_handlers.c", - "line": 322, + "line": 334, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "createAdminUser", @@ -1445,7 +1506,7 @@ "route_pattern": "/api/auth/users/#", "handler": "handle_users_get", "source": "src/web/libuv_api_handlers.c", - "line": 323, + "line": 335, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "getAdminUser", @@ -1457,7 +1518,7 @@ "route_pattern": "/api/auth/users/#", "handler": "handle_users_update", "source": "src/web/libuv_api_handlers.c", - "line": 324, + "line": 336, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "updateAdminUser", @@ -1469,7 +1530,7 @@ "route_pattern": "/api/auth/users/#", "handler": "handle_users_delete", "source": "src/web/libuv_api_handlers.c", - "line": 325, + "line": 337, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "deleteAdminUser", @@ -1481,7 +1542,7 @@ "route_pattern": "/api/auth/users/#/api-key", "handler": "handle_users_generate_api_key", "source": "src/web/libuv_api_handlers.c", - "line": 326, + "line": 338, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "createLegacyApiKey", @@ -1493,7 +1554,7 @@ "route_pattern": "/api/auth/users/#/password", "handler": "handle_users_change_password", "source": "src/web/libuv_api_handlers.c", - "line": 327, + "line": 339, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "changePassword", @@ -1505,7 +1566,7 @@ "route_pattern": "/api/auth/users/#/password-lock", "handler": "handle_users_password_lock", "source": "src/web/libuv_api_handlers.c", - "line": 328, + "line": 340, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "setUserPasswordLock", @@ -1517,7 +1578,7 @@ "route_pattern": "/api/auth/users/#/login-lockout/clear", "handler": "handle_users_clear_login_lockout", "source": "src/web/libuv_api_handlers.c", - "line": 329, + "line": 341, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "clearUserLoginLockout", @@ -1529,7 +1590,7 @@ "route_pattern": "/api/authorization/actions", "handler": "handle_get_authorization_actions", "source": "src/web/libuv_api_handlers.c", - "line": 332, + "line": 344, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "listAuthorizationActions", @@ -1541,7 +1602,7 @@ "route_pattern": "/api/authorization/simulate", "handler": "handle_post_authorization_simulate", "source": "src/web/libuv_api_handlers.c", - "line": 334, + "line": 346, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "simulateAuthorization", @@ -1553,7 +1614,7 @@ "route_pattern": "/api/authorization/roles", "handler": "handle_get_authorization_roles", "source": "src/web/libuv_api_handlers.c", - "line": 336, + "line": 348, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "listAuthorizationRoles", @@ -1565,7 +1626,7 @@ "route_pattern": "/api/authorization/roles", "handler": "handle_post_authorization_role", "source": "src/web/libuv_api_handlers.c", - "line": 338, + "line": 350, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "createAuthorizationRole", @@ -1577,7 +1638,7 @@ "route_pattern": "/api/authorization/roles/#", "handler": "handle_put_authorization_role", "source": "src/web/libuv_api_handlers.c", - "line": 340, + "line": 352, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "updateAuthorizationRole", @@ -1589,7 +1650,7 @@ "route_pattern": "/api/authorization/roles/#", "handler": "handle_delete_authorization_role", "source": "src/web/libuv_api_handlers.c", - "line": 342, + "line": 354, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "deleteAuthorizationRole", @@ -1601,7 +1662,7 @@ "route_pattern": "/api/authorization/users/#/tokens", "handler": "handle_get_user_api_tokens", "source": "src/web/libuv_api_handlers.c", - "line": 344, + "line": 356, "coverage": "identity-token", "classification": "identity-self-service", "operation_id": "listUserTokens", @@ -1612,7 +1673,7 @@ "route_pattern": "/api/authorization/users/#/tokens", "handler": "handle_post_user_api_token", "source": "src/web/libuv_api_handlers.c", - "line": 347, + "line": 359, "coverage": "identity-token", "classification": "identity-self-service", "operation_id": "createUserToken", @@ -1623,7 +1684,7 @@ "route_pattern": "/api/authorization/users/#/tokens/#", "handler": "handle_delete_user_api_token", "source": "src/web/libuv_api_handlers.c", - "line": 350, + "line": 362, "coverage": "identity-token", "classification": "identity-self-service", "operation_id": "revokeUserToken", @@ -1634,7 +1695,7 @@ "route_pattern": "/api/authorization/users/#", "handler": "handle_get_user_authorization", "source": "src/web/libuv_api_handlers.c", - "line": 353, + "line": 365, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "getUserAuthorizationPolicy", @@ -1646,7 +1707,7 @@ "route_pattern": "/api/authorization/users/#", "handler": "handle_put_user_authorization", "source": "src/web/libuv_api_handlers.c", - "line": 355, + "line": 367, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "setUserAuthorizationPolicy", @@ -1658,7 +1719,7 @@ "route_pattern": "/api/audit/events/export", "handler": "handle_get_audit_export", "source": "src/web/libuv_api_handlers.c", - "line": 359, + "line": 371, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "exportAuditEvents", @@ -1670,7 +1731,7 @@ "route_pattern": "/api/audit/events", "handler": "handle_get_audit_events", "source": "src/web/libuv_api_handlers.c", - "line": 361, + "line": 373, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "listAuditEvents", @@ -1682,7 +1743,7 @@ "route_pattern": "/api/audit/settings", "handler": "handle_get_audit_settings", "source": "src/web/libuv_api_handlers.c", - "line": 363, + "line": 375, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "getAuditSettings", @@ -1694,7 +1755,7 @@ "route_pattern": "/api/audit/settings", "handler": "handle_put_audit_settings", "source": "src/web/libuv_api_handlers.c", - "line": 365, + "line": 377, "coverage": "identity-admin", "classification": "protected-review-per-route", "operation_id": "updateAuditSettings", @@ -1706,7 +1767,7 @@ "route_pattern": "/api/events/catalog", "handler": "handle_get_event_catalog", "source": "src/web/libuv_api_handlers.c", - "line": 369, + "line": 381, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "getEventCatalog", @@ -1717,7 +1778,7 @@ "route_pattern": "/api/event-destinations", "handler": "handle_get_event_destinations", "source": "src/web/libuv_api_handlers.c", - "line": 371, + "line": 383, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "listEventDestinations", @@ -1728,7 +1789,7 @@ "route_pattern": "/api/event-destinations", "handler": "handle_post_event_destination", "source": "src/web/libuv_api_handlers.c", - "line": 373, + "line": 385, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "createEventDestination", @@ -1739,7 +1800,7 @@ "route_pattern": "/api/event-destinations/#", "handler": "handle_get_event_destination", "source": "src/web/libuv_api_handlers.c", - "line": 375, + "line": 387, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "getEventDestination", @@ -1750,7 +1811,7 @@ "route_pattern": "/api/event-destinations/#", "handler": "handle_put_event_destination", "source": "src/web/libuv_api_handlers.c", - "line": 377, + "line": 389, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "updateEventDestination", @@ -1761,7 +1822,7 @@ "route_pattern": "/api/event-destinations/#", "handler": "handle_delete_event_destination", "source": "src/web/libuv_api_handlers.c", - "line": 379, + "line": 391, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "deleteEventDestination", @@ -1772,7 +1833,7 @@ "route_pattern": "/api/storage-archive", "handler": "handle_get_storage_archive", "source": "src/web/libuv_api_handlers.c", - "line": 382, + "line": 394, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStorageArchive", @@ -1784,7 +1845,7 @@ "route_pattern": "/api/storage-archive/retry", "handler": "handle_post_storage_archive_retry", "source": "src/web/libuv_api_handlers.c", - "line": 383, + "line": 395, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "retryStorageArchive", @@ -1796,7 +1857,7 @@ "route_pattern": "/api/storage-policies/#/apply", "handler": "handle_post_storage_policy_apply", "source": "src/web/libuv_api_handlers.c", - "line": 384, + "line": 396, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "applyStoragePolicy", @@ -1808,7 +1869,7 @@ "route_pattern": "/api/storage-targets", "handler": "handle_get_storage_targets", "source": "src/web/libuv_api_handlers.c", - "line": 387, + "line": 399, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "listStorageTargets", @@ -1820,7 +1881,7 @@ "route_pattern": "/api/storage-targets", "handler": "handle_post_storage_target", "source": "src/web/libuv_api_handlers.c", - "line": 389, + "line": 401, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "createStorageTarget", @@ -1832,7 +1893,7 @@ "route_pattern": "/api/storage-targets/#/probe", "handler": "handle_post_storage_target_probe", "source": "src/web/libuv_api_handlers.c", - "line": 391, + "line": 403, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "probeStorageTarget", @@ -1844,7 +1905,7 @@ "route_pattern": "/api/storage-targets/#", "handler": "handle_get_storage_target", "source": "src/web/libuv_api_handlers.c", - "line": 393, + "line": 405, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStorageTarget", @@ -1856,7 +1917,7 @@ "route_pattern": "/api/storage-targets/#", "handler": "handle_put_storage_target", "source": "src/web/libuv_api_handlers.c", - "line": 395, + "line": 407, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "updateStorageTarget", @@ -1868,7 +1929,7 @@ "route_pattern": "/api/storage-targets/#", "handler": "handle_delete_storage_target", "source": "src/web/libuv_api_handlers.c", - "line": 397, + "line": 409, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "deleteStorageTarget", @@ -1880,7 +1941,7 @@ "route_pattern": "/api/storage-migrations", "handler": "handle_get_storage_migrations", "source": "src/web/libuv_api_handlers.c", - "line": 399, + "line": 411, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "listStorageMigrations", @@ -1892,7 +1953,7 @@ "route_pattern": "/api/storage-migrations", "handler": "handle_post_storage_migration", "source": "src/web/libuv_api_handlers.c", - "line": 401, + "line": 413, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "createStorageMigration", @@ -1904,7 +1965,7 @@ "route_pattern": "/api/storage-migrations/#/cancel", "handler": "handle_post_storage_migration_cancel", "source": "src/web/libuv_api_handlers.c", - "line": 403, + "line": 415, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "cancelStorageMigration", @@ -1916,7 +1977,7 @@ "route_pattern": "/api/storage-migrations/#/retry", "handler": "handle_post_storage_migration_retry", "source": "src/web/libuv_api_handlers.c", - "line": 405, + "line": 417, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "retryStorageMigration", @@ -1928,7 +1989,7 @@ "route_pattern": "/api/storage-migrations/#", "handler": "handle_get_storage_migration", "source": "src/web/libuv_api_handlers.c", - "line": 407, + "line": 419, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStorageMigration", @@ -1940,7 +2001,7 @@ "route_pattern": "/api/storage-pools", "handler": "handle_get_storage_pools", "source": "src/web/libuv_api_handlers.c", - "line": 409, + "line": 421, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "listStoragePools", @@ -1952,7 +2013,7 @@ "route_pattern": "/api/storage-pools", "handler": "handle_post_storage_pool", "source": "src/web/libuv_api_handlers.c", - "line": 411, + "line": 423, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "createStoragePool", @@ -1964,7 +2025,7 @@ "route_pattern": "/api/storage-pools/#", "handler": "handle_get_storage_pool", "source": "src/web/libuv_api_handlers.c", - "line": 413, + "line": 425, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStoragePool", @@ -1976,7 +2037,7 @@ "route_pattern": "/api/storage-pools/#", "handler": "handle_put_storage_pool", "source": "src/web/libuv_api_handlers.c", - "line": 415, + "line": 427, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "updateStoragePool", @@ -1988,7 +2049,7 @@ "route_pattern": "/api/storage-pools/#", "handler": "handle_delete_storage_pool", "source": "src/web/libuv_api_handlers.c", - "line": 417, + "line": 429, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "deleteStoragePool", @@ -2000,7 +2061,7 @@ "route_pattern": "/api/storage-compliance", "handler": "handle_get_storage_compliance", "source": "src/web/libuv_api_handlers.c", - "line": 419, + "line": 431, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStorageCompliance", @@ -2012,7 +2073,7 @@ "route_pattern": "/api/storage-policies", "handler": "handle_get_storage_policies", "source": "src/web/libuv_api_handlers.c", - "line": 421, + "line": 433, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "listStoragePolicies", @@ -2024,7 +2085,7 @@ "route_pattern": "/api/storage-policies", "handler": "handle_post_storage_policy", "source": "src/web/libuv_api_handlers.c", - "line": 423, + "line": 435, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "createStoragePolicy", @@ -2036,7 +2097,7 @@ "route_pattern": "/api/storage-policies/preview", "handler": "handle_post_storage_policy_preview", "source": "src/web/libuv_api_handlers.c", - "line": 425, + "line": 437, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "previewStoragePolicy", @@ -2048,7 +2109,7 @@ "route_pattern": "/api/storage-policies/#", "handler": "handle_get_storage_policy", "source": "src/web/libuv_api_handlers.c", - "line": 427, + "line": 439, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "getStoragePolicy", @@ -2060,7 +2121,7 @@ "route_pattern": "/api/storage-policies/#", "handler": "handle_put_storage_policy", "source": "src/web/libuv_api_handlers.c", - "line": 429, + "line": 441, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "updateStoragePolicy", @@ -2072,7 +2133,7 @@ "route_pattern": "/api/storage-policies/#", "handler": "handle_delete_storage_policy", "source": "src/web/libuv_api_handlers.c", - "line": 431, + "line": 443, "coverage": "storage", "classification": "protected-review-per-route", "operation_id": "deleteStoragePolicy", @@ -2084,7 +2145,7 @@ "route_pattern": "/api/event-routes/preview", "handler": "handle_post_event_route_preview", "source": "src/web/libuv_api_handlers.c", - "line": 433, + "line": 445, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "previewEventRoute", @@ -2095,7 +2156,7 @@ "route_pattern": "/api/event-routes", "handler": "handle_get_event_routes", "source": "src/web/libuv_api_handlers.c", - "line": 435, + "line": 447, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "listEventRoutes", @@ -2106,7 +2167,7 @@ "route_pattern": "/api/event-routes", "handler": "handle_post_event_route", "source": "src/web/libuv_api_handlers.c", - "line": 437, + "line": 449, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "createEventRoute", @@ -2117,7 +2178,7 @@ "route_pattern": "/api/event-routes/#", "handler": "handle_get_event_route", "source": "src/web/libuv_api_handlers.c", - "line": 439, + "line": 451, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "getEventRoute", @@ -2128,7 +2189,7 @@ "route_pattern": "/api/event-routes/#", "handler": "handle_put_event_route", "source": "src/web/libuv_api_handlers.c", - "line": 441, + "line": 453, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "updateEventRoute", @@ -2139,7 +2200,7 @@ "route_pattern": "/api/event-routes/#", "handler": "handle_delete_event_route", "source": "src/web/libuv_api_handlers.c", - "line": 443, + "line": 455, "coverage": "event-routing", "classification": "protected-review-per-route", "operation_id": "deleteEventRoute", @@ -2150,7 +2211,7 @@ "route_pattern": "/api/auth/users/#/totp/setup", "handler": "handle_totp_setup", "source": "src/web/libuv_api_handlers.c", - "line": 447, + "line": 459, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "setupTotp", @@ -2162,7 +2223,7 @@ "route_pattern": "/api/auth/users/#/totp/verify", "handler": "handle_totp_verify", "source": "src/web/libuv_api_handlers.c", - "line": 448, + "line": 460, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "verifyTotp", @@ -2174,7 +2235,7 @@ "route_pattern": "/api/auth/users/#/totp/disable", "handler": "handle_totp_disable", "source": "src/web/libuv_api_handlers.c", - "line": 449, + "line": 461, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "disableTotp", @@ -2186,7 +2247,7 @@ "route_pattern": "/api/auth/users/#/totp/status", "handler": "handle_totp_status", "source": "src/web/libuv_api_handlers.c", - "line": 450, + "line": 462, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "getTotpStatus", @@ -2198,7 +2259,7 @@ "route_pattern": "/api/auth/login/totp", "handler": "handle_auth_login_totp", "source": "src/web/libuv_api_handlers.c", - "line": 451, + "line": 463, "coverage": "identity-mfa-password", "classification": "identity-mfa-password", "operation_id": "completeTotpLogin", @@ -2210,7 +2271,7 @@ "route_pattern": "/api/onvif/discovery/status", "handler": "handle_get_onvif_discovery_status", "source": "src/web/libuv_api_handlers.c", - "line": 454, + "line": 466, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getOnvifDiscoveryStatus", @@ -2222,7 +2283,7 @@ "route_pattern": "/api/onvif/devices", "handler": "handle_get_discovered_onvif_devices", "source": "src/web/libuv_api_handlers.c", - "line": 455, + "line": 467, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getOnvifDevices", @@ -2234,7 +2295,7 @@ "route_pattern": "/api/onvif/discovery/discover", "handler": "handle_post_discover_onvif_devices", "source": "src/web/libuv_api_handlers.c", - "line": 456, + "line": 468, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "discoverOnvifDevices", @@ -2246,7 +2307,7 @@ "route_pattern": "/api/onvif/device/profiles", "handler": "handle_get_onvif_device_profiles", "source": "src/web/libuv_api_handlers.c", - "line": 457, + "line": 469, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "getOnvifProfiles", @@ -2258,7 +2319,7 @@ "route_pattern": "/api/onvif/device/add", "handler": "handle_post_add_onvif_device_as_stream", "source": "src/web/libuv_api_handlers.c", - "line": 458, + "line": 470, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "addOnvifDevice", @@ -2270,7 +2331,7 @@ "route_pattern": "/api/onvif/device/claim", "handler": "handle_post_claim_onvif_device", "source": "src/web/libuv_api_handlers.c", - "line": 459, + "line": 471, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "claimOnvifDevice", @@ -2282,7 +2343,7 @@ "route_pattern": "/api/onvif/device/test", "handler": "handle_post_test_onvif_connection", "source": "src/web/libuv_api_handlers.c", - "line": 460, + "line": 472, "coverage": "device-control", "classification": "protected-review-per-route", "operation_id": "testOnvifDevice", @@ -2294,7 +2355,7 @@ "route_pattern": "/api/recordings/thumbnail/#/#", "handler": "handle_recordings_thumbnail", "source": "src/web/libuv_api_handlers.c", - "line": 464, + "line": 476, "coverage": "recording-read", "classification": "camera-replay-read", "operation_id": "getRecordingThumbnail", @@ -2305,7 +2366,7 @@ "route_pattern": "/api/recordings/play/#", "handler": "handle_recordings_playback", "source": "src/web/libuv_api_handlers.c", - "line": 465, + "line": 477, "coverage": "recording-read", "classification": "camera-replay-read", "operation_id": "playRecording", @@ -2316,7 +2377,7 @@ "route_pattern": "/api/recordings/download/#", "handler": "handle_recordings_download", "source": "src/web/libuv_api_handlers.c", - "line": 466, + "line": 478, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "downloadRecording", @@ -2328,7 +2389,7 @@ "route_pattern": "/api/recordings/files/check", "handler": "handle_check_recording_file", "source": "src/web/libuv_api_handlers.c", - "line": 467, + "line": 479, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "checkEvidenceRecordingFile", @@ -2340,7 +2401,7 @@ "route_pattern": "/api/recordings/files", "handler": "handle_delete_recording_file", "source": "src/web/libuv_api_handlers.c", - "line": 468, + "line": 480, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "deleteEvidenceRecordingFile", @@ -2352,7 +2413,7 @@ "route_pattern": "/api/recordings/batch-delete/progress/#", "handler": "handle_batch_delete_progress", "source": "src/web/libuv_api_handlers.c", - "line": 469, + "line": 481, "coverage": "recording-action", "classification": "creator-token-bound-job", "operation_id": "getRecordingDeletionProgress", @@ -2364,7 +2425,7 @@ "route_pattern": "/api/recordings/batch-delete", "handler": "handle_batch_delete_recordings", "source": "src/web/libuv_api_handlers.c", - "line": 470, + "line": 482, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "deleteRecordings", @@ -2376,7 +2437,7 @@ "route_pattern": "/api/recordings/batch-download/status/#", "handler": "handle_batch_download_status", "source": "src/web/libuv_api_handlers.c", - "line": 471, + "line": 483, "coverage": "recording-action", "classification": "creator-token-bound-job", "operation_id": "getRecordingExportStatus", @@ -2388,7 +2449,7 @@ "route_pattern": "/api/recordings/batch-download/result/#", "handler": "handle_batch_download_result", "source": "src/web/libuv_api_handlers.c", - "line": 472, + "line": 484, "coverage": "recording-action", "classification": "creator-token-bound-job", "operation_id": "downloadRecordingExport", @@ -2400,7 +2461,7 @@ "route_pattern": "/api/recordings/batch-download", "handler": "handle_batch_download_recordings", "source": "src/web/libuv_api_handlers.c", - "line": 473, + "line": 485, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "createRecordingExport", @@ -2412,7 +2473,7 @@ "route_pattern": "/api/recordings/protected", "handler": "handle_get_protected_recordings", "source": "src/web/libuv_api_handlers.c", - "line": 474, + "line": 486, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "countEvidenceProtected", @@ -2424,7 +2485,7 @@ "route_pattern": "/api/recordings/batch-protect", "handler": "handle_batch_protect_recordings", "source": "src/web/libuv_api_handlers.c", - "line": 475, + "line": 487, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "protectRecordings", @@ -2436,7 +2497,7 @@ "route_pattern": "/api/recordings/sync", "handler": "handle_post_recordings_sync", "source": "src/web/libuv_api_handlers.c", - "line": 476, + "line": 488, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "syncEvidenceRecordings", @@ -2448,7 +2509,7 @@ "route_pattern": "/api/recordings/tags", "handler": "handle_get_recording_tags", "source": "src/web/libuv_api_handlers.c", - "line": 477, + "line": 489, "coverage": "recording-read", "classification": "replay-filtered-read", "operation_id": "listRecordingTags", @@ -2459,7 +2520,7 @@ "route_pattern": "/api/recordings/detection-labels", "handler": "handle_get_recording_detection_labels", "source": "src/web/libuv_api_handlers.c", - "line": 478, + "line": 490, "coverage": "recording-read", "classification": "replay-filtered-read", "operation_id": "listRecordingDetectionLabels", @@ -2470,7 +2531,7 @@ "route_pattern": "/api/recordings/batch-tags", "handler": "handle_batch_recording_tags", "source": "src/web/libuv_api_handlers.c", - "line": 479, + "line": 491, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "updateRecordingTagsBatch", @@ -2482,7 +2543,7 @@ "route_pattern": "/api/recordings/#/tags", "handler": "handle_get_recording_tags_by_id", "source": "src/web/libuv_api_handlers.c", - "line": 480, + "line": 492, "coverage": "recording-read", "classification": "camera-replay-read", "operation_id": "getRecordingTags", @@ -2493,7 +2554,7 @@ "route_pattern": "/api/recordings/#/tags", "handler": "handle_put_recording_tags", "source": "src/web/libuv_api_handlers.c", - "line": 481, + "line": 493, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "setRecordingTags", @@ -2505,7 +2566,7 @@ "route_pattern": "/api/recordings/#/protect", "handler": "handle_put_recording_protect", "source": "src/web/libuv_api_handlers.c", - "line": 482, + "line": 494, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "setRecordingProtection", @@ -2517,7 +2578,7 @@ "route_pattern": "/api/recordings/#/retention", "handler": "handle_put_recording_retention", "source": "src/web/libuv_api_handlers.c", - "line": 483, + "line": 495, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "setRecordingRetention", @@ -2529,7 +2590,7 @@ "route_pattern": "/api/recordings/#", "handler": "handle_get_recording", "source": "src/web/libuv_api_handlers.c", - "line": 484, + "line": 496, "coverage": "recording-read", "classification": "camera-replay-read", "operation_id": "getRecording", @@ -2540,7 +2601,7 @@ "route_pattern": "/api/recordings/#", "handler": "handle_delete_recording", "source": "src/web/libuv_api_handlers.c", - "line": 485, + "line": 497, "coverage": "recording-action", "classification": "capture-identity-action", "operation_id": "deleteRecording", @@ -2552,7 +2613,7 @@ "route_pattern": "/api/recordings", "handler": "handle_get_recordings", "source": "src/web/libuv_api_handlers.c", - "line": 486, + "line": 498, "coverage": "recording-read", "classification": "replay-filtered-read", "operation_id": "listRecordings", @@ -2563,7 +2624,7 @@ "route_pattern": "/api/timeline/segments-by-ids", "handler": "handle_get_timeline_segments_by_ids", "source": "src/web/libuv_api_handlers.c", - "line": 489, + "line": 501, "coverage": "recording-read", "classification": "replay-filtered-read", "operation_id": "getTimelineSegmentsByIds", @@ -2574,7 +2635,7 @@ "route_pattern": "/api/timeline/segments", "handler": "handle_get_timeline_segments", "source": "src/web/libuv_api_handlers.c", - "line": 490, + "line": 502, "coverage": "recording-read", "classification": "camera-replay-read", "operation_id": "getTimelineSegments", @@ -2585,7 +2646,7 @@ "route_pattern": "/api/timeline/manifest", "handler": "handle_timeline_manifest", "source": "src/web/libuv_api_handlers.c", - "line": 491, + "line": 503, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "getEvidenceManifest", @@ -2597,7 +2658,7 @@ "route_pattern": "/api/timeline/play", "handler": "handle_timeline_playback", "source": "src/web/libuv_api_handlers.c", - "line": 492, + "line": 504, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "redirectEvidencePlayback", @@ -2609,7 +2670,7 @@ "route_pattern": "/api/investigations/timeline", "handler": "handle_post_investigation_timeline", "source": "src/web/libuv_api_handlers.c", - "line": 493, + "line": 505, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "queryEvidenceTimeline", @@ -2621,7 +2682,7 @@ "route_pattern": "/api/investigations/segment-at", "handler": "handle_post_investigation_segment_at", "source": "src/web/libuv_api_handlers.c", - "line": 494, + "line": 506, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "resolveEvidenceSegment", @@ -2633,7 +2694,7 @@ "route_pattern": "/api/investigations/search", "handler": "handle_post_investigation_search", "source": "src/web/libuv_api_handlers.c", - "line": 495, + "line": 507, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "searchEvidence", @@ -2645,7 +2706,7 @@ "route_pattern": "/api/investigations/recordings/preview", "handler": "handle_post_investigation_recording_preview", "source": "src/web/libuv_api_handlers.c", - "line": 496, + "line": 508, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "previewEvidenceRecordings", @@ -2657,7 +2718,7 @@ "route_pattern": "/api/investigations/thumbnail-samples", "handler": "handle_post_investigation_thumbnail_samples", "source": "src/web/libuv_api_handlers.c", - "line": 497, + "line": 509, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "sampleEvidenceThumbnails", @@ -2669,7 +2730,7 @@ "route_pattern": "/api/investigations/thumbnail/#/#", "handler": "handle_investigation_thumbnail", "source": "src/web/libuv_api_handlers.c", - "line": 498, + "line": 510, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "getEvidenceThumbnail", @@ -2681,7 +2742,7 @@ "route_pattern": "/api/investigation-bookmarks/#", "handler": "handle_get_investigation_bookmark", "source": "src/web/libuv_api_handlers.c", - "line": 499, + "line": 511, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "getEvidenceBookmark", @@ -2693,7 +2754,7 @@ "route_pattern": "/api/investigation-bookmarks/#", "handler": "handle_put_investigation_bookmark", "source": "src/web/libuv_api_handlers.c", - "line": 500, + "line": 512, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "updateEvidenceBookmark", @@ -2705,7 +2766,7 @@ "route_pattern": "/api/investigation-bookmarks/#", "handler": "handle_delete_investigation_bookmark", "source": "src/web/libuv_api_handlers.c", - "line": 501, + "line": 513, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "deleteEvidenceBookmark", @@ -2717,7 +2778,7 @@ "route_pattern": "/api/investigation-bookmarks", "handler": "handle_get_investigation_bookmarks", "source": "src/web/libuv_api_handlers.c", - "line": 502, + "line": 514, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "listEvidenceBookmarks", @@ -2729,7 +2790,7 @@ "route_pattern": "/api/investigation-bookmarks", "handler": "handle_post_investigation_bookmark", "source": "src/web/libuv_api_handlers.c", - "line": 503, + "line": 515, "coverage": "evidence", "classification": "protected-review-per-route", "operation_id": "createEvidenceBookmark", @@ -2741,7 +2802,7 @@ "route_pattern": "/hls/#/#", "handler": "handle_direct_hls_request", "source": "src/web/libuv_api_handlers.c", - "line": 507, + "line": 519, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2751,7 +2812,7 @@ "route_pattern": "/go2rtc/api/ws", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 514, + "line": 526, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2761,7 +2822,7 @@ "route_pattern": "/go2rtc/api/webrtc", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 515, + "line": 527, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2771,7 +2832,7 @@ "route_pattern": "/go2rtc/api/streams", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 517, + "line": 529, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2781,7 +2842,7 @@ "route_pattern": "/go2rtc/api/reload", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 519, + "line": 531, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2791,7 +2852,7 @@ "route_pattern": "/go2rtc/api/stream.m3u8", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 521, + "line": 533, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2801,7 +2862,7 @@ "route_pattern": "/go2rtc/api/hls/*", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 523, + "line": 535, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2811,7 +2872,7 @@ "route_pattern": "/go2rtc/api/frame.jpeg", "handler": "handle_go2rtc_proxy", "source": "src/web/libuv_api_handlers.c", - "line": 525, + "line": 537, "coverage": "excluded", "classification": "media-protocol", "reason": "Binary/signaling protocol; see docs/api/contracts/media-protocols.md and media-authority-evidence.md for request, authority, status, payload and owned protocol evidence." @@ -2825,7 +2886,7 @@ "eligibility": "authenticated", "classification": "protected-discovery", "source": "src/web/libuv_api_handlers.c", - "line": 96, + "line": 97, "canonical_path": "/api/openapi.json" }, { @@ -2833,11 +2894,11 @@ "route_pattern": "/api/internal/media-lease", "handler": "handle_internal_media_lease", "source": "src/web/libuv_api_handlers.c", - "line": 510, + "line": 522, "coverage": "excluded", "classification": "internal-protocol", "reason": "Loopback-only opaque lease renewal between managed go2rtc and LightNVR; deliberately excluded from public discovery." } ], - "source_sha256": "17c0b9896b310c308858d2360e80bdb23e50056cb5387909490126966ae62e8a" + "source_sha256": "5abcb74cf897b88acb15337307b4f1b2962dfb123f6a9b416274692deb556c5e" } diff --git a/docs/api/openapi.json b/docs/api/openapi.json index 013e41397..c06316cfc 100644 --- a/docs/api/openapi.json +++ b/docs/api/openapi.json @@ -63,6 +63,10 @@ { "name": "Device controls", "description": "PTZ, imaging and ONVIF inventory and device commands." + }, + { + "name": "Integrations", + "description": "Durable detection observations, evidence images, history cursors and integration capabilities (INT_01)." } ], "x-lightnvr-coverage": { @@ -300,9 +304,14 @@ "setStreamRetention", "getStreamDetectionEngines", "setStreamDetectionEngines", - "getIceServers" + "getIceServers", + "getIntegrationCapabilities", + "listObservations", + "getObservation", + "getObservationSnapshot", + "putObservationClassification" ], - "description": "232 reviewed OpenAPI operations; eight registered media-protocol surfaces and one internal callback have explicit inventory dispositions outside this document. Discovery is filtered by current authority and never grants access." + "description": "237 reviewed OpenAPI operations; eight registered media-protocol surfaces and one internal callback have explicit inventory dispositions outside this document. Discovery is filtered by current authority and never grants access." }, "x-lightnvr-validators": [ "StreamSummaryPage", @@ -610,7 +619,18 @@ "StreamDetectionEngine", "StreamDetectionEnginesRequest", "StreamDetectionEngines", - "IceServersResponse" + "IceServersResponse", + "Observation", + "ObservationPage", + "ObservationEvidence", + "ObservationTransform", + "ObservationChangeEvent", + "ObservationClassification", + "ObservationClassificationWrite", + "ObservationClassificationResult", + "IntegrationCapabilities", + "ObservationError", + "ObservationPipelineDiagnostics" ], "paths": { "/api/streams": { @@ -29053,202 +29073,945 @@ }, "description": "Return configured ICE servers for a principal permitted to view at least one camera. TURN credentials are intentionally provided to that authorized browser." } - } - }, - "components": { - "securitySchemes": { - "sessionCookie": { - "type": "apiKey", - "in": "cookie", - "name": "session", - "description": "Existing authenticated session cookie; checked before other credentials. The explorer uses this method only." - }, - "basicAuth": { - "type": "http", - "scheme": "basic", - "description": "Existing database username/password authentication; restricted when a required password change is pending." - }, - "apiKeyHeader": { - "type": "apiKey", - "in": "header", - "name": "X-API-Key", - "description": "Legacy API key or scoped token. Scope intersects the active owner\u2019s authority." - }, - "bearerToken": { - "type": "http", - "scheme": "bearer", - "description": "Legacy API key or scoped token through Authorization. Use a cookie-free client when discovering token-specific permissions." - }, - "legacyApiKeyHeader": { - "type": "apiKey", - "in": "header", - "name": "X-API-Key", - "description": "Legacy API key only. This operation rejects scoped API tokens even when their owner could call it." - }, - "legacyBearer": { - "type": "http", - "scheme": "bearer", - "description": "Legacy API key through Authorization: Bearer. Scoped tokens are not accepted on this operation. A valid session cookie still takes precedence." - } }, - "schemas": { - "StreamStatus": { - "type": "string", - "enum": [ - "Stopped", - "Starting", - "Running", - "Stopping", - "Error", - "Reconnecting", - "Degraded", - "Unknown" - ] - }, - "PlaybackTransport": { - "type": "string", - "enum": [ - "auto", - "webrtc_only", - "mse_only", - "hls_only", - "webrtc_then_mse", - "mse_then_hls" - ] - }, - "WeeklySchedule": { - "type": "array", - "items": { - "type": "boolean" - }, - "minItems": 168, - "maxItems": 168, - "description": "Seven days \u00d7 24 hours, indexed day*24+hour. true permits recording in that slot; interpreted using the configured recorder schedule timezone." - }, - "StreamDetailsBase": { - "type": "object", - "properties": { - "camera_uuid": { - "type": "string", - "description": "Stable camera UUID. Older migrated rows may have an empty string; no instance identifiers are embedded in this document." - }, - "location_uuid": { - "type": "string", - "description": "Primary location UUID, or an empty string when unset." + "/api/integrations/capabilities": { + "get": { + "operationId": "getIntegrationCapabilities", + "summary": "Get integration capabilities", + "tags": [ + "Integrations" + ], + "security": [ + { + "sessionCookie": [] }, - "name": { - "type": "string", - "description": "Current stream name; lookup is by name, not camera_uuid." + { + "basicAuth": [] }, - "url": { - "type": "string", - "description": "Configured input URL. For readers without camera.configure, URL userinfo is stripped; unsupported URL formats may become an empty string. For configurators the legacy/custom form may remain, while ONVIF/separate-credential configurations still strip URL userinfo." + { + "apiKeyHeader": [] }, - "eptz_config": { - "type": "string", - "maxLength": 1023, - "description": "Empty string disables browser dewarping; otherwise a serialized version-1 JSON document. It is a string on the wire, not an object." + { + "bearerToken": [] + } + ], + "x-lightnvr-authorization": { + "version": 1, + "kind": "authenticated" + }, + "description": "Requires an authenticated principal and returns only its scope: source identity, contract versions, operations (supported and authorized), limits, history, delivery and the cameras where the principal holds live.view, snapshot.create or observations.annotate (contract \u00a79). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.", + "responses": { + "200": { + "description": "Capability document for the calling principal.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/IntegrationCapabilities" + } + } + } }, - "codec": { - "type": "string" + "401": { + "description": "No usable authenticated credential (existing auth layer body).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + } }, - "detection_model": { - "type": "string" + "500": { + "description": "Internal failure (code internal_error).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "detection_object_filter": { - "type": "string" + "503": { + "description": "Temporarily unavailable (code service_unavailable).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds before retrying.", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + } + } + } + }, + "/api/observations": { + "get": { + "operationId": "listObservations", + "summary": "List observation history", + "tags": [ + "Integrations" + ], + "security": [ + { + "sessionCookie": [] }, - "detection_object_filter_list": { - "type": "string" + { + "basicAuth": [] }, - "onvif_profile": { - "type": "string" + { + "apiKeyHeader": [] }, - "tags": { - "type": "string" + { + "bearerToken": [] + } + ], + "x-lightnvr-authorization": { + "version": 1, + "kind": "camera", + "requirements": [ + { + "action": "live.view", + "resource": "raw" + } + ] + }, + "description": "Cursor-paged observation changes, ordered by persistence sequence and bounded by a watermark taken at query start (contract \u00a77). Results are limited to capture cameras where the principal holds live.view; an explicitly requested camera without it returns 403. limit counts observations after in-page dedup. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.", + "responses": { + "200": { + "description": "History page. Items hold current state; dedupe by (source, id, revision).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationPage" + } + } + } }, - "motion_trigger_source": { - "type": "string" + "400": { + "description": "Invalid filter (invalid_request) or malformed cursor (invalid_cursor).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "enabled": { - "type": "boolean" + "403": { + "description": "A requested camera_uuid is not authorized for live.view (code forbidden).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "streaming_enabled": { - "type": "boolean" + "410": { + "description": "Cursor older than retained history, from another DB epoch, or ahead of the head (code cursor_expired); resume from oldest_cursor.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "record": { - "type": "boolean" + "401": { + "description": "No usable authenticated credential (existing auth layer body).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + } }, - "detection_based_recording": { - "type": "boolean" + "500": { + "description": "Internal failure (code internal_error).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "record_audio": { - "type": "boolean" + "503": { + "description": "Temporarily unavailable (code service_unavailable).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds before retrying.", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + } + }, + "parameters": [ + { + "name": "camera_uuid", + "in": "query", + "required": false, + "description": "Comma-separated capture camera UUIDs, at most 10 (contract D1). Default: all cameras where the principal holds live.view.", + "schema": { + "type": "string", + "maxLength": 369, + "pattern": "^[0-9a-fA-F-]{36}(,[0-9a-fA-F-]{36}){0,9}$" + } }, - "audio_voice_enhancement": { - "type": "boolean" + { + "name": "label", + "in": "query", + "required": false, + "description": "Comma-separated original detector labels, at most 8 of 1..31 bytes; exact ASCII case-insensitive match (contract D1).", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 255 + } }, - "isOnvif": { - "type": "boolean" + { + "name": "since", + "in": "query", + "required": false, + "description": "Inclusive lower bound on observed_at.", + "schema": { + "$ref": "#/components/schemas/ObservationTime" + } }, - "backchannel_enabled": { - "type": "boolean" + { + "name": "until", + "in": "query", + "required": false, + "description": "Exclusive upper bound on observed_at; must be after since.", + "schema": { + "$ref": "#/components/schemas/ObservationTime" + } }, - "ptz_enabled": { - "type": "boolean" + { + "name": "cursor", + "in": "query", + "required": false, + "description": "Opaque cursor from next_cursor or oldest_cursor, or the literal head (start live-only). Absent: start of retained history.", + "schema": { + "type": "string", + "minLength": 1, + "maxLength": 256 + } }, - "ptz_has_home": { - "type": "boolean" + { + "name": "limit", + "in": "query", + "required": false, + "description": "Observations per page.", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 500, + "default": 100 + } + } + ] + } + }, + "/api/observations/{id}": { + "get": { + "operationId": "getObservation", + "summary": "Get observation", + "tags": [ + "Integrations" + ], + "security": [ + { + "sessionCookie": [] }, - "record_on_schedule": { - "type": "boolean" + { + "basicAuth": [] }, - "detection_record_on_schedule": { - "type": "boolean" + { + "apiKeyHeader": [] }, - "privacy_mode": { - "type": "boolean" + { + "bearerToken": [] + } + ], + "x-lightnvr-authorization": { + "version": 1, + "kind": "camera", + "requirements": [ + { + "action": "live.view", + "resource": "raw" + } + ] + }, + "description": "One observation with its classifications, authorized by live.view on its immutable capture camera (contract \u00a78.1). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.", + "responses": { + "200": { + "description": "Current observation state.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Observation" + } + } + } }, - "has_sub_stream": { - "type": "boolean" + "400": { + "description": "Invalid observation ID (code invalid_request).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "go2rtc_hls_available": { - "type": "boolean" + "404": { + "description": "Unknown observation, no live.view on its capture camera, or the capture camera was deleted; indistinguishable (code observation_not_found).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "width": { - "type": "integer", - "description": "Detected metadata; zero is valid when unavailable." + "401": { + "description": "No usable authenticated credential (existing auth layer body).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + } }, - "height": { - "type": "integer", - "description": "Detected metadata; zero is valid when unavailable." + "500": { + "description": "Internal failure (code internal_error).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "fps": { - "type": "integer", - "description": "Detected metadata; zero is valid when unavailable." + "503": { + "description": "Temporarily unavailable (code service_unavailable).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds before retrying.", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Observation ID.", + "schema": { + "$ref": "#/components/schemas/ObservationUuid" + } + } + ] + } + }, + "/api/observations/{id}/snapshot": { + "get": { + "operationId": "getObservationSnapshot", + "summary": "Get observation evidence image", + "tags": [ + "Integrations" + ], + "security": [ + { + "sessionCookie": [] }, - "priority": { - "type": "integer" + { + "basicAuth": [] }, - "segment_duration": { - "type": "integer" + { + "apiKeyHeader": [] }, - "detection_threshold": { - "type": "integer", - "description": "Integer percentage emitted from stored float confidence, including zero. The existing update handler does not clamp this to 0\u2013100." + { + "bearerToken": [] + } + ], + "x-lightnvr-authorization": { + "version": 1, + "kind": "camera", + "requirements": [ + { + "action": "snapshot.create", + "resource": "raw" + } + ] + }, + "description": "The unannotated evidence JPEG that was the inference input or submitted image (contract \u00a78.2). Requires snapshot.create on the capture camera; a caller with live.view but not snapshot.create gets 403 (contract D4). Never substitutes a latest frame or another asset. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.", + "responses": { + "200": { + "description": "Evidence image bytes; not JSON.", + "headers": { + "Cache-Control": { + "description": "private, max-age=300", + "schema": { + "type": "string" + } + }, + "ETag": { + "description": "Quoted asset ID.", + "schema": { + "type": "string" + } + } + }, + "content": { + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } + } }, - "detection_interval": { - "type": "integer" + "202": { + "description": "Evidence is still pending (code evidence_pending). Honour Retry-After and give up polling after 120 s total.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + }, + "headers": { + "Retry-After": { + "description": "Integer seconds, 1..30.", + "schema": { + "type": "integer", + "minimum": 1, + "maximum": 30 + } + } + } }, - "pre_detection_buffer": { - "type": "integer" + "304": { + "description": "If-None-Match matched the ETag." }, - "post_detection_buffer": { - "type": "integer" + "400": { + "description": "Invalid observation ID (code invalid_request).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } }, - "protocol": { - "type": "integer", - "description": "Stored transport code: 0 TCP, 1 UDP. Existing configuration paths may retain other numeric values." + "403": { + "description": "live.view is held but snapshot.create is not (code forbidden).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "404": { + "description": "Evidence unavailable with a reason (evidence_unavailable), or the observation is unknown or not visible (observation_not_found).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "410": { + "description": "Evidence media expired (code evidence_expired); metadata remains readable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "401": { + "description": "No usable authenticated credential (existing auth layer body).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + } + }, + "500": { + "description": "Internal failure (code internal_error).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "503": { + "description": "Temporarily unavailable (code service_unavailable).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds before retrying.", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Observation ID.", + "schema": { + "$ref": "#/components/schemas/ObservationUuid" + } + }, + { + "name": "If-None-Match", + "in": "header", + "required": false, + "schema": { + "type": "string" + } + } + ] + } + }, + "/api/observations/{id}/classifications/{namespace}/{name}": { + "put": { + "operationId": "putObservationClassification", + "summary": "Put observation classification", + "tags": [ + "Integrations" + ], + "security": [ + { + "sessionCookie": [] + }, + { + "basicAuth": [] + }, + { + "apiKeyHeader": [] + }, + { + "bearerToken": [] + } + ], + "x-lightnvr-authorization": { + "version": 1, + "kind": "camera", + "requirements": [ + { + "action": "observations.annotate", + "resource": "raw" + } + ] + }, + "description": "Create or update one classification annotation on an observation (contract \u00a710). Authorized with observations.annotate on the observation's capture-time camera (contract \u00a712.1); authorization is evaluated before the namespace and the body, so an observation the caller cannot see is an indistinguishable 404. The key is two path segments, {namespace}/{name}; namespace must equal the server-derived writer namespace of the authenticated user (D3, advertised as capabilities principal.writer_namespace). Identical retries are no-ops (200, changed=false). A stored change bumps the observation revision and publishes an io.lightnvr.detection.observation_changed.v1 event with change_cause classification. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.", + "responses": { + "200": { + "description": "Annotation updated, or identical retry (changed=false).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationClassificationResult" + } + } + } + }, + "201": { + "description": "Annotation created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationClassificationResult" + } + } + } + }, + "400": { + "description": "Invalid or unknown body field (code invalid_request).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "403": { + "description": "live.view without observations.annotate on the capture camera (forbidden), or a key namespace or body writer field that is not the caller's derived namespace, or a key created by another user (namespace_forbidden).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "404": { + "description": "Unknown observation, neither live.view nor observations.annotate on its capture camera, or the capture camera was deleted; indistinguishable (code observation_not_found).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "409": { + "description": "revision_conflict or owner_decision_locked (current carries the stored annotation when the key exists), or annotation_limit (limit).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "422": { + "description": "input_asset_id, input_revision or the evidence state does not match the observation evidence (code evidence_mismatch, with observation_asset_id and evidence_state).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "401": { + "description": "No usable authenticated credential (existing auth layer body).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiError" + } + } + } + }, + "500": { + "description": "Internal failure (code internal_error).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + } + }, + "503": { + "description": "Temporarily unavailable (code service_unavailable).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationError" + } + } + }, + "headers": { + "Retry-After": { + "description": "Seconds before retrying.", + "schema": { + "type": "integer", + "minimum": 1 + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "Observation ID.", + "schema": { + "$ref": "#/components/schemas/ObservationUuid" + } + }, + { + "name": "namespace", + "in": "path", + "required": true, + "description": "Writer namespace.", + "schema": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + }, + { + "name": "name", + "in": "path", + "required": true, + "description": "Annotation name.", + "schema": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ObservationClassificationWrite" + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "sessionCookie": { + "type": "apiKey", + "in": "cookie", + "name": "session", + "description": "Existing authenticated session cookie; checked before other credentials. The explorer uses this method only." + }, + "basicAuth": { + "type": "http", + "scheme": "basic", + "description": "Existing database username/password authentication; restricted when a required password change is pending." + }, + "apiKeyHeader": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + "description": "Legacy API key or scoped token. Scope intersects the active owner\u2019s authority." + }, + "bearerToken": { + "type": "http", + "scheme": "bearer", + "description": "Legacy API key or scoped token through Authorization. Use a cookie-free client when discovering token-specific permissions." + }, + "legacyApiKeyHeader": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + "description": "Legacy API key only. This operation rejects scoped API tokens even when their owner could call it." + }, + "legacyBearer": { + "type": "http", + "scheme": "bearer", + "description": "Legacy API key through Authorization: Bearer. Scoped tokens are not accepted on this operation. A valid session cookie still takes precedence." + } + }, + "schemas": { + "StreamStatus": { + "type": "string", + "enum": [ + "Stopped", + "Starting", + "Running", + "Stopping", + "Error", + "Reconnecting", + "Degraded", + "Unknown" + ] + }, + "PlaybackTransport": { + "type": "string", + "enum": [ + "auto", + "webrtc_only", + "mse_only", + "hls_only", + "webrtc_then_mse", + "mse_then_hls" + ] + }, + "WeeklySchedule": { + "type": "array", + "items": { + "type": "boolean" + }, + "minItems": 168, + "maxItems": 168, + "description": "Seven days \u00d7 24 hours, indexed day*24+hour. true permits recording in that slot; interpreted using the configured recorder schedule timezone." + }, + "StreamDetailsBase": { + "type": "object", + "properties": { + "camera_uuid": { + "type": "string", + "description": "Stable camera UUID. Older migrated rows may have an empty string; no instance identifiers are embedded in this document." + }, + "location_uuid": { + "type": "string", + "description": "Primary location UUID, or an empty string when unset." + }, + "name": { + "type": "string", + "description": "Current stream name; lookup is by name, not camera_uuid." + }, + "url": { + "type": "string", + "description": "Configured input URL. For readers without camera.configure, URL userinfo is stripped; unsupported URL formats may become an empty string. For configurators the legacy/custom form may remain, while ONVIF/separate-credential configurations still strip URL userinfo." + }, + "eptz_config": { + "type": "string", + "maxLength": 1023, + "description": "Empty string disables browser dewarping; otherwise a serialized version-1 JSON document. It is a string on the wire, not an object." + }, + "codec": { + "type": "string" + }, + "detection_model": { + "type": "string" + }, + "detection_object_filter": { + "type": "string" + }, + "detection_object_filter_list": { + "type": "string" + }, + "onvif_profile": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "motion_trigger_source": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "streaming_enabled": { + "type": "boolean" + }, + "record": { + "type": "boolean" + }, + "detection_based_recording": { + "type": "boolean" + }, + "record_audio": { + "type": "boolean" + }, + "audio_voice_enhancement": { + "type": "boolean" + }, + "isOnvif": { + "type": "boolean" + }, + "backchannel_enabled": { + "type": "boolean" + }, + "ptz_enabled": { + "type": "boolean" + }, + "ptz_has_home": { + "type": "boolean" + }, + "record_on_schedule": { + "type": "boolean" + }, + "detection_record_on_schedule": { + "type": "boolean" + }, + "privacy_mode": { + "type": "boolean" + }, + "has_sub_stream": { + "type": "boolean" + }, + "go2rtc_hls_available": { + "type": "boolean" + }, + "width": { + "type": "integer", + "description": "Detected metadata; zero is valid when unavailable." + }, + "height": { + "type": "integer", + "description": "Detected metadata; zero is valid when unavailable." + }, + "fps": { + "type": "integer", + "description": "Detected metadata; zero is valid when unavailable." + }, + "priority": { + "type": "integer" + }, + "segment_duration": { + "type": "integer" + }, + "detection_threshold": { + "type": "integer", + "description": "Integer percentage emitted from stored float confidence, including zero. The existing update handler does not clamp this to 0\u2013100." + }, + "detection_interval": { + "type": "integer" + }, + "pre_detection_buffer": { + "type": "integer" + }, + "post_detection_buffer": { + "type": "integer" + }, + "protocol": { + "type": "integer", + "description": "Stored transport code: 0 TCP, 1 UDP. Existing configuration paths may retain other numeric values." }, "retention_days": { "type": "integer", @@ -29305,6 +30068,31 @@ "error_message": { "type": "string", "description": "Present only when an Error state has a recorded reason." + }, + "observation_evidence_enabled": { + "type": "boolean", + "default": false, + "description": "INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations." + }, + "observation_labels": { + "type": "string", + "maxLength": 511, + "default": "", + "description": "Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13)." + }, + "observation_max_age_days": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "default": 7, + "description": "Evidence media maximum age in days (contract section 13)." + }, + "observation_max_bytes": { + "type": "integer", + "minimum": 67108864, + "maximum": 1099511627776, + "default": 2147483648, + "description": "Evidence media byte budget for this camera, 64 MiB..1 TiB (contract section 13)." } }, "required": [ @@ -30387,6 +31175,27 @@ "set_privacy_mode": { "type": "null", "description": "A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form." + }, + "observation_evidence_enabled": { + "type": "boolean", + "description": "INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + "observation_labels": { + "type": "string", + "maxLength": 511, + "description": "Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + "observation_max_age_days": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "description": "Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + "observation_max_bytes": { + "type": "integer", + "minimum": 67108864, + "maximum": 1099511627776, + "description": "Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." } }, "required": [], @@ -31408,7 +32217,8 @@ "lpr.read", "lpr.search", "lpr.export", - "lpr.delete" + "lpr.delete", + "observations.annotate" ] }, "ApiTokenCreateRequest": { @@ -34656,7 +35466,8 @@ "lpr.read", "lpr.search", "lpr.export", - "lpr.delete" + "lpr.delete", + "observations.annotate" ] }, "camera_uuid": { @@ -37739,6 +38550,9 @@ }, "uptimeCapability": { "type": "string" + }, + "observation_pipeline": { + "$ref": "#/components/schemas/ObservationPipelineDiagnostics" } }, "additionalProperties": true, @@ -37760,6 +38574,276 @@ "streamStorage" ] }, + "ObservationPipelineDiagnostics": { + "type": "object", + "description": "INT_01 observation pipeline diagnostics (observation_diagnostics_to_json(), contract section 15). Counters are per process run unless noted; per-camera rows come from the last retention pass and may be up to 5 minutes old.", + "properties": { + "active": { + "type": "boolean", + "description": "False when evidence is unused: no queue exists, counters are zero and no observations are retained." + }, + "accepted": { + "type": "integer", + "minimum": 0 + }, + "dropped": { + "type": "object", + "description": "Observations dropped at admission, by reason.", + "properties": { + "queue_full": { + "type": "integer", + "minimum": 0 + }, + "budget_exhausted": { + "type": "integer", + "minimum": 0 + }, + "disabled": { + "type": "integer", + "minimum": 0 + }, + "untracked": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "queue_full", + "budget_exhausted", + "disabled", + "untracked" + ], + "additionalProperties": true + }, + "evidence": { + "type": "object", + "properties": { + "queue_allocated": { + "type": "boolean" + }, + "worker_running": { + "type": "boolean" + }, + "backlog_items": { + "type": "integer", + "minimum": 0 + }, + "backlog_bytes": { + "type": "integer", + "minimum": 0 + }, + "high_water_items": { + "type": "integer", + "minimum": 0 + }, + "high_water_bytes": { + "type": "integer", + "minimum": 0 + }, + "drop_ring_depth": { + "type": "integer", + "minimum": 0 + }, + "ready": { + "type": "integer", + "minimum": 0 + }, + "unavailable_submitted": { + "type": "integer", + "minimum": 0 + }, + "bytes_written": { + "type": "integer", + "minimum": 0 + }, + "failures": { + "type": "object", + "properties": { + "encode_failed": { + "type": "integer", + "minimum": 0 + }, + "write_failed": { + "type": "integer", + "minimum": 0 + }, + "db_error": { + "type": "integer", + "minimum": 0 + }, + "disabled": { + "type": "integer", + "minimum": 0 + }, + "interrupted": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "encode_failed", + "write_failed", + "db_error", + "disabled", + "interrupted" + ], + "additionalProperties": true + } + }, + "required": [ + "queue_allocated", + "worker_running", + "backlog_items", + "backlog_bytes", + "high_water_items", + "high_water_bytes", + "drop_ring_depth", + "ready", + "unavailable_submitted", + "bytes_written", + "failures" + ], + "additionalProperties": true + }, + "history": { + "type": "object", + "properties": { + "available": { + "type": "boolean", + "description": "False when the observation tables could not be read; the DB-derived values are then zero." + }, + "observations": { + "type": "integer", + "minimum": 0 + }, + "oldest_seq": { + "type": "integer", + "minimum": 0 + }, + "head_seq": { + "type": "integer", + "minimum": 0 + }, + "oldest_observed_at": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/ObservationTime" + } + ] + }, + "epoch_regenerations": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "available", + "observations", + "oldest_seq", + "head_seq", + "oldest_observed_at", + "epoch_regenerations" + ], + "additionalProperties": true + }, + "publications": { + "type": "object", + "properties": { + "pending": { + "type": "integer", + "minimum": 0 + }, + "failed": { + "type": "integer", + "minimum": 0 + }, + "expired": { + "type": "integer", + "minimum": 0 + }, + "relay_lag_seconds": { + "type": "number", + "minimum": 0, + "description": "Age of the oldest pending publication; 0 when none is pending." + }, + "relay_running": { + "type": "boolean" + }, + "relayed": { + "type": "integer", + "minimum": 0 + }, + "relayed_no_route": { + "type": "integer", + "minimum": 0 + } + }, + "required": [ + "pending", + "failed", + "expired", + "relay_lag_seconds", + "relay_running", + "relayed", + "relayed_no_route" + ], + "additionalProperties": true + }, + "budget_exhausted_cameras": { + "type": "integer", + "minimum": 0 + }, + "cameras": { + "type": "array", + "items": { + "type": "object", + "properties": { + "camera_uuid": { + "type": "string", + "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + }, + "retained_bytes": { + "type": "integer", + "minimum": 0 + }, + "max_bytes": { + "type": "integer", + "minimum": 0 + }, + "max_age_days": { + "type": "integer", + "minimum": 0 + }, + "budget_exhausted": { + "type": "boolean" + } + }, + "required": [ + "camera_uuid", + "retained_bytes", + "max_bytes", + "max_age_days", + "budget_exhausted" + ], + "additionalProperties": true + } + } + }, + "required": [ + "active", + "accepted", + "dropped", + "evidence", + "history", + "publications", + "budget_exhausted_cameras", + "cameras" + ], + "additionalProperties": true + }, "SystemLogs": { "type": "object", "properties": { @@ -42789,6 +43873,9 @@ }, "end_timestamp": { "type": "integer" + }, + "observation": { + "$ref": "#/components/schemas/EvidenceDetectionObservation" } }, "required": [ @@ -42803,6 +43890,34 @@ ], "additionalProperties": false }, + "EvidenceDetectionObservation": { + "type": "object", + "description": "INT_01 P2 (L15). Present only when the detection links to an observation of the same camera: observed in the same second \u00b11 with the same label (ASCII case-insensitive), nearest second first. classification is the annotation to display \u2014 owner_confirmed[0] before automated[0] of GET /api/observations/{id} \u2014 or null; the detection's own label and confidence are the detector's and never change.", + "properties": { + "id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "owner_confirmed": { + "type": "boolean" + }, + "classification": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationClassification" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "id", + "owner_confirmed", + "classification" + ], + "additionalProperties": false + }, "EvidenceDetections": { "type": "object", "properties": { @@ -46892,6 +48007,27 @@ "set_privacy_mode": { "type": "null", "description": "A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form." + }, + "observation_evidence_enabled": { + "type": "boolean", + "description": "INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + "observation_labels": { + "type": "string", + "maxLength": 511, + "description": "Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + "observation_max_age_days": { + "type": "integer", + "minimum": 1, + "maximum": 90, + "description": "Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + "observation_max_bytes": { + "type": "integer", + "minimum": 67108864, + "maximum": 1099511627776, + "description": "Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." } }, "required": [ @@ -47635,6 +48771,1230 @@ "ice_servers" ], "additionalProperties": true + }, + "ObservationUuid": { + "type": "string", + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", + "description": "Lowercase canonical UUID (contract \u00a72)." + }, + "ObservationTime": { + "type": "string", + "format": "date-time", + "description": "UTC RFC 3339 with millisecond precision and a Z suffix (contract \u00a72)." + }, + "ObservationEvidenceState": { + "type": "string", + "enum": [ + "pending", + "ready", + "unavailable", + "expired" + ] + }, + "ObservationEvidenceReason": { + "type": "string", + "enum": [ + "queue_full", + "budget_exhausted", + "encode_failed", + "write_failed", + "interrupted", + "no_matching_media", + "disabled", + "retention_expired", + "deleted" + ], + "description": "Bounded reason (contract \u00a74.3). Consumers treat an unknown reason by its state." + }, + "ObservationBox": { + "type": "object", + "properties": { + "format": { + "const": "xywh_normalized" + }, + "x": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "y": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "width": { + "type": "number", + "exclusiveMinimum": 0, + "maximum": 1 + }, + "height": { + "type": "number", + "exclusiveMinimum": 0, + "maximum": 1 + } + }, + "required": [ + "format", + "x", + "y", + "width", + "height" + ], + "additionalProperties": true, + "description": "Top-left corner and size normalized to the stored evidence image (contract \u00a73.2). Consumers never re-apply transform." + }, + "ObservationTransform": { + "type": "object", + "properties": { + "mode": { + "type": "string", + "enum": [ + "none", + "resize", + "letterbox" + ] + }, + "source_width": { + "type": "integer", + "minimum": 1 + }, + "source_height": { + "type": "integer", + "minimum": 1 + }, + "inference_width": { + "type": "integer", + "minimum": 1 + }, + "inference_height": { + "type": "integer", + "minimum": 1 + }, + "scale_x": { + "type": "number", + "exclusiveMinimum": 0 + }, + "scale_y": { + "type": "number", + "exclusiveMinimum": 0 + }, + "pad_x": { + "type": "number", + "minimum": 0 + }, + "pad_y": { + "type": "number", + "minimum": 0 + } + }, + "required": [ + "mode", + "source_width", + "source_height", + "inference_width", + "inference_height", + "scale_x", + "scale_y", + "pad_x", + "pad_y" + ], + "additionalProperties": true, + "description": "Audit record of the inference-to-stored-image mapping: x = (u - pad_x) / scale_x, y = (v - pad_y) / scale_y (contract \u00a74.4)." + }, + "ObservationDetector": { + "type": "object", + "properties": { + "engine_key": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "engine_type": { + "type": "string", + "enum": [ + "object", + "api", + "onvif", + "external" + ] + }, + "model": { + "type": [ + "string", + "null" + ], + "maxLength": 128, + "description": "Model basename only, never a path." + }, + "model_version": { + "type": [ + "string", + "null" + ], + "maxLength": 64 + } + }, + "required": [ + "engine_key", + "engine_type", + "model", + "model_version" + ], + "additionalProperties": true + }, + "ObservationTrack": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^[0-9]{1,64}$" + }, + "namespace": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "session": { + "$ref": "#/components/schemas/ObservationUuid" + } + }, + "required": [ + "id", + "namespace", + "session" + ], + "additionalProperties": true, + "description": "Track hint only; compare id, namespace and session together. No lifecycle is implied (contract \u00a73.4)." + }, + "ObservationRecordingReference": { + "type": "object", + "properties": { + "recording_id": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "recording_id" + ], + "additionalProperties": true + }, + "ObservationEvidence": { + "type": "object", + "properties": { + "asset_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "state": { + "$ref": "#/components/schemas/ObservationEvidenceState" + }, + "reason": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationEvidenceReason" + }, + { + "type": "null" + } + ] + }, + "origin": { + "type": [ + "string", + "null" + ], + "enum": [ + "inference_input", + "submitted_image", + null + ] + }, + "media_type": { + "type": [ + "string", + "null" + ], + "enum": [ + "image/jpeg", + null + ] + }, + "width": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "height": { + "type": [ + "integer", + "null" + ], + "minimum": 1 + }, + "bytes": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "captured_at": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTime" + }, + { + "type": "null" + } + ] + }, + "expires_at": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTime" + }, + { + "type": "null" + } + ] + }, + "transform": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTransform" + }, + { + "type": "null" + } + ] + }, + "snapshot_url": { + "type": "string", + "pattern": "^/api/observations/[0-9a-f-]{36}/snapshot$", + "description": "Present only when state is ready. Relative and authenticated." + } + }, + "required": [ + "asset_id", + "state", + "reason", + "origin", + "media_type", + "width", + "height", + "bytes", + "captured_at", + "expires_at", + "transform" + ], + "additionalProperties": true, + "description": "Evidence asset (contract \u00a74). Every observation has one; siblings in a batch share asset_id.", + "allOf": [ + { + "if": { + "type": "object", + "properties": { + "state": { + "enum": [ + "pending", + "ready" + ] + } + } + }, + "then": { + "type": "object", + "properties": { + "reason": { + "type": "null" + } + } + }, + "else": { + "type": "object", + "properties": { + "reason": { + "$ref": "#/components/schemas/ObservationEvidenceReason" + } + } + } + }, + { + "if": { + "type": "object", + "properties": { + "state": { + "const": "ready" + } + } + }, + "then": { + "type": "object", + "properties": { + "snapshot_url": { + "type": "string" + } + }, + "required": [ + "snapshot_url" + ] + }, + "else": { + "type": "object", + "not": { + "type": "object", + "properties": { + "snapshot_url": { + "type": "string" + } + }, + "required": [ + "snapshot_url" + ] + } + } + } + ] + }, + "ObservationClassification": { + "type": "object", + "properties": { + "key": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$" + }, + "writer_namespace": { + "type": "string", + "pattern": "^[a-z0-9][a-z0-9_.-]{0,31}$" + }, + "classifier": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "model_version": { + "type": [ + "string", + "null" + ], + "maxLength": 64 + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "taxonomy_id": { + "type": [ + "string", + "null" + ], + "maxLength": 64 + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "classified_at": { + "$ref": "#/components/schemas/ObservationTime" + }, + "input_asset_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "input_revision": { + "type": "integer", + "minimum": 1 + }, + "kind": { + "type": "string", + "enum": [ + "automated", + "owner_confirmed" + ] + }, + "annotation_revision": { + "type": "integer", + "minimum": 1 + }, + "created_at": { + "$ref": "#/components/schemas/ObservationTime" + }, + "updated_at": { + "$ref": "#/components/schemas/ObservationTime" + } + }, + "required": [ + "key", + "writer_namespace", + "classifier", + "model_version", + "label", + "taxonomy_id", + "confidence", + "classified_at", + "input_asset_id", + "input_revision", + "kind", + "annotation_revision", + "created_at", + "updated_at" + ], + "additionalProperties": true, + "description": "Stored classification annotation (contract \u00a710.3). P2." + }, + "ObservationClassifications": { + "type": "object", + "properties": { + "automated": { + "type": "array", + "maxItems": 8, + "items": { + "$ref": "#/components/schemas/ObservationClassification" + } + }, + "owner_confirmed": { + "type": "array", + "maxItems": 8, + "items": { + "$ref": "#/components/schemas/ObservationClassification" + } + } + }, + "required": [ + "automated", + "owner_confirmed" + ], + "additionalProperties": true, + "description": "Annotations split by kind, each ordered by updated_at descending. Both lists are empty in P1." + }, + "ObservationClassificationWrite": { + "type": "object", + "properties": { + "classifier": { + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "model_version": { + "type": [ + "string", + "null" + ], + "maxLength": 64 + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "taxonomy_id": { + "type": [ + "string", + "null" + ], + "maxLength": 64 + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "classified_at": { + "$ref": "#/components/schemas/ObservationTime" + }, + "input_asset_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "input_revision": { + "type": "integer", + "minimum": 1 + }, + "kind": { + "type": "string", + "enum": [ + "automated", + "owner_confirmed" + ] + }, + "expected_revision": { + "type": "integer", + "minimum": 0, + "description": "0 or absent means the key must not exist yet." + }, + "writer_namespace": { + "type": "string", + "description": "Optional. Accepted and ignored when it names the principal's derived namespace; any other value returns 403 namespace_forbidden." + }, + "writer": { + "type": "string", + "description": "Same rule as writer_namespace." + } + }, + "required": [ + "classifier", + "label", + "confidence", + "classified_at", + "input_asset_id", + "input_revision", + "kind" + ], + "additionalProperties": false, + "description": "PUT body (contract \u00a710.3). Closed: unknown fields return 400. The writer namespace is derived by the server from the authenticated user, never from this body." + }, + "ObservationClassificationResult": { + "type": "object", + "properties": { + "changed": { + "type": "boolean" + }, + "observation_revision": { + "type": "integer", + "minimum": 1 + }, + "annotation": { + "$ref": "#/components/schemas/ObservationClassification" + } + }, + "required": [ + "changed", + "observation_revision", + "annotation" + ], + "additionalProperties": true + }, + "Observation": { + "type": "object", + "properties": { + "id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "source": { + "type": "string", + "pattern": "^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "camera_uuid": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "stream_name_at_capture": { + "type": "string", + "maxLength": 63, + "description": "Display only." + }, + "batch_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "object_index": { + "type": "integer", + "minimum": 0, + "maximum": 19 + }, + "observed_at": { + "$ref": "#/components/schemas/ObservationTime" + }, + "created_at": { + "$ref": "#/components/schemas/ObservationTime" + }, + "detector": { + "$ref": "#/components/schemas/ObservationDetector" + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 31, + "description": "Original detector label (1..31 bytes); never modified by classification." + }, + "confidence": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "box": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationBox" + }, + { + "type": "null" + } + ] + }, + "evidence": { + "$ref": "#/components/schemas/ObservationEvidence" + }, + "track": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTrack" + }, + { + "type": "null" + } + ] + }, + "recording_reference": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationRecordingReference" + }, + { + "type": "null" + } + ] + }, + "revision": { + "type": "integer", + "minimum": 1 + }, + "classifications": { + "$ref": "#/components/schemas/ObservationClassifications" + } + }, + "required": [ + "id", + "source", + "camera_uuid", + "stream_name_at_capture", + "batch_id", + "object_index", + "observed_at", + "created_at", + "detector", + "label", + "confidence", + "box", + "evidence", + "track", + "recording_reference", + "revision", + "classifications" + ], + "additionalProperties": true, + "description": "One detected object in one accepted inference result (contract \u00a73). Readers ignore unknown fields." + }, + "ObservationPage": { + "type": "object", + "properties": { + "items": { + "type": "array", + "maxItems": 500, + "items": { + "$ref": "#/components/schemas/Observation" + } + }, + "next_cursor": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "has_more": { + "type": "boolean" + } + }, + "required": [ + "items", + "next_cursor", + "has_more" + ], + "additionalProperties": true, + "description": "History page (contract \u00a77.2-7.3). Empty items with has_more=true is valid; continue from next_cursor." + }, + "ObservationChangeEvent": { + "type": "object", + "properties": { + "observation_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "revision": { + "type": "integer", + "minimum": 1 + }, + "camera_uuid": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "label": { + "type": "string", + "minLength": 1, + "maxLength": 31 + }, + "observed_at": { + "$ref": "#/components/schemas/ObservationTime" + }, + "change_cause": { + "type": "string", + "enum": [ + "created", + "evidence", + "classification" + ] + }, + "evidence_state": { + "$ref": "#/components/schemas/ObservationEvidenceState" + }, + "evidence_reason": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationEvidenceReason" + }, + { + "type": "null" + } + ] + }, + "asset_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "snapshot_url": { + "type": "string", + "pattern": "^/api/observations/[0-9a-f-]{36}/snapshot$" + } + }, + "required": [ + "observation_id", + "revision", + "camera_uuid", + "label", + "observed_at", + "change_cause", + "evidence_state", + "evidence_reason", + "asset_id" + ], + "additionalProperties": true, + "description": "Data payload of io.lightnvr.detection.observation_changed.v1 (contract \u00a76.3). Consumers skip classification-only changes for re-classification.", + "allOf": [ + { + "if": { + "type": "object", + "properties": { + "evidence_state": { + "enum": [ + "pending", + "ready" + ] + } + } + }, + "then": { + "type": "object", + "properties": { + "evidence_reason": { + "type": "null" + } + } + }, + "else": { + "type": "object", + "properties": { + "evidence_reason": { + "$ref": "#/components/schemas/ObservationEvidenceReason" + } + } + } + }, + { + "if": { + "type": "object", + "properties": { + "evidence_state": { + "const": "ready" + } + } + }, + "then": { + "type": "object", + "properties": { + "snapshot_url": { + "type": "string" + } + }, + "required": [ + "snapshot_url" + ] + }, + "else": { + "type": "object", + "not": { + "type": "object", + "properties": { + "snapshot_url": { + "type": "string" + } + }, + "required": [ + "snapshot_url" + ] + } + } + } + ] + }, + "ObservationError": { + "type": "object", + "properties": { + "error": { + "type": "string" + }, + "code": { + "type": "string", + "enum": [ + "invalid_request", + "invalid_cursor", + "forbidden", + "namespace_forbidden", + "observation_not_found", + "evidence_unavailable", + "evidence_pending", + "evidence_expired", + "cursor_expired", + "revision_conflict", + "owner_decision_locked", + "annotation_limit", + "evidence_mismatch", + "not_implemented", + "service_unavailable", + "internal_error" + ] + }, + "field": { + "type": "string" + }, + "reason": { + "$ref": "#/components/schemas/ObservationEvidenceReason" + }, + "retry_after_seconds": { + "type": "integer", + "minimum": 1, + "maximum": 30 + }, + "expired_at": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTime" + }, + { + "type": "null" + } + ] + }, + "oldest_cursor": { + "type": "string", + "minLength": 1, + "maxLength": 256 + }, + "oldest_observed_at": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTime" + }, + { + "type": "null" + } + ] + }, + "current": { + "$ref": "#/components/schemas/ObservationClassification" + }, + "limit": { + "type": "integer", + "minimum": 1 + }, + "observation_asset_id": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "evidence_state": { + "$ref": "#/components/schemas/ObservationEvidenceState" + } + }, + "required": [ + "error", + "code" + ], + "additionalProperties": true, + "description": "Coded error (contract \u00a711, D2): a valid ApiError with a string error and a stable code. Clients branch on status and code, never on the message." + }, + "IntegrationCapabilityOperation": { + "type": "object", + "properties": { + "supported": { + "type": "boolean" + }, + "authorized": { + "type": "boolean" + }, + "reason": { + "type": "string", + "enum": [ + "not_implemented", + "not_available_in_this_version", + "not_authorized", + "invalid_writer_namespace", + "reserved_writer_namespace" + ] + } + }, + "required": [ + "supported", + "authorized" + ], + "additionalProperties": true + }, + "IntegrationCapabilities": { + "type": "object", + "properties": { + "contract_versions": { + "type": "object", + "properties": { + "observations": { + "type": "integer", + "minimum": 1 + }, + "classifications": { + "type": "integer", + "minimum": 1 + }, + "observation_event": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "observations", + "classifications", + "observation_event" + ], + "additionalProperties": true + }, + "source": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + "kind": { + "const": "lightnvr" + }, + "installation_uuid": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "product_version": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "id", + "kind", + "installation_uuid", + "product_version" + ], + "additionalProperties": true + }, + "principal": { + "type": "object", + "properties": { + "writer_namespace": { + "type": [ + "string", + "null" + ], + "pattern": "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + }, + "required": [ + "writer_namespace" + ], + "additionalProperties": true + }, + "operations": { + "type": "object", + "properties": { + "observations.history": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "observations.snapshot": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "observations.annotate": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "track_lifecycle": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "recorded_frame": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "event_clip": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "range_clip": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + } + }, + "required": [ + "observations.history", + "observations.snapshot", + "observations.annotate", + "track_lifecycle", + "recorded_frame", + "event_clip", + "range_clip" + ], + "additionalProperties": { + "$ref": "#/components/schemas/IntegrationCapabilityOperation" + }, + "description": "Unsupported features are declared explicitly; never infer support from an error." + }, + "limits": { + "type": "object", + "properties": { + "history_page_default": { + "type": "integer", + "minimum": 1 + }, + "history_page_max": { + "type": "integer", + "minimum": 1 + }, + "history_filter_cameras_max": { + "type": "integer", + "minimum": 1 + }, + "history_filter_labels_max": { + "type": "integer", + "minimum": 1 + }, + "max_objects_per_batch": { + "type": "integer", + "minimum": 1 + }, + "label_max_bytes": { + "type": "integer", + "minimum": 1 + }, + "snapshot_retry_after_max_seconds": { + "type": "integer", + "minimum": 1 + }, + "annotations_per_observation_max": { + "type": "integer", + "minimum": 1 + } + }, + "required": [ + "history_page_default", + "history_page_max", + "history_filter_cameras_max", + "history_filter_labels_max", + "max_objects_per_batch", + "label_max_bytes", + "snapshot_retry_after_max_seconds", + "annotations_per_observation_max" + ], + "additionalProperties": true + }, + "history": { + "type": "object", + "properties": { + "retention_days": { + "type": "integer", + "minimum": 1 + }, + "oldest_cursor": { + "type": [ + "string", + "null" + ], + "maxLength": 256 + }, + "oldest_observed_at": { + "anyOf": [ + { + "$ref": "#/components/schemas/ObservationTime" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "retention_days", + "oldest_cursor", + "oldest_observed_at" + ], + "additionalProperties": true + }, + "delivery": { + "type": "object", + "properties": { + "mqtt_enabled": { + "type": "boolean" + }, + "topic": { + "type": [ + "string", + "null" + ], + "description": "Configured topic with a literal {camera_uuid} placeholder." + } + }, + "required": [ + "mqtt_enabled", + "topic" + ], + "additionalProperties": true + }, + "cameras": { + "type": "array", + "maxItems": 1024, + "items": { + "type": "object", + "properties": { + "camera_uuid": { + "$ref": "#/components/schemas/ObservationUuid" + }, + "evidence_enabled": { + "type": "boolean" + }, + "actions": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "live.view", + "snapshot.create", + "observations.annotate" + ] + } + } + }, + "required": [ + "camera_uuid", + "evidence_enabled", + "actions" + ], + "additionalProperties": true + } + } + }, + "required": [ + "contract_versions", + "source", + "principal", + "operations", + "limits", + "history", + "delivery", + "cameras" + ], + "additionalProperties": true, + "description": "Capability document scoped to the calling principal (contract \u00a79)." } } } diff --git a/docs/integrations/OBSERVATIONS_CONTRACT.md b/docs/integrations/OBSERVATIONS_CONTRACT.md new file mode 100644 index 000000000..67f8486ec --- /dev/null +++ b/docs/integrations/OBSERVATIONS_CONTRACT.md @@ -0,0 +1,1164 @@ +# Observations, Evidence and Classification Contract (INT_01 ADR) + +**Status**: Frozen for P1/P2 implementation (task C1). Budgets in §13 are +final: task X2 measured them on the C4 fixture (§19). +**Contract version**: `observations` 1, `classifications` 1, event +`io.lightnvr.detection.observation_changed.v1`. +**Source PRD**: [INT_01 — Event Evidence and YA-WAMF Integration](../prd/INT_01_Event_Enrichment_YAWAMF.md) +**Plan**: [INT_01 swarm plan](../../int01-event-enrichment-yawamf-plan.md) +**Baselines**: lightNVR `0ff0d2e3` (version 0.47.3); YA-WAMF `53540f48` (v2.23.1). + +This document is normative. "MUST", "MUST NOT", "SHOULD" and "MAY" have their +RFC 2119 meanings. Every L\* (lightNVR) and W\* (YA-WAMF) task implements this +contract. Where a task needs a field, status, code or limit not listed here, the +contract is amended first; implementations do not invent one. + +The canonical machine-readable schemas are added to +[`docs/api/openapi.json`](../api/openapi.json) by task C2, and canonical JSON +examples are added under `tests/fixtures/integrations/observations/` by task +C3. If this document and OpenAPI disagree, fix the discrepancy before merging +either. + +## Contents + +1. [Decisions and deviations](#1-decisions-and-deviations) +2. [Conventions](#2-conventions) +3. [Observation object](#3-observation-object) +4. [Evidence asset](#4-evidence-asset) +5. [Change log, revisions and publication](#5-change-log-revisions-and-publication) +6. [Change event](#6-change-event-iolightnvrdetectionobservation_changedv1) +7. [History and cursor](#7-history-and-cursor-get-apiobservations) +8. [Single observation and snapshot](#8-single-observation-and-snapshot) +9. [Capability document](#9-capability-document-get-apiintegrationscapabilities) +10. [Classification annotations (P2)](#10-classification-annotations-p2) +11. [Error catalog](#11-error-catalog) +12. [Authorization and audit](#12-authorization-and-audit) +13. [Budgets and configuration](#13-budgets-and-configuration) +14. [Retention and storage](#14-retention-and-storage) +15. [Diagnostics](#15-diagnostics) +16. [YA-WAMF consumer rules](#16-ya-wamf-consumer-rules) +17. [Reserved identifiers](#17-reserved-identifiers) +18. [Supported-version matrix (stub)](#18-supported-version-matrix-stub) +19. [Performance](#19-performance) +20. [PRD traceability](#20-prd-traceability) + +## 1. Decisions and deviations + +Plan design decisions 1–9 are adopted. This section records where the contract +refines or deliberately deviates from them, with the reason. + +| # | Topic | Contract | Plan text | Reason | +| --- | --- | --- | --- | --- | +| D1 | Multi-value query filters | `camera_uuid` and `label` are **comma-separated** single parameters (`camera_uuid=a,b`), max 10 cameras and 8 labels | `camera_uuid[]`, `label[]` | `http_request_get_query_param()` returns only the first occurrence of a key, and `http_request_t.query_string` is 1,024 bytes (`include/web/request_response.h:50`). Comma lists fit; repeated keys would silently drop values. | +| D2 | Error body shape | Every coded error is `{"error": "", "code": "", ...}` | `{code, oldest_cursor, oldest_observed_at}` | Keeps every response a valid existing `ApiError` schema (`error` is required there). | +| D3 | Writer namespace | Derived from the **authenticated user's username** (normalized), and authority is pinned to the **user id** stored on the row; the capability document advertises it | "derived from the API token's identity" | API tokens have only a UUID and free-text description (`api_token_t`). A token-UUID namespace would orphan annotations on token rotation. Usernames are unique and user ids are never reused (`AUTOINCREMENT`, migration 0006). W10 MUST use the advertised namespace rather than hard-coding `yawamf`; the recipe (L12) creates a dedicated user named `yawamf` so the namespace is `yawamf`. | +| D4 | Snapshot denial when metadata is visible | `404` when the caller cannot read the observation at all; `403 forbidden` when the caller holds `live.view` but not `snapshot.create` on the capture camera | "`404` for unknown or unauthorized" | The observation's existence is already visible to that caller via metadata, so a `403` leaks nothing and is more actionable. Callers without any read authority still get an indistinguishable `404`. | +| D5 | Every observation has an asset row | `evidence.asset_id` is never null; observations without media reference an asset in state `unavailable` with no file | L1: `asset_uuid` nullable | One evidence shape for consumers; reasons live on the asset row. L1 MAY keep the column nullable, but L2 always sets it. | +| D6 | Change rows snapshot evidence state | `observation_changes` additionally stores `evidence_state` and `evidence_reason` at that revision | not listed in L1 | The relay must publish the state *at that revision*, not the current state, so a retried message keeps the same meaning. | +| D7 | Publication states | `pending`, `relayed`, `relayed_no_route`, `expired`, `failed` | L1: `pending\|relayed\|failed`; L5 adds `relayed_no_route` | `expired` covers rows not relayed before the event's delivery expiry (cursor recovery applies). Listed once so L1's CHECK matches L5. | +| D8 | Outbox share cap | The relay stops enqueuing while this type holds ≥ 2,000 pending outbox rows | not specified | The default outbox holds 10,000 rows, and `info` events cannot evict others. Without a cap, a broker outage lets observation changes crowd out `camera.offline`-class warnings. | +| D9 | DB-restore epoch detection | Epoch is regenerated by in-process restore paths **and** at startup when a sidecar head marker shows the DB seq rewound (§7.6) | "regenerated on restore or import" | `restore_database_from_backup()` (called from `db_core.c` corruption recovery) and manual file restores both copy the old `observation_meta.epoch_uuid`. A sidecar outside the DB detects a manual restore. | +| D10 | Head cursor | `cursor=head` returns no items and a cursor at the current watermark | not specified | Lets a consumer start live-only without paging all retained history. Optional for consumers. | +| D11 | Notification cooldown name | YA-WAMF's existing bound is `settings.notifications.notification_cooldown_minutes` | "existing visit cooldown" | Name of the actual setting (`notification_service.py:108`). | + +## 2. Conventions + +- **Source identity.** `source` is the installation URN + `urn:lightnvr:`, from `event_identity_get_source()` + (`system_settings.event_installation_uuid`). It is the same value as the + envelope `source` in [EVENT_CONTRACT.md](../EVENT_CONTRACT.md). +- **UUIDs** are lowercase canonical 36-character strings. Observation, asset + and batch IDs are random UUIDv4. Message IDs are UUIDv5 (§5.4). +- **Times** in API bodies are UTC RFC 3339 with millisecond precision and a + `Z` suffix, for example `2026-11-08T14:03:12.481Z`. The DB stores integer + milliseconds (`*_ms`). Envelope `time` keeps the existing second-precision + format. +- **Camera identity** is always the immutable capture-time `camera_uuid` + (Fleet 01). Stream names are display metadata only and are never used for + identity or authorization. +- **No filesystem paths** appear in any API body or event. Media is referenced + by authenticated logical URLs whose JSON key ends in `_url`. This matches the + envelope validator, which rejects keys named `path` or containing `_path` or + `token`, and rejects `/`-prefixed values unless the key ends in `_url` + (`src/core/event_envelope.c:232-306`). +- **Unknown fields.** Response readers MUST ignore unknown fields. Request + bodies for PUT (§10) are closed: unknown top-level fields return `400`. + +## 3. Observation object + +One observation is one detected object in one accepted inference result. A +detector result batch with N surviving objects (after zone, object-filter and +threshold filtering) yields N observations sharing one evidence asset. +Independently sampled frames are distinct observations, even of the same bird. +Motion-only engines (`engine_type = motion`) never create observations. + +### 3.1 Fields + +| Field | Type | Null | Bounds and rules | +| --- | --- | --- | --- | +| `id` | uuid | no | Immutable observation ID (`object_observations.uuid`). | +| `source` | string | no | Installation URN (§2). Immutable. | +| `camera_uuid` | uuid | no | Capture-time camera identity, resolved from the stream row at insert. Never reassigned by rename, delete or name reuse. | +| `stream_name_at_capture` | string | no | Display name when observed (≤ 63 chars). Display only. | +| `batch_id` | uuid | no | Accepted inference batch. `UNIQUE(batch_id, object_index)` makes a batch retry a no-op. | +| `object_index` | integer | no | 0..19. Position in the filtered batch. | +| `observed_at` | time | no | Frame capture or inference time (camera-side). | +| `created_at` | time | no | Persistence time of revision 1. Distinct from `observed_at`. | +| `detector` | object | no | §3.3. | +| `label` | string | no | Original detector label, 1..31 bytes (`MAX_LABEL_LENGTH` 32 including NUL, `include/video/detection_result.h`). Never modified by classification. | +| `confidence` | number | no | Original detector confidence, 0.0..1.0 inclusive. | +| `box` | object | yes | §3.2. Null when the detector reports a class without spatial data (ONVIF, some external engines). | +| `evidence` | object | no | §4. | +| `track` | object | yes | §3.4. Null when the detector has no track. | +| `recording_reference` | object | yes | §3.5. | +| `revision` | integer | no | ≥ 1. §5.2. | +| `classifications` | object | no | `{ "automated": [], "owner_confirmed": [] }`, §10. Always present; both lists are empty in P1. | + +A batch holds at most 20 objects (`MAX_DETECTIONS`). An object over that limit +is never stored. + +### 3.2 `box` and coordinate space + +```json +{ "format": "xywh_normalized", "x": 0.412, "y": 0.288, "width": 0.120, "height": 0.165 } +``` + +- `format` is always `xywh_normalized` in version 1. `x`, `y` are the top-left + corner. +- Coordinates are normalized to the **stored evidence image** + (`evidence.width` × `evidence.height`), not to the main stream, a recording, + or the model tensor. lightNVR applies the transform before storing the box. + A consumer crops with `px = x * evidence.width` (and so on) and never + re-applies `transform`. +- Values are clamped to [0, 1] and rounded to 6 decimals. `width` and + `height` are > 0. `x + width` ≤ 1 and `y + height` ≤ 1 after clamping. + +### 3.3 `detector` + +| Field | Type | Null | Rule | +| --- | --- | --- | --- | +| `engine_key` | string | no | `stream_detection_engines.engine_key` (migration 0077), for example `legacy-primary`. ≤ 64 chars. | +| `engine_type` | enum | no | `object`, `api`, `onvif` or `external` (the 0077 CHECK set minus `motion`). | +| `model` | string | yes | Model **basename** only, for example `ssd_mobilenet_v2.sod`. Never a path. ≤ 128 chars. | +| `model_version` | string | yes | Version or hash when the engine reports one. ≤ 64 chars. | + +### 3.4 `track` + +```json +{ "id": "17", "namespace": "legacy-primary", "session": "9b0c7d1e-5f43-4b8e-9d0e-0b6c1f2a3d4e" } +``` + +- `id` is the detector's track ID as a decimal string (1..64 chars). It is + omitted when `track_id < 0`, in which case `track` is null. +- `namespace` is the `engine_key` that issued the ID. +- `session` is a UUIDv4 generated once per lightNVR process start (a boot + session, not the host boot ID). A reused track number after restart + therefore never joins two unrelated tracks. Consumers MUST compare all three + fields together. +- A track is a hint only. It does not establish physical-object identity, and + no lifecycle (`new/update/end`) is implied. + +### 3.5 `recording_reference` + +```json +{ "recording_id": 81234 } +``` + +This is an optional logical association with the recording open when observed +(`recording_id` FK `ON DELETE SET NULL`). Absence never blocks snapshot +classification, and recording deletion never deletes evidence (§14). P3 adds +offsets; version 1 has none. + +### 3.6 Example (ready, single bird) + +```json +{ + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { "engine_key": "legacy-primary", "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", "model_version": null }, + "label": "bird", + "confidence": 0.87, + "box": { "format": "xywh_normalized", "x": 0.412, "y": 0.288, "width": 0.12, "height": 0.165 }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, "height": 360, "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { "mode": "none", "source_width": 640, "source_height": 360, + "inference_width": 640, "inference_height": 360, + "scale_x": 1.0, "scale_y": 1.0, "pad_x": 0, "pad_y": 0 }, + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "track": null, + "recording_reference": { "recording_id": 81234 }, + "revision": 2, + "classifications": { "automated": [], "owner_confirmed": [] } +} +``` + +## 4. Evidence asset + +An evidence asset is the unannotated image that was the actual inference input +(local) or the exact submitted image (HTTP API). One asset MAY be shared by +every observation in its batch. + +### 4.1 Fields (`evidence`) + +| Field | Type | Null | Rule | +| --- | --- | --- | --- | +| `asset_id` | uuid | no | `observation_assets.uuid`. Shared by sibling observations (D5). | +| `state` | enum | no | `pending`, `ready`, `unavailable` or `expired`. | +| `reason` | enum | yes | Null for `pending` and `ready`; required for `unavailable` and `expired` (§4.3). | +| `origin` | enum | yes | `inference_input` (local decoded frame), `submitted_image` (HTTP detector request body, or the go2rtc snapshot fetched **as** the inference input), or null when there is no media. A low-resolution inference image MUST NOT be labelled as a recording frame. | +| `media_type` | string | yes | `image/jpeg` when media exists or existed. | +| `width`, `height` | integer | yes | Pixel size of the stored image. Set once `ready`, and kept after `expired`. | +| `bytes` | integer | yes | File size. Set once `ready`. | +| `captured_at` | time | yes | Capture time of the frame. Equals the observation's `observed_at` for local inference. | +| `expires_at` | time | yes | Current estimate of media expiry from the age budget (byte pressure can expire it earlier). Advertises actual remaining availability; it is not a guarantee. | +| `transform` | object | yes | §4.4. Null when there is no media. | +| `snapshot_url` | string | yes | `/api/observations/{id}/snapshot`. Present only when `state = ready`. Relative, authenticated, never a filesystem path. | + +### 4.2 State machine + +```text + +--> ready --+--> expired (retention_expired | deleted) + | +--> unavailable (write_failed: file missing at startup) +pending ----+ + +--> unavailable (encode_failed | write_failed | interrupted | disabled) + +(created directly) unavailable (queue_full | budget_exhausted | no_matching_media) +``` + +- Transitions are compare-and-set on the current state. Two racing + transitions from the same state: exactly one wins. +- `unavailable` and `expired` are terminal. `pending` never moves back. +- `ready` is committed only after the file is fsynced, renamed from + `*.jpg.tmp` to its final name, and the directory is fsynced. The `ready` + transition, its change rows and publication rows are one SQLite transaction + (§5). +- Expiry keeps the metadata (dimensions, transform, captured_at) and drops the + media. + +### 4.3 Reason enum (bounded) + +| Reason | Valid with state | Meaning | +| --- | --- | --- | +| `queue_full` | unavailable | Admission dropped the frame: per-camera or global queue full. | +| `budget_exhausted` | unavailable | The byte budget is exhausted and only protected assets remain to evict. | +| `encode_failed` | unavailable | The JPEG encode failed. | +| `write_failed` | unavailable | A write, fsync or rename failed, or a `ready` file was missing at reconciliation. | +| `interrupted` | unavailable | Shutdown or crash before finalization; resolved by startup reconciliation. | +| `no_matching_media` | unavailable | The source (ONVIF, external events) cannot supply the matching frame. A separately timed context image is never substituted. | +| `disabled` | unavailable | Evidence was disabled for the camera between admission and encode. | +| `retention_expired` | expired | Age or byte retention evicted the media. | +| `deleted` | expired | Explicit evidence deletion by policy or an operator. | + +Adding a reason is a minor contract change: consumers MUST treat an unknown +`unavailable` reason as `unavailable` and an unknown `expired` reason as +`expired`. + +### 4.4 `transform` + +| Field | Type | Meaning | +| --- | --- | --- | +| `mode` | enum | `none`, `resize` (independent x/y scale, no padding) or `letterbox` (uniform scale plus padding). | +| `source_width`, `source_height` | integer | Decoded camera frame the detector input was derived from (detection-stream resolution, not main stream). | +| `inference_width`, `inference_height` | integer | Image or tensor the model consumed. | +| `scale_x`, `scale_y` | number | `inference / stored` along each axis (> 0). | +| `pad_x`, `pad_y` | number | Inference-space padding in pixels on the left and top (≥ 0; 0 unless `letterbox`). | + +The mapping from an inference-space pixel `(u, v)` to a stored-image pixel is +`x = (u - pad_x) / scale_x`, `y = (v - pad_y) / scale_y`. The transform is +recorded for audit. Boxes are already mapped (§3.2). + +- Local SOD/TFLite: stored image = full decoded frame. `mode = none`, or + `resize` when SOD pre-resizes (`src/video/sod_detection.c`). +- HTTP API: stored image = the exact submitted JPEG bytes. `mode` is `none` + unless the response declares a server-side letterbox. + +### 4.5 Storage layout + +Files are stored at +`/observations///.jpg`, +with a `.jpg.tmp` suffix while being written. The layout is internal and never +appears in the API. Evidence does not use the legacy MQTT 100-per-camera +snapshot policy. + +## 5. Change log, revisions and publication + +### 5.1 Tables (implemented by L1/L2) + +- `observation_changes(seq INTEGER PRIMARY KEY AUTOINCREMENT, observation_uuid, + revision, cause, evidence_state, evidence_reason, created_at_ms, + UNIQUE(observation_uuid, revision))`. +- `observation_publications(change_seq PK/FK, message_id, state, attempts, + last_error, updated_at_ms)`. `state` is one of `pending`, `relayed`, + `relayed_no_route`, `expired` or `failed` (D7). +- `observation_meta(epoch_uuid, oldest_seq)` holds a single row (§7.5). + +### 5.2 Revision semantics + +- Revision 1 is written at creation with cause `created`. Its + `evidence_state` is `pending`, or `unavailable` for drops and + `no_matching_media`. +- Each evidence state change adds +1 with cause `evidence`, applied to + **every** observation referencing the asset. +- Each annotation create or update that changes stored data (not a no-op + retry) adds +1 with cause `classification` (P2). +- Revisions are monotonic per observation, never reused, and never skipped. +- The observation row, its change row and its publication row are written in + **one** transaction, using the `db_auth.c` locking pattern (plan decision 2). + No mutex-taking `db_*` helper is called inside it. + +### 5.3 `change_cause` + +| Value | Consumer action | +| --- | --- | +| `created` | New observation. Classify only if `evidence_state = ready`. | +| `evidence` | Evidence changed. Classify when it becomes `ready`; mark media expired or unavailable otherwise. | +| `classification` | Annotation feedback. Consumers MUST NOT re-classify or re-notify. Display only. | + +### 5.4 Message ID + +`message_id = UUIDv5(NS, source + "|" + observation_id + "|" + revision)`, where +`revision` is the decimal string and NS is the frozen namespace +`0bc01005-a431-522b-9124-a114f67d187a`. NS itself is +`UUIDv5(NAMESPACE_URL, "https://lightnvr.io/ns/observation-change/v1")`. + +Test vector: source `urn:lightnvr:11111111-1111-4111-8111-111111111111`, +observation `55555555-5555-4555-8555-555555555555`, revision `2` → +`43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9`. + +The ID is computed once at change insert, stored in +`observation_publications.message_id`, and passed to +`event_envelope_create_with_severity_and_id()`. Relay restarts and outbox +retries reuse it. lightNVR has no UUIDv5 helper today; L5 adds one in +`src/utils/uuid.c` using mbedTLS SHA-1, which `src/core/totp.c` already links. + +### 5.5 Relay delivery + +- The relay enqueues pending publications in `seq` order through + `event_producer_enqueue_durable()`. L5 exports this wrapper around the + static `durably_enqueue_event` (`src/core/event_producers.c:91`). +- `enqueued` and `duplicate` → `relayed`. `no_route` and `mqtt_disabled` → + `relayed_no_route`. `full` or an error → stays `pending` with backoff. +- A pending row older than the type's delivery expiry (§6.1) → `expired`. An + envelope validation failure → `failed`. +- In-process `event_bus_publish` runs after the outbox step. +- Delivery is **at least once**. Consumers dedupe on `source + message_id`. + Outbox pruning means a late retry may redeliver. +- A suppressed route is not deletion or an authorization decision. The cursor + API (§7) is the recovery path for every non-`relayed` row. +- Outbox share cap (D8): while ≥ 2,000 outbox rows of this type are pending, + the relay pauses and leaves publications `pending`. + +## 6. Change event `io.lightnvr.detection.observation_changed.v1` + +### 6.1 Registry entry (added by L5 to `EVENT_TYPES[]` in `src/core/event_envelope.c`) + +| Property | Value | +| --- | --- | +| `type` | `io.lightnvr.detection.observation_changed.v1` | +| `family` | `detection` | +| `description` | `A durable object observation was created or changed` | +| `severity` | `EVENT_SEVERITY_INFO` (static) | +| `sensitivity` | `EVENT_SENSITIVITY_OPERATIONAL` | +| `media_policy` | `EVENT_MEDIA_REFERENCE_ALLOWED` | +| `expected_rate` | `EVENT_RATE_HIGH` | +| `subject_kind` | `EVENT_SUBJECT_CAMERA`, subject `camera/` | +| `default_expiry_seconds` | `3600` (matches `detection.object.v1`; the cursor is the recovery path) | + +The envelope `id` is the message ID (§5.4). `time` is the change row's +`created_at_ms` truncated to seconds, so a retry has an identical envelope. + +### 6.2 MQTT topic + +The topic uses the existing normalized form +`{topic_prefix}/v1/events/io.lightnvr.detection.observation_changed.v1/` +(`src/core/mqtt_client.c:902`). Retain is disabled. Existing +`detection.object.v1`, `{topic_prefix}/detections/{stream_name}`, snapshot and +Home Assistant topics are unchanged. + +### 6.3 Data payload + +```json +{ + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 2, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.481Z", + "change_cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "asset_id": "88888888-8888-4888-8888-888888888888", + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" +} +``` + +| Field | Required | Rule | +| --- | --- | --- | +| `observation_id` | yes | UUID. | +| `revision` | yes | Integer ≥ 1. | +| `camera_uuid` | yes | UUID equal to the subject's UUID. | +| `label` | yes | 1..31 bytes. | +| `observed_at` | yes | RFC 3339. | +| `change_cause` | yes | `created`, `evidence` or `classification`. | +| `evidence_state` | yes | State **at this revision** (D6). | +| `evidence_reason` | yes (nullable) | Reason enum (§4.3); non-null iff the state is `unavailable` or `expired`. | +| `asset_id` | yes | UUID; siblings share it. | +| `snapshot_url` | no | Present iff `evidence_state = ready`. | + +The event is a change notification. Box, transform and classifications are +fetched with `GET /api/observations/{id}`, which is authoritative. The L5 +validator rejects missing required fields, a subject/`camera_uuid` mismatch, an +unknown cause or state, and an inconsistent reason or `snapshot_url`. The +generic privacy rules still apply. + +## 7. History and cursor (`GET /api/observations`) + +### 7.1 Query parameters + +| Param | Type | Default | Rule | +| --- | --- | --- | --- | +| `camera_uuid` | comma list of uuid | all authorized | ≤ 10 values (D1). Each must be authorized for `live.view`, otherwise `403 forbidden`. Unknown UUIDs are treated as unauthorized. | +| `label` | comma list | all | ≤ 8 values, each 1..31 bytes, exact ASCII case-insensitive match on the original label. | +| `since` | RFC 3339 | none | Inclusive lower bound on `observed_at`. | +| `until` | RFC 3339 | none | Exclusive upper bound on `observed_at`. `since < until` when both are given, otherwise `400`. | +| `cursor` | string | start of retained history | Opaque cursor (§7.4), or the literal `head` (D10). | +| `limit` | integer | 100 | 1..500. Counts **observations** after in-page dedup. | + +Without a camera filter, the query is implicitly limited to cameras where the +principal holds `live.view`. Observations whose capture camera is deleted are +excluded, and single fetches of them return `404`. This follows the existing +deleted-camera behaviour of timeline/playback. + +### 7.2 Response + +```json +{ "items": [ /* Observation */ ], "next_cursor": "eyJ2IjoxLCJzZXEiOjEwNDIs...", "has_more": false } +``` + +`items` hold **current** observation state at read time, ordered by the seq of +the change that included them. An item's `revision` may exceed the change that +selected it. Consumers dedupe by `(source, id, revision)`. + +### 7.3 Page algorithm (normative) + +1. Decode the cursor to `S` (§7.4) and validate it (§7.5). An absent cursor + means `S = oldest_seq - 1`. +2. Take the watermark `W = head` once, at query start. `head` = + `max(MAX(seq), oldest_seq - 1)`. +3. Scan changes with `S < seq ≤ W` in ascending `seq`, applying authorization + and filters. Filters use immutable observation fields only. +4. Add each matching change's observation if it is not already in the page. + Stop **before** a change that would add observation number `limit + 1`. + Also stop after scanning 5,000 change rows (scan cap, §13). +5. `next_cursor` = the seq of the last change **scanned**, whether included or + filtered out, or `S` if none were scanned. It is never derived from the last + returned item. +6. `has_more` = whether any change exists with `next_cursor < seq ≤ W`. + +Consequences: + +- An empty `items` with `has_more = true` is valid (scan cap or filters), and + clients MUST continue. +- A pending→ready transition is a new change with a higher seq, so it is + returned even after the `created` change was read. +- Writes after `W` are seen on a later request, so pages never loop. +- `cursor=head` returns `items: []`, `next_cursor` at `W` and + `has_more: false`. + +A cursor is valid only for the principal and filter set that produced it. A +consumer that changes cameras or labels, or gains permissions, MUST restart from +an absent cursor to see older changes for the new scope. + +### 7.4 Cursor encoding + +The cursor is base64url without padding of the compact JSON +`{"v":1,"seq":,"epoch":""}`, for example +`{"v":1,"seq":1042,"epoch":"66666666-6666-4666-8666-666666666666"}` → +`eyJ2IjoxLCJzZXEiOjEwNDIsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0`. + +Clients MUST treat it as opaque. Malformed input, `v ≠ 1`, a negative seq or +over 256 chars returns `400 invalid_cursor`. lightNVR has no base64url helper +today (`include/utils/base64.h` is standard alphabet); L2 or L8 adds one. + +### 7.5 Expiry rules (`410 cursor_expired`) + +`observation_meta.oldest_seq` is the lowest seq that may still be retained +(last pruned seq + 1; initially 1). A cursor `S` returns `410` when any of +these hold: + +- `S < oldest_seq - 1`: a change after `S` was pruned. +- `epoch ≠ observation_meta.epoch_uuid`: DB restore, import or recreation. +- `S > head`: seq rewound without an epoch change. + +```json +{ + "error": "Cursor is older than retained observation history", + "code": "cursor_expired", + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "oldest_observed_at": "2026-10-09T00:00:04.112Z" +} +``` + +`oldest_cursor` encodes `oldest_seq - 1` in the **current** epoch. +`oldest_observed_at` is the minimum retained `observed_at`, or null when +history is empty. The consumer records "recovery required" and resumes from +`oldest_cursor`. Changes in the gap are reported as unrecoverable, never +silently skipped. + +### 7.6 DB-generation epoch (D9) + +- `epoch_uuid` is created with the table (L1) and regenerated by: + 1. any in-process restore or import, including both + `restore_database_from_backup()` call sites in `src/database/db_core.c` + and any future import tool, via `observation_meta_regenerate_epoch()`; + 2. startup rewind detection. A sidecar + `/observations/.cursor_head.json` holds `{epoch, head_seq}`, + written atomically (tmp + rename) when the head advances, at most once + per second by the relay. At startup, if the sidecar epoch equals the DB + epoch but the DB `head < head_seq`, the DB was restored, so the epoch is + regenerated. A missing sidecar is recreated without regenerating. +- The epoch is not `event_installation_uuid`, which survives a restore while + `seq` rewinds. +- Known residual window: a restore to a backup newer than the last sidecar + write is caught only by `S > head`. This is accepted and covered by L11's + restore test. + +## 8. Single observation and snapshot + +### 8.1 `GET /api/observations/{id}` + +Returns `200` with one Observation (§3), including classifications. An unknown +ID, an unauthorized ID (no `live.view` on the capture camera) and a deleted +capture camera are indistinguishable: `404 observation_not_found`. + +### 8.2 `GET /api/observations/{id}/snapshot` + +| Evidence state | Status | Headers | Body | +| --- | --- | --- | --- | +| `ready` | `200` | `Content-Type: image/jpeg`, `Cache-Control: private, max-age=300`, `ETag: ""` | JPEG bytes, unannotated | +| `pending` | `202` | `Retry-After: <1..30>` | `{error, code:"evidence_pending", retry_after_seconds}` | +| `unavailable` | `404` | | `{error, code:"evidence_unavailable", reason}` | +| `expired` | `410` | | `{error, code:"evidence_expired", reason, expired_at}` | +| (no read authority / unknown) | `404` | | `{error, code:"observation_not_found"}` | +| (`live.view` but no `snapshot.create`) | `403` | | `{error, code:"forbidden"}` (D4) | + +- It never substitutes a latest or go2rtc frame, and never returns a + different asset. +- `If-None-Match` matching the ETag returns `304`. +- `Retry-After`: `1` when the asset is under 5 s old, otherwise + `min(30, 2 × queue items ahead)`. It is always an integer from 1 to 30. + lightNVR has no per-asset queue position, so it uses the whole evidence + backlog (frames queued for encoding) as an upper bound on the items ahead. + Clients honour it and give up polling after 120 s total, relying on the + `evidence` change event or the cursor instead. + +## 9. Capability document (`GET /api/integrations/capabilities`) + +This endpoint requires an authenticated principal (OpenAPI kind +`authenticated`). It returns only that principal's scope. Unsupported features +are declared explicitly; consumers MUST NOT infer support from an endpoint +returning an error. + +```json +{ + "contract_versions": { + "observations": 1, + "classifications": 1, + "observation_event": "io.lightnvr.detection.observation_changed.v1" + }, + "source": { + "id": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "kind": "lightnvr", + "installation_uuid": "11111111-1111-4111-8111-111111111111", + "product_version": "0.48.0" + }, + "principal": { "writer_namespace": "yawamf" }, + "operations": { + "observations.history": { "supported": true, "authorized": true }, + "observations.snapshot": { "supported": true, "authorized": true }, + "observations.annotate": { "supported": false, "authorized": false, "reason": "not_available_in_this_version" }, + "track_lifecycle": { "supported": false, "authorized": false, "reason": "not_implemented" }, + "recorded_frame": { "supported": false, "authorized": false, "reason": "not_implemented" }, + "event_clip": { "supported": false, "authorized": false, "reason": "not_implemented" }, + "range_clip": { "supported": false, "authorized": false, "reason": "not_implemented" } + }, + "limits": { + "history_page_default": 100, + "history_page_max": 500, + "history_filter_cameras_max": 10, + "history_filter_labels_max": 8, + "max_objects_per_batch": 20, + "label_max_bytes": 31, + "snapshot_retry_after_max_seconds": 30, + "annotations_per_observation_max": 8 + }, + "history": { + "retention_days": 30, + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjAs...", + "oldest_observed_at": "2026-10-09T00:00:04.112Z" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras": [ + { "camera_uuid": "22222222-2222-4222-8222-222222222222", + "evidence_enabled": true, + "actions": ["live.view", "snapshot.create"] } + ] +} +``` + +- `supported` means the installation implements the operation. `authorized` + means the principal holds the required action on at least one camera. + `reason` is present when either is false. Its values are + `not_implemented`, `not_available_in_this_version`, `not_authorized`, + `invalid_writer_namespace` and `reserved_writer_namespace`. +- `observations.annotate` was `supported: false` (`not_available_in_this_version`) + before L14. Since L14 it is `supported: true`, and `authorized` is true when + the principal has a writer namespace and holds `observations.annotate` on at + least one camera. Otherwise `reason` is `reserved_writer_namespace` or + `invalid_writer_namespace` (no namespace; this wins) or `not_authorized`. +- `principal.writer_namespace` is null when the principal has no valid + namespace (§10.2). +- `cameras` lists at most 1,024 cameras where the principal holds any of + `live.view`, `snapshot.create` or `observations.annotate`. `actions` is the + subset held. Full camera discovery stays on the existing per-camera-filtered + `GET /api/streams` (`listStreams`), which returns `camera_uuid` and needs no + administrator settings access. +- `history.*` and `delivery.*` reflect current configuration. `topic` uses + the configured prefix, with `{camera_uuid}` left as a literal placeholder. + +## 10. Classification annotations (P2) + +### 10.1 Key + +The path key is `/`, sent URL-encoded as one path +segment (`%2F`) or as two segments, `…/classifications/{namespace}/{name}`. +C2 picks one form and OpenAPI records it; the two-segment form is preferred. +Both parts match `^[a-z0-9][a-z0-9_.-]{0,31}$`. + +### 10.2 Writer namespace and identity + +- The namespace is **derived by the server** from the authenticated principal: + the user's username, lowercased, with every character outside `[a-z0-9_.-]` + replaced by `-`. It must then match the pattern above, or the principal has + no namespace (`invalid_writer_namespace`). It is never taken from the + request body (D3). +- Reserved namespaces cannot be derived. They are `lightnvr`, `system`, + `owner` and `local` (`reserved_writer_namespace`). `lightnvr` is held for + future native owner decisions. +- Each row stores `writer_user_id` and the namespace at creation. A write to an + existing key is allowed only when `writer_user_id` matches, so a later user + reusing a username cannot overwrite (`403 namespace_forbidden`). +- A key whose namespace is not the principal's derived namespace returns + `403 namespace_forbidden`. The same applies if the body contains a + `writer_namespace` or `writer` field naming another namespace; when it names + the principal's own namespace, the field is accepted and ignored. + +### 10.3 Annotation object + +| Field | Type | Null | Rule | +| --- | --- | --- | --- | +| `key` | string | no | `/` (response only). | +| `writer_namespace` | string | no | Response only. | +| `classifier` | string | no | 1..64 bytes, for example `yawamf`. | +| `model_version` | string | yes | ≤ 64 bytes. | +| `label` | string | no | 1..128 bytes UTF-8, for example `Black-capped Chickadee`. General object classes are allowed; there are no bird-specific fields. | +| `taxonomy_id` | string | yes | ≤ 64 bytes, for example `inat:144815`. Opaque to lightNVR. | +| `confidence` | number | no | 0.0..1.0. | +| `classified_at` | time | no | Classifier's own time. | +| `input_asset_id` | uuid | no | Must equal the observation's `evidence.asset_id`. | +| `input_revision` | integer | no | 1 ≤ value ≤ the observation's current revision. | +| `kind` | enum | no | `automated` or `owner_confirmed`. | +| `annotation_revision` | integer | no | Response only. Starts at 1 and increases by 1 per stored change. | +| `created_at`, `updated_at` | time | no | Response only. | + +A PUT body holds exactly the request fields above (`classifier`, +`model_version`, `label`, `taxonomy_id`, `confidence`, `classified_at`, +`input_asset_id`, `input_revision`, `kind`) plus an optional +`expected_revision` (integer ≥ 0; 0 or absent means "must not exist"). +Unknown fields return `400`. The original detector `label`, `confidence` and +`box` are never modified. + +### 10.4 PUT rules, evaluated in order + +1. Authenticate and resolve the observation's capture camera. + - No `live.view` and no `observations.annotate` on it → `404 + observation_not_found`. + - `live.view` without `observations.annotate` → `403 forbidden`. +2. Namespace checks (§10.2) → `403 namespace_forbidden`. +3. Body validation → `400 invalid_request`. +4. Evidence check: `input_asset_id` ≠ the observation's asset, `input_revision` + out of range, or the evidence state not in {`ready`, `expired`} → `422 + evidence_mismatch`. +5. **Idempotent retry.** The key exists and every request field equals the + stored value (confidence compared to 1e-6) → `200`, `changed: false`. This + holds regardless of `expected_revision`, so a retry after a lost response + is a no-op. Nothing is written and the revision is not bumped. +6. **Owner precedence.** The stored row is `owner_confirmed` and the request is + `automated` → `409 owner_decision_locked`. An `owner_confirmed` request MAY + replace either kind, subject to rule 7. +7. **Revision.** If the key exists, `expected_revision` must equal + `annotation_revision`; if absent, `expected_revision` must be 0 or omitted. + A mismatch → `409 revision_conflict`. +8. Otherwise store the annotation (create → `201`, update → `200`). In the same + transaction, bump the observation revision with cause `classification` and + write its change and publication rows. + +Response for `200` and `201`: + +```json +{ "changed": true, "observation_revision": 4, + "annotation": { "key": "yawamf/species", "writer_namespace": "yawamf", "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", "input_revision": 2, + "kind": "automated", "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", "updated_at": "2026-11-08T14:03:15.120Z" } } +``` + +The `409` bodies carry `current` (the stored annotation) so the writer can +reconcile. + +### 10.5 Reads and display + +`classifications.automated` and `classifications.owner_confirmed` list +annotations by `kind`, ordered by `updated_at` descending. Each key appears in +exactly one list. Displays (L15) show owner-confirmed results over automated +ones, and always keep the original detector label and confidence visible. + +There are at most 8 keys per observation. Creating a ninth key returns +`409 annotation_limit` (§11). This check runs at step 8 of §10.4, before the +insert. + +## 11. Error catalog + +Every error body has the form `{"error": "", "code": "", +...extra}` (D2). Clients branch on `status` + `code` and never on the message. + +| Status | `code` | Endpoints | Extra fields | Consumer outcome class | +| --- | --- | --- | --- | --- | +| 400 | `invalid_request` | all | `field` (optional) | permanent; fix the request | +| 400 | `invalid_cursor` | list | | restart without a cursor | +| 401 | (existing auth layer body) | all | | credentials invalid | +| 403 | `forbidden` | list (unauthorized `camera_uuid`), snapshot (D4), PUT | | permission missing | +| 403 | `namespace_forbidden` | PUT | | permanent; misconfigured writer | +| 404 | `observation_not_found` | get, snapshot, PUT | | `Missing` (unknown or not visible) | +| 404 | `evidence_unavailable` | snapshot | `reason` | `Unavailable` (permanent) | +| 202 | `evidence_pending` | snapshot | `retry_after_seconds`; `Retry-After` header | retry later | +| 410 | `evidence_expired` | snapshot | `reason`, `expired_at` | `Expired` (permanent) | +| 410 | `cursor_expired` | list | `oldest_cursor`, `oldest_observed_at` | `RecoveryRequired` | +| 409 | `revision_conflict` | PUT | `current` | refetch and reconcile | +| 409 | `owner_decision_locked` | PUT | `current` | drop the automated write | +| 409 | `annotation_limit` | PUT | `limit` | permanent | +| 422 | `evidence_mismatch` | PUT | `observation_asset_id`, `evidence_state` | drop with reason | +| 501 | `not_implemented` | C2 stubs (the PUT until L14; no route returns it since) | | capability absent | +| 503 | `service_unavailable` | all | `Retry-After` header | `TemporarilyUnreachable` | +| 500 | `internal_error` | all | | `TemporarilyUnreachable` | + +Network failures and timeouts on the consumer side also map to +`TemporarilyUnreachable`. `202`, `404 evidence_unavailable`, `410 +evidence_expired`, `404 observation_not_found` and `410 cursor_expired` are the +distinct A8 outcomes. + +## 12. Authorization and audit + +### 12.1 Mapping (confirms plan decision 7) + +| Operation | OpenAPI `x-lightnvr-authorization` | Action | Notes | +| --- | --- | --- | --- | +| `getIntegrationCapabilities` | `{version:1, kind:"authenticated"}` | none | Body scoped to the principal. | +| `listObservations` | `{version:1, kind:"camera", requirements:[{action:"live.view", resource:"raw"}]}` | `live.view` | Same as `getEvidenceDetections` (`GET /api/detection/results/{name}`). The set is evaluated per capture camera. | +| `getObservation` | camera, `live.view` | `live.view` | | +| `getObservationSnapshot` | camera, `snapshot.create` | `snapshot.create` | Same as `getEvidenceSnapshot`. Plus `live.view` for the D4 distinction. | +| `putObservationClassification` | camera, `observations.annotate` | `observations.annotate` | P2. | + +- Authorization always resolves the observation's capture-time `camera_uuid` + through Fleet 02. Handlers use `httpd_evaluate_camera_identity_action()`, + not the `httpd_authorize_*` helpers that write `403` responses themselves. + Those helpers would turn an opaque-ID miss into a `403` and leak existence. +- `observations.annotate` (P2, L13): + - It is appended to the C enum in `include/core/authorization.h` and to + `action_catalog[]` in `src/core/authorization.c`, as + `{AUTHZ_OBSERVATIONS_ANNOTATE, "observations.annotate", "Integrations", + "Annotate detection observations with classifier results", true, false, + true}`. It is camera-scoped and non-destructive. + - Migration 0096 inserts it with `sort_order 200` and `bit_index 19`. 0052 + defined bits 0–14 (via 0056) and 0078 bits 15–18; no other migration + touches `authz_actions`. + - Only the built-in Administrator role receives it (the 0078 pattern). + - It is listed in the three OpenAPI action enums. + - It is never required for reads, and `camera.configure` and admin are never + required for writes. + +### 12.2 Audit + +| Event | Mechanism | Recorded | +| --- | --- | --- | +| Metadata reads | Existing read-decision modes (`audit_log_authorization`) for `live.view` | as today | +| Snapshot fetch | `audit_log_authorization` for `snapshot.create`, with `target_type = "observation"` and `target_uuid` = observation ID | camera_uuid, asset_id, outcome | +| Annotation PUT (every outcome) | `audit_log_operation` with action `observations.annotate`, operation `annotate`, `target_type = "observation"` | camera_uuid, key, kind, `result` (`created\|updated\|noop\|revision_conflict\|owner_decision_locked\|evidence_mismatch\|namespace_forbidden`, plus `forbidden`, `observation_not_found`, `invalid_request`, `annotation_limit` and `internal_error`), annotation_revision before/after (null when unknown); the principal and `api_token_uuid` come from the standard audit columns. The audit `outcome` is `success` for created/updated/noop, `denied` for forbidden, namespace_forbidden and a hidden observation's 404, `error` for 500 and `failure` otherwise. A malformed observation ID (400 before lookup) and 401 are not audited. | + +Image bytes, request bodies beyond the fields listed, tokens, cookies and +authorization headers are never logged or audited. + +## 13. Budgets and configuration + +Final. X2 (A11) ran the C4 fixture with evidence on every camera at the +maximum detector cadence, at these defaults and with the queue saturated, and +kept every default (§19). The "X2 basis" column says what supports each value. + +| Setting | Scope | Default | Bounds | Storage | X2 basis | +| --- | --- | --- | --- | --- | --- | +| Evidence enabled | camera | **off** | bool | `streams.observation_evidence_enabled` | Off-by-default is a product decision (PRD R6); the disabled runs show no queue or worker | +| Eligible labels | camera | `''` (all object labels) | CSV, ≤ 16 labels | `streams.observation_labels` | Not load-relevant | +| Media max age | camera | 7 days | 1..90 | `streams.observation_max_age_days` | Policy value; not load-relevant | +| Media max bytes | camera | 2 GiB | 64 MiB..1 TiB | `streams.observation_max_bytes` | At the maximum load (1 frame/s per camera, about 100 KB per 720p JPEG) 2 GiB holds about 6 hours. X2 ran at the 64 MiB minimum and stayed under it (45 MB per camera); eviction is covered by L4/L11 tests, not by X2 | +| History retention | global | 30 days | 1..365 | `system_settings.observation_history_retention_days` | Policy value; not load-relevant | +| Global queue bytes | global | 32 MiB | 4..512 MiB | `system_settings.observation_global_queue_bytes` | Default: high-water 11.1 MB / 4 items, no drops. 4 MiB minimum: saturated (56% `queue_full`), limits held | +| Per-camera queue depth | constant | 8 frames | | compile-time | Per-camera high-water was 1 frame at maximum cadence | +| Global queue depth | constant | 64 frames | | compile-time | High-water 4 | +| Drop-record ring | constant | 256 records | | compile-time | High-water 2 while saturated; 0 untracked drops | +| Rejected-frame release bound | derived | max(global queue bytes, 32 MiB) | | compile-time | Added by X2: rejected frames are freed by the worker. Pending high-water 5.5 MB; 0 inline frees | +| Encoder worker concurrency | constant | 1 | | compile-time | Kept up with 4 frames/s at 720p (no backlog growth) | +| Relay batch | constant | 64 publications | | compile-time | Relay lag ≤ 2 ms; no publications pending | +| Relay outbox share cap | constant | 2,000 pending rows | | compile-time (D8) | Not reached (no broker in the fixture; X1 A5 exercised it) | +| History scan cap per page | constant | 5,000 changes | | compile-time | Not on the recording path; not load-tested by X2 | + +- With evidence disabled on every camera, no queue is allocated, no worker + thread starts, and no images are written. +- Admission never blocks. It returns `ACCEPTED`, `DROPPED_QUEUE_FULL`, + `DROPPED_BUDGET` or `DISABLED` immediately. X2 measured the slowest + submission of a run at 405 µs, and p99 at 50 µs or less + (`lightnvr_observation_submit_latency_seconds`). A rejected frame is freed + by the worker, not by the producer. +- Settings are changed with `camera.configure` (L9) and take effect without a + restart. + +## 14. Retention and storage + +- Media expiry is per camera. The oldest unprotected assets beyond the age or + byte budget become `expired(retention_expired)`, and their files are deleted + only when no retained observation references them and they are not + `protected`. +- A shared asset survives until its last reference is pruned. +- History prune deletes change rows in ascending seq while their `created_at` + is older than the history retention. It then deletes observations with no + remaining change rows, and assets with no remaining observations. It sets + `oldest_seq` = last pruned seq + 1 in the same transaction. +- Media retention (7 days) is shorter than metadata retention (30 days), so an + `expired` observation stays queryable. +- If the byte budget is exhausted and only protected assets remain, new work is + rejected as `budget_exhausted` and the flag is surfaced (§15). +- Recording deletion never deletes evidence, and evidence deletion never + deletes recordings. +- Startup reconciliation runs before the worker and relay: + - delete `*.jpg.tmp` files; + - `pending` assets without a final file → `unavailable(interrupted)`; + - `ready` assets with a missing file → `unavailable(write_failed)`; + - delete orphan final files. +- Rollback: disable evidence per camera; evidence and annotations are kept. The + 0095 and 0096 down-migrations drop data, so take a backup first. + +## 15. Diagnostics + +L10 exposes these through the existing metrics and health surfaces. Names are +final in L10; the semantics here are fixed. + +`GET /api/metrics` (Prometheus, `system.admin`) families, all present with +zero values while evidence is unused: + +| Item | Metric | +| --- | --- | +| Accepted | `lightnvr_observation_accepted_total` | +| Dropped by reason | `lightnvr_observation_dropped_total{reason="queue_full"\|"budget_exhausted"\|"disabled"\|"untracked"}` | +| Backlog | `lightnvr_observation_evidence_backlog_items`, `_backlog_bytes`, `_drop_ring_depth` | +| High-water marks | `lightnvr_observation_evidence_high_water_items`, `_high_water_bytes`, `_camera_high_water_items` (highest backlog of one camera) | +| Configured limits (X2) | `lightnvr_observation_evidence_limit_items{scope="global"\|"camera"}`, `_limit_bytes{scope="global"\|"camera"\|"discard"}` | +| Producer submit latency (X2) | `lightnvr_observation_submit_latency_seconds` histogram (`le` 1 µs … 10 ms, +Inf) and `lightnvr_observation_submit_latency_max_seconds` | +| Rejected frames (X2) | `lightnvr_observation_evidence_discards_total{mode="deferred"\|"inline"}`, `_discard_pending_bytes` | +| Queue exists | `lightnvr_observation_evidence_queue_allocated`, `_worker_running` (0 when unused) | +| Outcomes | `lightnvr_observation_evidence_ready_total`, `_unavailable_total`, `_bytes_written_total` | +| Failures by reason | `lightnvr_observation_evidence_failures_total{reason="encode_failed"\|"write_failed"\|"db_error"\|"disabled"\|"interrupted"}` | +| Retained bytes | `lightnvr_observation_evidence_retained_bytes{camera_uuid}`, `_budget_bytes{camera_uuid}` | +| Budget exhausted | `lightnvr_observation_evidence_budget_exhausted{camera_uuid}`, `_budget_exhausted_cameras` | +| Oldest cursor | `lightnvr_observation_history_oldest_seq` (cursor = seq − 1), `_head_seq`, `_oldest_observed_timestamp_seconds`, `_observations` | +| Publications | `lightnvr_observation_publications{state="pending"\|"failed"\|"expired"}` | +| Relay | `lightnvr_observation_relay_lag_seconds`, `_relay_running`, `_relay_events_total{outcome}`, `lightnvr_observation_history_epoch_regenerations_total` | + +Per-camera rows cover cameras holding `ready` media as of the last L4 +retention pass (at most 5 minutes old). The same snapshot is returned as +`observation_pipeline` in `GET /api/system/info` (OpenAPI schema +`ObservationPipelineDiagnostics`) and shown on the System page; +`active: false` there means the feature is unused. + +- Accepted observations total. +- Dropped observations by reason (`queue_full`, `budget_exhausted`, + `disabled`), plus `dropped_untracked` when the drop ring is full. +- Evidence backlog (items and bytes), queue high-water marks, and failures by + reason. +- Retained evidence bytes per camera. +- Oldest recoverable cursor seq and `oldest_observed_at`. +- Publications `pending`, `failed` and `expired`, and relay lag in seconds (age + of the oldest pending publication). +- The `budget_exhausted` flag per camera. +- Setup probe: `GET /api/observations?camera_uuid=&limit=1`. + - `200` with empty `items` means "no eligible detections yet". + - Transport or auth failures surface as HTTP or network errors and are never + reported as an empty list. + +## 16. YA-WAMF consumer rules + +These rules bind W1–W10 and refine plan decision 8. + +### 16.1 Identity mapping + +| YA-WAMF column | lightNVR value | +| --- | --- | +| `detections.frigate_event` (`UNIQUE NOT NULL`, the internal event key) | `lnvr::` (78 chars; cannot collide with Frigate's `.-`) | +| `source_kind` | `lightnvr` (Frigate rows: `frigate`) | +| `source_instance` | `installation_uuid` from `capabilities.source.installation_uuid` | +| `external_observation_id` | observation `id` | +| `source_camera_id` | `camera_uuid` (identity) | +| `camera_name` | `stream_name_at_capture` (display only) | + +Other tables (`bird_observations`, media cache, `processing_job_state`, +favourites, corrections) keep keying on `frigate_event`. + +### 16.2 Admission + +1. Dedupe deliveries on `source + envelope id`. +2. Drop `change_cause = classification` messages for classification and + notification. They may refresh the display only. +3. Before acting, fetch current state with `GET /api/observations/{id}`, which + is authoritative; the payload is only a trigger. Admission is idempotent on + the observation key. If a detection row exists, a later change never creates + another detection or notification. +4. Classify only when the current `evidence.state = ready`. + - `pending` → do nothing durable. The ready transition arrives later as its + own change, live or by cursor. + - `unavailable` or `expired` before admission → record "skipped: " + in diagnostics. No detection row is created. +5. Crop each observation with its own box against the evidence image + dimensions (§3.2). Siblings are classified separately. +6. Live and recovery paths share steps 1–5. + +### 16.3 Multi-bird mapping + +- One observation maps to one detection row with one `bird_observations` row + at `bird_index = 0`. +- Several objects in one frame become sibling detections that share + `evidence.asset_id`. The full image is cached once per asset; crops are per + observation. +- Visit grouping (camera + time window) places siblings in the same visit and + never claims identity. +- Intended difference: Frigate counts a two-bird event as one detection with + two `bird_observations`, while lightNVR yields two detections. W4 and W9 + must test visit counts, leaderboard counts and multi-bird display for + siblings, and document any intended difference. + +### 16.4 Notification policy for sources without `track_lifecycle` + +For these sources, each admitted observation is a single-shot lifecycle: + +| `notify_mode` | Frigate (unchanged) | lightNVR | +| --- | --- | --- | +| `silent` | never | never | +| `final` | on `end` only | on the **first admitted ready classification** of a detection | +| `standard` | `new`, or `update` that inserted | first admitted ready classification | +| `realtime` | `new` + updates | first admitted ready classification; never on `classification` changes | +| default | `notify_on_insert` / `notify_on_update` | `notify_on_insert` at first admission | + +- All modes stay bounded by `settings.notifications.notification_cooldown_minutes` + (D11). +- W7 records the intent durably in the detection-save transaction by + extending the existing `mark_notified` / `notified_at` state. It does not + add a parallel flag. +- Items admitted by history recovery get the intent `suppressed_recovery` (no + standard notification) by default. +- Ambiguous external delivery is recorded as `unknown` and surfaced, never + retried blindly. + +### 16.5 Recovery and cursor + +- `source_cursors(source_kind, source_instance, cursor, epoch, updated_at, + last_error)` stores the cursor together with a fingerprint of the camera and + label filter. A changed filter resets it to absent (§7.3). +- Advance the cursor only after every item in the page is durably admitted or + recorded as skipped with a reason. +- `410 cursor_expired` → store "recovery required", resume from + `oldest_cursor`, and report the gap in health. +- Retryable failures go to `processing_job_state` with a dedicated `pipeline`. + Write-back uses `pipeline = 'lightnvr_writeback'`. No new job tables. + +### 16.6 Snapshot outcomes + +| Response | Outcome | +| --- | --- | +| `202` | `Pending`; honour `Retry-After`, give up after 120 s | +| `404 evidence_unavailable` | `Unavailable(reason)` | +| `410` | `Expired(reason)` | +| `404 observation_not_found` | `Missing` | +| network error, `5xx` | `TemporarilyUnreachable` | + +Recovery never replaces evidence with a current frame. + +### 16.7 Write-back (W10) + +- PUT to `/species` with `kind = automated` for a + model result, or `owner_confirmed` for an owner correction. +- Send `expected_revision` from the last stored response. +- `409 revision_conflict` → refetch and reconcile; a local owner correction + wins. +- `409 owner_decision_locked` → drop the automated write. +- `422` → drop with a reason. +- Write-back never creates detections or notifications, and an outage leaves + the YA-WAMF result usable and marked "sync pending". + +## 17. Reserved identifiers + +| Item | Value | Owner task | +| --- | --- | --- | +| lightNVR migration (P1) | `0095_add_object_observations.sql` (main is at `0094`; re-check before merge) | L1 | +| lightNVR migration (P2) | `0096_add_observation_classifications.sql` | L13 | +| Authz action | `observations.annotate`, `bit_index 19`, `sort_order 200` | L13 | +| Event type | `io.lightnvr.detection.observation_changed.v1` | L5 | +| Message-ID namespace | `0bc01005-a431-522b-9124-a114f67d187a` | L5 | +| Tables | `object_observations`, `observation_assets`, `observation_changes`, `observation_publications`, `observation_meta`, `observation_classifications` (`observation_asset_refs` only if per-reference protection is needed) | L1, L13 | +| Operation IDs | `getIntegrationCapabilities`, `listObservations`, `getObservation`, `getObservationSnapshot`, `putObservationClassification` (never `*Evidence*`) | C2 | +| OpenAPI schemas | `Observation`, `ObservationEvidence`, `ObservationTransform`, `ObservationPage`, `ObservationChangeEvent`, `ObservationClassification`, `IntegrationCapabilities`, `ObservationError` | C2 | +| Storage paths | `/observations/…`, `.cursor_head.json` | L3, L4, L5 | +| Reserved writer namespaces | `lightnvr`, `system`, `owner`, `local` | L14 | +| YA-WAMF key prefix | `lnvr:` | W2, W4 | +| YA-WAMF job pipeline | `lightnvr_writeback` (others named by W6) | W6, W10 | + +## 18. Supported-version matrix (stub) + +X1 and X3 fill this in with the tested versions; L12 publishes it. The +published matrix, with what each component was tested by, is in the +[YA-WAMF deployment recipe](YA_WAMF.md#supported-version-matrix); the rows +below stay the release targets. + +| Component | Version | Contract | Status | +| --- | --- | --- | --- | +| lightNVR | first release containing L1–L11 (≥ 0.48.0, TBD) | observations 1, event v1 | not yet released | +| lightNVR (P2) | first release containing L13–L15 (TBD) | classifications 1 | not yet released; X3: A12, A13 and A7-annotate pass end to end with YA-WAMF write-back | +| YA-WAMF | fork branch `feature/lightnvr-provider` on v2.23.1 (`53540f48`); write-back needs `de28e1e4` or later | observations 1, classifications 1 | prototype; upstream acceptance or a named maintainer required (PRD §7) | +| Frigate (YA-WAMF source) | as supported by YA-WAMF v2.23.1 | n/a | unchanged | +| MQTT broker | Mosquitto 2.x | | X1: tested with 2.0.22 | +| Bird-capable detector / model | X1 pinned a loopback HTTP colour detector, plus SOD VOC `tiny20.sod` on a real photo | | real HTTP and LiteRT bird models pending (A14) | + +## 19. Performance + +Task C4 appends the declared performance fixture (hardware, stream count, +resolutions, fps, detector cadence, retention budgets) and the disabled +baseline here. X2 compares enabled and disabled runs on the same input. The +release threshold is zero additional recording gaps and ≤ 5% higher p95 +recording-path latency. X2 also measures memory, CPU, disk and queue high-water +marks, plus producer submit latency, which must never wait on I/O. X2 finalized +the values in §13. + +**X2 result (2026-11-08, A11): pass.** + +- **Setup.** + - Fixture: the C4 fixture (4 × 1280×720 at 15 fps, 10 s segments, SOD + `tiny20.sod` every 1 s on every stream), with a 600 s window. + - Source: a bird clip (`make-bird-clip.sh`) on which every inference + detected a bird, giving 2,404–2,408 detections per window. + - Binary: `9ed02bb4`, Release. + - Evidence was on for every camera. + - Modes: + - defaults; + - saturated: a 4 MiB queue, so 56% of submissions were `queue_full`. + - Each enabled run was compared with a disabled run on the same binary and + clip. +- **Data and tooling.** The data is in + `tests/performance/observations/results/2026-11-08-x2-*.json`. The gate + output is `2026-11-08-x2-gate.json`. The procedure is in + [the harness README](../../tests/performance/observations/README.md). +- **Gating checks, all passing in all four pairs:** + - Zero recording gaps from all three sources. + - The segment-interval p95 changed by −0.004% to +0.04%. + - Evidence and observations were written to the DB. + - Queue limits held. + - Every drop has a bounded reason, and none were untracked. + - There were no encode, write or DB failures. + - The producer submit maximum stayed under 1 ms. +- **DB-mutex probe (advisory, not gating).** + - The DB probe fails its own repeatability check: disabled runs vary by + 2.4–5.0% against the 2% criterion. Under C4's rule it is therefore + reported but does not gate. + - It is a recorded finding. The median is unchanged, but the p95 rises: + - pooled over all runs: +9.9% at defaults and +6.5% saturated (about + +1.3 ms and +0.9 ms); + - per pair: +2.2% to +13.4%. + - Host load varied between runs, and this is not separated from it. + - The likely cause is the extra WAL traffic from the evidence worker: + autocheckpoints then run inside commits that hold the global DB mutex. + Reducing admission does not remove it, because drops still insert their + `unavailable` rows. + - Follow-up: checkpoint off the global mutex, or batch the worker's + commits. +- **Resources, enabled vs disabled.** + - CPU: 151–158% vs 149–156% of one core. + - RSS high-water: 882–929 MB vs 873–888 MB. + - Disk writes: 2.9–3.3 MB/s vs 2.06 MB/s. + +## 20. PRD traceability + +| PRD ID | Requirement (summary) | Contract section | Implementing tasks | +| --- | --- | --- | --- | +| R1 | Stable observation ID per object, batch ID + object index, camera UUID and installation | §2, §3.1, §5.2 | L1, L2, L6, L7 | +| R1 | Field table: id/source/camera, observed vs created, detector, label/confidence, box, evidence, track, recording_reference, revision | §3.1–§3.5, §4.1 | L1, L2, L8 | +| R1 | Rename/delete never transfers history; reused track ID never joins; recording not required | §2, §3.4, §3.5, §7.1 | L2, L6, L11 | +| R2 | Owned copy of local inference frame, encoded off-thread; HTTP retains submitted image + transform; no second go2rtc fetch | §4.1 (`origin`), §4.4, §13 | L3, L6, L7 | +| R2 | Unannotated image + transform metadata; low-res not described as recording image | §3.2, §4.1, §4.4 | L3, L6, L7 | +| R2 | States pending/ready/unavailable/expired + bounded reason; ONVIF unavailable | §4.2, §4.3 | L2, L3, L7 | +| R2 | Ready only after readable + committed; atomic finalize; startup reconciliation; transactional publication record | §4.2, §5.1, §5.2, §14 | L2, L3, L4, L5 | +| R3 | Existing topics unchanged; new versioned event via registry/routing/outbox | §6 | L5 | +| R3 | One message ID per revision, stable across retries; dedupe by source + message_id / observation_id | §5.4, §5.5, §16.2 | L5, W4 | +| R3 | Authorized camera, label, time filters; bounded pages; opaque persistence-ordered cursor; watermark | §7.1–§7.4 | L2, L8 | +| R3 | Expired cursor → recovery-required + boundary; route suppression is not deletion; classification-only changes do not reclassify | §5.3, §5.5, §7.5, §7.6 | L2, L5, L8, W4, W6 | +| R4 | Capabilities, list, get, snapshot, PUT classification; distinct resource; pending retry guidance; expired vs unknown; no cross-scope leak | §7, §8, §9, §10, §11 | C2, L8, L14 | +| R5 | Annotation fields; bounded, general (no bird-specific core fields) | §10.3 | L13, L14 | +| R5 | Writer owns namespace; same payload no-op; expected revision; reject mismatched evidence; cannot claim another writer or overwrite owner; detector output preserved; automated vs owner separate | §10.2, §10.4, §10.5 | L13, L14, W10 | +| R5 | Minimal classification display in lightNVR | §10.5 | L15 | +| R6 | Fleet 02 capture-time identity; `live.view` metadata; `snapshot.create` images; narrow annotate action; audit without credentials/bytes | §12 | L8, L13, L14 | +| R6 | Per-camera opt-in, age/byte limits, bounded queues/concurrency; no legacy fixed-count policy; shared assets; recording vs evidence deletion explicit | §4.5, §13, §14 | L3, L4, L9 | +| R6 | Retention advertises real availability; budget exhaustion visible; never block recording | §4.1 (`expires_at`), §9 (`history`), §13, §14 | L3, L4, L8, X2 | +| R6 | Diagnostics; disabled deployments allocate nothing | §13, §15 | L3, L10 | +| T1 | Provider interface; Frigate provider preserved; lightNVR provider; no synthetic `end`; no plugin framework | §9, §16.2, §16.4 | W1, W3, W5 | +| T2 | Source kind/instance/external ID identity; legacy backfill; versioned migration; source change never reassigns history | §16.1, §17 | W2 | +| T3 | Provider-specific parsing; media by source identity; history/cursor outcomes; capability-gated services; no `/api/events/` from lightNVR config | §9, §11, §16.2, §16.6 | W3, W4, W6, W8 | +| T4 | Capability-declared features (not inferred from errors); classify ready only; per-object regions; cursor advances after durable accept/skip; durable notification intent | §9, §16.2–§16.5 | W4, W6, W7, W10 | +| A1 | Stable identity + expected label/region | §3, §16.1 | L6, L11, X1 | +| A2 | Moving bird: saved photo and region match inference input (local + HTTP) | §3.2, §4.1, §4.4 | L6, L7, X1 | +| A3 | Two birds → separate identities, aligned regions; shared evidence retained until all refs expire | §3, §14, §16.3 | L2, L4, W4, W9 | +| A4 | Replay ×10 + overlapping recovery → one detection, one notification intent | §5.4, §16.2, §16.4 | W4, W6, W7, W9 | +| A5 | Broker down, 100 observations, restart → all recoverable incl. pending→ready | §5.5, §7.3 | L2, L5, L11, W6 | +| A6 | Interrupted writes/publication: no ready ref to unfinished file; committed changes recovered | §4.2, §5.2, §5.5, §14 | L3, L4, L5, L11 | +| A7 | Camera-scoped token isolation for list/fetch/annotate; rename/delete/name reuse | §7.1, §8, §10.2, §12 | L8, L11, L14 | +| A8 | Distinct outcomes for expired media, expired cursor, unavailable, temporary | §8.2, §11, §16.6 | L8, L11, W5 | +| A9 | Snapshot-only source completes setup; unsupported jobs do not run and explain why | §9 | W3, W8 | +| A10 | Frigate regression preserved across migration | §16.4 (Frigate column), §16.1 | W0, W1–W9 | +| A11 | Saturation: limits hold, drops have reasons, recording healthy, enqueue never waits | §13, §15, §19 | L3, X2 | +| A12 | Write-back retry, stale revisions, owner overwrite: one annotation, explicit conflicts, detector output intact | §10.4, §11 | L13, L14, W10, X3 | +| A13 | Classification updates do not retrigger; feedback outage leaves result usable | §5.3, §16.2, §16.7 | W4, W10, X3 | +| A14 | Operator recipe with scoped credentials; reconnect recovery; versions recorded | §9, §12, §18 | L12, X1 | diff --git a/docs/integrations/YA_WAMF.md b/docs/integrations/YA_WAMF.md new file mode 100644 index 000000000..4fb5cedc7 --- /dev/null +++ b/docs/integrations/YA_WAMF.md @@ -0,0 +1,488 @@ +# YA-WAMF with lightNVR: deployment recipe + +This recipe connects lightNVR (recording and bird detection) to +[YetAnother-WhosAtMyFeeder](https://github.com/Jellman86/YetAnother-WhosAtMyFeeder) +(YA-WAMF: species identification, photographs, visits and notifications). It +is the minimal deployment PRD +[INT_01 §9](../prd/INT_01_Event_Enrichment_YAWAMF.md#9-rollout-and-compatibility) +asks for: API permissions, broker routes, bird detection, retention, +diagnostics and rollback. + +The wire behaviour is normative in the +[observations contract](OBSERVATIONS_CONTRACT.md). This page links to it and +does not restate it. + +> **Status.** Both sides are on unreleased branches. X1 has run this recipe end +> to end against real processes, with synthetic fixtures, a pinned detector +> and a pinned classifier +> ([`tests/integration/yawamf_e2e`](../../tests/integration/yawamf_e2e/README.md)). +> The A14 pilot on a real camera has not happened yet. See the +> [supported-version matrix](#supported-version-matrix). + +## Contents + +1. [How the pieces fit](#how-the-pieces-fit) +2. [Prerequisites](#prerequisites) +3. [Step 1: a bird-capable detector](#step-1-a-bird-capable-detector) +4. [Step 2: turn on evidence per camera](#step-2-turn-on-evidence-per-camera) +5. [Step 3: MQTT and event routes](#step-3-mqtt-and-event-routes) +6. [Step 4: a lightNVR user and scoped token for YA-WAMF](#step-4-a-lightnvr-user-and-scoped-token-for-ya-wamf) +7. [Step 5: configure YA-WAMF](#step-5-configure-ya-wamf) +8. [Step 6: verify](#step-6-verify) +9. [Optional: send species back to lightNVR (P2)](#optional-send-species-back-to-lightnvr-p2) +10. [Retention and budgets](#retention-and-budgets) +11. [Diagnostics](#diagnostics) +12. [Delivery guarantees](#delivery-guarantees) +13. [Rollback](#rollback) +14. [Supported-version matrix](#supported-version-matrix) + +## How the pieces fit + +1. A lightNVR object detector reports a `bird` on a decoded frame. +2. If evidence is on for that camera, lightNVR stores a durable **observation** + for each eligible object: its label, confidence and box, and one JPEG + **evidence** image shared by all objects found in that frame. +3. Each revision of an observation is published as + `io.lightnvr.detection.observation_changed.v1` on + `/v1/events/io.lightnvr.detection.observation_changed.v1/` + ([contract §6](OBSERVATIONS_CONTRACT.md#6-change-event-iolightnvrdetectionobservation_changedv1)). +4. YA-WAMF treats the event as a trigger only. It reads the observation with + `GET /api/observations/{id}`, fetches the image with + `GET /api/observations/{id}/snapshot`, crops each bird by its own box and + classifies it. +5. When YA-WAMF misses events (a broker outage or a restart), it pages + `GET /api/observations` with a cursor + ([contract §7](OBSERVATIONS_CONTRACT.md#7-history-and-cursor-get-apiobservations)). +6. Optionally (P2), YA-WAMF writes the species back as a classification + annotation, which lightNVR shows beside the detection. + +**What the evidence is.** It is the low-resolution image the detector actually +ran on: the decoded detection frame at the detection stream's resolution for +local models, or the exact JPEG lightNVR sent to an HTTP detector. It is not a +frame from the recording. Boxes are normalized to this stored image, and the +`transform` records how it maps to the source and inference sizes +([contract §3.2](OBSERVATIONS_CONTRACT.md#32-box-and-coordinate-space), +[§4.4](OBSERVATIONS_CONTRACT.md#44-transform)). YA-WAMF never replaces +missing evidence with a current frame. + +## Prerequisites + +- lightNVR built from a commit listed in the + [matrix](#supported-version-matrix), with MQTT support (`-DENABLE_MQTT=ON`, + the default for full builds). Migrations `0095` and `0096` run automatically on first start. +- An MQTT broker both applications can reach, for example Mosquitto. +- YA-WAMF built from the `feature/lightnvr-provider` branch listed in the + matrix. +- An administrator login on lightNVR. Steps 2–4 need `camera.configure`, + `events.configure` and `users.manage`, which the built-in Administrator role + holds. + +## Step 1: a bird-capable detector + +YA-WAMF only receives what lightNVR's detector reports. The model must output a +`bird` class, or another label that YA-WAMF acts on (YA-WAMF's +`frigate.ingest_labels`, default `bird`). A model that only finds people or +cars produces nothing to identify. + +| Detector | Gives usable evidence | Notes | +| --- | --- | --- | +| Local SOD model (`.sod`) | yes | The image is the decoded frame; the transform mode is `resize`. VOC-class models include `bird`. | +| Local TFLite model on LiteRT (`.tflite`) | yes | Needs a `-DENABLE_LITERT=ON` build and `[detection_engine] enabled = true` ([LiteRT](../CONFIGURATION.md#in-process-litert-engine)); the transform mode is `letterbox`. COCO-class models include `bird`. | +| HTTP API detector (light-object-detect or DOODS2) | yes | The image is the JPEG that was submitted. See the [wire formats](../CONFIGURATION.md#wire-formats). | +| ONVIF camera events | no | Observations are recorded with evidence `unavailable(no_matching_media)` and no box. YA-WAMF skips them with that reason. | +| Motion | no | Motion results never create observations. | + +- The detector runs inside the stream's detection lifecycle, so set + `detection_based_recording: true` and `detection_model` on the stream (with + `record: true` as well if you want continuous recording plus detection + annotations). See [SOD integration](../SOD_INTEGRATION.md) and + [Configuration](../CONFIGURATION.md#api-detection-settings). +- In the stream API, `detection_threshold` is a percentage from 0 to 100 + (`50` means 50%). A fraction such as `0.5` is read as 0.5% and lets almost + every low-confidence box through, and each one becomes an observation. +- With several engines on one stream, each local object engine submits its own + observations. Extra HTTP `api` engines are isolated in multi-engine mode and + produce no evidence ([Detection engines](../DETECTION_ENGINES.md#observation-evidence-int_01)). + +## Step 2: turn on evidence per camera + +Evidence is **off by default** on every camera. Turn it on only for feeder +cameras. While no camera has it on, lightNVR allocates no queue, starts no +worker and writes no images +([contract §13](OBSERVATIONS_CONTRACT.md#13-budgets-and-configuration)). + +In the UI, open the camera's settings and expand **Observation evidence for +integrations**. Or use the stream API, which needs `camera.configure` on that +camera: + +```http +PUT /api/streams/feeder-east +Content-Type: application/json + +{ + "observation_evidence_enabled": true, + "observation_labels": "bird", + "observation_max_age_days": 7, + "observation_max_bytes": 2147483648 +} +``` + +| Field | Default | Bounds | +| --- | --- | --- | +| `observation_evidence_enabled` | `false` | boolean | +| `observation_labels` | `""` (every label) | at most 16 comma-separated labels of 1–31 printable ASCII bytes; matched case-insensitively | +| `observation_max_age_days` | `7` | 1–90 | +| `observation_max_bytes` | `2147483648` (2 GiB) | 67108864 (64 MiB) to 1099511627776 (1 TiB) | + +Out-of-range values return `400`. A valid request answers `202 Accepted` and +the change applies asynchronously, without a restart; confirm it with +`GET /api/streams/{name}/full`. Stream names may contain only letters, digits, +`.`, `-` and `_`. Set +`observation_labels` to the labels YA-WAMF acts on, so other objects use no +evidence budget. + +Record each feeder camera's `camera_uuid` from `GET /api/streams`. YA-WAMF +identifies cameras by UUID, not by name. + +## Step 3: MQTT and event routes + +1. Enable the default broker in `lightnvr.ini` and restart lightNVR + ([MQTT settings](../MQTT_INTEGRATION.md#2-configure-lightnvr)): + + ```ini + [mqtt] + enabled = true + broker_host = mqtt + broker_port = 1883 + topic_prefix = lightnvr + qos = 1 + ``` + + `topic_prefix` must equal YA-WAMF's `LIGHTNVR__MQTT_TOPIC_PREFIX`. lightNVR + advertises the topic it uses in `delivery.topic` of + `GET /api/integrations/capabilities`, and YA-WAMF's connection test reports + `topic_prefix_mismatch` when they differ. YA-WAMF reads the topic from the + default `[mqtt]` prefix, so use the `mqtt:default` destination. + +2. **Event routes.** With no event routes stored, lightNVR publishes every + normalized event, observation changes included, and nothing else is needed. + Once **any** route exists, only events that match an enabled route are + published. In that case add a route for the observation event + ([event routes](../API.md#event-routes); needs `events.configure`): + + ```http + POST /api/event-routes + Content-Type: application/json + + { + "name": "YA-WAMF observations", + "destination": "mqtt:default", + "event_types": ["io.lightnvr.detection.observation_changed.v1"], + "camera_scope": { + "type": "selector", + "selector": { + "version": 1, + "expression": {"op": "camera_uuid", "values": [""]} + } + } + } + ``` + + Do **not** add a detection predicate (`labels_any`, `zone_ids_any`, + `min_confidence`). Predicates only match `io.lightnvr.detection.object.v1`, + so a route with one never matches observation events, and those changes are + recorded as `relayed_no_route` instead of being published. For the same + reason, do not add the observation type to an existing route that has a + label predicate. Leave suppression at zero: debounce, cooldown or grouping + would drop observation revisions, which YA-WAMF then recovers only through + history. + +Existing detection topics, snapshots and Home Assistant topics are unchanged +([MQTT integration](../MQTT_INTEGRATION.md#observation-change-events-int_01)). + +## Step 4: a lightNVR user and scoped token for YA-WAMF + +YA-WAMF authenticates with one scoped API token. Give it a dedicated user. + +1. **Create the user** (Users page, or `POST /api/auth/users`), for example + username `yawamf` with the Viewer role (`"role": 2`). The username matters + for P2: lightNVR derives the writer namespace for annotations from it + (lowercased, characters outside `[a-z0-9_.-]` replaced by `-`). `lightnvr`, + `system`, `owner` and `local` are reserved and give no namespace + ([contract §10.2](OBSERVATIONS_CONTRACT.md#102-writer-namespace-and-identity)). +2. **Create a role** with the actions YA-WAMF needs + (`POST /api/authorization/roles`, [roles](../API.md#manage-authorization-roles)). + Read `policy_version` from `GET /api/authorization/roles` first and send it + as `expected_policy_version`; without it the request fails with `400`: + + ```json + { + "expected_policy_version": 12, + "name": "YA-WAMF", + "description": "YA-WAMF feeder integration", + "actions": ["live.view", "snapshot.create", "observations.annotate"] + } + ``` + + | Action | Needed for | + | --- | --- | + | `live.view` | camera list, observation history and single reads | + | `snapshot.create` | evidence images (`/snapshot`) | + | `observations.annotate` | P2 write-back only | + + Only the built-in Administrator role holds `observations.annotate` by + default; no other built-in role has it. +3. **Grant the role to the user on the feeder cameras only** + (`PUT /api/authorization/users/{user_id}`, + [user policy](../API.md#manage-a-users-authorization-policy)), with a + `camera_uuid` selector, a tag selector or a shared collection as the scope. + This replaces the Viewer grant, so the user sees nothing else. As with the + role, send the `policy_version` from `GET /api/authorization/users/{user_id}` + as `expected_policy_version`, with `"mode": "policy"` and the complete + `grants` list. +4. **Issue a scoped token** for that user from **Users → Manage API access**, + or with `POST /api/authorization/users/{user_id}/tokens` + ([scoped tokens](../API.md#manage-scoped-api-tokens)): + + ```json + { + "description": "YA-WAMF feeder integration", + "expires_at": 1819075200, + "actions": ["live.view", "snapshot.create", "observations.annotate"], + "scope": { + "type": "selector", + "selector": {"version": 1, "expression": {"op": "camera_uuid", "values": [""]}} + } + } + ``` + + The token must list each action explicitly; it can do only the + intersection of its own actions and the user's current grants. Leave + `observations.annotate` out if you will not use write-back. Expiry is at + most 366 days, so plan a rotation. The `secret` is shown once. + +An observation on a camera the token cannot view is answered exactly like an +unknown ID (`404 observation_not_found`). A token with `live.view` but not +`snapshot.create` gets `403` on `/snapshot` +([contract §12.1](OBSERVATIONS_CONTRACT.md#121-mapping-confirms-plan-decision-7)). + +## Step 5: configure YA-WAMF + +Set these in YA-WAMF's environment, or in **Settings → Connection** (the +source card), which writes the same settings. They are documented in YA-WAMF's +`docs/setup/environment-variables.md`. + +| Variable | Value | +| --- | --- | +| `LIGHTNVR__ENABLED` | `true`. Frigate stays the default until this is set. | +| `LIGHTNVR__API_URL` | lightNVR base URL, for example `http://lightnvr:8080` (`http` or `https`, no credentials). | +| `LIGHTNVR__TOKEN` | the scoped token secret from step 4. | +| `LIGHTNVR__MQTT_TOPIC_PREFIX` | the lightNVR `[mqtt] topic_prefix` (default `lightnvr`). | +| `LIGHTNVR__CAMERA_UUIDS` | comma-separated feeder camera UUIDs; empty means every camera the token may view. | +| `LIGHTNVR__SOURCE_INSTANCE` | optional: a stable name (letters, digits and `-`, at most 64) used instead of the lightNVR installation UUID, so history stays attached across a lightNVR reinstall. | +| `LIGHTNVR__REQUEST_TIMEOUT_SECONDS` | optional, 1–60 (default 10). | +| `LIGHTNVR__WRITEBACK_ENABLED` | optional, default `false`; see [P2](#optional-send-species-back-to-lightnvr-p2). | + +- YA-WAMF reaches lightNVR's events through its existing broker settings + (`FRIGATE__MQTT_SERVER`, `FRIGATE__MQTT_PORT`, `FRIGATE__MQTT_AUTH`, + `FRIGATE__MQTT_USERNAME`, `FRIGATE__MQTT_PASSWORD`). Point them at the same + broker lightNVR publishes to. +- `LIGHTNVR__CAMERA_UUIDS` needs YA-WAMF `b89846d0` or later. Older builds of + the branch do not start when the variable holds a UUID. +- **Restart YA-WAMF** after changing the source or any lightNVR setting. The + running provider is not rebuilt in place, and the settings page says when a + restart is pending. A failed test or save never switches providers. +- YA-WAMF applies its source-identity migration (Alembic `d4e1a7c93b52`) on + the first start of this build. Back up `/data` first; YA-WAMF's + `MIGRATION.md` ("Source identity for each detection") covers what changes + and how to roll back. +- **First start is live-only.** With no stored cursor, YA-WAMF starts at + `cursor=head` and does not import retained lightNVR history. To import a + range, use **Settings → Data → Backup & maintenance tools → Scan History**. + Items imported this way do not send the normal notification. + +## Step 6: verify + +1. In YA-WAMF, **Settings → Connection → Test lightNVR connection** reports + four results: + - API access: the token is accepted, history is authorized, and each chosen + camera answers the setup probe; + - event connectivity: the broker answers and the topic prefixes agree; + - evidence access: snapshots are authorized and every chosen camera keeps + evidence; + - features: video clips and recordings are listed as unsupported. That is + expected and does not affect health. + + It also shows the latest eligible detection and its photo state. "No bird + detections yet" means lightNVR answered but has reported no bird in the last + 30 days; it is not an error. +2. The same setup probe by hand, with the YA-WAMF token: + + ```bash + curl -H "Authorization: Bearer $TOKEN" \ + "http://lightnvr:8080/api/integrations/capabilities" + curl -H "Authorization: Bearer $TOKEN" \ + "http://lightnvr:8080/api/observations?camera_uuid=&limit=1" + ``` + + - `200` with an empty `items` list: authorized, no eligible detections yet. + - `403`: the token lacks `live.view` on that camera. It is never reported + as an empty list. + - In the capabilities document, check `principal.writer_namespace`, + `operations`, `delivery.mqtt_enabled` and `delivery.topic`, and that each + feeder camera has `evidence_enabled: true` + ([contract §9](OBSERVATIONS_CONTRACT.md#9-capability-document-get-apiintegrationscapabilities)). +3. Watch the topic while a bird visits: + + ```bash + mosquitto_sub -h mqtt -t 'lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/#' -v + ``` + + A new observation usually publishes `created` with evidence `pending`, then + `evidence` with `ready`. + +## Optional: send species back to lightNVR (P2) + +1. Make sure the token's user holds `observations.annotate` on the feeder + cameras and the token lists it (step 4). Capabilities should show + `operations["observations.annotate"].authorized: true`. +2. Set `LIGHTNVR__WRITEBACK_ENABLED=true` (or **Send species to lightNVR** in + **Settings → Connection**) and restart YA-WAMF. + +YA-WAMF then writes `/species` with `kind: automated` for a +model result and `kind: owner_confirmed` for an owner correction. An owner +decision is never replaced by an automated result. While lightNVR is +unreachable, results wait as "sync pending". Turning the switch on does not +send detections saved while it was off. lightNVR shows the newest owner +decision, otherwise the newest automated result, beside the detection on the +timeline and in the recording player; the detector's own label and confidence +stay visible ([contract §10](OBSERVATIONS_CONTRACT.md#10-classification-annotations-p2)). + +Without `observations.annotate`, results stay "sync pending" and are given up +after about two days of retries. + +X3 ran this end to end (A12, A13 and A7-annotate; see the +[matrix](#supported-version-matrix)). Known gap: only a new detection and an +owner correction (**Edit species**, single or bulk) are sent. A species +changed later by **Reclassify** or by video classification stays local until +the owner corrects or confirms it. + +## Retention and budgets + +([contract §14](OBSERVATIONS_CONTRACT.md#14-retention-and-storage)) + +- **Evidence images** expire per camera, by `observation_max_age_days` or + `observation_max_bytes`, whichever comes first, oldest first. An expired + observation stays readable, and `/snapshot` answers + `410 evidence_expired`. Evidence lives under `/observations/` + and is independent of recording retention in both directions. +- **History** (observation metadata and the change log) is kept for 30 days + (`system_settings.observation_history_retention_days`). This release has no + API or UI setting for it. +- **Cursor expiry.** A cursor older than retained history, or one from before + a database restore, gets `410 cursor_expired` with `oldest_cursor` + ([contract §7.5](OBSERVATIONS_CONTRACT.md#75-expiry-rules-410-cursor_expired)). + YA-WAMF then marks the source `recovery_required`, resumes from + `oldest_cursor` and shows the gap on its Health page until you acknowledge + it. +- When a camera's byte budget is used up and only protected images remain, new + evidence is rejected as `budget_exhausted` and flagged in diagnostics. +- The budget defaults are final. The X2 saturation gate measured them with + evidence on four 720p cameras at the maximum detector cadence: no recording + gaps and no recording-path slowdown + ([contract §19](OBSERVATIONS_CONTRACT.md#19-performance)). + +## Diagnostics + +| Where | What | Who can read it | +| --- | --- | --- | +| YA-WAMF connection test and Health page | API, broker, evidence and feature checks; latest detection and photo state; recovery state; write-back counts | YA-WAMF owner | +| YA-WAMF `GET /api/diagnostics/source`, `/health` | source status, skipped workflows, `source_recovery`, `notification_intents`, `classification_writeback` | YA-WAMF owner | +| lightNVR `GET /api/observations?camera_uuid=X&limit=1` | the setup probe above | the YA-WAMF token | +| lightNVR `GET /api/metrics` | `lightnvr_observation_*` Prometheus families: accepted, dropped by reason, backlog and high-water marks, evidence failures, retained bytes and budgets per camera, oldest and head cursor seq, publications by state, relay lag | `system.admin` | +| lightNVR `GET /api/system/info` → `observation_pipeline`, and the **Observation pipeline** card on the System page | the same snapshot as JSON | `system.admin` | + +The metric names are listed in +[contract §15](OBSERVATIONS_CONTRACT.md#15-diagnostics), and the JSON shape is +`ObservationPipelineDiagnostics` in [`openapi.json`](../api/openapi.json). +YA-WAMF's scoped token cannot read the last two rows; use an administrator +login. `active: false` means no camera has produced evidence in this process +run and nothing is retained. Per-camera rows come from the last retention pass, +so they can be up to five minutes old. + +What to look for: + +- `dropped{reason="queue_full"}` or `budget_exhausted` rising: the camera + produces more evidence than the budgets allow. +- `publications{state="pending"}` and `relay_lag_seconds` rising: the outbox + is not draining, usually because the broker is down. The cursor API still + serves these changes. +- Many `relay_events_total{outcome="no_route"}`: routes exist but none + matches the observation type (see [step 3](#step-3-mqtt-and-event-routes)). + +## Delivery guarantees + +- **MQTT delivery is at least once.** A change can be delivered again, after a + retry, a reconnect or a restart, with the same envelope `id`. YA-WAMF + deduplicates on `source` + `id` and admits each observation at most once + ([contract §5.5](OBSERVATIONS_CONTRACT.md#55-relay-delivery), + [§16.2](OBSERVATIONS_CONTRACT.md#162-admission)). +- Undelivered changes expire from the outbox after one hour. The history + cursor is the durable recovery path. +- **YA-WAMF notifications are at most once.** A notification whose outcome is + unclear (a timeout after sending, or a crash during sending) is recorded as + `unknown` and shown in diagnostics, never resent. After a hard stop, some + notifications can show `unknown`; that is expected. + +## Rollback + +- **Stop the integration** without losing data: + - in YA-WAMF, set `LIGHTNVR__ENABLED=false` (Frigate becomes the source + again) and restart; + - in lightNVR, set `observation_evidence_enabled: false` on each camera. + No new evidence is collected. Existing observations, evidence images and + annotations are kept and expire under the normal retention rules. + - Revoke the YA-WAMF token (`DELETE /api/authorization/users/{user_id}/tokens/{token_uuid}`). +- **Database rollback drops data.** The `-- migrate:down` sections of + `0096_add_observation_classifications.sql` and + `0095_add_object_observations.sql` drop every annotation, observation and + evidence row; evidence files stay on disk. Take a lightNVR backup + (`POST /api/system/backup`, [System backup](../API.md#system-backup)) before + upgrading, and restore it rather than running the down sections when you + need to go back. Running an older lightNVR binary against a database with + 0095/0096 applied has not been tested. +- **YA-WAMF.** Follow its `MIGRATION.md` for migration `d4e1a7c93b52`. In + short: restore the backup taken before the update. An older YA-WAMF image is + not supported once a non-Frigate source has been used. + +## Supported-version matrix + +This publishes [contract §18](OBSERVATIONS_CONTRACT.md#18-supported-version-matrix-stub). +Neither branch is released or pushed. + +| Component | Version | Contract | +| --- | --- | --- | +| lightNVR | branch `feat/int01-event-evidence`; X1 ran a binary built from `e294f956` and X3 one built from `bcf5290f` (version string 0.47.3; the last behaviour change is `ddea7404`, later commits are docs, tests and plan updates) | `observations` 1, `classifications` 1, event `io.lightnvr.detection.observation_changed.v1` | +| YA-WAMF | branch `feature/lightnvr-provider` at `1cbbae74`, on v2.23.1 (`53540f48`), for both X1 and X3. It needs `b89846d0` or later to start with `LIGHTNVR__CAMERA_UUIDS` set, and `de28e1e4` or later for write-back (P2) | `observations` 1, `classifications` 1 | +| MQTT broker | Mosquitto 2.0.22 (X1) | n/a | +| Frigate (YA-WAMF source) | as supported by YA-WAMF v2.23.1 | n/a, unchanged | + +What has been tested, and how: + +| Area | Status | Covered by | +| --- | --- | --- | +| lightNVR observation, evidence, relay, retention, API and annotation units (C, including ASan/UBSan builds) | tested | `tests/unit/test_db_observations`, `test_observation_evidence`, `test_observation_relay`, `test_observation_retention`, `test_observation_crash_boundaries`, `test_api_handlers_observations`, `test_db_observation_classifications`, and others listed in the plan | +| Local detectors (SOD, LiteRT) producing evidence | tested (unit) | `test_detection_evidence`, `test_detection_observation_evidence` | +| HTTP API detector (light-object-detect and DOODS2 formats), ONVIF unavailable evidence | tested (unit, loopback detector) | `test_api_detection_observation_evidence` | +| lightNVR binary end to end: HTTP detector → observation → MQTT, history, crash and restart, restore, per-camera isolation, expiry outcomes (A1, A2, A5–A8) | tested (Playwright contract suite, in-process MQTT sink) | `tests/contracts/observations*.api.spec.ts` | +| Annotation PUT and display (A12, A7-annotate, L15) | tested (Playwright contract and UI) | `tests/contracts/observations.api.spec.ts`, `tests/integration/specs/detection-classification.ui.spec.ts` | +| YA-WAMF ingestion, recovery, notification intent, setup, write-back (A1, A3, A4, A9, A10) | tested (pytest against a fake lightNVR and fake broker driven by the shared fixtures; Playwright UI) | `backend/tests/test_lightnvr_acceptance.py` and the W5–W10 test modules in YA-WAMF | +| Frigate regression after the YA-WAMF migration | tested | `backend/tests/test_frigate_regression_baseline.py` in YA-WAMF | +| Both applications together through a real broker, this recipe run step by step (X1: A1, A2, A3, A4, A5, A6, A8) | tested, in host-process mode: real Mosquitto, the lightNVR binary and the YA-WAMF backend as host processes. The container mode in the same directory is documented but has not been run | `tests/integration/yawamf_e2e/run_e2e.py`; results in `tests/integration/yawamf_e2e/results/x1-host-20261108.json` | +| MQTT broker | tested: Mosquitto 2.0.22 (local-only mode, QoS 1, no persistence), including a broker outage | X1 | +| HTTP bird detector | tested with a pinned stand-in: the loopback `x1-colour-detector 1.0` in the light-object-detect wire format, which finds synthetic red and blue birds by colour. A real HTTP bird model (light-object-detect or DOODS2) is **pending** | X1 | +| Local bird detector | tested once: SOD VOC `tiny20.sod` (classes include `bird`) on a real bird photo crossing the frame (X1 `A2-local`; optional, because the photo is not committed). LiteRT/TFLite end to end is **pending** | X1 | +| YA-WAMF classifier | tested with a pinned stand-in: `x1-colour-rule-v1`, which replaces `classify_async_live` only and names a crop by its saturated red or blue pixels. YA-WAMF's real bird model is **pending** | X1 | +| P2 end to end (A12, A13, A7-annotate) with `LIGHTNVR__WRITEBACK_ENABLED=true` | tested, in the same host-process mode (X3). The species shows in YA-WAMF, in lightNVR's annotation, `GET /api/observations/{id}` and the detection results. Replays and stale revisions keep one annotation. An owner correction arrives as `owner_confirmed`, and a later automated result is refused. The classification change causes no classifier call or notification-intent change. A 47 s lightNVR outage leaves the YA-WAMF detection intact, and the write-back completes 14 s after lightNVR returns. A token without `observations.annotate` gets `403`. The X1 scenarios pass in the same run with write-back on | `tests/integration/yawamf_e2e/run_e2e.py` (`p2_scenarios.py`); results in `tests/integration/yawamf_e2e/results/x3-host-20261108.json`, RED run without write-back in `x3-host-20261108-red-nowriteback.json` | +| Saturation and performance gate (A11), final budget defaults | **pending** | X2 | +| Real-camera operator pilot (A14) | **pending**; checklist ready | [`A14_PILOT_CHECKLIST.md`](../../tests/integration/yawamf_e2e/A14_PILOT_CHECKLIST.md) | diff --git a/docs/prd/INT_01_Event_Enrichment_YAWAMF.md b/docs/prd/INT_01_Event_Enrichment_YAWAMF.md new file mode 100644 index 000000000..79d369a21 --- /dev/null +++ b/docs/prd/INT_01_Event_Enrichment_YAWAMF.md @@ -0,0 +1,431 @@ +# PRD — Event Evidence and YA-WAMF Integration + +**Status**: In progress — P0 contract frozen in [OBSERVATIONS_CONTRACT.md](../integrations/OBSERVATIONS_CONTRACT.md) (C1). P1 and P2 are implemented and unit/contract tested on unreleased branches (lightNVR `feat/int01-event-evidence`, YA-WAMF `feature/lightnvr-provider`), with the [deployment recipe and supported-version matrix](../integrations/YA_WAMF.md) published (L12). Cross-repo end-to-end (X1, X3), the saturation gate (X2) and the real-camera pilot (A14) remain +**Created**: 2026-11-08 +**Owner**: TBD — lightNVR integration owner; YA-WAMF counterpart to be identified +**Scope**: Extend lightNVR with reusable observation, evidence, and classification +contracts, then refactor YetAnother-WhosAtMyFeeder (YA-WAMF) to consume those +contracts alongside its existing Frigate integration. +**Baseline**: lightNVR `0ff0d2e3`; YA-WAMF development commit +`53540f485a238e615246d2239cffe23d725903ad`. Published YA-WAMF releases may differ. + +## 1. Product decision + +A feeder-camera owner should be able to use lightNVR for recording and initial +bird detection, and YA-WAMF for species identification, photographs, visit +history, and notifications. Choosing lightNVR should not require rebuilding +YA-WAMF's specialist application inside the NVR. + +Extend lightNVR where a capability benefits external consumers generally: +durable observations, correctly paired evidence, recoverable history, and +classification feedback. Refactor YA-WAMF where its current assumptions are +specific to Frigate: event parsing, media retrieval, source identity, lifecycle, +and feature availability. + +The first integration must work with individual observations. It must not +require lightNVR to manufacture Frigate-style tracked-object visits or add a new +object tracker. YA-WAMF already groups detections into visits; its grouping +remains presentation logic and must not imply that separate photographs show +the same physical bird. + +```text +Camera -> lightNVR object detector -> durable observation + matching photograph + -> existing event bus / MQTT + | + v + YA-WAMF lightNVR provider + | + classification / visits / notifications + | + optional classification write-back + v + lightNVR observation annotations +``` + +## 2. Goals and boundaries + +### Goals + +- Deliver the first useful outcome: a lightNVR bird observation becomes a + species-labelled photograph in YA-WAMF, linked to the original observation. +- Recover retained observations after subscriber downtime without duplicate + detections or repeated standard notifications. +- Preserve Frigate behavior and existing YA-WAMF installations while adding a + second source implementation. +- Keep inference, species taxonomy, bird dashboards, and notifications in + YA-WAMF; keep camera recording and evidence authority in lightNVR. +- Establish contracts usable by another classifier without YA-WAMF-specific + payloads or database columns in lightNVR. + +### Non-goals for the initial release + +- Full Frigate API compatibility, a Frigate emulation service, or an MQTT-only + topic-renaming bridge. +- A new lightNVR object tracker, individual-bird identity recognition, or + synthetic `new/update/end` messages for untracked observations. +- Porting YA-WAMF's models, dashboard, BirdNET integration, or notification + providers into lightNVR. +- Generic plugin execution, arbitrary scripts, or remote model execution in the + lightNVR process. +- Simultaneous multi-NVR ingestion within one YA-WAMF deployment. Identity must + permit it later, but initial configuration selects one source. +- Video analysis, best-frame search across recordings, or full-visit clips in + the first snapshot-based integration. + +## 3. Existing foundation and gaps + +| Area | Current foundation | Required change | +| --- | --- | --- | +| Detection | Local and external object engines; labels, confidence, normalized boxes, optional track IDs | An eligible detector must identify `bird`; motion-only input does not satisfy this flow | +| Events | Versioned envelopes, camera UUIDs, routing, durable MQTT outbox | Persist an observation identity distinct from a delivery-message identity | +| Images | Legacy MQTT worker captures go2rtc JPEGs and serves authenticated snapshot URLs | Retain evidence from the actual inference input, independently of broker connectivity | +| History | Detection database and recording associations | Bounded, paginated observation history with durable recovery cursors and explicit retention limits | +| Feedback | Detection labels and existing authorization/audit infrastructure | Store external classifications separately from original detector results | +| YA-WAMF | Frigate ingestion, model inference, media cache, visits, corrections, notifications | Introduce a source boundary and capability-aware workflows | + +The current normalized detection producer does not populate a snapshot reference. +The legacy snapshot path captures an image after detection, requires the default +MQTT connection, and retains at most 100 snapshots per camera. Its timestamped +image is not proof that the image and detection box came from the same frame. +The new evidence path must not depend on those assumptions. + +Existing MQTT envelope IDs identify individual facts and their retries. They +must not be repurposed as tracked-object identities. Existing topics and +consumers remain compatible throughout this work. + +## 4. User experience + +### Setup + +The owner selects **lightNVR** as the source in YA-WAMF, supplies its API URL, +camera-scoped token, and MQTT connection, and selects authorized feeder cameras. +Connection testing reports API access, event connectivity, evidence access, and +available features separately. Camera discovery must not require lightNVR +administrator settings access. + +Setup explains that a bird-capable object detector is required. A diagnostic +shows the latest eligible observation and whether its photograph is ready, +missing, or expired. No detection is distinguished from a transport failure. + +### Normal operation + +A bird appears; lightNVR saves its observation and matching photograph. YA-WAMF +retrieves the photograph, classifies it, and displays the species and source +camera in its existing Explorer. An observation without usable evidence remains +visible in diagnostics but does not trigger classification of a newer image. + +When the owner enables write-back, the species and classifier confidence also +appear in lightNVR's observation detail. The original `bird` label and detector +confidence remain available. Write-back failure does not remove the YA-WAMF +detection or cause another notification. + +### Recovery and limited capabilities + +After an outage, YA-WAMF imports retained observations from its saved cursor. +Historical recovery is silent by default and distinguishes expired evidence +from temporary retrieval failure. Unsupported video features are disabled with +a short explanation; they do not make the source appear unhealthy. + +## 5. lightNVR requirements + +### R1 — Durable observation identity + +Create one stable observation ID per detected object in an accepted inference +result, associated with the immutable camera UUID and installation identity. +Several objects from one frame may reference the same evidence asset. Include +an inference batch ID and object index so retries of the same accepted batch do +not create additional observations. Independently sampled frames are distinct +observations even when they contain the same bird. + +The observation contains: + +| Field | Contract | +| --- | --- | +| `id`, `source`, `camera_uuid` | Immutable observation, installation, and capture-time camera identities | +| `observed_at`, `created_at` | Observation time and persistence time, explicitly distinct | +| `detector` | Engine identity and available model/version provenance | +| `label`, `confidence` | Original detector result; confidence in `0..1` | +| `box` | Optional normalized coordinates with explicit format and evidence coordinate space | +| `evidence` | Asset ID, state, capture time, dimensions, transform/alignment metadata, and authenticated media reference | +| `track` | Optional source-provided ID, namespace and session; absent when tracking is unavailable | +| `recording_reference` | Optional logical association; absence must not prevent snapshot classification | +| `revision` | Monotonic revision for later evidence or classification changes | + +Renaming or removing a camera must not transfer its historical observations to +another camera. A reused detector track number after restart must not join two +unrelated tracks. Do not require a recording to exist: eligible observations +also work when detection runs without recording. + +### R2 — Evidence paired with inference + +For local inference, retain an owned copy or safely referenced buffer of the +actual input frame before its lifetime ends. Encode and store it outside the +capture thread. For HTTP inference, retain the submitted image and the +coordinate transform used to interpret the response. Do not fetch a second +go2rtc image and treat it as the inference frame. + +Save an unannotated image with sufficient metadata to map the detection box +correctly after resizing or letterboxing. Crops may be derived from that image; +the original evidence association must remain auditable. A low-resolution +inference image is acceptable for the first release and must not be described +as a higher-resolution recording image. + +Evidence states are `pending`, `ready`, `unavailable`, and `expired`. Include a +bounded machine-readable reason for failure. For ONVIF or external events that +cannot supply matching media, report unavailable evidence; a separately timed +context image must never be represented as aligned detection evidence. + +An evidence-ready event may be published only after the asset is readable and +its metadata is committed. Use atomic file finalization and recoverable database +state transitions; startup reconciliation must resolve interrupted writes and +clean orphaned temporary files. Commit the durable observation change and a +recoverable publication record together, then feed the existing router/outbox. +Crashing between persistence and MQTT publication must not strand the change. + +### R3 — History and delivery + +Keep current detection topics unchanged. Register a new versioned observation +change event through the existing event registry, routing, and outbox. Each +committed revision has one message ID preserved across delivery retries; the +payload references the stable observation ID, revision, camera, and evidence +state. Consumers deduplicate messages by `source + message_id` and saved +observations by `source + observation_id`. + +History must support authorized camera selection, label and time filters, +bounded page sizes, and an opaque change cursor ordered by persistence rather +than camera time. Pending-to-ready transitions must be recoverable through this +cursor even when the original observation was read earlier. Use a bounded page +watermark so concurrent writes cannot skip or repeat work indefinitely. + +Expired cursors return an explicit recovery-required response and the available +history boundary. A suppressed MQTT route is not a deletion or authorization +decision: API recovery uses the consumer's selected cameras/labels and current +permissions. Changes caused only by classification feedback must not launch +classification again. + +### R4 — Proposed API surface + +These paths are proposed contracts, to be finalized in P0 and represented in +the canonical OpenAPI document before implementation. Use a distinct observation +resource rather than changing existing `/api/events/catalog` semantics. + +| Operation | Purpose | +| --- | --- | +| `GET /api/integrations/capabilities` | Authorized source identity, contract versions, supported operations and limits | +| `GET /api/observations` | Filtered history/change recovery with pagination and cursor | +| `GET /api/observations/{id}` | Current observation, evidence state and classifications | +| `GET /api/observations/{id}/snapshot` | Authenticated matching image; no substitution with a latest frame | +| `PUT /api/observations/{id}/classifications/{key}` | Idempotent classifier-owned annotation, gated by separate write authority | + +Reuse existing authorized camera discovery where sufficient. Pending evidence +returns a documented retryable response with bounded retry guidance; expired +evidence is distinguishable from an unknown observation for authorized callers. +Never expose existence or media through another camera's authorization scope. + +### R5 — Classification feedback + +Annotations store classifier identity, model/version, result label, optional +taxonomy identifier, confidence, classified-at time, input evidence ID/revision, +and whether the result is automated or owner-confirmed. Keep the schema bounded; +it must support general object classification without bird-specific core fields. + +The authenticated integration owns its annotation namespace. The same key and +payload are a no-op on retry; conflicting updates require an expected revision. +Reject mismatched evidence references. An integration cannot claim another +writer's identity or overwrite an owner's correction. Preserve the original +detector output and expose automated results and owner decisions separately. + +P2 adds a minimal classification display to existing detection/observation +detail, including species, confidence, source, and a link back to the +observation. A dedicated bird dashboard or taxonomy browser is out of scope. + +### R6 — Authorization, storage and resource limits + +All reads and writes resolve capture-time camera identity through Fleet 02. +Metadata reads reuse the appropriate existing camera-view authority; image +retrieval follows `snapshot.create`. P0 must reconcile these mappings with the +current API contract. Add a narrowly scoped classification-write action if no +existing action fits; do not require administrator access or reuse camera +configuration permission for annotation writes. Audit mutations without copying +credentials or image bytes into logs. + +Use per-camera opt-in, age and byte limits, plus bounded worker queues and +concurrency. Reuse existing storage primitives where applicable; evidence files +must not inherit the legacy fixed-count snapshot policy. Assets shared by several +observations are deleted only when no retained reference or protection requires +them. Recording deletion and evidence deletion are separately explicit. + +Retention may expire media while keeping observation metadata; advertise the +actual remaining availability rather than guaranteeing indefinite replay. +Broker downtime, slow consumers, and classifiers must not retain media forever. +If a protected asset cannot be evicted and the budget is exhausted, reject new +evidence work with a visible reason. Never block recording to wait for disk, +network, inference feedback, or queue space. + +Report accepted/dropped observations, evidence backlog/failures, retained bytes, +oldest recoverable cursor, and publication failures through existing diagnostics. +Deployments with this feature disabled should not allocate its frame-copy queue +or perform its image writes. + +## 6. YA-WAMF refactor requirements + +### T1 — Introduce an NVR source boundary + +Extract a small provider interface for source identity, authorized cameras, +capabilities, observation ingestion/history, media retrieval, and optional +classification write-back. Keep existing Frigate behavior in a Frigate provider; +implement a lightNVR provider using R1–R6. + +The common input represents an observation plus optional lifecycle metadata. +Frigate retains its tracked-event start/update/end semantics and final-frame +refinement. lightNVR supplies independent observations and evidence-readiness +updates. The common pipeline must not invent an `end` message to make an +untracked observation classifiable. + +Refactor the working ingestion and media paths first. Avoid a general plugin +framework or a wholesale rename of every historical Frigate identifier. + +### T2 — Migrate source identity safely + +Add source kind, source instance, and external observation ID to the identity +used by detections, cached media, durable jobs, favourites, and corrections. +Scope duplicate detection to that tuple, including existing rows. + +Backfill legacy rows as belonging to the configured Frigate source through an +explicit migration. Preserve their IDs, photographs, favourites, corrections, +and queued work. Version the database migration and document backup/rollback +requirements; an older binary must not silently operate on incompatible data. +Changing the configured NVR must not reassign previous history to the new source. + +### T3 — Remove Frigate assumptions from shared workflows + +| Current area | Required treatment | +| --- | --- | +| `mqtt_service.py`, `event_processor.py` | Provider-specific parsing; common admission, idempotency and classification; lifecycle hooks only where supplied | +| `frigate_client.py` | Retain as Frigate implementation behind the provider interface | +| `routers/proxy.py`, `media_cache.py` | Resolve media by source identity; add neutral routes while preserving existing Frigate URLs | +| `backfill_service.py`, missing-event reconciliation | Provider history/cursor handling; distinguish missing, expired, unsupported and temporarily unreachable | +| HQ snapshot, recording snapshot and full-visit services | Invoke only for supported capabilities; preserve Frigate's current behavior | +| Notification orchestration and favourites | Build links and fetch evidence through the provider; preserve cached media and owner decisions | +| Settings, onboarding and health UI | Source selector, scoped connection test and capability-specific controls | + +Audit direct Frigate HTTP calls beyond the central client, including background +jobs and notification attachments. A lightNVR configuration must never make an +accidental `/api/events/{id}/...` request because a shared workflow bypassed the +provider interface. + +### T4 — Capability-aware classification and recovery + +The first lightNVR provider advertises snapshots and history, with optional +write-back when P2 is available. Tracking lifecycle, recorded-frame selection, +event clips and recording-range clips remain independently declared features. +Unsupported features are not inferred from an endpoint returning an error. + +Classify only ready evidence. Duplicate deliveries, evidence retries and stale +revisions must not produce duplicate detections. Multiple objects in one frame +must be classified using their own aligned regions. Use bounded admission and +existing visit-grouping controls to handle repeated observations without +claiming that temporal grouping establishes object identity. + +Advance the recovery cursor only after work is durably accepted or explicitly +recorded as skipped with a reason. Persist retryable failures. Live/recovery +overlap shares the same deduplication identity. Record notification intent +durably so event replay does not schedule it again; external delivery ambiguity +must remain visible rather than promising exactly-once notifications. + +## 7. Delivery phases + +| Phase | Deliverable | Exit gate | +| --- | --- | --- | +| P0 — Contract and fixtures | Freeze observation/evidence schemas, API errors, permission mappings, budgets, provider capabilities and supported source versions; capture Frigate regression fixtures | Both implementations can consume the agreed fixtures; current Frigate behavior has a reproducible baseline | +| P1 — Snapshot integration | lightNVR durable observations, paired evidence, history and MQTT changes; YA-WAMF provider boundary, migration, lightNVR setup and classification | One feeder camera produces labelled photographs; disconnect/restart recovery and Frigate regression checks pass | +| P2 — Classification feedback | Scoped annotation API, YA-WAMF write-back, correction precedence and minimal lightNVR detail display | Species appears in both products; retry, authorization and correction tests pass | +| P3 — Optional recorded media | Event/range media retrieval and recorded-frame refinement only where reusable lightNVR primitives support them | Correct time/box alignment, partial coverage, retention and permissions pass dedicated media acceptance | + +P0 precedes changes to either runtime contract. Within P1, extract the Frigate +provider while preserving behavior, implement lightNVR evidence/history, then +connect the lightNVR provider and run the end-to-end suite. P1 is a useful +standalone release; P2 and P3 must not block its snapshot workflow. + +The target is upstreamable YA-WAMF changes. An upstream merge is an external +dependency, not an acceptance assumption. A maintained branch may host the +prototype; production release requires either upstream acceptance or a named +owner for maintaining that branch and its version compatibility. No unmodified +YA-WAMF compatibility claim is made. + +## 8. Acceptance criteria + +| ID | Scenario and required result | Gate | +| --- | --- | --- | +| A1 | On a declared bird-capable detector and recorded input fixture, every admitted observation has a stable source/camera identity and the expected object label/region | P1 | +| A2 | A moving-bird fixture changes position between inference and later frames; the saved photograph and region still match the inference input. Repeat for local and HTTP detectors | P1 | +| A3 | Two birds in one frame create separate observation identities with correctly aligned regions; shared evidence is retained until all applicable references expire | P1 | +| A4 | Replaying each MQTT message ten times and overlapping API recovery creates one YA-WAMF detection per source observation and one standard notification intent | P1 | +| A5 | Stop the broker, create 100 admitted observations, restart both applications and reconnect within retention: all 100 are recoverable, including pending-to-ready transitions | P1 | +| A6 | Interrupt evidence writes and publication at each persistence boundary: no ready reference points at an unfinished file, and committed changes can be recovered after restart | P1 | +| A7 | A camera-scoped token cannot list, fetch or annotate another camera's data. Rename, delete and name-reuse fixtures never transfer historical access or identity | P1/P2 | +| A8 | Expired media, expired cursors, unavailable evidence and temporary errors produce distinct documented outcomes; recovery never replaces evidence with a current frame | P1 | +| A9 | A snapshot-only source completes setup and classification; unsupported clip/refinement jobs do not run and controls explain their unavailability | P1 | +| A10 | Existing Frigate fixtures preserve initial classification, update recovery, end refinement, false-positive handling, corrections, favourites, backfill and clips across migration | P1 | +| A11 | Saturate evidence queues and storage budgets under a repeatable recording workload: limits hold, drops have reasons, recording remains healthy and producer enqueue never waits for I/O | P1 | +| A12 | Repeat write-back, send stale revisions and attempt to overwrite an owner correction: one authorized annotation is maintained, conflicts are explicit and detector output stays intact | P2 | +| A13 | Classification updates do not trigger another classification/notification cycle; feedback outage leaves the YA-WAMF result usable | P2 | +| A14 | Operator installs from the documented recipe using scoped credentials, sees a real feeder visit, exercises reconnect recovery, and records the tested source/model versions | P1 pilot | + +P0 must record the performance fixture's hardware, stream count, resolutions, +frame rates, detector cadence and retention budgets. Compare integration enabled +and disabled on identical input. The proposed release threshold is no additional +recording gaps and no more than a 5% increase in p95 recording-path latency; +measure memory, CPU, disk use and queue high-water marks as well. If the threshold +cannot be met, reduce admitted evidence work or revise the design before release. +Classification latency is measured separately from NVR capture latency. + +Species accuracy depends on detector/model/image quality. Integration acceptance +checks the expected result on pinned model fixtures and records real-camera +outcomes; it does not promise a universal species-accuracy percentage. + +## 9. Rollout and compatibility + +- Ship lightNVR evidence collection disabled by default; enable it for selected + cameras with explicit storage and admission budgets. +- Preserve existing detection APIs, MQTT topics, Home Assistant behavior and + legacy snapshot consumers. Add contract fixtures to prevent regressions. +- Keep Frigate as the default for existing YA-WAMF configurations. Source + changes require explicit configuration; failures never switch providers. +- Publish a supported-version matrix and a minimal deployment recipe covering + API permissions, broker routes, bird detection, retention and diagnostics. +- Rollback disables the new ingestion/provider path without deleting retained + evidence or annotations. Database rollback follows the documented migration + strategy; it is not assumed safe merely because a feature flag is off. +- P3 must reuse recording/storage authority and report partial or unavailable + coverage. It must not fabricate a full-visit clip from incomplete recordings. + +## 10. Risks and decisions to close + +| Risk or decision | Required resolution | +| --- | --- | +| No persistent tracks from a selected detector | Support observations first; keep visit grouping in YA-WAMF and document its limits | +| Excess classifications from frequent frames | P0 sets admission budgets; YA-WAMF uses bounded work queues and visit controls, with visible skip reasons | +| Evidence expires before recovery | Expose actual retention boundaries, cache admitted photographs in YA-WAMF, and report unrecoverable history | +| Frigate dependencies remain outside the provider | Audit proxy, background, health, notification and archive paths; test that lightNVR invokes only supported operations | +| Native inference buffer ownership is unsafe | Require owned/ref-counted evidence and exercise shutdown, restart and queue-saturation paths | +| Upstream YA-WAMF declines the integration | Identify a maintenance owner before production release; do not substitute broad Frigate emulation by default | +| Annotation authority and correction precedence | Freeze action mappings and writer/owner rules in P0 before exposing mutation endpoints | +| Resource defaults are unmeasured | P0 chooses explicit bounded defaults from the declared performance fixture; defaults are a release prerequisite | + +## 11. Dependencies and references + +- [Fleet 01 — Camera Identity](FLEET_01_Camera_Identity_Organization.md), + [Fleet 02 — Scoped Authorization](FLEET_02_Scoped_Authorization_Audit.md), and + [Fleet 03 — Event Bus & MQTT Routes](FLEET_03_Event_Bus_MQTT_Routes.md). +- [Engineering API contracts](ENG_01_Safety_Contracts_TypeScript.md), + [event contract](../EVENT_CONTRACT.md), + [MQTT integration](../MQTT_INTEGRATION.md), and + [detection-engine boundaries](../DETECTION_ENGINES.md). +- Current lightNVR [observation producer](../../src/core/event_producers.c), + [legacy MQTT snapshots](../../src/core/mqtt_client.c), and + [snapshot retrieval](../../src/web/api_handlers_detection_results.c). +- YA-WAMF [integration contract](https://github.com/Jellman86/YetAnother-WhosAtMyFeeder/blob/53540f485a238e615246d2239cffe23d725903ad/docs/integrations/frigate.md), + [event processing](https://github.com/Jellman86/YetAnother-WhosAtMyFeeder/blob/53540f485a238e615246d2239cffe23d725903ad/backend/app/services/event_processor.py), + [Frigate client](https://github.com/Jellman86/YetAnother-WhosAtMyFeeder/blob/53540f485a238e615246d2239cffe23d725903ad/backend/app/services/frigate_client.py), and + [visit grouping](https://github.com/Jellman86/YetAnother-WhosAtMyFeeder/blob/53540f485a238e615246d2239cffe23d725903ad/backend/app/services/visit_grouping.py). diff --git a/docs/prd/README.md b/docs/prd/README.md index 5f07a8374..558fd4e10 100644 --- a/docs/prd/README.md +++ b/docs/prd/README.md @@ -37,6 +37,7 @@ The other roadmap entries below retain their separately dated status reviews. | --- | --- | | [Recording Retention Policies](PRD_Recording_Retention_Policies.md) | Per-stream retention and deletion policies (implemented — see [the summary](../internal/SUMMARY_Recording_Retention.md) and [the API quick reference](../QUICKREF_Retention_API.md)) | | [Storage 02 — Hot Storage & External Recording Archive](STORAGE_02_Hot_Cold_Archive.md) | Draft (2026-09-13): bounded hot PVC storage, dedicated bucket or mounted archive, protected indefinite retention, and unified playback and cleanup; extends Storage 01 | +| [Integration 01 — Event Evidence & YA-WAMF](INT_01_Event_Enrichment_YAWAMF.md) | In progress (2026-11-08): P1 snapshot integration and P2 classification feedback implemented on unreleased branches in both repos, unit and contract tested; [deployment recipe](../integrations/YA_WAMF.md) published. Cross-repo E2E (X1, X3), saturation gate (X2) and real-camera pilot (A14) remain | ### Fleet control plane diff --git a/include/core/authorization.h b/include/core/authorization.h index 8e8fb9d1c..15dd881f5 100644 --- a/include/core/authorization.h +++ b/include/core/authorization.h @@ -41,6 +41,7 @@ typedef enum { AUTHZ_LPR_SEARCH, AUTHZ_LPR_EXPORT, AUTHZ_LPR_DELETE, + AUTHZ_OBSERVATIONS_ANNOTATE, AUTHZ_ACTION_COUNT } authorization_action_t; diff --git a/include/core/config.h b/include/core/config.h index 5b3347af5..cdfd0bb7f 100644 --- a/include/core/config.h +++ b/include/core/config.h @@ -71,6 +71,10 @@ typedef enum { #define PLAYBACK_TRANSPORT_MAX 24 #define EPTZ_CONFIG_MAX 1024 +// <= 16 labels of <= 31 bytes, comma separated (contract section 13). +#define OBSERVATION_LABELS_MAX 512 +#define OBSERVATION_DEFAULT_MAX_AGE_DAYS 7 +#define OBSERVATION_DEFAULT_MAX_BYTES 2147483648LL /** Return true when value is one of the persisted playback transport values. */ bool playback_transport_is_valid(const char *value); @@ -186,6 +190,14 @@ typedef struct { // Versioned JSON for browser-side fisheye dewarping. Empty disables ePTZ. // The source stream and recordings always remain untouched. char eptz_config[EPTZ_CONFIG_MAX]; + + // INT_01 per-camera observation evidence (migration 0095, contract + // section 13). Off by default; an empty label list means every object + // label. Values <= 0 for the budgets are saved as the defaults. + bool observation_evidence_enabled; + char observation_labels[OBSERVATION_LABELS_MAX]; + int observation_max_age_days; // media max age (default 7) + int64_t observation_max_bytes; // media byte budget (default 2 GiB) } stream_config_t; // Size of recording schedule text buffer: 168 values + 167 commas + null terminator @@ -343,7 +355,7 @@ typedef struct { int go2rtc_webrtc_listen_port; // WebRTC listen port (default: 8555) bool go2rtc_stun_enabled; // Enable STUN servers (default: true) char go2rtc_stun_server[256]; // Primary STUN server (default: stun.l.google.com:19302) - char go2rtc_external_ip[64]; // Optional: External IP for NAT (empty = auto-detect) + char go2rtc_external_ip[256]; // Optional: WebRTC candidate list (IPs/hosts, comma-separated; empty = auto-detect) char go2rtc_ice_servers[512]; // Optional: Custom ICE servers (comma-separated) // Browser-side WebRTC live-view timeouts (milliseconds). Exposed to the web diff --git a/include/core/event_producers.h b/include/core/event_producers.h index a5de8aa77..f4ea2a875 100644 --- a/include/core/event_producers.h +++ b/include/core/event_producers.h @@ -13,9 +13,31 @@ #include #include +#include "core/event_envelope.h" #include "telemetry/system_health_evaluator.h" #include "video/detection_result.h" +typedef enum { + EVENT_PRODUCER_ENQUEUE_FAILED = -1, /* router error or outbox error */ + EVENT_PRODUCER_ENQUEUE_ENQUEUED = 0, /* at least one new outbox row */ + EVENT_PRODUCER_ENQUEUE_DUPLICATE = 1, /* every destination already had it */ + EVENT_PRODUCER_ENQUEUE_NO_ROUTE = 2, /* routes exist; none matched or suppressed */ + EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED = 3, /* only the default broker, which is off */ + EVENT_PRODUCER_ENQUEUE_FULL = 4 /* outbox refused for capacity */ +} event_producer_enqueue_result_t; + +const char *event_producer_enqueue_result_name(event_producer_enqueue_result_t result); + +/* + * Route an already-built envelope and persist it in the durable outbox for + * every accepted destination (the path system-health transitions use). It + * performs no network I/O and does not publish to the in-process bus; callers + * do that after a non-failure result. Replaying the same event ID is safe: + * the outbox deduplicates on source + event ID + destination. + */ +event_producer_enqueue_result_t event_producer_enqueue_durable( + const event_envelope_t *event, char *error, size_t error_size); + /* * Normalize and enqueue an object-detection fact. This function performs no * MQTT or other network I/O. camera_uuid is the immutable fleet identity; diff --git a/include/core/observation_diagnostics.h b/include/core/observation_diagnostics.h new file mode 100644 index 000000000..fd15dff5b --- /dev/null +++ b/include/core/observation_diagnostics.h @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L10: observation pipeline diagnostics + * (docs/integrations/OBSERVATIONS_CONTRACT.md section 15). + * + * One read-only snapshot of the L3 evidence queue, the L4 retention pass, the + * L5 relay and the observation tables, serialized two ways: + * - Prometheus text for GET /api/metrics (api_metrics_render_observations() + * in src/web/api_handlers_metrics.c); + * - JSON for GET /api/system/info ("observation_pipeline"), shown by the + * System view. + * + * Collecting never starts the evidence worker or allocates its queue: with + * evidence disabled on every camera the counters are zero and + * queue_allocated is false. + */ + +#ifndef LIGHTNVR_CORE_OBSERVATION_DIAGNOSTICS_H +#define LIGHTNVR_CORE_OBSERVATION_DIAGNOSTICS_H + +#include +#include + +#include + +#include "core/observation_evidence.h" +#include "core/observation_relay.h" +#include "core/observation_retention.h" +#include "database/db_observations.h" + +typedef struct observation_diagnostics_snapshot { + int64_t collected_at_ms; + + observation_evidence_stats_t evidence; + observation_relay_stats_t relay; + + /* Observation tables (db_observation_diagnostics_get); false when the DB + * could not be read, in which case the fields below are zero. */ + bool db_available; + observation_db_diagnostics_t db; + + /* L4 per-camera status as of its last pass (cameras holding ready + * media). Heap-allocated; release with observation_diagnostics_free(). */ + observation_retention_camera_status_t *cameras; + int camera_count; + int budget_exhausted_cameras; /* L4 flag count (authoritative) */ +} observation_diagnostics_t; + +/* Fill a snapshot at now_ms (0 = current wall clock). Returns 0, or -1 when + * out is NULL. A DB read failure is reported through db_available. */ +int observation_diagnostics_collect(observation_diagnostics_t *out, int64_t now_ms); +void observation_diagnostics_free(observation_diagnostics_t *diagnostics); + +/* Relay lag: age of the oldest pending publication in seconds, 0 when none. */ +double observation_diagnostics_relay_lag_seconds(const observation_diagnostics_t *diagnostics); + +/* JSON object for system info ("observation_pipeline"); NULL on OOM. */ +cJSON *observation_diagnostics_to_json(const observation_diagnostics_t *diagnostics); + +/* Contract section 8.2 Retry-After for a pending snapshot: 1 while the asset + * is under 5 s old, otherwise min(30, 2 x items_ahead), always 1..30. + * Callers without a per-item queue position pass the evidence backlog + * (observation_evidence_stats_t.backlog_items), an upper bound. */ +#define OBS_SNAPSHOT_RETRY_AFTER_MAX 30 +int observation_snapshot_retry_after_seconds(int64_t asset_age_ms, uint64_t items_ahead); + +#endif /* LIGHTNVR_CORE_OBSERVATION_DIAGNOSTICS_H */ diff --git a/include/core/observation_evidence.h b/include/core/observation_evidence.h new file mode 100644 index 000000000..d1963c542 --- /dev/null +++ b/include/core/observation_evidence.h @@ -0,0 +1,250 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L3: evidence frame queue and encoder worker (contract: + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 4 and 13). + * + * Producers (detection threads, the HTTP API detector) hand over an owned + * RGB24 frame or the exact submitted JPEG bytes plus the observation batch. + * Admission is lock-free with respect to the worker: it never waits on I/O, + * on the DB mutex or on any lock the worker holds, and returns immediately. + * The single worker thread inserts the batch (db_observation_insert_batch, + * asset pending), encodes the JPEG with the shared jpeg_encoder_cache_* code, + * writes .tmp, fsyncs, renames, fsyncs the directory and only then + * commits pending -> ready. Any failure unlinks the temp file and commits + * pending -> unavailable(encode_failed|write_failed). + * + * Producer-side drops (queue_full, budget_exhausted) push a fixed-size drop + * record (batch metadata, no image) onto a bounded ring; the worker inserts + * the batch directly as unavailable(). When that ring is full too the + * observation is lost and dropped_untracked is incremented. + * + * Storage layout (contract section 4.5). The DB stores the path relative to + * the storage root passed to observation_evidence_init(): + * + * observations///.jpg + * + * is the UTC date of the batch's observed_at_ms. While being + * written the file is ".jpg.tmp" in the same directory. Files are + * created 0640, directories 0750. L4 reconciliation resolves the absolute + * path as /. + * + * Lifecycle: observation_evidence_init() only records configuration. The + * worker thread, the drop ring and any per-item memory are created lazily by + * the first submission, so with evidence disabled on every camera nothing is + * allocated and no thread runs (PRD R6). Start order in main.c: L4 + * reconciliation -> observation_evidence_init() -> L5 relay -> detection. + * Mutations need event_identity_init() to have run (L2 message IDs). + */ + +#ifndef LIGHTNVR_CORE_OBSERVATION_EVIDENCE_H +#define LIGHTNVR_CORE_OBSERVATION_EVIDENCE_H + +#include +#include +#include + +#include "database/db_observations.h" + +/* Contract section 13 constants. */ +#define OBS_EVIDENCE_PER_CAMERA_DEPTH 8 +#define OBS_EVIDENCE_GLOBAL_DEPTH 64 +#define OBS_EVIDENCE_DROP_RING_SIZE 256 /* power of two */ +#define OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES (32LL * 1024 * 1024) +#define OBS_EVIDENCE_JPEG_QUALITY 85 +#define OBS_EVIDENCE_DIR "observations" +#define OBS_EVIDENCE_DEFAULT_DRAIN_MS 2000 +#define OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS 10 /* 9 bounds + +Inf */ + +typedef enum { + OBS_EVIDENCE_ACCEPTED = 0, + OBS_EVIDENCE_DROPPED_QUEUE_FULL = 1, /* batch recorded as unavailable(queue_full) */ + OBS_EVIDENCE_DROPPED_BUDGET = 2, /* batch recorded as unavailable(budget_exhausted) */ + OBS_EVIDENCE_DISABLED = 3, /* not initialized or shutting down: nothing recorded */ + OBS_EVIDENCE_INVALID = -1 /* bad arguments: nothing recorded */ +} observation_evidence_result_t; + +typedef enum { + OBS_TRANSFORM_NONE = 0, + OBS_TRANSFORM_RESIZE, + OBS_TRANSFORM_LETTERBOX +} observation_transform_mode_t; + +/* Contract section 4.4. scale = inference / stored along each axis. */ +typedef struct { + observation_transform_mode_t mode; + int source_width, source_height; + int inference_width, inference_height; + double scale_x, scale_y; + double pad_x, pad_y; +} observation_evidence_transform_t; + +typedef struct { + const char *storage_path; /* required; evidence root is /observations */ + int64_t global_max_bytes; /* 0 = OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES + (system_settings.observation_global_queue_bytes) */ + int global_max_items; /* 0 = OBS_EVIDENCE_GLOBAL_DEPTH */ + int per_camera_max_items; /* 0 = OBS_EVIDENCE_PER_CAMERA_DEPTH */ + int64_t per_camera_max_bytes; /* 0 = global_max_bytes (one frame may use the whole + global budget; the depth bound keeps fairness) */ +} observation_evidence_config_t; + +/* Records configuration and enables admission. Allocates nothing and starts + * no thread. Returns 0, or -1 on bad config / already initialized. */ +int observation_evidence_init(const observation_evidence_config_t *config); + +/* Stops admission (new submits return DISABLED), lets the worker encode for + * up to drain_timeout_ms (< 0 = OBS_EVIDENCE_DEFAULT_DRAIN_MS), then records + * every item still queued as unavailable(interrupted) without encoding, + * inserts pending drop records, joins the worker and frees everything. Safe to + * call when never initialized or never started. init() may be called again. */ +void observation_evidence_shutdown(int drain_timeout_ms); + +/* + * Submit a decoded inference frame. Takes ownership of rgb24 on EVERY return. + * When not accepted it is released with free(); for a tracked drop the worker + * does that, so the producer never pays for unmapping a large frame (bounded + * by limit_discard_bytes pending). rgb24 is packed RGB24, + * width * 3 bytes per row, and must be allocated with at least + * RGB24_SWS_TAIL_PADDING bytes of slack after width * height * 3, exactly as + * ffmpeg_frame_to_rgb24_padded() returns it. batch is copied; its + * camera_uuid is the capture-time identity. transform NULL = mode none with + * source = inference = stored size. asset_uuid_out (optional) receives the + * asset ID for ACCEPTED and for tracked drops, "" otherwise. + * + * Never blocks: no I/O, no DB access, no lock the worker holds (< 1 ms). + */ +observation_evidence_result_t observation_evidence_submit( + const observation_batch_input_t *batch, + uint8_t *rgb24, int width, int height, + const observation_evidence_transform_t *transform, + char asset_uuid_out[OBS_UUID_SIZE]); + +/* Same as observation_evidence_submit() for the HTTP API detector (L7): the + * exact submitted JPEG bytes (ownership transferred on every return) are + * stored without re-encoding, origin submitted_image. width/height are the + * JPEG's pixel size. */ +observation_evidence_result_t observation_evidence_submit_jpeg( + const observation_batch_input_t *batch, + uint8_t *jpeg, size_t jpeg_size, int width, int height, + const observation_evidence_transform_t *transform, + char asset_uuid_out[OBS_UUID_SIZE]); + +/* Record a batch whose source cannot supply media (ONVIF / external events: + * reason "no_matching_media"), via the drop ring, so the calling thread never + * touches the DB. Returns ACCEPTED when queued, DROPPED_QUEUE_FULL when the + * ring is full (dropped_untracked), DISABLED or INVALID. */ +observation_evidence_result_t observation_evidence_submit_unavailable( + const observation_batch_input_t *batch, const char *reason, + char asset_uuid_out[OBS_UUID_SIZE]); + +/* L4: per-camera "budget exhausted, only protected assets remain" flag. + * While set, admission for that camera returns DROPPED_BUDGET. Memory only; + * works before init and while the worker is not started. */ +void observation_evidence_set_budget_exhausted(const char *camera_uuid, bool exhausted); +bool observation_evidence_is_budget_exhausted(const char *camera_uuid); + +/* Optional per-camera enablement probe consulted by the worker right before + * encoding (contract reason "disabled": evidence turned off between admission + * and encode). NULL (default) = always enabled. Called on the worker thread + * only, outside any DB transaction, so it may read the DB. */ +typedef bool (*observation_evidence_enabled_fn)(const char *camera_uuid, void *ctx); +void observation_evidence_set_enabled_probe(observation_evidence_enabled_fn fn, void *ctx); + +/* Stock probe (main.c installs it): streams.observation_evidence_enabled for + * the camera via get_stream_config_by_uuid(); an unknown camera is disabled. */ +bool observation_evidence_stream_enabled_probe(const char *camera_uuid, void *ctx); + +/* Serialize a transform (contract section 4.4) as compact JSON. NULL = + * identity for width x height. Returns 0, or -1 when invalid / too small. */ +int observation_evidence_transform_json(const observation_evidence_transform_t *transform, + int width, int height, char *out, size_t out_size); + +/* Relative path of an asset file (layout above). Returns 0 or -1. */ +int observation_evidence_relative_path(const char *camera_uuid, const char *asset_uuid, + int64_t observed_at_ms, char *out, size_t out_size); + +/* ---- Diagnostics (L10) ------------------------------------------------- */ + +typedef struct { + bool initialized; + bool worker_running; + bool allocated; /* drop ring or worker exists */ + uint64_t accepted; + uint64_t dropped_queue_full; + uint64_t dropped_budget; + uint64_t dropped_disabled; /* submits while not initialized / shutting down */ + uint64_t dropped_untracked; /* drop ring full: observation lost */ + uint64_t unavailable_submitted; /* submit_unavailable() records queued */ + uint64_t ready; /* pending -> ready committed */ + uint64_t failed_encode; + uint64_t failed_write; + uint64_t failed_db; /* insert / transition errors */ + uint64_t disabled_at_encode; + uint64_t interrupted; /* abandoned at shutdown */ + uint64_t bytes_written; + uint32_t backlog_items; + uint64_t backlog_bytes; + uint32_t drop_ring_depth; + uint32_t high_water_items; + uint64_t high_water_bytes; + uint32_t budget_exhausted_cameras; + /* X2 (A11): configured admission bounds and the highest item count any + * single camera held this process run. */ + uint32_t limit_items; + uint32_t limit_camera_items; + uint64_t limit_bytes; + uint64_t limit_camera_bytes; + uint32_t camera_high_water_items; + /* Producer time spent inside every submit that reached admission (every + * result except DISABLED), this process run. Buckets are NOT cumulative: + * bucket i counts calls with latency <= bounds_ns[i] and > bounds_ns[i-1]; + * the last bucket counts calls above the largest bound. */ + uint64_t submit_latency_count; + uint64_t submit_latency_sum_ns; + uint64_t submit_latency_max_ns; + uint64_t submit_latency_bucket[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS]; + /* Frames of rejected submissions: released by the worker (deferred, so the + * producer never pays for unmapping a large buffer) or, past + * limit_discard_bytes pending or with the drop ring full, inline. */ + uint64_t discard_deferred; + uint64_t discard_inline; + uint64_t discard_pending_bytes; + uint64_t limit_discard_bytes; /* max(limit_bytes, OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES) */ +} observation_evidence_stats_t; + +void observation_evidence_get_stats(observation_evidence_stats_t *out); + +/* Upper bounds (ns) of submit_latency_bucket[0 .. BUCKETS-2]; strictly + * increasing (1 us .. 10 ms). The last bucket is +Inf. */ +extern const uint64_t + observation_evidence_submit_latency_bounds_ns[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1]; + +/* ---- Test hooks ---------------------------------------------------------- */ + +typedef enum { + OBS_EVIDENCE_STAGE_ITEM_BEGIN = 0, /* worker picked an item (may block to stall the queue) */ + OBS_EVIDENCE_STAGE_BEFORE_RENAME, /* tmp written + fsynced; nonzero = inject failure */ + OBS_EVIDENCE_STAGE_ITEM_DONE, + OBS_EVIDENCE_STAGE_AFTER_INSERT, /* batch + pending asset committed, nothing written yet */ + OBS_EVIDENCE_STAGE_BEFORE_COMMIT /* final file renamed + dir fsynced, pending->ready not + * yet committed */ +} observation_evidence_stage_t; + +/* Invoked on the worker thread. A nonzero return at BEFORE_RENAME is treated + * as a write failure; other stages ignore the return value. NULL clears. + * Intended for tests and fault injection only: the hook can be installed only + * by code linked into the process (there is no configuration, environment or + * API path to it), so a production build can never trigger it. The L11 + * crash-boundary test (tests/unit/test_observation_crash_boundaries.c) raises + * SIGKILL from AFTER_INSERT, BEFORE_RENAME and BEFORE_COMMIT. */ +typedef int (*observation_evidence_stage_fn)(observation_evidence_stage_t stage, + const char *path, void *ctx); +void observation_evidence_set_stage_hook(observation_evidence_stage_fn fn, void *ctx); + +#endif /* LIGHTNVR_CORE_OBSERVATION_EVIDENCE_H */ diff --git a/include/core/observation_relay.h b/include/core/observation_relay.h new file mode 100644 index 000000000..4c0c7af0f --- /dev/null +++ b/include/core/observation_relay.h @@ -0,0 +1,142 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L5: observation publication relay (contract + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 5.5, 6 and 7.6). + * + * The DB layer stages one observation_publications row per observation + * revision in the same transaction as the change. The relay turns pending rows + * into io.lightnvr.detection.observation_changed.v1 envelopes (deterministic + * message ID), hands them to the router -> durable outbox through + * event_producer_enqueue_durable(), marks them relayed / relayed_no_route only + * after that, and then notifies in-process subscribers via event_bus_publish. + * Delivery is at least once. + */ + +#ifndef LIGHTNVR_CORE_OBSERVATION_RELAY_H +#define LIGHTNVR_CORE_OBSERVATION_RELAY_H + +#include +#include +#include + +#include "core/event_envelope.h" +#include "database/db_observations.h" + +#define OBSERVATION_CHANGED_EVENT_TYPE "io.lightnvr.detection.observation_changed.v1" + +/* D8: stop enqueuing while this many outbox rows of the type are undelivered. */ +#define OBSERVATION_RELAY_OUTBOX_CAP 2000 +#define OBSERVATION_RELAY_DEFAULT_BATCH 64 +#define OBSERVATION_RELAY_MAX_BATCH 256 +#define OBSERVATION_RELAY_DEFAULT_POLL_MS 1000 +#define OBSERVATION_RELAY_MAX_BACKOFF_MS 30000 +/* D9 sidecar, relative to the storage path. The file, its format and the + * rewind check are L4's (observation_cursor_head_* in observation_retention.h); + * the relay only schedules writes. */ +#define OBSERVATION_CURSOR_HEAD_FILE "observations/.cursor_head.json" + +typedef struct { + int batch_size; /* 0 = default; clamped to 1..MAX_BATCH */ + int poll_interval_ms; /* 0 = 1000 */ + int outbox_cap; /* 0 = OBSERVATION_RELAY_OUTBOX_CAP */ + int startup_drain_ms; /* 0 = 5000; bound on the synchronous start drain */ + const char *storage_path; /* NULL = g_config.storage_path ("" disables D9 sidecar) */ +} observation_relay_options_t; + +typedef struct { + bool running; + uint64_t passes; + uint64_t relayed; /* outbox accepted (enqueued or duplicate) */ + uint64_t duplicates; /* subset of relayed */ + uint64_t relayed_no_route; /* route suppressed / no match / MQTT disabled */ + uint64_t expired; /* older than the type's delivery expiry */ + uint64_t failed; /* envelope build/validation failures */ + uint64_t enqueue_full; /* outbox FULL; row left pending */ + uint64_t enqueue_errors; /* router/outbox error; row left pending */ + uint64_t mark_errors; /* publication_mark failures */ + uint64_t read_errors; /* pending-publication read failures */ + uint64_t cap_pauses; /* D8 cap reached */ + uint64_t backoffs; + uint64_t sidecar_writes; + uint64_t sidecar_errors; + uint64_t epoch_regenerations; /* D9 rewind detections */ + int64_t last_relayed_seq; + int64_t oldest_pending_created_at_ms; /* from the last pass; 0 = none seen */ + int last_pass_pending; /* rows read by the last pass */ + int64_t outbox_type_pending; /* last observed D8 count */ + int consecutive_failures; + int64_t backoff_until_ms; /* 0 = not backing off */ +} observation_relay_stats_t; + +typedef struct { + int read; /* pending rows read */ + int processed; /* rows moved out of pending */ + bool backoff; /* FULL, error or cap: caller should back off */ + bool cap_reached; +} observation_relay_pass_t; + +/* Build the section 6 envelope for one staged publication. The envelope id is + * the stored message ID and time is the change created_at truncated to + * seconds, so a retry produces an identical envelope. */ +int observation_relay_build_envelope(const observation_publication_t *publication, + const char *source, event_envelope_t *event, + char *error, size_t error_size); + +/* One bounded seq-ordered pass. Usable without the thread (tests, drain). + * Returns the number of rows moved out of pending, or -1 on a read error. */ +int observation_relay_run_once(const observation_relay_options_t *options, + observation_relay_pass_t *pass); + +/* D9: observation_cursor_head_reconcile() plus relay stats. Regenerates the + * epoch when the sidecar epoch equals the DB epoch but its head_seq is ahead + * of the DB head (the DB was restored). Rewrites the sidecar. Returns 1 when + * the epoch was regenerated, 0 otherwise, -1 on error. storage_path NULL = + * g_config. */ +int observation_relay_check_cursor_head(const char *storage_path); + +/* Write the sidecar now via observation_cursor_head_write(). */ +int observation_relay_write_cursor_head(const char *storage_path); + +/* Run the D9 check, drain pending publications (bounded), then start the + * steady-state thread (condvar wake + poll). Idempotent. NULL = defaults. */ +int observation_relay_start(const observation_relay_options_t *options); + +/* Stop the thread. Pending rows stay pending for the next start. */ +void observation_relay_stop(void); + +/* Wake the relay after committing publication rows. Cheap and safe to call + * from any thread, including while the relay is stopped. The DB layer calls + * it after its own commits; L13-style callers using + * db_observation_bump_revision_locked() call it after their COMMIT. */ +void observation_relay_notify(void); + +void observation_relay_get_stats(observation_relay_stats_t *stats); +void observation_relay_reset_stats(void); /* tests */ + +/* ---- Test hooks ---------------------------------------------------------- */ + +typedef enum { + OBS_RELAY_STAGE_BEFORE_ENQUEUE = 0, /* change committed, envelope built, outbox untouched */ + OBS_RELAY_STAGE_AFTER_ENQUEUE, /* outbox accepted the envelope, publication still + * pending */ + OBS_RELAY_STAGE_AFTER_MARK /* publication marked relayed / relayed_no_route; the + * outbox row is not yet delivered */ +} observation_relay_stage_t; + +/* Invoked on the relay pass for each row that reaches the stage; the return + * value is ignored. NULL clears. Like observation_evidence_set_stage_hook(), + * it is reachable only from code linked into the process (no configuration, + * environment or API path), so production cannot trigger it. The L11 + * crash-boundary test raises SIGKILL from it. */ +typedef void (*observation_relay_stage_fn)(observation_relay_stage_t stage, + const observation_publication_t *publication, + void *ctx); +void observation_relay_set_stage_hook(observation_relay_stage_fn fn, void *ctx); + +#endif /* LIGHTNVR_CORE_OBSERVATION_RELAY_H */ diff --git a/include/core/observation_retention.h b/include/core/observation_retention.h new file mode 100644 index 000000000..77d641716 --- /dev/null +++ b/include/core/observation_retention.h @@ -0,0 +1,136 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L4: startup reconciliation, retention and eviction of observation + * evidence (docs/integrations/OBSERVATIONS_CONTRACT.md sections 4.2, 4.5, + * 7.6, 13, 14 and 15). + * + * Files live under /observations/// + * .jpg (".jpg.tmp" while being written). observation_assets.path + * holds the path relative to ("observations/..."); a path + * relative to /observations or an absolute path inside it is + * also accepted. Nothing outside /observations is ever + * deleted, so recordings are never touched. + * + * Startup order (main.c, design decision 2): observation_retention_startup() + * runs before the L3 evidence worker and the L5 relay start. Periodic runs + * (observation_retention_maintenance_tick(), driven by the storage manager + * heartbeat) are no-ops until startup reconciliation has completed, and never + * touch pending assets, which belong to the worker. + */ + +#ifndef LIGHTNVR_CORE_OBSERVATION_RETENTION_H +#define LIGHTNVR_CORE_OBSERVATION_RETENTION_H + +#include +#include + +#include "database/db_observations.h" + +#define OBS_RETENTION_DIR "observations" +#define OBS_CURSOR_HEAD_FILE ".cursor_head.json" + +#define OBS_RETENTION_INTERVAL_SEC 300 /* periodic pass cadence */ +#define OBS_RETENTION_MAX_EVICTIONS 256 /* asset expiries/deletions per pass */ +#define OBS_RETENTION_MAX_PRUNED_CHANGES 5000 /* history change rows per pass */ +#define OBS_RETENTION_PRUNE_BATCH 500 /* change rows per prune transaction */ +#define OBS_RETENTION_DEFAULT_HISTORY_DAYS 30 /* section 13: 1..365 */ +#define OBS_RETENTION_DEFAULT_MAX_AGE_DAYS 7 +#define OBS_RETENTION_DEFAULT_MAX_BYTES 2147483648LL + +typedef struct { + int tmp_files_deleted; /* *.jpg.tmp under the observations tree */ + int stale_files_deleted; /* .jpg with no ready asset row pointing at it */ + int pending_interrupted; /* pending -> unavailable(interrupted) */ + int ready_missing; /* ready with a missing file -> unavailable(write_failed) */ + bool epoch_regenerated; /* D9 sidecar rewind detection fired */ +} observation_reconcile_result_t; + +/* Startup reconciliation (section 14). Must run after the DB and the event + * identity are initialized and before the evidence worker starts. Order: + * cursor-head rewind check (before any transition advances the head), file + * sweep, pending -> interrupted, ready-missing -> write_failed, sidecar + * rewrite. Idempotent. Returns 0, or -1 when the DB could not be read (the + * pass is then not marked done and periodic eviction stays off). */ +int observation_retention_startup(const char *storage_path, + observation_reconcile_result_t *result); +bool observation_retention_startup_done(void); + +typedef struct { + int assets_expired; /* ready -> expired(retention_expired) by age or bytes */ + int unreferenced_deleted; /* asset rows left with no observation, reclaimed */ + int files_deleted; + int changes_pruned; + int observations_pruned; + int cameras_budget_exhausted; /* over the byte budget with only protected media left */ + bool more_work; /* a per-pass bound was hit; run again soon */ + int64_t oldest_seq; /* observation_meta.oldest_seq after the pass */ +} observation_retention_result_t; + +/* One bounded periodic pass at now_ms: + * 1. prune history older than observation_history_retention_days (advances + * observation_meta.oldest_seq); + * 2. reclaim assets with no remaining observation (file, then row); + * 3. per camera: expire unprotected ready media older than the age budget, + * where an asset's age is its newest reference (a shared asset survives + * until its last reference expires), then evict oldest unprotected ready + * media until within the byte budget; set the camera's budget-exhausted + * flag when still over budget with only protected media left. + * Expiry is ready -> expired(retention_expired) first (CAS), then the file is + * unlinked, so a crash in between leaves a stale file the next startup sweep + * removes. Returns 0, 1 when skipped because startup reconciliation has not + * completed, or -1 on a DB error. */ +int observation_retention_run(const char *storage_path, int64_t now_ms, + observation_retention_result_t *result); + +/* Storage-manager hook (heartbeat). Rate-limited to OBS_RETENTION_INTERVAL_SEC + * unless the previous pass hit a bound; uses g_config.storage_path. Cheap + * no-op before startup reconciliation. */ +void observation_retention_maintenance_tick(void); + +/* ---- Budget-exhausted flag (read by L3 admission and L10) -------------- */ + +/* Each pass also pushes flag changes to L3 through + * observation_evidence_set_budget_exhausted() (weak reference: a no-op when + * the evidence worker is not linked), so L3 admission returns DROPPED_BUDGET + * without calling back into this module. + * + * Non-blocking: lock-free when no camera is flagged, otherwise a try-lock + * (returns false on contention). */ +bool observation_retention_budget_exhausted(const char *camera_uuid); +int observation_retention_budget_exhausted_count(void); + +typedef struct { + char camera_uuid[OBS_UUID_SIZE]; + int64_t retained_bytes; /* ready media bytes after the last pass */ + int64_t max_bytes; + int max_age_days; + bool budget_exhausted; +} observation_retention_camera_status_t; + +/* Copies up to max per-camera rows from the last pass; returns the count. */ +int observation_retention_camera_status(observation_retention_camera_status_t *out, int max); + +/* ---- D9 cursor head sidecar (/observations/.cursor_head.json) -- */ + +/* Atomic tmp + fsync + rename + dir fsync of {"epoch":..., "head_seq":N}. + * L5 calls this when the head advances (at most once per second). */ +int observation_cursor_head_write(const char *storage_path, const char *epoch_uuid, + int64_t head_seq); +/* 0 on success, 1 when missing, -1 when unreadable or malformed. */ +int observation_cursor_head_read(const char *storage_path, char epoch_uuid[OBS_UUID_SIZE], + int64_t *head_seq); +/* Section 7.6: same epoch with DB head < sidecar head means the DB was + * restored, so the epoch is regenerated. A missing or malformed sidecar is + * recreated without regenerating. The sidecar is rewritten with the current + * epoch and head either way. */ +int observation_cursor_head_reconcile(const char *storage_path, bool *regenerated); + +void observation_retention_reset_for_testing(void); + +#endif /* LIGHTNVR_CORE_OBSERVATION_RETENTION_H */ diff --git a/include/database/db_embedded_migrations.h b/include/database/db_embedded_migrations.h index 5826ff584..e0821fe8e 100644 --- a/include/database/db_embedded_migrations.h +++ b/include/database/db_embedded_migrations.h @@ -2138,6 +2138,222 @@ static const char migration_0094_down[] = "ALTER TABLE users DROP COLUMN totp_failed_attempts;\n" "ALTER TABLE users DROP COLUMN totp_last_used_step;"; +static const char migration_0095_up[] = + "CREATE TABLE observation_assets (\n" + " uuid TEXT PRIMARY KEY,\n" + " camera_uuid TEXT NOT NULL,\n" + " state TEXT NOT NULL CHECK (state IN ('pending', 'ready', 'unavailable', 'expired')),\n" + " reason TEXT CHECK (reason IS NULL OR length(reason) BETWEEN 1 AND 32),\n" + " origin TEXT CHECK (origin IS NULL OR origin IN ('inference_input', 'submitted_image')),\n" + " media_type TEXT,\n" + " path TEXT,\n" + " bytes INTEGER CHECK (bytes IS NULL OR bytes >= 0),\n" + " width INTEGER CHECK (width IS NULL OR width > 0),\n" + " height INTEGER CHECK (height IS NULL OR height > 0),\n" + " captured_at_ms INTEGER,\n" + " transform_json TEXT,\n" + " protected INTEGER NOT NULL DEFAULT 0 CHECK (protected IN (0, 1)),\n" + " expires_at_ms INTEGER,\n" + " created_at_ms INTEGER NOT NULL,\n" + " updated_at_ms INTEGER NOT NULL,\n" + " CHECK ((state IN ('pending', 'ready')) = (reason IS NULL))\n" + ");\n" + "\n" + "-- Reconciliation scans by state; retention walks one camera's ready assets\n" + "-- oldest first.\n" + "CREATE INDEX idx_observation_assets_state_camera\n" + "ON observation_assets(state, camera_uuid, created_at_ms);\n" + "\n" + "CREATE TABLE object_observations (\n" + " uuid TEXT PRIMARY KEY,\n" + " camera_uuid TEXT NOT NULL,\n" + " stream_name_at_capture TEXT NOT NULL,\n" + " batch_id TEXT NOT NULL,\n" + " object_index INTEGER NOT NULL CHECK (object_index BETWEEN 0 AND 19),\n" + " observed_at_ms INTEGER NOT NULL,\n" + " created_at_ms INTEGER NOT NULL,\n" + " engine_key TEXT NOT NULL,\n" + " engine_type TEXT NOT NULL CHECK (engine_type IN ('object', 'api', 'onvif', 'external')),\n" + " model TEXT,\n" + " model_version TEXT,\n" + " label TEXT NOT NULL CHECK (length(label) BETWEEN 1 AND 31),\n" + " confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0),\n" + " box_x REAL,\n" + " box_y REAL,\n" + " box_width REAL,\n" + " box_height REAL,\n" + " track_id TEXT,\n" + " track_namespace TEXT,\n" + " track_session TEXT,\n" + " recording_id INTEGER REFERENCES recordings(id) ON DELETE SET NULL,\n" + " asset_uuid TEXT REFERENCES observation_assets(uuid),\n" + " revision INTEGER NOT NULL DEFAULT 1 CHECK (revision >= 1),\n" + " UNIQUE(batch_id, object_index),\n" + " -- Normalized xywh relative to the stored evidence image; all or nothing.\n" + " -- The 1e-6 slack absorbs rounding to 6 decimals. IS NOT NULL is explicit\n" + " -- because a CHECK that evaluates to NULL passes.\n" + " CHECK ((box_x IS NULL AND box_y IS NULL AND box_width IS NULL AND box_height IS NULL)\n" + " OR (box_x IS NOT NULL AND box_y IS NOT NULL\n" + " AND box_width IS NOT NULL AND box_height IS NOT NULL\n" + " AND box_x >= 0.0 AND box_y >= 0.0\n" + " AND box_width > 0.0 AND box_height > 0.0\n" + " AND box_x + box_width <= 1.000001 AND box_y + box_height <= 1.000001)),\n" + " CHECK ((track_id IS NULL AND track_namespace IS NULL AND track_session IS NULL)\n" + " OR (length(track_id) BETWEEN 1 AND 64\n" + " AND track_namespace IS NOT NULL AND track_session IS NOT NULL))\n" + ");\n" + "\n" + "CREATE INDEX idx_object_observations_camera_time\n" + "ON object_observations(camera_uuid, observed_at_ms);\n" + "\n" + "CREATE INDEX idx_object_observations_label_time\n" + "ON object_observations(label, observed_at_ms);\n" + "\n" + "CREATE INDEX idx_object_observations_created\n" + "ON object_observations(created_at_ms);\n" + "\n" + "-- Asset transitions bump every referencing observation; refcount is derived.\n" + "CREATE INDEX idx_object_observations_asset\n" + "ON object_observations(asset_uuid);\n" + "\n" + "-- Keeps the ON DELETE SET NULL action cheap on every recording delete.\n" + "CREATE INDEX idx_object_observations_recording\n" + "ON object_observations(recording_id);\n" + "\n" + "-- seq is the history cursor: monotonic persistence order, never reused.\n" + "CREATE TABLE observation_changes (\n" + " seq INTEGER PRIMARY KEY AUTOINCREMENT,\n" + " observation_uuid TEXT NOT NULL REFERENCES object_observations(uuid) ON DELETE CASCADE,\n" + " revision INTEGER NOT NULL CHECK (revision >= 1),\n" + " cause TEXT NOT NULL CHECK (cause IN ('created', 'evidence', 'classification')),\n" + " evidence_state TEXT NOT NULL CHECK (evidence_state IN ('pending', 'ready', 'unavailable', 'expired')),\n" + " evidence_reason TEXT CHECK (evidence_reason IS NULL OR length(evidence_reason) BETWEEN 1 AND 32),\n" + " created_at_ms INTEGER NOT NULL,\n" + " UNIQUE(observation_uuid, revision),\n" + " CHECK ((evidence_state IN ('pending', 'ready')) = (evidence_reason IS NULL)),\n" + " CHECK ((cause = 'created') = (revision = 1))\n" + ");\n" + "\n" + "CREATE TABLE observation_publications (\n" + " change_seq INTEGER PRIMARY KEY REFERENCES observation_changes(seq) ON DELETE CASCADE,\n" + " message_id TEXT NOT NULL UNIQUE,\n" + " state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN (\n" + " 'pending', 'relayed', 'relayed_no_route', 'expired', 'failed'\n" + " )),\n" + " attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),\n" + " last_error TEXT,\n" + " updated_at_ms INTEGER NOT NULL\n" + ");\n" + "\n" + "-- The relay reads pending rows in seq order.\n" + "CREATE INDEX idx_observation_publications_pending\n" + "ON observation_publications(change_seq) WHERE state = 'pending';\n" + "\n" + "-- One row. epoch_uuid identifies this DB generation for cursors (contract\n" + "-- D9); oldest_seq is the lowest seq that may still be retained (prune\n" + "-- boundary, initially 1). Kept out of system_settings so it can be written\n" + "-- inside observation transactions.\n" + "CREATE TABLE observation_meta (\n" + " id INTEGER PRIMARY KEY CHECK (id = 1),\n" + " epoch_uuid TEXT NOT NULL CHECK (length(epoch_uuid) = 36),\n" + " oldest_seq INTEGER NOT NULL DEFAULT 1 CHECK (oldest_seq >= 1)\n" + ");\n" + "\n" + "INSERT INTO observation_meta (id, epoch_uuid, oldest_seq)\n" + "VALUES (\n" + " 1,\n" + " lower(\n" + " hex(randomblob(4)) || '-' ||\n" + " hex(randomblob(2)) || '-4' ||\n" + " substr(hex(randomblob(2)), 2) || '-' ||\n" + " substr('89ab', (abs(random()) % 4) + 1, 1) ||\n" + " substr(hex(randomblob(2)), 2) || '-' ||\n" + " hex(randomblob(6))\n" + " ),\n" + " 1\n" + ");\n" + "\n" + "-- Per-camera evidence settings (contract section 13). Evidence is off by\n" + "-- default; an empty label list means every object label.\n" + "ALTER TABLE streams ADD COLUMN observation_evidence_enabled INTEGER NOT NULL DEFAULT 0\n" + " CHECK (observation_evidence_enabled IN (0, 1));\n" + "ALTER TABLE streams ADD COLUMN observation_labels TEXT NOT NULL DEFAULT '';\n" + "ALTER TABLE streams ADD COLUMN observation_max_age_days INTEGER NOT NULL DEFAULT 7\n" + " CHECK (observation_max_age_days > 0);\n" + "ALTER TABLE streams ADD COLUMN observation_max_bytes INTEGER NOT NULL DEFAULT 2147483648\n" + " CHECK (observation_max_bytes > 0);\n" + "\n" + "INSERT OR IGNORE INTO system_settings (key, value)\n" + "VALUES ('observation_history_retention_days', '30');\n" + "\n" + "INSERT OR IGNORE INTO system_settings (key, value)\n" + "VALUES ('observation_global_queue_bytes', '33554432');"; + +static const char migration_0095_down[] = + "-- Drops all observation history and evidence metadata; take a backup first.\n" + "-- Evidence files under /observations are left on disk.\n" + "DELETE FROM system_settings WHERE key IN (\n" + " 'observation_history_retention_days', 'observation_global_queue_bytes'\n" + ");\n" + "ALTER TABLE streams DROP COLUMN observation_max_bytes;\n" + "ALTER TABLE streams DROP COLUMN observation_max_age_days;\n" + "ALTER TABLE streams DROP COLUMN observation_labels;\n" + "ALTER TABLE streams DROP COLUMN observation_evidence_enabled;\n" + "DROP TABLE IF EXISTS observation_meta;\n" + "DROP INDEX IF EXISTS idx_observation_publications_pending;\n" + "DROP TABLE IF EXISTS observation_publications;\n" + "DROP TABLE IF EXISTS observation_changes;\n" + "DROP INDEX IF EXISTS idx_object_observations_recording;\n" + "DROP INDEX IF EXISTS idx_object_observations_asset;\n" + "DROP INDEX IF EXISTS idx_object_observations_created;\n" + "DROP INDEX IF EXISTS idx_object_observations_label_time;\n" + "DROP INDEX IF EXISTS idx_object_observations_camera_time;\n" + "DROP TABLE IF EXISTS object_observations;\n" + "DROP INDEX IF EXISTS idx_observation_assets_state_camera;\n" + "DROP TABLE IF EXISTS observation_assets;"; + +static const char migration_0096_up[] = + "CREATE TABLE observation_classifications (\n" + " observation_uuid TEXT NOT NULL REFERENCES object_observations(uuid) ON DELETE CASCADE,\n" + " key TEXT NOT NULL CHECK (length(key) BETWEEN 3 AND 65),\n" + " writer_namespace TEXT NOT NULL CHECK (length(writer_namespace) BETWEEN 1 AND 32),\n" + " writer_user_id INTEGER NOT NULL CHECK (writer_user_id > 0),\n" + " classifier TEXT NOT NULL CHECK (length(classifier) BETWEEN 1 AND 64),\n" + " model_version TEXT CHECK (model_version IS NULL OR length(model_version) BETWEEN 1 AND 64),\n" + " label TEXT NOT NULL CHECK (length(CAST(label AS BLOB)) BETWEEN 1 AND 128),\n" + " taxonomy_id TEXT CHECK (taxonomy_id IS NULL OR length(taxonomy_id) BETWEEN 1 AND 64),\n" + " confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0),\n" + " classified_at_ms INTEGER NOT NULL CHECK (classified_at_ms >= 0),\n" + " input_asset_uuid TEXT NOT NULL CHECK (length(input_asset_uuid) = 36),\n" + " input_revision INTEGER NOT NULL CHECK (input_revision >= 1),\n" + " kind TEXT NOT NULL CHECK (kind IN ('automated', 'owner_confirmed')),\n" + " annotation_revision INTEGER NOT NULL DEFAULT 1 CHECK (annotation_revision >= 1),\n" + " created_at_ms INTEGER NOT NULL,\n" + " updated_at_ms INTEGER NOT NULL,\n" + " PRIMARY KEY (observation_uuid, key),\n" + " -- The key's namespace segment is the writer's namespace.\n" + " CHECK (substr(key, 1, length(writer_namespace) + 1) = writer_namespace || '/')\n" + ");\n" + "\n" + "-- New persisted action-mask positions are appended; existing positions never\n" + "-- move. Built-in roles fail closed: only Administrator receives the action.\n" + "INSERT INTO authz_actions\n" + " (action_key, category, description, camera_scoped, destructive, sort_order, bit_index)\n" + "VALUES\n" + " ('observations.annotate', 'Integrations',\n" + " 'Annotate detection observations with classifier results', 1, 0, 200, 19);\n" + "\n" + "INSERT INTO authz_role_actions (role_uuid, action_key)\n" + "SELECT '00000000-0000-4000-8000-000000000001', action_key\n" + "FROM authz_actions\n" + "WHERE action_key = 'observations.annotate';"; + +static const char migration_0096_down[] = + "-- Drops all classification annotations; take a backup first.\n" + "DELETE FROM authz_role_actions WHERE action_key = 'observations.annotate';\n" + "DELETE FROM authz_actions WHERE action_key = 'observations.annotate';\n" + "DROP TABLE IF EXISTS observation_classifications;"; + static const migration_t embedded_migrations_data[] = { { .version = "0001", @@ -2797,8 +3013,22 @@ static const migration_t embedded_migrations_data[] = { .sql_down = migration_0094_down, .is_embedded = true }, + { + .version = "0095", + .description = "add_object_observations", + .sql_up = migration_0095_up, + .sql_down = migration_0095_down, + .is_embedded = true + }, + { + .version = "0096", + .description = "add_observation_classifications", + .sql_up = migration_0096_up, + .sql_down = migration_0096_down, + .is_embedded = true + }, }; -#define EMBEDDED_MIGRATIONS_COUNT 94 +#define EMBEDDED_MIGRATIONS_COUNT 96 #endif /* DB_EMBEDDED_MIGRATIONS_H */ diff --git a/include/database/db_event_outbox.h b/include/database/db_event_outbox.h index f84d2cd7e..ac00a5e30 100644 --- a/include/database/db_event_outbox.h +++ b/include/database/db_event_outbox.h @@ -98,6 +98,11 @@ int db_event_outbox_prune_terminal(int64_t updated_before, int limit, int db_event_outbox_get_stats(const char *destination, int64_t now, event_outbox_stats_t *stats); +/* Count undelivered (pending or delivering) rows of one event type across all + * destinations. Used by producers that cap their share of the outbox. */ +int db_event_outbox_count_undelivered_type(const char *event_type, + int64_t *count); + void db_event_outbox_item_clear(event_outbox_item_t *item); #endif /* LIGHTNVR_DB_EVENT_OUTBOX_H */ diff --git a/include/database/db_observation_classifications.h b/include/database/db_observation_classifications.h new file mode 100644 index 000000000..c291864f1 --- /dev/null +++ b/include/database/db_observation_classifications.h @@ -0,0 +1,203 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 P2 classification annotations (contract: + * docs/integrations/OBSERVATIONS_CONTRACT.md section 10; migration 0096). + * + * An annotation is keyed by (observation_uuid, "/"). + * Write authority is pinned to the writer_user_id stored when the key was + * created (D3), so a later user reusing the username cannot overwrite it. + * + * db_observation_classification_put() runs the section 10.4 rules that need + * stored state, in contract order, inside one BEGIN IMMEDIATE transaction + * under the global DB mutex (db_auth.c pattern): + * + * writer check -> evidence check -> idempotent retry -> owner precedence + * -> expected_revision CAS -> per-observation key limit -> store. + * + * A stored change bumps the observation revision with cause classification + * and writes its change and publication rows in the same transaction, then + * wakes the publication relay after COMMIT. The observation's detector fields + * (label, confidence, box, ...) are never modified. Authentication, camera + * authorization, namespace derivation and body parsing are the caller's (L14). + */ + +#ifndef LIGHTNVR_DATABASE_DB_OBSERVATION_CLASSIFICATIONS_H +#define LIGHTNVR_DATABASE_DB_OBSERVATION_CLASSIFICATIONS_H + +#include +#include +#include + +#include "database/db_observations.h" + +#define OBS_CLS_SEGMENT_MAX 32 /* ^[a-z0-9][a-z0-9_.-]{0,31}$ */ +#define OBS_CLS_NAMESPACE_SIZE (OBS_CLS_SEGMENT_MAX + 1) +#define OBS_CLS_KEY_SIZE (2 * OBS_CLS_SEGMENT_MAX + 2) /* "/" + NUL */ +#define OBS_CLS_CLASSIFIER_SIZE 65 /* 1..64 bytes */ +#define OBS_CLS_MODEL_VERSION_SIZE 65 /* 0..64 bytes; "" = null */ +#define OBS_CLS_LABEL_SIZE 129 /* 1..128 bytes UTF-8 */ +#define OBS_CLS_TAXONOMY_SIZE 65 /* 0..64 bytes; "" = null */ +#define OBS_CLS_MAX_PER_OBSERVATION 8 /* annotations_per_observation_max */ +#define OBS_CLS_CONFIDENCE_EPSILON 1e-6 /* idempotent-retry comparison */ + +typedef enum { + OBS_CLS_KIND_AUTOMATED = 0, + OBS_CLS_KIND_OWNER_CONFIRMED = 1 +} observation_classification_kind_t; + +/* Outcomes of db_observation_classification_put(). Non-negative values are + * contract outcomes (the transaction committed only for CREATED/UPDATED); + * negative values are failures. */ +typedef enum { + DB_OBS_CLS_CREATED = 0, /* 201, changed=true */ + DB_OBS_CLS_UPDATED = 1, /* 200, changed=true */ + DB_OBS_CLS_NOOP = 2, /* 200, changed=false (identical retry) */ + DB_OBS_CLS_REVISION_CONFLICT = 3, /* 409 revision_conflict (+current if any) */ + DB_OBS_CLS_OWNER_LOCKED = 4, /* 409 owner_decision_locked (+current) */ + DB_OBS_CLS_EVIDENCE_MISMATCH = 5, /* 422 evidence_mismatch (+asset, state) */ + DB_OBS_CLS_WRITER_MISMATCH = 6, /* 403 namespace_forbidden; current withheld */ + DB_OBS_CLS_LIMIT = 7, /* 409 annotation_limit (+limit) */ + DB_OBS_CLS_ERROR = -1, /* SQLite failure; everything rolled back */ + DB_OBS_CLS_INVALID = -2, /* argument / bounds validation (400) */ + DB_OBS_CLS_NOT_FOUND = -3 /* observation unknown (404) */ +} db_obs_cls_result_t; + +const char *observation_classification_kind_name(observation_classification_kind_t kind); +/* 0 on success, -1 for anything other than "automated"/"owner_confirmed". */ +int observation_classification_kind_parse(const char *name, + observation_classification_kind_t *kind); +/* True for ^[a-z0-9][a-z0-9_.-]{0,31}$ (a key namespace or name segment). */ +bool observation_classification_segment_is_valid(const char *segment); + +typedef struct { + char key[OBS_CLS_KEY_SIZE]; /* "/" */ + char writer_namespace[OBS_CLS_NAMESPACE_SIZE]; + int64_t writer_user_id; /* internal; never in API bodies */ + char classifier[OBS_CLS_CLASSIFIER_SIZE]; + char model_version[OBS_CLS_MODEL_VERSION_SIZE]; /* "" = null */ + char label[OBS_CLS_LABEL_SIZE]; + char taxonomy_id[OBS_CLS_TAXONOMY_SIZE]; /* "" = null */ + double confidence; + int64_t classified_at_ms; + char input_asset_uuid[OBS_UUID_SIZE]; + int input_revision; + observation_classification_kind_t kind; + int annotation_revision; + int64_t created_at_ms; + int64_t updated_at_ms; +} observation_classification_t; + +typedef struct { + /* Writer identity, derived by the caller from the authenticated user. */ + const char *writer_namespace; /* must equal the key's namespace segment */ + int64_t writer_user_id; /* > 0 */ + const char *name; /* key name segment */ + /* Request fields (section 10.3). NULL or "" = null for the optional ones. */ + const char *classifier; + const char *model_version; + const char *label; + const char *taxonomy_id; + double confidence; /* 0..1 */ + int64_t classified_at_ms; /* >= 0 */ + const char *input_asset_uuid; + int input_revision; /* >= 1 */ + observation_classification_kind_t kind; + int expected_revision; /* >= 0; 0 = absent ("must not exist") */ +} observation_classification_input_t; + +typedef struct { + /* Stored annotation after CREATED/UPDATED/NOOP; the current one for + * REVISION_CONFLICT (when the key exists) and OWNER_LOCKED. Withheld for + * WRITER_MISMATCH so another writer's values never leak. */ + bool has_annotation; + observation_classification_t annotation; + int observation_revision; /* after the write; current otherwise; 0 = unknown */ + int64_t change_seq; /* the classification change row; 0 unless written */ + /* Set for every non-negative outcome (observation found); 422 body. */ + char observation_asset_uuid[OBS_UUID_SIZE]; + observation_asset_state_t evidence_state; + int limit; /* OBS_CLS_MAX_PER_OBSERVATION, for 409 annotation_limit */ +} observation_classification_result_t; + +/* Create or update one annotation. Requires the installation source + * (event_identity_init()). Returns a db_obs_cls_result_t. */ +int db_observation_classification_put(const char *observation_uuid, + const observation_classification_input_t *input, + observation_classification_result_t *result); + +typedef struct { + int automated_count; + observation_classification_t automated[OBS_CLS_MAX_PER_OBSERVATION]; + int owner_confirmed_count; + observation_classification_t owner_confirmed[OBS_CLS_MAX_PER_OBSERVATION]; +} observation_classification_set_t; + +/* Section 10.5 read: annotations split by kind, each list ordered by + * updated_at descending (key ascending on ties). Uses a private read-only + * connection, never the global DB mutex. An unknown observation yields two + * empty lists and DB_OBS_OK; authorization is the caller's. Returns DB_OBS_OK, + * DB_OBS_INVALID or DB_OBS_ERROR. */ +int db_observation_classifications_list(const char *observation_uuid, + observation_classification_set_t *out); + +/* The same read for several observations (a history page) on one read-only + * connection and one prepared statement. visit is called once per UUID, in + * order, with that observation's set (valid only during the call); a non-zero + * return stops the walk and is returned. Returns DB_OBS_OK, DB_OBS_INVALID, + * DB_OBS_ERROR or the visitor's value. */ +typedef int (*observation_classification_visit_fn)(size_t index, + const observation_classification_set_t *set, + void *context); +int db_observation_classifications_list_many(const char *const *observation_uuids, size_t count, + observation_classification_visit_fn visit, + void *context); + +/* ---- detection -> observation link (INT_01 L15) ------------------------- + * + * Legacy detection rows carry no observation_uuid (the L3 worker creates the + * observation asynchronously, after the detection row is written), so the + * link is a join: + * + * object_observations.camera_uuid = camera_uuid + * AND observed_at_ms / 1000 within +/-1 s of the detection timestamp + * AND label equal case-insensitively (ASCII), + * + * served by idx_object_observations_camera_time. When several observations + * match, the nearest second wins, then the earliest observed_at_ms, then the + * UUID, so the choice is deterministic. Only camera_uuid's observations are + * ever considered: the caller passes the camera it already authorized, so an + * observation captured by another camera can never be linked. */ + +#define OBS_DETECTION_LINK_WINDOW_SECONDS 1 + +typedef struct { + const char *label; /* detector label (detections.label) */ + int64_t timestamp; /* detections.timestamp, epoch seconds */ +} observation_detection_ref_t; + +typedef struct { + bool linked; + char observation_uuid[OBS_UUID_SIZE]; + /* The annotation to display: owner_confirmed[0] when any owner decision + * exists, else automated[0] (each list newest updated_at first). */ + bool has_classification; + bool owner_confirmed; /* at least one owner_confirmed annotation */ + observation_classification_t classification; +} observation_detection_link_t; + +/* Link count detections of one camera on one read-only connection (one + * prepared match and one annotation statement). out[i] corresponds to + * refs[i]; an unmatched or label-less ref is linked=false. Returns DB_OBS_OK, + * DB_OBS_INVALID (bad camera UUID or NULL arrays) or DB_OBS_ERROR (out is + * then all unlinked). */ +int db_observation_link_detections(const char *camera_uuid, + const observation_detection_ref_t *refs, size_t count, + observation_detection_link_t *out); + +#endif /* LIGHTNVR_DATABASE_DB_OBSERVATION_CLASSIFICATIONS_H */ diff --git a/include/database/db_observations.h b/include/database/db_observations.h new file mode 100644 index 000000000..0d3a06fef --- /dev/null +++ b/include/database/db_observations.h @@ -0,0 +1,361 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 durable object observations (contract: + * docs/integrations/OBSERVATIONS_CONTRACT.md, sections 3-5, 7 and 14). + * + * Every mutating call is one BEGIN IMMEDIATE transaction under the global DB + * mutex (db_auth.c pattern) and writes the observation/asset rows, their + * observation_changes rows and their observation_publications rows (with the + * deterministic UUIDv5 message ID) atomically. No mutex-taking db_* helper is + * called inside. Reads use a private read-only connection inside one read + * transaction, so they never hold the global DB mutex. + * + * Mutations need the installation source (event_identity_init() must have + * run) because message IDs are derived from it. + */ + +#ifndef LIGHTNVR_DATABASE_DB_OBSERVATIONS_H +#define LIGHTNVR_DATABASE_DB_OBSERVATIONS_H + +#include +#include +#include +#include + +#include "core/config.h" + +#define OBS_UUID_SIZE 37 +#define OBS_MAX_BATCH 20 /* MAX_DETECTIONS; object_index 0..19 */ +#define OBS_LABEL_SIZE 32 /* 1..31 bytes */ +#define OBS_ENGINE_KEY_SIZE 65 +#define OBS_ENGINE_TYPE_SIZE 16 +#define OBS_MODEL_SIZE 129 +#define OBS_MODEL_VERSION_SIZE 65 +#define OBS_TRACK_ID_SIZE 65 +#define OBS_STREAM_NAME_SIZE 64 /* stream_name_at_capture <= 63 */ +#define OBS_REASON_SIZE 33 +#define OBS_ORIGIN_SIZE 24 +#define OBS_MEDIA_TYPE_SIZE 24 +#define OBS_TRANSFORM_SIZE 1024 +#define OBS_CURSOR_SIZE 257 /* contract: cursor over 256 chars is invalid */ +#define OBS_ERROR_TEXT_SIZE 256 + +#define OBS_LIST_DEFAULT_LIMIT 100 +#define OBS_LIST_MAX_LIMIT 500 +#define OBS_LIST_SCAN_CAP 5000 /* scan cap per page (sections 7.3, 13) */ +#define OBS_QUERY_MAX_LABELS 8 + +/* Message-ID namespace (contract section 5.4). */ +#define OBS_MESSAGE_ID_NAMESPACE "0bc01005-a431-522b-9124-a114f67d187a" + +typedef enum { + DB_OBS_OK = 0, + DB_OBS_DUPLICATE = 1, /* insert retry: nothing written, existing IDs returned */ + DB_OBS_CAS_LOST = 2, /* transition: asset is no longer in the from-state */ + DB_OBS_ERROR = -1, /* SQLite/IO failure; everything rolled back */ + DB_OBS_INVALID = -2, /* argument or contract validation failure */ + DB_OBS_NOT_FOUND = -3, + DB_OBS_INVALID_CURSOR = -4, /* 400 invalid_cursor */ + DB_OBS_CURSOR_EXPIRED = -5, /* 410 cursor_expired */ + DB_OBS_CONFLICT = -6 /* batch_id reused for another camera */ +} db_obs_result_t; + +typedef enum { + OBS_ASSET_PENDING = 0, + OBS_ASSET_READY, + OBS_ASSET_UNAVAILABLE, + OBS_ASSET_EXPIRED +} observation_asset_state_t; + +typedef enum { + OBS_CAUSE_CREATED = 0, + OBS_CAUSE_EVIDENCE, + OBS_CAUSE_CLASSIFICATION +} observation_change_cause_t; + +typedef enum { + OBS_PUBLICATION_PENDING = 0, + OBS_PUBLICATION_RELAYED, + OBS_PUBLICATION_RELAYED_NO_ROUTE, + OBS_PUBLICATION_EXPIRED, + OBS_PUBLICATION_FAILED +} observation_publication_state_t; + +const char *observation_asset_state_name(observation_asset_state_t state); +int observation_asset_state_parse(const char *name, observation_asset_state_t *state); +const char *observation_change_cause_name(observation_change_cause_t cause); +const char *observation_publication_state_name(observation_publication_state_t state); + +/* True when reason is in the bounded section 4.3 enum and valid with state. + * pending/ready require reason == NULL or "". */ +bool observation_reason_is_valid(observation_asset_state_t state, const char *reason); + +/* Deterministic message ID: UUIDv5(NS, source|observation_id|revision). */ +int observation_message_id(const char *source, const char *observation_uuid, + int revision, char out[OBS_UUID_SIZE]); + +/* Opaque cursor: base64url (no padding) of {"v":1,"seq":N,"epoch":""}. */ +int observation_cursor_encode(int64_t seq, const char *epoch_uuid, + char *out, size_t out_size); +/* DB_OBS_OK or DB_OBS_INVALID_CURSOR (malformed, v != 1, seq < 0, > 256 chars). */ +int observation_cursor_decode(const char *cursor, int64_t *seq, + char epoch_uuid[OBS_UUID_SIZE]); + +/* ---- Insert ---------------------------------------------------------- */ + +typedef struct { + int object_index; /* 0..19, unique in the batch */ + char label[OBS_LABEL_SIZE]; + double confidence; /* 0..1 */ + bool has_box; /* normalized xywh to the stored image */ + double box_x, box_y, box_width, box_height; + bool has_track; + char track_id[OBS_TRACK_ID_SIZE]; + char track_namespace[OBS_ENGINE_KEY_SIZE]; + char track_session[OBS_UUID_SIZE]; +} observation_object_input_t; + +typedef struct { + char batch_id[OBS_UUID_SIZE]; + char camera_uuid[OBS_UUID_SIZE]; /* capture-time identity from the stream row */ + char stream_name[OBS_STREAM_NAME_SIZE]; /* display only; "" = taken from the stream row */ + int64_t observed_at_ms; + char engine_key[OBS_ENGINE_KEY_SIZE]; + char engine_type[OBS_ENGINE_TYPE_SIZE]; /* object|api|onvif|external */ + char model[OBS_MODEL_SIZE]; /* basename or "" */ + char model_version[OBS_MODEL_VERSION_SIZE]; + int64_t recording_id; /* 0 = none */ + int object_count; /* 1..OBS_MAX_BATCH */ + observation_object_input_t objects[OBS_MAX_BATCH]; +} observation_batch_input_t; + +typedef struct { + char asset_uuid[OBS_UUID_SIZE]; /* "" = generate a UUIDv4 */ + observation_asset_state_t state; /* initial: pending or unavailable */ + char reason[OBS_REASON_SIZE]; /* required iff unavailable */ + char origin[OBS_ORIGIN_SIZE]; /* "", inference_input, submitted_image */ + char media_type[OBS_MEDIA_TYPE_SIZE]; /* "" or image/jpeg */ + int64_t captured_at_ms; /* 0 = observed_at_ms */ + char transform_json[OBS_TRANSFORM_SIZE]; /* "" = NULL */ + int64_t expires_at_ms; /* 0 = NULL */ +} observation_asset_input_t; + +typedef struct { + char asset_uuid[OBS_UUID_SIZE]; + int object_count; + char observation_uuids[OBS_MAX_BATCH][OBS_UUID_SIZE]; /* ordered by object_index */ + int64_t last_seq; /* 0 on duplicate */ +} observation_insert_result_t; + +/* Insert the asset row, then one observation per object (revision 1, cause + * created) with its change and publication rows. A retry of the same batch_id + * is a no-op returning DB_OBS_DUPLICATE and the existing IDs. */ +int db_observation_insert_batch(const observation_batch_input_t *batch, + const observation_asset_input_t *asset, + observation_insert_result_t *result); + +/* ---- Evidence transitions -------------------------------------------- */ + +typedef struct { + const char *path; /* ready: required (internal; never in API bodies) */ + int64_t bytes; /* ready: >= 0 */ + int width, height; /* ready: > 0 */ + int64_t expires_at_ms; /* optional, 0 = unchanged */ +} observation_asset_media_t; + +typedef struct { + int observations_bumped; + int64_t last_seq; +} observation_transition_result_t; + +/* Compare-and-set the asset from -> to (section 4.2 machine), then bump every + * referencing observation's revision with cause evidence plus change and + * publication rows. DB_OBS_CAS_LOST when the asset is no longer in from. */ +int db_observation_asset_transition(const char *asset_uuid, + observation_asset_state_t from, + observation_asset_state_t to, + const char *reason, + const observation_asset_media_t *media, + observation_transition_result_t *result); + +/* For callers already inside a db_observations-style transaction (caller holds + * get_db_mutex() and has run BEGIN IMMEDIATE), e.g. L13 classifications. Bumps + * the revision, snapshots the current asset state into a new change row and + * writes its pending publication. source is the installation URN. */ +int db_observation_bump_revision_locked(sqlite3 *db, const char *source, + const char *observation_uuid, + observation_change_cause_t cause, + int64_t now_ms, + int64_t *seq_out, int *revision_out); + +/* ---- Reads ------------------------------------------------------------ */ + +typedef struct { + char uuid[OBS_UUID_SIZE]; + char camera_uuid[OBS_UUID_SIZE]; + observation_asset_state_t state; + char reason[OBS_REASON_SIZE]; + char origin[OBS_ORIGIN_SIZE]; + char media_type[OBS_MEDIA_TYPE_SIZE]; + char path[MAX_PATH_LENGTH]; /* internal only */ + bool has_bytes; + int64_t bytes; + int width, height; /* 0 = NULL */ + int64_t captured_at_ms; /* 0 = NULL */ + char transform_json[OBS_TRANSFORM_SIZE]; + bool is_protected; + int64_t expires_at_ms; /* 0 = NULL */ + int64_t created_at_ms; + int64_t updated_at_ms; +} observation_asset_record_t; + +typedef struct { + char uuid[OBS_UUID_SIZE]; + char camera_uuid[OBS_UUID_SIZE]; + char stream_name_at_capture[OBS_STREAM_NAME_SIZE]; + char batch_id[OBS_UUID_SIZE]; + int object_index; + int64_t observed_at_ms; + int64_t created_at_ms; + char engine_key[OBS_ENGINE_KEY_SIZE]; + char engine_type[OBS_ENGINE_TYPE_SIZE]; + char model[OBS_MODEL_SIZE]; /* "" = NULL */ + char model_version[OBS_MODEL_VERSION_SIZE]; + char label[OBS_LABEL_SIZE]; + double confidence; + bool has_box; + double box_x, box_y, box_width, box_height; + bool has_track; + char track_id[OBS_TRACK_ID_SIZE]; + char track_namespace[OBS_ENGINE_KEY_SIZE]; + char track_session[OBS_UUID_SIZE]; + int64_t recording_id; /* 0 = NULL */ + int revision; + observation_asset_record_t asset; +} observation_record_t; + +/* Current state of one observation. DB_OBS_NOT_FOUND when unknown or when the + * capture camera no longer has a streams row. Authorization is the caller's. */ +int db_observation_get(const char *observation_uuid, observation_record_t *out); +int db_observation_asset_get(const char *asset_uuid, observation_asset_record_t *out); + +typedef struct { + /* Authorized camera set (already intersected with any request filter). + * Ignored when all_cameras is true; an empty set yields no items. */ + const char (*camera_uuids)[OBS_UUID_SIZE]; + size_t camera_count; + bool all_cameras; + char labels[OBS_QUERY_MAX_LABELS][OBS_LABEL_SIZE]; /* ASCII case-insensitive */ + size_t label_count; + bool has_since; + int64_t since_ms; /* inclusive, observed_at */ + bool has_until; + int64_t until_ms; /* exclusive, observed_at */ + int limit; /* 1..500; 0 = default 100 */ +} observation_list_query_t; + +typedef struct { + observation_record_t *items; /* heap; free with db_observation_page_free */ + size_t count; + char next_cursor[OBS_CURSOR_SIZE]; + bool has_more; + /* Set when the result is DB_OBS_CURSOR_EXPIRED. */ + char oldest_cursor[OBS_CURSOR_SIZE]; + bool has_oldest_observed_at; + int64_t oldest_observed_at_ms; +} observation_page_t; + +/* Section 7.3 page algorithm. cursor is NULL/"" (start of retained history), + * "head" (D10) or an opaque cursor. Returns DB_OBS_OK, DB_OBS_INVALID, + * DB_OBS_INVALID_CURSOR or DB_OBS_CURSOR_EXPIRED (page->oldest_* set). */ +int db_observations_list(const observation_list_query_t *query, const char *cursor, + observation_page_t *page); +void db_observation_page_free(observation_page_t *page); + +typedef struct { + char epoch_uuid[OBS_UUID_SIZE]; + int64_t oldest_seq; + int64_t head_seq; /* max(MAX(seq), oldest_seq - 1) */ +} observation_meta_t; + +int db_observation_meta_get(observation_meta_t *out); +/* Regenerate the DB-generation epoch (D9: restore/import/rewind detection). */ +int db_observation_meta_regenerate_epoch(void); + +/* ---- Diagnostics (L10) ------------------------------------------------- */ + +typedef struct { + observation_meta_t meta; /* oldest recoverable cursor = oldest_seq - 1 */ + bool has_oldest_observed_at; + int64_t oldest_observed_at_ms; /* MIN(observed_at_ms) of retained observations */ + int64_t observations; /* retained observation rows */ + int64_t publications_pending; + int64_t publications_failed; + int64_t publications_expired; + int64_t oldest_pending_created_at_ms; /* change created_at of the oldest pending row; 0 = none */ +} observation_db_diagnostics_t; + +/* One read-only snapshot (single WAL read transaction). DB_OBS_OK or + * DB_OBS_ERROR. */ +int db_observation_diagnostics_get(observation_db_diagnostics_t *out); + +/* ---- Publications (L5 relay) ------------------------------------------ */ + +typedef struct { + int64_t change_seq; + char message_id[OBS_UUID_SIZE]; + int attempts; + char observation_uuid[OBS_UUID_SIZE]; + int revision; + observation_change_cause_t cause; + observation_asset_state_t evidence_state; /* at this revision (D6) */ + char evidence_reason[OBS_REASON_SIZE]; + int64_t change_created_at_ms; + char camera_uuid[OBS_UUID_SIZE]; + char label[OBS_LABEL_SIZE]; + int64_t observed_at_ms; + char asset_uuid[OBS_UUID_SIZE]; +} observation_publication_t; + +/* Pending publications in seq order. Returns the count (0..max) or < 0. */ +int db_observation_pending_publications(observation_publication_t *out, int max); + +/* Pending -> state. state may be PENDING to record a retryable failure. + * attempts is incremented and last_error stored (NULL clears). Returns + * DB_OBS_NOT_FOUND, DB_OBS_CAS_LOST when no longer pending, or DB_OBS_OK. */ +int db_observation_publication_mark(int64_t change_seq, + observation_publication_state_t state, + const char *last_error); + +/* Post-commit notification (L5 relay wakeup). Called after a committed + * db_observation_insert_batch() or db_observation_asset_transition() that + * staged publication rows, outside the DB mutex. NULL clears it. Callers of + * db_observation_bump_revision_locked() own their COMMIT and must notify + * themselves (observation_relay_notify()). */ +typedef void (*db_observation_publication_hook_t)(void); +void db_observations_set_publication_hook(db_observation_publication_hook_t hook); + +/* ---- Retention -------------------------------------------------------- */ + +typedef struct { + int changes_deleted; + int observations_deleted; + int assets_deleted; + int64_t oldest_seq; +} observation_prune_result_t; + +/* Delete up to batch change rows in ascending seq while created_at_ms < + * before_ms, then their observations with no remaining change rows, then + * unreferenced assets among them whose state is unavailable or expired (a + * ready/pending asset keeps its row so L4 can delete its file first). Advances + * observation_meta.oldest_seq to last pruned seq + 1 in the same transaction. */ +int db_observations_prune(int64_t before_ms, int batch, + observation_prune_result_t *result); + +#endif /* LIGHTNVR_DATABASE_DB_OBSERVATIONS_H */ diff --git a/include/utils/base64.h b/include/utils/base64.h index 5ba653082..4ee2e29be 100644 --- a/include/utils/base64.h +++ b/include/utils/base64.h @@ -31,4 +31,24 @@ int base64_encode(const unsigned char *input, size_t input_len, */ char *base64_encode_alloc(const unsigned char *input, size_t input_len); +/** + * Encode input as unpadded base64url (RFC 4648 section 5) into dst, NUL + * terminated. Needs ceil(4 * input_len / 3) + 1 bytes. + * + * @return 0 on success, -1 when dst is too small or the arguments are invalid. + */ +int base64url_encode(const unsigned char *input, size_t input_len, + char *dst, size_t dst_size, size_t *out_len); + +/** + * Strictly decode unpadded base64url. Rejects padding, the standard '+' and + * '/' characters, any other byte outside the alphabet, an impossible length + * (len % 4 == 1) and non-zero trailing bits, so each value has exactly one + * accepted encoding. + * + * @return 0 on success, -1 on invalid input or when dst is too small. + */ +int base64url_decode(const char *src, size_t src_len, + unsigned char *dst, size_t dst_size, size_t *out_len); + #endif /* LIGHTNVR_UTILS_BASE64_H */ diff --git a/include/utils/uuid.h b/include/utils/uuid.h index 3d6c283b0..1d5483710 100644 --- a/include/utils/uuid.h +++ b/include/utils/uuid.h @@ -9,6 +9,7 @@ #define LIGHTNVR_UTILS_UUID_H #include +#include #define LIGHTNVR_UUID_STRING_SIZE 37 @@ -18,4 +19,10 @@ bool lightnvr_uuid_is_valid(const char *value); /* Generate a cryptographically random RFC 4122 version-4 UUID. */ int lightnvr_uuid_generate_v4(char output[LIGHTNVR_UUID_STRING_SIZE]); +/* Generate the RFC 4122 name-based version-5 (SHA-1) UUID of name within the + * canonical namespace UUID. Output is lowercase canonical. Returns 0 or -1. */ +int lightnvr_uuid_generate_v5(const char *namespace_uuid, const char *name, + size_t name_len, + char output[LIGHTNVR_UUID_STRING_SIZE]); + #endif /* LIGHTNVR_UTILS_UUID_H */ diff --git a/include/utils/webrtc_candidates.h b/include/utils/webrtc_candidates.h new file mode 100644 index 000000000..3cf0c573c --- /dev/null +++ b/include/utils/webrtc_candidates.h @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +#ifndef LIGHTNVR_UTILS_WEBRTC_CANDIDATES_H +#define LIGHTNVR_UTILS_WEBRTC_CANDIDATES_H + +#define GO2RTC_MAX_EXTERNAL_CANDIDATES 8 +#define GO2RTC_CANDIDATE_SIZE 128 + +/** + * Parse the go2rtc "External IP" setting into WebRTC candidates. + * + * Entries are separated by commas, semicolons or whitespace. Each entry is: + * "*" or "auto" -> "*:" (auto-detect) + * IPv4 or hostname[:port] -> ":" + * IPv6 or [IPv6][:port] -> "[]:" + * stun:host[:port] -> passed through + * Duplicates collapse. Output strings are safe inside a double-quoted YAML scalar. + * + * @return number of candidates written (0 when the list is empty or NULL), + * or -1 when any entry is invalid or there are more than max entries. + */ +int go2rtc_parse_external_candidates(const char *list, int port, + char out[][GO2RTC_CANDIDATE_SIZE], int max); + +#endif /* LIGHTNVR_UTILS_WEBRTC_CANDIDATES_H */ diff --git a/include/video/api_detection.h b/include/video/api_detection.h index b1b1673ef..2a46cddec 100644 --- a/include/video/api_detection.h +++ b/include/video/api_detection.h @@ -14,6 +14,7 @@ #include #include "core/config.h" +#include "core/observation_evidence.h" #include "video/detection_result.h" // Model type for API-based detection @@ -84,6 +85,63 @@ int api_detection_parse_response(const char *json, const api_detection_options_t *options, detection_result_t *result); +/* + * INT_01 L7: optional geometry and provenance a detection server may declare + * next to "detections" (both wire formats). Absent fields keep today's + * behaviour: boxes normalized to the submitted JPEG, transform mode none. + * + * "letterbox": { + * "width": 640, "height": 640, // letterboxed inference image (required, > 0) + * "scale": 0.5, // or "scale_x"/"scale_y": inference / submitted + * // (absent = uniform min(width/W, height/H)) + * "pad_x": 0, "pad_y": 140, // inference-space left/top padding in pixels + * // (absent = centred: (width - round(W*scale)) / 2) + * "box_units": "normalized" // or "pixels": boxes are in inference space, + * }, // normalized to width x height or in pixels + * "model": "yolov8n", "model_version": "8.3.0" + * + * When a letterbox is declared the boxes in "detections" are in inference + * space; api_detection_apply_response_geometry() maps them onto the submitted + * image (contract section 4.4: x = (u - pad_x) / scale_x). + */ +typedef struct { + bool letterbox; /* a valid "letterbox" object was present */ + int inference_width, inference_height; + double scale_x, scale_y; /* <= 0: derive from the submitted size */ + double pad_x, pad_y; /* < 0: derive (centred) */ + bool pixel_units; /* box_units == "pixels" */ + char model[128]; /* "" = not declared */ + char model_version[64]; /* "" = not declared */ +} api_detection_response_meta_t; + +/** api_detection_parse_response() that also fills meta (may be NULL). Boxes + * are left exactly as the server sent them. */ +int api_detection_parse_response_meta(const char *json, + const api_detection_options_t *options, + detection_result_t *result, + api_detection_response_meta_t *meta); + +/* + * Map result's boxes onto the submitted image (image_w x image_h) according + * to meta and fill transform (contract section 4.4; may be NULL): + * - no letterbox: boxes untouched; mode none, inference = image size; + * - letterbox: boxes mapped from inference space and re-normalized to the + * image (not clamped; the evidence layer clamps); mode letterbox with the + * declared or derived scale and padding. + * source_w/source_h is the decoded frame the JPEG came from (<= 0 = the + * image itself, e.g. a go2rtc snapshot). Returns 0, or -1 on bad input + * (result untouched). + */ +int api_detection_apply_response_geometry(const api_detection_response_meta_t *meta, + int image_w, int image_h, + int source_w, int source_h, + detection_result_t *result, + observation_evidence_transform_t *transform); + +/** Pixel size of a baseline/progressive JPEG from its SOFn marker. Returns 0, + * or -1 when the bytes are not a parseable JPEG. */ +int api_detection_jpeg_dimensions(const uint8_t *jpeg, size_t size, int *width, int *height); + int init_api_detection_system(void); void shutdown_api_detection_system(void); @@ -101,6 +159,35 @@ int detect_objects_api_with_options(const char *api_url, const unsigned char *fr time_t frame_timestamp, const api_detection_options_t *options); +/* + * INT_01 L7: capture-time identity and cached evidence settings for one API + * detection call. Built by the caller (the detection thread copies them from + * its context; no DB read per frame). NULL, or camera_uuid NULL/"", keeps the + * legacy behaviour: no observation and stream-name keyed DB rows. + * + * With a camera_uuid the detections are stored and published against that + * immutable identity. When evidence_enabled and a surviving detection + * (zone + object filter, confidence >= threshold) has an eligible label, the + * exact JPEG bytes submitted to the server become the observation evidence + * (origin submitted_image); their ownership moves to the L3 queue. + */ +typedef struct { + const char *camera_uuid; /* streams.camera_uuid */ + const char *engine_key; /* NULL/"" = "legacy-primary" */ + bool evidence_enabled; /* streams.observation_evidence_enabled (cached) */ + const char *labels_csv; /* streams.observation_labels (cached); NULL/"" = all */ + int64_t observed_at_ms; /* inference-input capture time; 0 = derive at call start */ +} api_detection_evidence_t; + +/** detect_objects_api_with_options() plus observation evidence (NULL = none). */ +int detect_objects_api_with_evidence(const char *api_url, const unsigned char *frame_data, + int width, int height, int channels, + detection_result_t *result, const char *stream_name, + float threshold, uint64_t recording_id, + time_t frame_timestamp, + const api_detection_options_t *options, + const api_detection_evidence_t *evidence); + bool api_detection_should_use_go2rtc_snapshot(const unsigned char *frame_data, int width, int height, @@ -117,4 +204,12 @@ int detect_objects_api_snapshot_with_options(const char *api_url, const char *st uint64_t recording_id, time_t frame_timestamp, const api_detection_options_t *options); +/** detect_objects_api_snapshot_with_options() plus observation evidence: the + * go2rtc snapshot IS the inference input, so it is the evidence. */ +int detect_objects_api_snapshot_with_evidence(const char *api_url, const char *stream_name, + detection_result_t *result, float threshold, + uint64_t recording_id, time_t frame_timestamp, + const api_detection_options_t *options, + const api_detection_evidence_t *evidence); + #endif /* LIGHTNVR_API_DETECTION_H */ diff --git a/include/video/detection_evidence.h b/include/video/detection_evidence.h new file mode 100644 index 000000000..a52284644 --- /dev/null +++ b/include/video/detection_evidence.h @@ -0,0 +1,181 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L6: local detector -> observation evidence glue (contract: + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 3 and 4.4). + * + * A sidecar to detection_result_t (whose layout the generated Rust and the + * tests depend on): the engine provenance, the per-camera evidence settings + * and the inference transform travel alongside the result instead of inside + * it. + * + * The capture happens on the thread that ran inference, while the decoded + * frame's RGB24 buffer is still alive (inside detect_on_decoded_frame in + * unified_detection_thread.c). That covers the main-stream keyframe path and + * the detection-stream background thread alike: the detection-stream result + * slot only carries the detection_result_t to the packet thread, and the + * evidence has already been handed to the L3 queue against the detection + * stream's own resolution by then. + * + * Nothing here touches the database or blocks: settings are cached in the + * detection context, IDs come from the local UUID generator and the hand-off + * is the non-blocking observation_evidence_submit(). + */ + +#ifndef LIGHTNVR_VIDEO_DETECTION_EVIDENCE_H +#define LIGHTNVR_VIDEO_DETECTION_EVIDENCE_H + +#include +#include +#include + +#include "core/observation_evidence.h" +#include "database/db_observations.h" +#include "video/detection_model.h" +#include "video/detection_result.h" + +/* Detector provenance for one batch (contract section 3.3). */ +typedef struct { + const char *engine_key; /* stream_detection_engines.engine_key; NULL/"" = "legacy-primary" */ + const char *engine_type; /* must be "object" for local inference (motion never observes) */ + const char *model_path; /* reduced to its basename; NULL/"" = model null */ + const char *model_version;/* NULL/"" = null */ +} detection_evidence_engine_t; + +/* Capture-time camera identity and its cached evidence settings. */ +typedef struct { + const char *camera_uuid; /* streams.camera_uuid (capture-time identity) */ + const char *stream_name; /* stream_name_at_capture (display only) */ + bool evidence_enabled; /* streams.observation_evidence_enabled */ + const char *labels_csv; /* streams.observation_labels; NULL/"" = every object label */ + int64_t recording_id; /* recording open at capture (in-memory), 0 = none */ +} detection_evidence_camera_t; + +typedef enum { + DETECTION_EVIDENCE_SUBMITTED = 0, /* handed to observation_evidence_submit(); see *submit_result */ + DETECTION_EVIDENCE_NOT_ENABLED, /* evidence off for the camera */ + DETECTION_EVIDENCE_NOT_ELIGIBLE, /* no surviving object has an eligible label */ + DETECTION_EVIDENCE_NOT_OBJECT_ENGINE, /* motion / onvif / api: not this path's job */ + DETECTION_EVIDENCE_ERROR /* invalid input or UUID failure */ +} detection_evidence_status_t; + +/* UUIDv4 generated once per process (contract section 3.4 track.session). + * Thread-safe; returns "" only if the generator fails. */ +const char *detection_evidence_boot_session(void); + +/* True when label is eligible under the camera's observation_labels CSV: + * an empty/NULL list admits every label; otherwise a case-insensitive match + * against any comma-separated entry (surrounding blanks ignored). */ +bool detection_evidence_label_eligible(const char *labels_csv, const char *label); + +/* Clamp a normalized xywh box into [0,1] with x + width <= 1, y + height <= 1. + * Returns false when nothing of positive area remains. */ +bool detection_evidence_clamp_box(double x, double y, double w, double h, + double *ox, double *oy, double *ow, double *oh); + +/* + * Build the observation batch for count surviving detections of one engine + * (pure apart from the batch_id UUIDv4). Objects whose label is ineligible, + * empty or too long, whose confidence is not finite, or whose box clamps to + * nothing (that object then gets no box rather than being dropped) are + * handled per contract; object_index is the position among the eligible + * objects. Track IDs are emitted only for track_id >= 0 with namespace = + * engine key and session = detection_evidence_boot_session(). + * + * Returns the object count written to out (0 = nothing eligible), or -1 on + * invalid input. + */ +int detection_evidence_build_batch(const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, + observation_batch_input_t *out); + +/* + * Inference transform for a frame of frame_w x frame_h that is stored as-is + * (contract section 4.4; boxes from local engines are already normalized to + * the full decoded frame, so the stored image is that frame): + * - net_w/net_h <= 0 or equal to the frame: mode none, scale 1; + * - letterbox false: mode resize, scale_x = net_w / frame_w, scale_y = net_h / frame_h + * (SOD: sod_cnn_prepare_image resizes independently per axis); + * - letterbox true: mode letterbox with the uniform scale and centred + * integer padding computed exactly as litert_engine.cc letterbox_rgb24(). + */ +void detection_evidence_transform_compute(int frame_w, int frame_h, + int net_w, int net_h, bool letterbox, + observation_evidence_transform_t *out); + +/* Model input geometry of a loaded embedded model: SOD CNN network size + * (resize), LiteRT input tensor (letterbox). Returns 0 and fills the outputs, + * or -1 when the model has no fixed input (RealNet, unknown, NULL) so the + * caller records mode none. */ +int detection_evidence_model_geometry(detection_model_t model, int *net_w, int *net_h, + bool *letterbox); + +/* Millisecond capture time for a frame stamped with whole seconds: the wall + * clock when it is still inside frame_ts's second, else frame_ts * 1000. */ +int64_t detection_evidence_observed_at_ms(time_t frame_ts); + +/* + * Build the batch and hand the owned RGB24 frame to the L3 queue. Takes + * ownership of rgb24 on EVERY return (freed when not submitted). rgb24 must + * carry RGB24_SWS_TAIL_PADDING slack (ffmpeg_frame_to_rgb24_padded()). + * transform NULL = mode none at width x height. submit_result (optional) + * receives the L3 result when DETECTION_EVIDENCE_SUBMITTED. + */ +detection_evidence_status_t detection_evidence_capture( + const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, + uint8_t *rgb24, int width, int height, + const observation_evidence_transform_t *transform, + observation_evidence_result_t *submit_result, + char asset_uuid_out[OBS_UUID_SIZE]); + +/* + * INT_01 L7: HTTP API detector. Build the batch and hand the EXACT submitted + * JPEG bytes (request body image, or the go2rtc snapshot fetched as the + * inference input) to observation_evidence_submit_jpeg(); nothing is + * re-encoded and no second image is ever fetched. engine->engine_type must be + * "api". width/height are the JPEG's pixel size and the boxes must already be + * normalized to it (api_detection maps a server-declared letterbox first). + * Takes ownership of jpeg on EVERY return (free()d when not submitted). + */ +detection_evidence_status_t detection_evidence_capture_jpeg( + const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, + uint8_t *jpeg, size_t jpeg_size, int width, int height, + const observation_evidence_transform_t *transform, + observation_evidence_result_t *submit_result, + char asset_uuid_out[OBS_UUID_SIZE]); + +/* True for the generic "motion" label ONVIF reports when the camera names no + * object class (case-insensitive). Such events never become observations. */ +bool detection_evidence_is_motion_label(const char *label); + +/* + * INT_01 L7: event sources that cannot supply the matching frame (ONVIF, + * external events; engine_type "onvif" or "external"). Object-class + * detections become observations whose asset is created directly as + * unavailable(no_matching_media) via observation_evidence_submit_unavailable(); + * "motion" detections are skipped (contract section 3: motion never + * observes). keep_boxes false records box = null (ONVIF's whole-frame box is + * synthetic). Never touches the DB or blocks. + */ +detection_evidence_status_t detection_evidence_record_no_media( + const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, bool keep_boxes, + observation_evidence_result_t *submit_result, + char asset_uuid_out[OBS_UUID_SIZE]); + +#endif /* LIGHTNVR_VIDEO_DETECTION_EVIDENCE_H */ diff --git a/include/video/go2rtc/go2rtc_process.h b/include/video/go2rtc/go2rtc_process.h index 68b2cc748..021dcbb8f 100644 --- a/include/video/go2rtc/go2rtc_process.h +++ b/include/video/go2rtc/go2rtc_process.h @@ -16,6 +16,10 @@ #include #include +/* go2rtc_parse_external_candidates() lives in utils so builds without + * go2rtc (ENABLE_GO2RTC=OFF) can still validate the setting. */ +#include "utils/webrtc_candidates.h" + /** * @brief Initialize the go2rtc process manager * diff --git a/include/video/onvif_device_management.h b/include/video/onvif_device_management.h index 498bf5631..5a13b3050 100644 --- a/include/video/onvif_device_management.h +++ b/include/video/onvif_device_management.h @@ -73,6 +73,15 @@ int add_onvif_device_as_stream(const onvif_device_info_t *device_info, const char *username, const char *password, const char *stream_name); +/** Results of test_onvif_connection_detail(). */ +enum { + ONVIF_TEST_OK = 0, + ONVIF_TEST_DEVICE_ERROR = -1, /* ONVIF answered but no usable profile */ + ONVIF_TEST_UNREACHABLE = -2, /* ONVIF service could not be reached */ + ONVIF_TEST_AUTH_FAILED = -3, /* the camera rejected the credentials */ + ONVIF_TEST_STREAM_FAILED = -4, /* ONVIF works, the stream does not */ +}; + /** * Test connection to an ONVIF device * @@ -83,4 +92,19 @@ int add_onvif_device_as_stream(const onvif_device_info_t *device_info, */ int test_onvif_connection(const char *url, const char *username, const char *password); +/** + * Like test_onvif_connection(), but returns an ONVIF_TEST_* code and writes a + * user-facing explanation (credentials and query strings removed) to detail. + * When the advertised stream is rtsps:// and cannot be opened, plain RTSP on + * port 554 with the same path is tried before reporting a stream failure. + */ +int test_onvif_connection_detail(const char *url, const char *username, const char *password, + char *detail, size_t detail_size); + +/** + * Rewrite an rtsps:// URI to rtsp:// on port 554, keeping userinfo, host and + * path. Returns 0 on success, -1 when uri is not rtsps:// or out is too small. + */ +int onvif_plain_rtsp_fallback_url(const char *uri, char *out, size_t out_size); + #endif /* ONVIF_DEVICE_MANAGEMENT_H */ diff --git a/include/video/sod_detection.h b/include/video/sod_detection.h index c8816e4b7..dfa97719c 100644 --- a/include/video/sod_detection.h +++ b/include/video/sod_detection.h @@ -54,6 +54,19 @@ int detect_with_sod_model(detection_model_t model, const unsigned char *frame_da */ bool is_sod_available(void); +/** + * Network input size of a loaded SOD CNN model (INT_01 L6 transform audit). + * + * detect_with_sod_model() pre-shrinks large frames to 2x this size with + * swscale and sod_cnn_prepare_image() then resizes independently per axis to + * exactly this size, so the model consumes a plain (non-letterboxed) resize + * of the full frame and its boxes come back normalized to that frame. + * + * @return 0 and fills width/height, or -1 when the model is not a ready SOD + * CNN (or SOD support is not compiled in) + */ +int sod_model_input_size(detection_model_t model, int *width, int *height); + /** * Safely clean up a SOD model * This function ensures proper cleanup of SOD model resources diff --git a/include/video/unified_detection_thread.h b/include/video/unified_detection_thread.h index dd218d12e..f62ca096f 100644 --- a/include/video/unified_detection_thread.h +++ b/include/video/unified_detection_thread.h @@ -117,6 +117,15 @@ typedef struct { int engine_count; int recording_grace_interval; bool has_onvif_engine; + + // INT_01 L6 observation evidence settings, cached from the stream row at + // thread start (and refreshed by unified_detection_set_observation_settings) + // so the inference threads never read the DB for this feature. + // observation_labels is guarded by mutex; the flag is read lock-free. + // atomic_int (0/1), not atomic_bool: the C->Rust conversion rejects + // atomic loads/stores on bool. + atomic_int observation_evidence_enabled; + char observation_labels[OBSERVATION_LABELS_MAX]; // Connection state atomic_int_fast64_t last_packet_time; @@ -377,4 +386,14 @@ int get_unified_detection_stats(const char *stream_name, */ void unified_detection_notify_motion(const char *stream_name, bool motion_active); +/** + * INT_01 L6: refresh the cached observation evidence settings of a running + * detection thread (streams.observation_evidence_enabled / observation_labels) + * so a settings change takes effect without a restart. No DB access. + * + * @return 0 when a running context was updated, -1 when none is running + */ +int unified_detection_set_observation_settings(const char *stream_name, bool enabled, + const char *labels_csv); + #endif /* UNIFIED_DETECTION_THREAD_H */ diff --git a/include/web/api_handlers_metrics.h b/include/web/api_handlers_metrics.h index c5fc7bd27..e62b1f8a6 100644 --- a/include/web/api_handlers_metrics.h +++ b/include/web/api_handlers_metrics.h @@ -28,4 +28,11 @@ void handle_get_metrics(const http_request_t *req, http_response_t *res); */ void handle_post_player_telemetry(const http_request_t *req, http_response_t *res); +struct observation_diagnostics_snapshot; +/** + * INT_01 L10: Prometheus families for the observation pipeline + * (lightnvr_observation_*). Pure: renders the given snapshot. Caller frees. + */ +char *api_metrics_render_observations(const struct observation_diagnostics_snapshot *diagnostics); + #endif /* LIGHTNVR_API_HANDLERS_METRICS_H */ diff --git a/include/web/api_handlers_observations.h b/include/web/api_handlers_observations.h new file mode 100644 index 000000000..e9ed05c3d --- /dev/null +++ b/include/web/api_handlers_observations.h @@ -0,0 +1,80 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +#ifndef LIGHTNVR_API_HANDLERS_OBSERVATIONS_H +#define LIGHTNVR_API_HANDLERS_OBSERVATIONS_H + +#include +#include + +#include + +#include "database/db_observation_classifications.h" +#include "database/db_observations.h" +#include "web/request_response.h" + +/* + * INT_01 observation and integration API + * (docs/integrations/OBSERVATIONS_CONTRACT.md). Operation IDs are in + * docs/api/openapi.json. + */ + +/** GET /api/integrations/capabilities (getIntegrationCapabilities). */ +void handle_get_integration_capabilities(const http_request_t *req, http_response_t *res); + +/** GET /api/observations (listObservations): cursor-paged history. */ +void handle_get_observations(const http_request_t *req, http_response_t *res); + +/** GET /api/observations/{id} (getObservation). */ +void handle_get_observation(const http_request_t *req, http_response_t *res); + +/** GET /api/observations/{id}/snapshot (getObservationSnapshot): evidence JPEG. */ +void handle_get_observation_snapshot(const http_request_t *req, http_response_t *res); + +/** + * PUT /api/observations/{id}/classifications/{namespace}/{name} + * (putObservationClassification, P2; contract section 10). Authorized with + * observations.annotate on the observation's capture-time camera; the writer + * namespace is derived from the authenticated user (D3). + */ +void handle_put_observation_classification(const http_request_t *req, http_response_t *res); + +/* ---- Shared serialization (also used by L14) ------------------------- */ + +#define OBS_WRITER_NAMESPACE_SIZE 33 /* ^[a-z0-9][a-z0-9_.-]{0,31}$ */ +#define OBS_TIME_TEXT_SIZE 32 /* 2026-11-08T14:03:12.481Z */ + +/* UTC RFC 3339 with milliseconds and a Z suffix (contract section 2). */ +int observation_format_time(int64_t ms, char *out, size_t out_size); +/* Parse RFC 3339 (fractional seconds optional, Z or +-hh:mm offset). 0 on success. */ +int observation_parse_time(const char *text, int64_t *ms); + +/* Contract section 10.2 / D3: normalized username. Returns 0 and fills out + * when the principal has a valid, non-reserved namespace; otherwise returns + * -1 (invalid) or -2 (reserved) and leaves out empty. */ +int observation_writer_namespace(const char *username, char out[OBS_WRITER_NAMESPACE_SIZE]); + +#define OBS_PUT_BODY_MAX 4096 /* bound on the PUT classification body */ + +/* Observation object (contract section 3) for one DB record. Never emits the + * internal asset path. classifications fills the section 10.5 lists (NULL = + * both empty). Returns NULL on allocation failure. */ +cJSON *observation_record_to_json(const observation_record_t *record, const char *source, + const observation_classification_set_t *classifications); + +/* Annotation object (contract section 10.3). Never emits writer_user_id. + * Returns NULL on allocation failure. */ +cJSON *observation_classification_to_json(const observation_classification_t *annotation); + +/* Optional "observation" member of a GET /api/detection/results/{name} + * detection (INT_01 L15): {id, owner_confirmed, classification} where + * classification is the displayed annotation (owner_confirmed[0] before + * automated[0]) or null. Returns NULL when the link is absent (unlinked + * detections carry no member) or on allocation failure. */ +cJSON *observation_detection_link_to_json(const observation_detection_link_t *link); + +#endif /* LIGHTNVR_API_HANDLERS_OBSERVATIONS_H */ diff --git a/include/web/openapi_document_generated.h b/include/web/openapi_document_generated.h index 995cda061..7dde5b6e6 100644 --- a/include/web/openapi_document_generated.h +++ b/include/web/openapi_document_generated.h @@ -57,2571 +57,2792 @@ static const unsigned char lightnvr_openapi_document[] = "tionRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"live.view\"," "\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"recordings.export\",\"snapshot.create\",\"ptz.contr" "ol\",\"evidence.protect\",\"recording.delete\",\"camera.configure\",\"fleet.execute_job\",\"lpr.read\",\"lpr" - ".search\",\"lpr.export\",\"lpr.delete\"],\"type\":\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[" - "0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"user_id\":{\"maxim" - "um\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"action\",\"camera_uuid\"],\"typ" - "e\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"storage.configure\",\"e" - "vents.configure\",\"users.manage\",\"system.admin\"],\"type\":\"string\"},\"camera_uuid\":false,\"user_id\":{" - "\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"action\"],\"type\":\"obje" - "ct\"}]},\"AdminUser\":{\"additionalProperties\":false,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"s" - "tring\",\"null\"]},\"authorization_mode\":{\"type\":\"string\"},\"created_at\":{\"type\":\"integer\"},\"email\":{" - "\"type\":\"string\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"is_active\":{\"type\":\"boolean\"},\"last_login\"" - ":{\"type\":\"integer\"},\"must_change_password\":{\"type\":\"boolean\"},\"password_change_locked\":{\"type\":\"" - "boolean\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"role_name\":{\"enum\":[\"admin\",\"user\",\"viewer" - "\",\"api\",\"unknown\"],\"type\":\"string\"},\"totp_enabled\":{\"type\":\"boolean\"},\"updated_at\":{\"type\":\"inte" - "ger\"},\"username\":{\"type\":\"string\"}},\"required\":[\"id\",\"username\",\"email\",\"role\",\"role_name\",\"crea" - "ted_at\",\"updated_at\",\"last_login\",\"is_active\",\"password_change_locked\",\"must_change_password\",\"t" - "otp_enabled\",\"authorization_mode\",\"allowed_login_cidrs\"],\"type\":\"object\"},\"AdminUserCreate\":{\"ad" - "ditionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"email\":" - "{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password\":{\"minLength\":8,\"type\":\"string\"},\"rol" - "e\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"username\":{\"maxLength\":32,\"minLength\":3,\"type\":\"string\"}" - "},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"AdminUserDeleted\":{\"additionalProperties\"" - ":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\"" - ",\"message\"],\"type\":\"object\"},\"AdminUserPolicy\":{\"additionalProperties\":false,\"properties\":{\"gran" - "t_count\":{\"minimum\":0,\"type\":\"integer\"},\"grants\":{\"items\":{\"$ref\":\"#/components/schemas/AdminPol" - "icyGrant\"},\"type\":\"array\"},\"mode\":{\"enum\":[\"legacy\",\"policy\"],\"type\":\"string\"},\"policy_version\":" - "{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"user_id\":{\"minimum\":1,\"type\":\"integer" - "\"}},\"required\":[\"user_id\",\"mode\",\"policy_version\",\"grant_count\",\"grants\"],\"type\":\"object\"},\"Admi" - "nUserUpdate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\"," - "\"null\"]},\"allowed_tags\":false,\"email\":{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password" - "\":{\"anyOf\":[{\"const\":\"\"},{\"minLength\":8,\"type\":\"string\"}]},\"role\":false,\"username\":{\"maxLength\":" - "32,\"minLength\":3,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"AdminUsers\":{\"additionalPrope" - "rties\":false,\"properties\":{\"users\":{\"items\":{\"$ref\":\"#/components/schemas/AdminUser\"},\"type\":\"ar" - "ray\"}},\"required\":[\"users\"],\"type\":\"object\"},\"AdminVersionRequest\":{\"additionalProperties\":true," - "\"properties\":{\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"" - "}},\"required\":[\"expected_policy_version\"],\"type\":\"object\"},\"ApiError\":{\"additionalProperties\":tr" - "ue,\"description\":\"Most errors use a string error. YAML validation may use valid=false and error." - "message. Extra diagnostic fields may be added; clients must not log privileged request or respon" - "se data.\",\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"string\"},{\"additionalProperties\":true,\"proper" - "ties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type\":\"object\"}]},\"valid\":{\"type\":\"b" - "oolean\"}},\"required\":[\"error\"],\"type\":\"object\"},\"ApiTokenCreateRequest\":{\"additionalProperties\":" - "true,\"description\":\"Requested action and camera scope must be contained in BOTH token owner and " - "requester authority. At most256 active tokens per owner. Additional request keys do not rewrite " - "user policy.\",\"properties\":{\"actions\":{\"description\":\"Duplicates collapse into an action mask.\"," - "\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"minItems\":1,\"type\":\"array\"},\"descri" - "ption\":{\"description\":\"Trimmed by server; must contain non-whitespace text, no control character" - "s, and fewer than128 UTF-8 bytes.\",\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"expires_at\":{" - "\"description\":\"Unix seconds strictly after server time and at most366 days ahead.\",\"maximum\":900" - "7199254740991,\"minimum\":1,\"type\":\"integer\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenScope\"" - "}},\"required\":[\"description\",\"expires_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenCreated\":" - "{\"additionalProperties\":true,\"description\":\"Only successful token creation returns a secret. No " - "secret value is included in examples.\",\"properties\":{\"secret\":{\"description\":\"One-time bearer se" - "cret; store securely after creation. Ordinary token-list/identity responses never include it.\",\"" - "pattern\":\"^lnvr_[0-9a-f]{64}$\",\"readOnly\":true,\"type\":\"string\"},\"token\":{\"$ref\":\"#/components/sc" - "hemas/ApiTokenMetadata\"}},\"required\":[\"secret\",\"token\"],\"type\":\"object\"},\"ApiTokenList\":{\"additi" - "onalProperties\":true,\"description\":\"Lists token metadata, including revoked/expired records. Sec" - "ret is never returned.\",\"properties\":{\"api_key\":false,\"count\":{\"minimum\":0,\"type\":\"integer\"},\"pa" - "ssword\":false,\"password_hash\":false,\"secret\":false,\"token_hash\":false,\"tokens\":{\"items\":{\"$ref\":" - "\"#/components/schemas/ApiTokenMetadata\"},\"type\":\"array\"},\"totp_secret\":false,\"totp_token\":false," - "\"user_id\":{\"type\":\"integer\"}},\"required\":[\"user_id\",\"count\",\"tokens\"],\"type\":\"object\"},\"ApiToken" - "Metadata\":{\"additionalProperties\":true,\"description\":\"Non-secret token record; prefix is an iden" - "tifier, never a reusable credential. Secret/hash fields are explicitly forbidden.\",\"properties\":" - "{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"array\"},\"api_key" - "\":false,\"created_at\":{\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"expires_at\":{\"type\":\"in" - "teger\"},\"last_used_at\":{\"type\":[\"integer\",\"null\"]},\"password\":false,\"password_hash\":false,\"prefi" - "x\":{\"type\":\"string\"},\"revoked_at\":{\"type\":[\"integer\",\"null\"]},\"scope\":{\"$ref\":\"#/components/sche" - "mas/ApiTokenStoredScope\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":fal" - "se,\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{1" - "2}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"description\",\"prefix\",\"expires_at\",\"revoked_at\",\"last" - "_used_at\",\"created_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenScope\":{\"anyOf\":[{\"additiona" - "lProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"ty" - "pe\":{\"const\":\"all\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properti" - "es\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"}," - "\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\"],\"type\":\"object\"},{\"additionalPropert" - "ies\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" - "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\"" - ":\"collection\"}},\"required\":[\"type\",\"collection_uuid\"],\"type\":\"object\"}],\"description\":\"All, sele" - "ctor or collection camera scope. Inapplicable selector/collection fields may be absent or null. " - "A narrow owner cannot delegate all scope simply because current matches happen to be identical.\"" - "},\"ApiTokenStoredScope\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{" - "\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"required\":[\"type\",\"selector\"," - "\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\"" - ":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"type\":{\"const\":\"selec" - "tor\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\"" - ":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" - "[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"co" - "llection\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"}]},\"AuditDecisionMo" - "de\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"$ref\":\"#/components/schemas/Authoriza" - "tionAction\"},\"category\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"mode\":{\"enum\":[\"off\"," - "\"summarize\",\"record\"],\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"description\",\"mode\"],\"t" - "ype\":\"object\"},\"AuditEvent\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string" - "\"},\"api_token_uuid\":{\"type\":[\"string\",\"null\"]},\"auth_method\":{\"type\":\"string\"},\"details\":{\"addit" - "ionalProperties\":true,\"type\":[\"object\",\"null\"]},\"occurred_at\":{\"type\":\"integer\"},\"outcome\":{\"typ" - "e\":\"string\"},\"principal_user_id\":{\"type\":[\"integer\",\"null\"]},\"principal_username\":{\"type\":\"strin" - "g\"},\"remote_address\":{\"type\":\"string\"},\"request_id\":{\"type\":\"string\"},\"target_type\":{\"type\":[\"st" - "ring\",\"null\"]},\"target_uuid\":{\"type\":[\"string\",\"null\"]},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a" - "-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"" - "occurred_at\",\"request_id\",\"principal_user_id\",\"principal_username\",\"auth_method\",\"api_token_uuid" - "\",\"action\",\"target_type\",\"target_uuid\",\"outcome\",\"remote_address\",\"details\"],\"type\":\"object\"},\"A" - "uditEventsPage\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer" - "\"},\"events\":{\"items\":{\"$ref\":\"#/components/schemas/AuditEvent\"},\"type\":\"array\"},\"page\":{\"minimum" - "\":1,\"type\":\"integer\"},\"page_size\":{\"minimum\":1,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"in" - "teger\"}},\"required\":[\"page\",\"page_size\",\"count\",\"total\",\"events\"],\"type\":\"object\"},\"AuditSetting" - "s\":{\"additionalProperties\":false,\"properties\":{\"allowed_decision_modes\":{\"items\":{\"$ref\":\"#/comp" - "onents/schemas/AuditDecisionMode\"},\"type\":\"array\"},\"pruned_events\":{\"minimum\":0,\"type\":\"integer\"" - "},\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"},\"summary_window_seconds\":{\"mini" - "mum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\",\"summary_window_seconds\",\"allowed_decisio" - "n_modes\"],\"type\":\"object\"},\"AuditSettingsWrite\":{\"additionalProperties\":true,\"anyOf\":[{\"properti" - "es\":{\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days" - "\"]},{\"properties\":{\"allowed_decision_modes\":{\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"" - "record\"],\"type\":\"string\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"t" - "ype\":\"object\"}},\"required\":[\"allowed_decision_modes\"]}],\"properties\":{\"allowed_decision_modes\":{" - "\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"},\"propertyNames\":{\"$" - "ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"object\"},\"retention_days\":{\"maximum\":36" - "50,\"minimum\":1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"AuthDemoIdentity\":{\"additional" - "Properties\":true,\"properties\":{\"api_key\":false,\"authenticated\":{\"const\":false},\"demo_mode\":{\"con" - "st\":true},\"must_change_password\":{\"const\":false},\"password\":false,\"password_hash\":false,\"role\":{" - "\"const\":\"viewer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"user" - "name\":{\"const\":\"demo\"}},\"required\":[\"authenticated\",\"demo_mode\",\"username\",\"role\",\"must_change_p" - "assword\"],\"type\":\"object\"},\"AuthDisabledIdentity\":{\"additionalProperties\":true,\"properties\":{\"ap" - "i_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":false},\"aut" - "h_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"must_change_password\":{\"const" - "\":false},\"password\":false,\"password_hash\":false,\"role\":{\"const\":\"admin\"},\"secret\":false,\"token_h" - "ash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"},\"usern" - "ame\":{\"const\":\"admin\"}},\"required\":[\"authenticated\",\"username\",\"role\",\"must_change_password\",\"au" - "th_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"ob" - "ject\"},\"AuthSession\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type" - "\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"id\":{\"max" - "imum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"idle_expires_at\":{\"minimum\":0,\"type\":\"inte" - "ger\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_activity_at\":{\"minimum\":0,\"type\":\"inte" - "ger\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[\"id\",\"created_at\",\"expires_at\"" - ",\"ip_address\",\"user_agent\",\"current\",\"last_activity_at\",\"idle_expires_at\"],\"type\":\"object\"},\"Aut" - "hSessionsResponse\":{\"additionalProperties\":false,\"properties\":{\"sessions\":{\"items\":{\"$ref\":\"#/co" - "mponents/schemas/AuthSession\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"sessions\"],\"type\":\"ob" - "ject\"},\"AuthVerifiedIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"auth_a" - "bsolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":true},\"auth_timeout_hours\":{\"ty" - "pe\":\"number\"},\"authenticated\":{\"const\":true},\"email\":{\"type\":\"string\"},\"id\":{\"type\":\"integer\"},\"" - "is_active\":{\"type\":\"boolean\"},\"must_change_password\":{\"type\":\"boolean\"},\"password\":false,\"passwo" - "rd_change_locked\":{\"type\":\"boolean\"},\"password_hash\":false,\"role\":{\"type\":\"string\"},\"role_id\":{\"" - "type\":\"integer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trust" - "ed_device_days\":{\"type\":\"number\"},\"username\":{\"type\":\"string\"}},\"required\":[\"authenticated\",\"id\"" - ",\"username\",\"email\",\"role\",\"role_id\",\"is_active\",\"password_change_locked\",\"must_change_password\"" - ",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\"" - ":\"object\"},\"AuthVerifyResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/AuthVerifiedIdentity\"},{" - "\"$ref\":\"#/components/schemas/AuthDisabledIdentity\"},{\"$ref\":\"#/components/schemas/AuthDemoIdenti" - "ty\"}],\"description\":\"Real credential identity, auth-disabled synthetic admin, or unauthenticated" - " demo viewer. Synthetic modes do not authorize personalized discovery.\"},\"AuthorizationAction\":{" - "\"enum\":[\"live.view\",\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"recordings.export\",\"snapsho" - "t.create\",\"ptz.control\",\"evidence.protect\",\"recording.delete\",\"camera.configure\",\"fleet.execute_" - "job\",\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin\",\"lpr.read\",\"lpr.search" - "\",\"lpr.export\",\"lpr.delete\"],\"type\":\"string\"},\"CameraCollection\":{\"additionalProperties\":false,\"" - "properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"effect" - "ive_count\":{\"minimum\":0,\"type\":\"integer\"},\"member_count\":{\"minimum\":0,\"type\":\"integer\"},\"name\":{" - "\"type\":\"string\"},\"owner_user_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"sel" - "ector\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FleetSelector\"},{\"type\":\"null\"}]},\"selector_redac" - "ted\":{\"type\":\"boolean\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"]},\"updated_" - "at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" - "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"" - "type\",\"shared\",\"owner_user_id\",\"member_count\",\"effective_count\",\"created_at\",\"updated_at\",\"selec" - "tor\",\"selector_redacted\"],\"type\":\"object\"},\"CameraCollectionCreate\":{\"additionalProperties\":fals" - "e,\"properties\":{\"description\":{\"maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLength\":127,\"minLeng" - "th\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"shared\":{\"type\"" - ":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"type\":\"string\"}},\"required\":[\"name\",\"type\"],\"type" - "\":\"object\"},\"CameraCollectionMembersRequest\":{\"additionalProperties\":false,\"properties\":{\"camera" - "_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"maxItems\":4096,\"type\":\"array\"}},\"re" - "quired\":[\"camera_uuids\"],\"type\":\"object\"},\"CameraCollectionMembersResponse\":{\"additionalProperti" - "es\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a" - "-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"type\":\"array\"},\"collection_uuid\":{\"" - "pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":" - "\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"collection_uuid\",\"camera_uuids\",\"" - "count\"],\"type\":\"object\"},\"CameraCollectionPreview\":{\"additionalProperties\":false,\"properties\":{\"" - "collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" - "fA-F]{12}$\",\"type\":\"string\"},\"matched_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"" - "additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{" - "4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"location_path\":{\"type\":\"str" - "ing\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\",\"location_path\"],\"type\":\"object" - "\"},\"maxItems\":50,\"type\":\"array\"}},\"required\":[\"collection_uuid\",\"matched_count\",\"sample\"],\"type\"" - ":\"object\"},\"CameraCollectionUpdate\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"" - "maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"selector" - "\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"sta" - "tic\",\"smart\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"CameraCollectionsResponse\":{\"add" - "itionalProperties\":false,\"properties\":{\"collections\":{\"items\":{\"$ref\":\"#/components/schemas/Came" - "raCollection\"},\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"collections\"" - ",\"count\"],\"type\":\"object\"},\"CameraFavorite\":{\"additionalProperties\":false,\"properties\":{\"camera_" - "uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"}},\"r" - "equired\":[\"camera_uuid\",\"created_at\"],\"type\":\"object\"},\"CameraFavoriteRemoval\":{\"additionalPrope" - "rties\":false,\"properties\":{\"removed\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"type\":\"boolean\"" - "}},\"required\":[\"success\",\"removed\"],\"type\":\"object\"},\"CameraFavoritesResponse\":{\"additionalPrope" - "rties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"favorites\":{\"items\":{\"$ref\":\"#/compo" - "nents/schemas/CameraFavorite\"},\"type\":\"array\"}},\"required\":[\"favorites\",\"can_modify\"],\"type\":\"ob" - "ject\"},\"CameraLocationAssignment\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$r" - "ef\":\"#/components/schemas/CameraUuid\"},\"location_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"" - "},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\",\"location_uuid\"" - "],\"type\":\"object\"},\"CameraLocationAssignmentRequest\":{\"additionalProperties\":false,\"properties\":" - "{\"location_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"location_uuid\"],\"type\"" - ":\"object\"},\"CameraTagAssignmentsRequest\":{\"additionalProperties\":false,\"properties\":{\"tag_uuids\"" - ":{\"items\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[" - "\"tag_uuids\"],\"type\":\"object\"},\"CameraTagAssignmentsResponse\":{\"additionalProperties\":false,\"prop" - "erties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" - "[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref" - "\":\"#/components/schemas/FleetTag\"},\"type\":\"array\"}},\"required\":[\"camera_uuid\",\"tags\",\"count\"],\"t" - "ype\":\"object\"},\"CameraUuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F" - "]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"ClientConfig\":{\"additionalProperties\":false,\"descriptio" - "n\":\"Safe browser playback configuration only. Deliberately closed shape: no storage paths, integ" - "rations, private settings or credentials. go2rtc_available may stay true while its lifecycle gua" - "rd is busy.\",\"properties\":{\"api_key\":false,\"audio_disabled\":{\"type\":\"boolean\"},\"auto_disabled\":{" - "\"type\":\"boolean\"},\"default_playback_transport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hls_onl" - "y\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"generate_t" - "humbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_available\":{\"type\":\"bo" - "olean\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_force_n" - "ative_hls\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"mse_disabled\":{\"type\":\"boolean\"" - "},\"password\":false,\"password_hash\":false,\"secret\":false,\"thumbnails_per_recording\":{\"type\":\"numb" - "er\"},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"web_auth_enabled\":{\"type\":\"boole" - "an\"},\"webrtc_connection_timeout_ms\":{\"type\":\"number\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webr" - "tc_ice_recovery_timeout_ms\":{\"type\":\"number\"}},\"required\":[\"go2rtc_enabled\",\"web_auth_enabled\",\"" - "demo_mode\",\"audio_disabled\",\"auto_disabled\",\"go2rtc_available\",\"webrtc_disabled\",\"hls_disabled\"," - "\"mse_disabled\",\"go2rtc_force_native_hls\",\"generate_thumbnails\",\"go2rtc_api_port\",\"webrtc_connect" - "ion_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\",\"thumbnails_per_recording\",\"go2rtc_base_path\",\"" - "default_playback_transport\"],\"type\":\"object\"},\"CredentialRevoked\":{\"additionalProperties\":false," - "\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"}" - ",\"DaynightRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"mode\":{\"maxLength\":15," - "\"minLength\":1,\"type\":\"string\"}},\"required\":[\"mode\"]},{\"properties\":{\"daynight\":{\"maxLength\":15,\"" - "minLength\":1,\"type\":\"string\"}},\"required\":[\"daynight\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLeng" - "th\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"ma" - "xLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"properties\":{\"daynight\":{\"ma" - "xLength\":15,\"minLength\":1,\"type\":\"string\"},\"force_persistence\":{\"type\":\"boolean\"},\"ir_cut_filter" - "\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"s" - "tring\"},\"mode\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"D" - "aynightResponse\":{\"additionalProperties\":false,\"properties\":{\"ir_cut_filter\":{\"type\":\"string\"},\"" - "mode\":{\"enum\":[\"day\",\"night\",\"auto\",\"unknown\"]},\"options_available\":{\"type\":\"boolean\"},\"source\":" - "{\"const\":\"onvif-imaging\"},\"success\":{\"const\":true},\"supported_modes\":{\"items\":{\"enum\":[\"day\",\"ni" - "ght\",\"auto\"]},\"type\":\"array\"},\"video_source_token\":{\"type\":\"string\"}},\"required\":[\"success\",\"sou" - "rce\",\"video_source_token\",\"mode\",\"supported_modes\",\"options_available\"],\"type\":\"object\"},\"Daynig" - "htUpdated\":{\"additionalProperties\":false,\"properties\":{\"ir_cut_filter\":{\"enum\":[\"On\",\"Off\",\"Auto" - "\"]},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"source\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\"" - ":true}},\"required\":[\"success\",\"source\",\"mode\",\"ir_cut_filter\"],\"type\":\"object\"},\"DetectionZone\":" - "{\"additionalProperties\":true,\"properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string" - "\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLen" - "gth\":63,\"minLength\":1,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"" - "},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polygon\":{\"items\":{\"additionalProperties\":true,\"prope" - "rties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"numbe" - "r\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"array\"},\"stream_na" - "me\":{\"type\":\"string\"}},\"required\":[\"id\",\"name\",\"enabled\",\"color\",\"polygon\",\"filter_classes\",\"min" - "_confidence\",\"stream_name\"],\"type\":\"object\"},\"DetectionZoneWrite\":{\"additionalProperties\":true,\"" - "properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"" - "},\"filter_classes\":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"" - "string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type" - "\":\"string\"},\"polygon\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"x\":{\"maximum\":1,\"mini" - "mum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"typ" - "e\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"array\"}},\"required\":[\"id\",\"name\",\"polygon\"],\"typ" - "e\":\"object\"},\"DeviceCommandResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\"" - ":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"DeviceEm" - "ptyRequest\":{\"additionalProperties\":false,\"properties\":{},\"required\":[],\"type\":\"object\"},\"Device" - "FloatRange\":{\"additionalProperties\":false,\"properties\":{\"max\":{\"type\":\"number\"},\"min\":{\"type\":\"n" - "umber\"}},\"required\":[\"min\",\"max\"],\"type\":\"object\"},\"DeviceSuccess\":{\"additionalProperties\":false" - ",\"properties\":{\"success\":{\"const\":true}},\"required\":[\"success\"],\"type\":\"object\"},\"EventCameraSco" - "pe\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"type\":{\"const\":\"all\",\"type\":\"string\"}" - "},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"selector\":{\"" - "$ref\":\"#/components/schemas/FleetSelector\"},\"type\":{\"const\":\"selector\",\"type\":\"string\"}},\"requir" - "ed\":[\"type\",\"selector\"],\"type\":\"object\"}]},\"EventCatalog\":{\"additionalProperties\":false,\"propert" - "ies\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"event_types\":{\"items\":{\"additionalProperties\":fals" - "e,\"properties\":{\"default_expiry_seconds\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"s" - "tring\"},\"dynamic_severity\":{\"type\":\"boolean\"},\"expected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"t" - "ype\":\"string\"},\"family\":{\"type\":\"string\"},\"maximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"w" - "arning\",\"error\",\"critical\"],\"type\":\"string\"},\"media_policy\":{\"type\":\"string\"},\"minimum_severity\"" - ":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"sensitivity\":{" - "\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type" - "\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"storage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\"" - ":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"severity\",\"dynamic_severity\",\"minimum_severity\"," - "\"maximum_severity\",\"subject_kind\",\"family\",\"description\",\"sensitivity\",\"media_policy\",\"default_e" - "xpiry_seconds\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":{\"const\":1,\"type\":\"integer\"}}," - "\"required\":[\"schema_version\",\"count\",\"event_types\"],\"type\":\"object\"},\"EventDestination\":{\"additi" - "onalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":" - "{\"password_configured\":{\"type\":\"boolean\"},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"require" - "d\":[\"username\",\"password_configured\"],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"p" - "roperties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255," - "\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"}" - ",\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"in" - "teger\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":" - "511,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"host\",\"port\",\"client_id\",\"topic_template\",\"stat" - "us_topic_template\",\"keepalive_seconds\",\"qos\"],\"type\":\"object\"},\"created_at\":{\"minimum\":0,\"type\":" - "\"integer\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"key\":{\"" - "maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"managed\":{\"const\":true,\"type\":\"boolean\"},\"name\":{\"" - "maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1" - ",\"type\":\"integer\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511," - "\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type" - "\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"require" - "d\":[\"mode\",\"ca_file\",\"cert_file\",\"key_file\"],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"str" - "ing\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"req" - "uired\":[\"uuid\",\"key\",\"name\",\"description\",\"enabled\",\"type\",\"broker\",\"authentication\",\"tls\",\"mana" - "ged\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"EventDestinationCreate\":{\"additiona" - "lProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"p" - "assword\":{\"maxLength\":255,\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}" - "},\"required\":[],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id" - "\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"" - "string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":655" - "35,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_" - "template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"typ" - "e\":\"string\"}},\"required\":[\"host\"],\"type\":\"object\"},\"description\":{\"maxLength\":511,\"type\":\"string" - "\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"tls\":{\"a" - "dditionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\"" - ":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[" - "\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"type" - "\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[\"name\",\"broker\"],\"type\":\"object\"},\"EventDestinat" - "ionUpdate\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":" - "false,\"properties\":{\"password\":{\"maxLength\":255,\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength" - "\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"p" - "roperties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255," - "\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"}" - ",\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"in" - "teger\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":" - "511,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"description\":{\"maxLength\":51" - "1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"st" - "ring\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalPr" - "operties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLengt" - "h\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\"," - "\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"type\":{\"const\":" - "\"mqtt\",\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EventDestinations\":{\"addition" - "alProperties\":false,\"properties\":{\"count\":{\"maximum\":64,\"minimum\":0,\"type\":\"integer\"},\"default_d" - "estination\":{\"additionalProperties\":false,\"properties\":{\"configuration_source\":{\"const\":\"setting" - "s\",\"type\":\"string\"},\"key\":{\"const\":\"mqtt:default\",\"type\":\"string\"},\"managed\":{\"const\":false,\"typ" - "e\":\"boolean\"},\"name\":{\"type\":\"string\"}},\"required\":[\"key\",\"name\",\"configuration_source\",\"managed" - "\"],\"type\":\"object\"},\"destinations\":{\"items\":{\"$ref\":\"#/components/schemas/EventDestination\"},\"ma" - "xItems\":64,\"type\":\"array\"}},\"required\":[\"default_destination\",\"count\",\"destinations\"],\"type\":\"ob" - "ject\"},\"EventMutationAck\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"t" - "ype\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"EventPredicate\":{\"additionalProperties" - "\":false,\"properties\":{\"detection\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{" - "\"labels_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1" - ",\"type\":\"array\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"zone_ids_any\":{\"ite" - "ms\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}}," - "\"required\":[],\"type\":\"object\"},\"health\":{\"additionalProperties\":false,\"minProperties\":1,\"propert" - "ies\":{\"condition_codes_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":" - "64,\"minItems\":1,\"type\":\"array\"},\"severities_any\":{\"items\":{\"enum\":[\"warning\",\"error\",\"critical\"]" - ",\"type\":\"string\"},\"maxItems\":3,\"minItems\":1,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"ver" - "sion\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\"],\"type\":\"object\"},\"EventRoute\":{\"addit" - "ionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraSco" - "pe\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"maxLength\":511,\"type\":\"string\"}" - ",\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"eve" - "nt_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"t" - "ype\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predica" - "te\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"revision\":{\"maximum\":9007199254740991,\"minim" - "um\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{" - "\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"typ" - "e\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window" - "_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000" - ",\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"debounce_seconds\",\"cooldown_seconds\",\"grouping_wind" - "ow_seconds\",\"max_events_per_minute\"],\"type\":\"object\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"" - "},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"enabled\",\"d" - "estination\",\"event_types\",\"camera_scope\",\"predicate\",\"schedule\",\"suppression\",\"revision\",\"create" - "d_at\",\"updated_at\"],\"type\":\"object\"},\"EventRouteCreate\":{\"additionalProperties\":false,\"propertie" - "s\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":5" - "11,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"ty" - "pe\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\"" - ":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\"" - ":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/comp" - "onents/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldo" - "wn_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400," - "\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"inte" - "ger\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"typ" - "e\":\"object\"}},\"required\":[\"name\",\"event_types\"],\"type\":\"object\"},\"EventRoutePreview\":{\"additiona" - "lProperties\":false,\"properties\":{\"camera_sample\":{\"items\":{\"additionalProperties\":false,\"propert" - "ies\":{\"camera_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"name\":{" - "\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\",\"location_path\"],\"type\":\"object\"},\"maxItems\":" - "20,\"type\":\"array\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"dynamic_s" - "everity\":{\"type\":\"boolean\"},\"expected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"ma" - "ximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"}," - "\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string" - "\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"s" - "ubject_kind\":{\"enum\":[\"camera\",\"storage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"r" - "equired\":[\"type\",\"expected_rate\",\"severity\",\"dynamic_severity\",\"minimum_severity\",\"maximum_sever" - "ity\",\"subject_kind\"],\"type\":\"object\"},\"type\":\"array\"},\"matched_camera_count\":{\"minimum\":0,\"type\"" - ":\"integer\"},\"sample_limit\":{\"const\":20,\"type\":\"integer\"},\"would_publish\":{\"const\":false,\"type\":\"" - "boolean\"}},\"required\":[\"matched_camera_count\",\"sample_limit\",\"camera_sample\",\"event_types\",\"woul" - "d_publish\"],\"type\":\"object\"},\"EventRoutePreviewRequest\":{\"additionalProperties\":false,\"propertie" - "s\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":5" - "11,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"ty" - "pe\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\"" - ":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\"" - ":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/comp" - "onents/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldo" - "wn_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400," - "\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"inte" - "ger\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"typ" - "e\":\"object\"}},\"required\":[\"name\",\"event_types\"],\"type\":\"object\"},\"EventRouteUpdate\":{\"additional" - "Properties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"}," - "\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"ty" - "pe\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1" - ",\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLeng" - "th\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"" - "},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/com" - "ponents/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"coold" - "own_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400" - ",\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"int" - "eger\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"ty" - "pe\":\"object\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EventRoutes\":{\"additionalProperties\":fa" - "lse,\"properties\":{\"count\":{\"maximum\":512,\"minimum\":0,\"type\":\"integer\"},\"routes\":{\"items\":{\"$ref\"" - ":\"#/components/schemas/EventRoute\"},\"maxItems\":512,\"type\":\"array\"}},\"required\":[\"routes\",\"count\"" - "],\"type\":\"object\"},\"EventSchedule\":{\"additionalProperties\":false,\"properties\":{\"timezone\":{\"maxL" - "ength\":64,\"minLength\":1,\"type\":\"string\"},\"version\":{\"const\":1,\"type\":\"integer\"},\"windows\":{\"item" - "s\":{\"additionalProperties\":false,\"properties\":{\"days\":{\"items\":{\"maximum\":6,\"minimum\":0,\"type\":\"" - "integer\"},\"maxItems\":7,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"end\":{\"pattern\":\"^(\077:[01" - "][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"},\"start\":{\"pattern\":\"^(\077:[01][0-9]|2[0-3]):[0-5][0-9" - "]$\",\"type\":\"string\"}},\"required\":[\"days\",\"start\",\"end\"],\"type\":\"object\"},\"maxItems\":64,\"type\":\"a" - "rray\"}},\"required\":[\"version\",\"timezone\",\"windows\"],\"type\":\"object\"},\"EvidenceBookmark\":{\"additi" - "onalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA" - "-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\"" - ":1,\"type\":\"array\",\"uniqueItems\":true},\"created_at\":{\"type\":\"integer\"},\"cursor_time\":{\"minimum\":1" - ",\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\":\"#/components/sch" - "emas/EvidenceBookmarkFilters\"},\"holds_recordings\":{\"const\":false},\"note\":{\"maxLength\":2047,\"type" - "\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9" - "a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"representative_result\":{\"$ref\":\"#/components/sche" - "mas/EvidenceBookmarkResult\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1" - ",\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"updated_at\":{\"type\":" - "\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a" - "-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"title\",\"note\",\"start_time\",\"end_time\",\"cursor_time\"," - "\"primary_camera_uuid\",\"camera_uuids\",\"filters\",\"representative_result\",\"uuid\",\"revision\",\"create" - "d_at\",\"updated_at\",\"holds_recordings\"],\"type\":\"object\"},\"EvidenceBookmarkCreate\":{\"additionalPro" - "perties\":true,\"description\":\"Camera list, primary camera, region and saved result must agree. Cu" - "rsor must lie inside the interval. The bookmark is owned by the current user (also for scoped to" - "kens), never grants access, and does not protect media from retention.\",\"properties\":{\"camera_uu" - "ids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-" - "F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"cursor_" - "time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\"" - ":\"#/components/schemas/EvidenceBookmarkFilters\"},\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"prim" - "ary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" - "fA-F]{12}$\",\"type\":\"string\"},\"representative_result\":{\"$ref\":\"#/components/schemas/EvidenceBookm" - "arkResult\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"" - "type\":\"string\"}},\"required\":[\"title\",\"start_time\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\"" - ",\"camera_uuids\",\"filters\"],\"type\":\"object\"},\"EvidenceBookmarkDelete\":{\"additionalProperties\":tru" - "e,\"properties\":{\"revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"objec" - "t\"},\"EvidenceBookmarkDeleted\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":tru" - "e}},\"required\":[\"success\"],\"type\":\"object\"},\"EvidenceBookmarkFilters\":{\"additionalProperties\":fa" - "lse,\"properties\":{\"capture_method\":{\"maxLength\":127,\"type\":\"string\"},\"event_type\":{\"maxLength\":1" - "27,\"type\":\"string\"},\"label\":{\"maxLength\":127,\"type\":\"string\"},\"location\":{\"maxLength\":127,\"type\"" - ":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protection\":{\"maxLength\":" - "127,\"type\":\"string\"},\"recording_tag\":{\"maxLength\":127,\"type\":\"string\"},\"region\":{\"additionalProp" - "erties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]" - "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1" - ",\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"" - "},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinim" - "um\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":" - "1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"obje" - "ct\"},\"source\":{\"maxLength\":127,\"type\":\"string\"},\"zone\":{\"maxLength\":127,\"type\":\"string\"}},\"requi" - "red\":[],\"type\":\"object\"},\"EvidenceBookmarkResult\":{\"anyOf\":[{\"additionalProperties\":false,\"prope" - "rties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[" - "0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection_id\":{\"type\":[\"string\",\"number\"]},\"end_time\":{\"type\"" - ":[\"string\",\"number\"]},\"event_type\":{\"type\":[\"string\",\"number\"]},\"label\":{\"type\":[\"string\",\"numbe" - "r\"]},\"recording_id\":{\"type\":[\"string\",\"number\"]},\"result_id\":{\"type\":[\"string\",\"number\"]},\"start" - "_time\":{\"type\":[\"string\",\"number\"]}},\"required\":[],\"type\":\"object\"},{\"type\":\"null\"}]},\"EvidenceB" - "ookmarkUpdate\":{\"additionalProperties\":true,\"properties\":{\"note\":{\"maxLength\":2047,\"type\":\"strin" - "g\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"str" - "ing\"}},\"required\":[\"title\",\"revision\"],\"type\":\"object\"},\"EvidenceBookmarks\":{\"additionalProperti" - "es\":false,\"properties\":{\"bookmarks\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"},\"m" - "axItems\":256,\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"bookmarks\",\"co" - "unt\"],\"type\":\"object\"},\"EvidenceDetection\":{\"additionalProperties\":false,\"properties\":{\"confiden" - "ce\":{\"type\":\"number\"},\"end_timestamp\":{\"type\":\"integer\"},\"height\":{\"type\":\"number\"},\"label\":{\"ty" - "pe\":\"string\"},\"timestamp\":{\"type\":\"integer\"},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\"" - ":{\"type\":\"number\"}},\"required\":[\"confidence\",\"x\",\"y\",\"width\",\"height\",\"label\",\"timestamp\",\"end_t" - "imestamp\"],\"type\":\"object\"},\"EvidenceDetections\":{\"additionalProperties\":false,\"properties\":{\"de" - "tections\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceDetection\"},\"type\":\"array\"},\"stream_sta" - "tus\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"timestamp\":{\"type\":\"string\"}},\"required\":[\"de" - "tections\",\"timestamp\",\"stream_status\"],\"type\":\"object\"},\"EvidenceFileCheck\":{\"oneOf\":[{\"addition" - "alProperties\":false,\"properties\":{\"exists\":{\"const\":false}},\"required\":[\"exists\"],\"type\":\"object" - "\"},{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":true},\"mtime\":{\"type\":\"integer\"" - "},\"size\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"exists\",\"size\",\"mtime\"],\"type\":\"object\"}]}" - ",\"EvidenceFileDeleted\":{\"additionalProperties\":false,\"properties\":{\"existed\":{\"type\":\"boolean\"}," - "\"status\":{\"enum\":[\"deleted\",\"deletion_pending\"],\"type\":\"string\"},\"success\":{\"const\":true}},\"requ" - "ired\":[\"success\",\"existed\",\"status\"],\"type\":\"object\"},\"EvidenceLprDeleted\":{\"additionalPropertie" - "s\":false,\"properties\":{\"deleted\":{\"const\":true}},\"required\":[\"deleted\"],\"type\":\"object\"},\"Eviden" - "ceLprExportRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-" - "fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"end_at" - "\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"},\"match\":{" - "\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"minimum\":1,\"t" - "ype\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"EvidenceLprRea" - "d\":{\"additionalProperties\":false,\"properties\":{\"attributes\":{\"additionalProperties\":false,\"prope" - "rties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"bottom\":{\"type\":\"n" - "umber\"},\"left\":{\"type\":\"number\"},\"right\":{\"type\":\"number\"},\"top\":{\"type\":\"number\"}},\"required\":[" - "\"left\",\"top\",\"right\",\"bottom\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"correlation_id\":{\"type\":[\"str" - "ing\",\"null\"]},\"country\":{\"type\":[\"string\",\"null\"]},\"direction\":{\"type\":[\"string\",\"null\"]},\"lane\"" - ":{\"type\":[\"string\",\"null\"]},\"object_id\":{\"type\":[\"string\",\"null\"]},\"plate_type\":{\"type\":[\"string" - "\",\"null\"]},\"region\":{\"type\":[\"string\",\"null\"]},\"vehicle_color\":{\"type\":[\"string\",\"null\"]},\"vehic" - "le_type\":{\"type\":[\"string\",\"null\"]},\"vendor_topic\":{\"type\":\"string\"}},\"required\":[\"country\",\"reg" - "ion\",\"plate_type\",\"direction\",\"lane\",\"vehicle_type\",\"vehicle_color\",\"object_id\",\"correlation_id\"" - ",\"vendor_topic\",\"bounding_box\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a" - "-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"confidence\":{\"anyOf\"" - ":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"id\":{\"pattern\":\"^[0-9a-fA-F]{8}-[" - "0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"observed_at\":{\"m" - "inimum\":1,\"type\":\"integer\"},\"plate\":{\"type\":\"string\"},\"received_at\":{\"minimum\":1,\"type\":\"integer" - "\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"source\":{\"type\":\"s" - "tring\"},\"stream_name\":{\"type\":\"string\"},\"type\":{\"const\":\"license_plate\"}},\"required\":[\"id\",\"type" - "\",\"camera_uuid\",\"stream_name\",\"observed_at\",\"received_at\",\"source\",\"plate\",\"confidence\",\"recordi" - "ng_id\",\"attributes\"],\"type\":\"object\"},\"EvidenceLprReads\":{\"additionalProperties\":false,\"properti" - "es\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"reads\":{\"items\":{\"$ref\":\"#/components/schemas/Evide" - "nceLprRead\"},\"maxItems\":1000,\"type\":\"array\"}},\"required\":[\"reads\",\"count\"],\"type\":\"object\"},\"Evi" - "denceLprSearchRequest\":{\"additionalProperties\":true,\"description\":\"Epoch milliseconds, end at/af" - "ter start within 366 days; match requires a canonicalizable plate (partial at least 3 canonical " - "characters). Search returns at most 100 rows.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA" - "-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"end_at\":" - "{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"match\":{\"en" - "um\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"minimum\":1,\"type" - "\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"EvidenceModel\":{\"" - "additionalProperties\":false,\"properties\":{\"description\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"}" - ",\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"size\":{\"minimum\":0,\"type\":\"integer\"},\"suppor" - "ted\":{\"type\":\"boolean\"},\"type\":{\"type\":\"string\"},\"unsupported_reason\":{\"type\":\"string\"}},\"requir" - "ed\":[\"id\",\"name\",\"path\",\"type\",\"supported\"],\"type\":\"object\"},\"EvidenceModels\":{\"additionalProper" - "ties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"litert_available\":{\"type\":\"boo" - "lean\"},\"message\":{\"type\":\"string\"},\"models\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceModel" - "\"},\"type\":\"array\"},\"sod_available\":{\"type\":\"boolean\"}},\"required\":[\"models\",\"count\",\"litert_avai" - "lable\",\"sod_available\"],\"type\":\"object\"},\"EvidencePreview\":{\"additionalProperties\":false,\"proper" - "ties\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"" - "permissions\":{\"additionalProperties\":false,\"properties\":{\"export\":{\"additionalProperties\":false," - "\"properties\":{\"all_allowed\":{\"type\":\"boolean\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"d" - "enied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"all_all" - "owed\"],\"type\":\"object\"},\"protect\":{\"additionalProperties\":false,\"properties\":{\"actionable_count\"" - ":{\"minimum\":0,\"type\":\"integer\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"denied_count\":{\"" - "minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"actionable_count\"],\"ty" - "pe\":\"object\"}},\"required\":[\"protect\",\"export\"],\"type\":\"object\"},\"protected_count\":{\"minimum\":0,\"" - "type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"recordings\":{\"items\":{\"$ref\":" - "\"#/components/schemas/EvidencePreviewRecording\"},\"maxItems\":200,\"type\":\"array\"},\"start_time\":{\"m" - "inimum\":1,\"type\":\"integer\"},\"total_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"unprotected_count\":{\"m" - "inimum\":0,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_count\",\"recording_count" - "\",\"total_bytes\",\"protected_count\",\"unprotected_count\",\"recordings\",\"permissions\"],\"type\":\"object" - "\"},\"EvidencePreviewRecording\":{\"additionalProperties\":false,\"properties\":{\"camera_name\":{\"type\":" - "\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}" - "-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"can_export\":{\"type\":\"boolean\"},\"can_protect\":{\"type\":\"boole" - "an\"},\"end_time\":{\"type\":\"integer\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"protected\":{\"type\":\"bool" - "ean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"},\"stream_name\":" - "{\"type\":\"string\"}},\"required\":[\"id\",\"camera_uuid\",\"camera_name\",\"stream_name\",\"start_time\",\"end_" - "time\",\"size_bytes\",\"protected\",\"can_protect\",\"can_export\"],\"type\":\"object\"},\"EvidenceProtectedCo" - "unt\":{\"additionalProperties\":false,\"properties\":{\"protected_count\":{\"minimum\":0,\"type\":\"integer\"" - "},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"protected_count\"],\"type\":\"object\"},\"EvidenceRang" - "eRequest\":{\"additionalProperties\":true,\"description\":\"End must exceed start by no more than 31 d" - "ays. All explicit cameras must exist and permit recordings.replay; mixed unauthorized input is r" - "ejected.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a" - "-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"a" - "rray\",\"uniqueItems\":true},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"t" - "ype\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceS" - "earch\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"}" - ",\"coverage\":{\"additionalProperties\":false,\"properties\":{\"complete\":{\"type\":\"boolean\"},\"incomplet" - "e_reasons\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"requested_end\":{\"minimum\":1,\"type\":\"integ" - "er\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"},\"searched_end\":{\"minimum\":1,\"type\":\"intege" - "r\"},\"searched_start\":{\"minimum\":1,\"type\":\"integer\"},\"spatial_metadata\":{\"oneOf\":[{\"additionalPro" - "perties\":false,\"properties\":{\"requested\":{\"const\":false}},\"required\":[\"requested\"],\"type\":\"objec" - "t\"},{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-" - "fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"complete\":{\"type\":\"bo" - "olean\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_int" - "ersection\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"rectangle\":{\"additionalProperties\":false,\"" - "properties\":{\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMini" - "mum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\"" - ":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"request" - "ed\":{\"const\":true},\"rows_with_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"rows_without_boxes\":{\"minim" - "um\":0,\"type\":\"integer\"},\"search_type\":{\"const\":\"metadata_search\"}},\"required\":[\"requested\",\"sear" - "ch_type\",\"camera_uuid\",\"match\",\"rows_with_boxes\",\"rows_without_boxes\",\"complete\",\"rectangle\"],\"t" - "ype\":\"object\"}]},\"unresolved_legacy_rows\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"requested" - "_start\",\"requested_end\",\"searched_start\",\"searched_end\",\"unresolved_legacy_rows\",\"spatial_metada" - "ta\",\"incomplete_reasons\",\"complete\"],\"type\":\"object\"},\"facets\":{\"additionalProperties\":false,\"pr" - "operties\":{\"cameras\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"" - "type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count" - "\"],\"type\":\"object\"},\"type\":\"array\"},\"capture_methods\":{\"items\":{\"additionalProperties\":false,\"pr" - "operties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"str" - "ing\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"event_types\":{\"items\":{\"ad" - "ditionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"" - "string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"" - "},\"labels\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"int" - "eger\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":" - "\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"coun" - "t\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required" - "\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"protection\":{\"items\":{\"additionalPropertie" - "s\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":" - "{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"recording_tags" + ".search\",\"lpr.export\",\"lpr.delete\",\"observations.annotate\"],\"type\":\"string\"},\"camera_uuid\":{\"pat" + "tern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"st" + "ring\"},\"user_id\":{\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"act" + "ion\",\"camera_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\"" + ":[\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin\"],\"type\":\"string\"},\"camera" + "_uuid\":false,\"user_id\":{\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id" + "\",\"action\"],\"type\":\"object\"}]},\"AdminUser\":{\"additionalProperties\":false,\"properties\":{\"allowed_" + "login_cidrs\":{\"type\":[\"string\",\"null\"]},\"authorization_mode\":{\"type\":\"string\"},\"created_at\":{\"ty" + "pe\":\"integer\"},\"email\":{\"type\":\"string\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"is_active\":{\"type\"" + ":\"boolean\"},\"last_login\":{\"type\":\"integer\"},\"must_change_password\":{\"type\":\"boolean\"},\"password_" + "change_locked\":{\"type\":\"boolean\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"role_name\":{\"enum\"" + ":[\"admin\",\"user\",\"viewer\",\"api\",\"unknown\"],\"type\":\"string\"},\"totp_enabled\":{\"type\":\"boolean\"},\"u" + "pdated_at\":{\"type\":\"integer\"},\"username\":{\"type\":\"string\"}},\"required\":[\"id\",\"username\",\"email\"," + "\"role\",\"role_name\",\"created_at\",\"updated_at\",\"last_login\",\"is_active\",\"password_change_locked\",\"" + "must_change_password\",\"totp_enabled\",\"authorization_mode\",\"allowed_login_cidrs\"],\"type\":\"object\"" + "},\"AdminUserCreate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"s" + "tring\",\"null\"]},\"email\":{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password\":{\"minLength\"" + ":8,\"type\":\"string\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"username\":{\"maxLength\":32,\"minLe" + "ngth\":3,\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"AdminUserDeleted\"" + ":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true" + "}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"AdminUserPolicy\":{\"additionalProperties\":f" + "alse,\"properties\":{\"grant_count\":{\"minimum\":0,\"type\":\"integer\"},\"grants\":{\"items\":{\"$ref\":\"#/com" + "ponents/schemas/AdminPolicyGrant\"},\"type\":\"array\"},\"mode\":{\"enum\":[\"legacy\",\"policy\"],\"type\":\"st" + "ring\"},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"user_id\":{\"mi" + "nimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"mode\",\"policy_version\",\"grant_count\",\"grants\"" + "],\"type\":\"object\"},\"AdminUserUpdate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_c" + "idrs\":{\"type\":[\"string\",\"null\"]},\"allowed_tags\":false,\"email\":{\"type\":\"string\"},\"is_active\":{\"ty" + "pe\":\"boolean\"},\"password\":{\"anyOf\":[{\"const\":\"\"},{\"minLength\":8,\"type\":\"string\"}]},\"role\":false," + "\"username\":{\"maxLength\":32,\"minLength\":3,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"Admin" + "Users\":{\"additionalProperties\":false,\"properties\":{\"users\":{\"items\":{\"$ref\":\"#/components/schema" + "s/AdminUser\"},\"type\":\"array\"}},\"required\":[\"users\"],\"type\":\"object\"},\"AdminVersionRequest\":{\"add" + "itionalProperties\":true,\"properties\":{\"expected_policy_version\":{\"maximum\":9007199254740991,\"min" + "imum\":1,\"type\":\"integer\"}},\"required\":[\"expected_policy_version\"],\"type\":\"object\"},\"ApiError\":{\"" + "additionalProperties\":true,\"description\":\"Most errors use a string error. YAML validation may us" + "e valid=false and error.message. Extra diagnostic fields may be added; clients must not log priv" + "ileged request or response data.\",\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"string\"},{\"additional" + "Properties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type\":\"objec" + "t\"}]},\"valid\":{\"type\":\"boolean\"}},\"required\":[\"error\"],\"type\":\"object\"},\"ApiTokenCreateRequest\":" + "{\"additionalProperties\":true,\"description\":\"Requested action and camera scope must be contained " + "in BOTH token owner and requester authority. At most256 active tokens per owner. Additional requ" + "est keys do not rewrite user policy.\",\"properties\":{\"actions\":{\"description\":\"Duplicates collaps" + "e into an action mask.\",\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"minItems\":1" + ",\"type\":\"array\"},\"description\":{\"description\":\"Trimmed by server; must contain non-whitespace te" + "xt, no control characters, and fewer than128 UTF-8 bytes.\",\"maxLength\":127,\"minLength\":1,\"type\":" + "\"string\"},\"expires_at\":{\"description\":\"Unix seconds strictly after server time and at most366 da" + "ys ahead.\",\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"scope\":{\"$ref\":\"#/component" + "s/schemas/ApiTokenScope\"}},\"required\":[\"description\",\"expires_at\",\"actions\",\"scope\"],\"type\":\"obj" + "ect\"},\"ApiTokenCreated\":{\"additionalProperties\":true,\"description\":\"Only successful token creati" + "on returns a secret. No secret value is included in examples.\",\"properties\":{\"secret\":{\"descript" + "ion\":\"One-time bearer secret; store securely after creation. Ordinary token-list/identity respon" + "ses never include it.\",\"pattern\":\"^lnvr_[0-9a-f]{64}$\",\"readOnly\":true,\"type\":\"string\"},\"token\":" + "{\"$ref\":\"#/components/schemas/ApiTokenMetadata\"}},\"required\":[\"secret\",\"token\"],\"type\":\"object\"}" + ",\"ApiTokenList\":{\"additionalProperties\":true,\"description\":\"Lists token metadata, including revo" + "ked/expired records. Secret is never returned.\",\"properties\":{\"api_key\":false,\"count\":{\"minimum\"" + ":0,\"type\":\"integer\"},\"password\":false,\"password_hash\":false,\"secret\":false,\"token_hash\":false,\"t" + "okens\":{\"items\":{\"$ref\":\"#/components/schemas/ApiTokenMetadata\"},\"type\":\"array\"},\"totp_secret\":f" + "alse,\"totp_token\":false,\"user_id\":{\"type\":\"integer\"}},\"required\":[\"user_id\",\"count\",\"tokens\"],\"t" + "ype\":\"object\"},\"ApiTokenMetadata\":{\"additionalProperties\":true,\"description\":\"Non-secret token r" + "ecord; prefix is an identifier, never a reusable credential. Secret/hash fields are explicitly f" + "orbidden.\",\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"}," + "\"type\":\"array\"},\"api_key\":false,\"created_at\":{\"type\":\"integer\"},\"description\":{\"type\":\"string\"}," + "\"expires_at\":{\"type\":\"integer\"},\"last_used_at\":{\"type\":[\"integer\",\"null\"]},\"password\":false,\"pas" + "sword_hash\":false,\"prefix\":{\"type\":\"string\"},\"revoked_at\":{\"type\":[\"integer\",\"null\"]},\"scope\":{\"" + "$ref\":\"#/components/schemas/ApiTokenStoredScope\"},\"secret\":false,\"token_hash\":false,\"totp_secret" + "\":false,\"totp_token\":false,\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9" + "a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"description\",\"prefix\",\"expire" + "s_at\",\"revoked_at\",\"last_used_at\",\"created_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenScop" + "e\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selec" + "tor\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalPr" + "operties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/" + "schemas/TokenSelector\"},\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\"],\"type\":\"obje" + "ct\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0" + "-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\"" + ":\"null\"},\"type\":{\"const\":\"collection\"}},\"required\":[\"type\",\"collection_uuid\"],\"type\":\"object\"}]," + "\"description\":\"All, selector or collection camera scope. Inapplicable selector/collection fields" + " may be absent or null. A narrow owner cannot delegate all scope simply because current matches " + "happen to be identical.\"},\"ApiTokenStoredScope\":{\"anyOf\":[{\"additionalProperties\":true,\"properti" + "es\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"requi" + "red\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"proper" + "ties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"" + "},\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"}" + ",{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-" + "fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"nu" + "ll\"},\"type\":{\"const\":\"collection\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"obj" + "ect\"}]},\"AuditDecisionMode\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"$ref\":\"#/comp" + "onents/schemas/AuthorizationAction\"},\"category\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"" + "},\"mode\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"}},\"required\":[\"action\",\"category\"," + "\"description\",\"mode\"],\"type\":\"object\"},\"AuditEvent\":{\"additionalProperties\":false,\"properties\":{" + "\"action\":{\"type\":\"string\"},\"api_token_uuid\":{\"type\":[\"string\",\"null\"]},\"auth_method\":{\"type\":\"st" + "ring\"},\"details\":{\"additionalProperties\":true,\"type\":[\"object\",\"null\"]},\"occurred_at\":{\"type\":\"i" + "nteger\"},\"outcome\":{\"type\":\"string\"},\"principal_user_id\":{\"type\":[\"integer\",\"null\"]},\"principal_" + "username\":{\"type\":\"string\"},\"remote_address\":{\"type\":\"string\"},\"request_id\":{\"type\":\"string\"},\"t" + "arget_type\":{\"type\":[\"string\",\"null\"]},\"target_uuid\":{\"type\":[\"string\",\"null\"]},\"uuid\":{\"pattern" + "\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string" + "\"}},\"required\":[\"uuid\",\"occurred_at\",\"request_id\",\"principal_user_id\",\"principal_username\",\"auth" + "_method\",\"api_token_uuid\",\"action\",\"target_type\",\"target_uuid\",\"outcome\",\"remote_address\",\"detai" + "ls\"],\"type\":\"object\"},\"AuditEventsPage\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"mi" + "nimum\":0,\"type\":\"integer\"},\"events\":{\"items\":{\"$ref\":\"#/components/schemas/AuditEvent\"},\"type\":\"" + "array\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"minimum\":1,\"type\":\"integer\"},\"total\"" + ":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"page\",\"page_size\",\"count\",\"total\",\"events\"],\"type\"" + ":\"object\"},\"AuditSettings\":{\"additionalProperties\":false,\"properties\":{\"allowed_decision_modes\":" + "{\"items\":{\"$ref\":\"#/components/schemas/AuditDecisionMode\"},\"type\":\"array\"},\"pruned_events\":{\"min" + "imum\":0,\"type\":\"integer\"},\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"},\"summar" + "y_window_seconds\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\",\"summary_window_s" + "econds\",\"allowed_decision_modes\"],\"type\":\"object\"},\"AuditSettingsWrite\":{\"additionalProperties\":" + "true,\"anyOf\":[{\"properties\":{\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"re" + "quired\":[\"retention_days\"]},{\"properties\":{\"allowed_decision_modes\":{\"additionalProperties\":{\"en" + "um\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/" + "AuthorizationAction\"},\"type\":\"object\"}},\"required\":[\"allowed_decision_modes\"]}],\"properties\":{\"a" + "llowed_decision_modes\":{\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"stri" + "ng\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"object\"},\"reten" + "tion_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"AuthDe" + "moIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"authenticated\":{\"const\":" + "false},\"demo_mode\":{\"const\":true},\"must_change_password\":{\"const\":false},\"password\":false,\"passw" + "ord_hash\":false,\"role\":{\"const\":\"viewer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false," + "\"totp_token\":false,\"username\":{\"const\":\"demo\"}},\"required\":[\"authenticated\",\"demo_mode\",\"usernam" + "e\",\"role\",\"must_change_password\"],\"type\":\"object\"},\"AuthDisabledIdentity\":{\"additionalProperties" + "\":true,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabl" + "ed\":{\"const\":false},\"auth_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"must_" + "change_password\":{\"const\":false},\"password\":false,\"password_hash\":false,\"role\":{\"const\":\"admin\"}" + ",\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":" + "{\"type\":\"number\"},\"username\":{\"const\":\"admin\"}},\"required\":[\"authenticated\",\"username\",\"role\",\"m" + "ust_change_password\",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_" + "device_days\"],\"type\":\"object\"},\"AuthSession\":{\"additionalProperties\":false,\"properties\":{\"create" + "d_at\":{\"minimum\":0,\"type\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"typ" + "e\":\"integer\"},\"id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"idle_expires_at\":{" + "\"minimum\":0,\"type\":\"integer\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_activity_at\":{" + "\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[\"id\",\"" + "created_at\",\"expires_at\",\"ip_address\",\"user_agent\",\"current\",\"last_activity_at\",\"idle_expires_at" + "\"],\"type\":\"object\"},\"AuthSessionsResponse\":{\"additionalProperties\":false,\"properties\":{\"sessions" + "\":{\"items\":{\"$ref\":\"#/components/schemas/AuthSession\"},\"maxItems\":32,\"type\":\"array\"}},\"required\"" + ":[\"sessions\"],\"type\":\"object\"},\"AuthVerifiedIdentity\":{\"additionalProperties\":true,\"properties\":" + "{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":true},\"" + "auth_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"email\":{\"type\":\"string\"},\"" + "id\":{\"type\":\"integer\"},\"is_active\":{\"type\":\"boolean\"},\"must_change_password\":{\"type\":\"boolean\"}," + "\"password\":false,\"password_change_locked\":{\"type\":\"boolean\"},\"password_hash\":false,\"role\":{\"type" + "\":\"string\"},\"role_id\":{\"type\":\"integer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"" + "totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"},\"username\":{\"type\":\"string\"}},\"require" + "d\":[\"authenticated\",\"id\",\"username\",\"email\",\"role\",\"role_id\",\"is_active\",\"password_change_locked" + "\",\"must_change_password\",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trus" + "ted_device_days\"],\"type\":\"object\"},\"AuthVerifyResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/" + "AuthVerifiedIdentity\"},{\"$ref\":\"#/components/schemas/AuthDisabledIdentity\"},{\"$ref\":\"#/component" + "s/schemas/AuthDemoIdentity\"}],\"description\":\"Real credential identity, auth-disabled synthetic a" + "dmin, or unauthenticated demo viewer. Synthetic modes do not authorize personalized discovery.\"}" + ",\"AuthorizationAction\":{\"enum\":[\"live.view\",\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"rec" + "ordings.export\",\"snapshot.create\",\"ptz.control\",\"evidence.protect\",\"recording.delete\",\"camera.co" + "nfigure\",\"fleet.execute_job\",\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin" + "\",\"lpr.read\",\"lpr.search\",\"lpr.export\",\"lpr.delete\",\"observations.annotate\"],\"type\":\"string\"},\"C" + "ameraCollection\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"i" + "nteger\"},\"description\":{\"type\":\"string\"},\"effective_count\":{\"minimum\":0,\"type\":\"integer\"},\"membe" + "r_count\":{\"minimum\":0,\"type\":\"integer\"},\"name\":{\"type\":\"string\"},\"owner_user_id\":{\"anyOf\":[{\"min" + "imum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"selector\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/F" + "leetSelector\"},{\"type\":\"null\"}]},\"selector_redacted\":{\"type\":\"boolean\"},\"shared\":{\"type\":\"boolea" + "n\"},\"type\":{\"enum\":[\"static\",\"smart\"]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"patt" + "ern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"str" + "ing\"}},\"required\":[\"uuid\",\"name\",\"description\",\"type\",\"shared\",\"owner_user_id\",\"member_count\",\"e" + "ffective_count\",\"created_at\",\"updated_at\",\"selector\",\"selector_redacted\"],\"type\":\"object\"},\"Came" + "raCollectionCreate\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"maxLength\":511,\"" + "type\":\"string\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/com" + "ponents/schemas/FleetSelector\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"t" + "ype\":\"string\"}},\"required\":[\"name\",\"type\"],\"type\":\"object\"},\"CameraCollectionMembersRequest\":{\"a" + "dditionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/C" + "ameraUuid\"},\"maxItems\":4096,\"type\":\"array\"}},\"required\":[\"camera_uuids\"],\"type\":\"object\"},\"Camer" + "aCollectionMembersResponse\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":" + "{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type" + "\":\"string\"},\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-f" + "A-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}" + "},\"required\":[\"collection_uuid\",\"camera_uuids\",\"count\"],\"type\":\"object\"},\"CameraCollectionPrevie" + "w\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-" + "9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"matched_count\":{\"m" + "inimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"camer" + "a_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" + "$\",\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"cam" + "era_uuid\",\"name\",\"location_path\"],\"type\":\"object\"},\"maxItems\":50,\"type\":\"array\"}},\"required\":[\"c" + "ollection_uuid\",\"matched_count\",\"sample\"],\"type\":\"object\"},\"CameraCollectionUpdate\":{\"additional" + "Properties\":false,\"properties\":{\"description\":{\"maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLeng" + "th\":127,\"minLength\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}," + "\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"type\":\"string\"}},\"required\":[],\"t" + "ype\":\"object\"},\"CameraCollectionsResponse\":{\"additionalProperties\":false,\"properties\":{\"collecti" + "ons\":{\"items\":{\"$ref\":\"#/components/schemas/CameraCollection\"},\"type\":\"array\"},\"count\":{\"minimum" + "\":0,\"type\":\"integer\"}},\"required\":[\"collections\",\"count\"],\"type\":\"object\"},\"CameraFavorite\":{\"ad" + "ditionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}" + ",\"created_at\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"created_at\"],\"type\":\"ob" + "ject\"},\"CameraFavoriteRemoval\":{\"additionalProperties\":false,\"properties\":{\"removed\":{\"type\":\"bo" + "olean\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"removed\"],\"type\":\"obje" + "ct\"},\"CameraFavoritesResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":" + "\"boolean\"},\"favorites\":{\"items\":{\"$ref\":\"#/components/schemas/CameraFavorite\"},\"type\":\"array\"}}," + "\"required\":[\"favorites\",\"can_modify\"],\"type\":\"object\"},\"CameraLocationAssignment\":{\"additionalPr" + "operties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"location" + "_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"re" + "quired\":[\"success\",\"camera_uuid\",\"location_uuid\"],\"type\":\"object\"},\"CameraLocationAssignmentRequ" + "est\":{\"additionalProperties\":false,\"properties\":{\"location_uuid\":{\"$ref\":\"#/components/schemas/C" + "ameraUuid\"}},\"required\":[\"location_uuid\"],\"type\":\"object\"},\"CameraTagAssignmentsRequest\":{\"addit" + "ionalProperties\":false,\"properties\":{\"tag_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/CameraUu" + "id\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"tag_uuids\"],\"type\":\"object\"},\"CameraTagAssignme" + "ntsResponse\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{" + "8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"min" + "imum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"array" + "\"}},\"required\":[\"camera_uuid\",\"tags\",\"count\"],\"type\":\"object\"},\"CameraUuid\":{\"pattern\":\"^[0-9a-f" + "A-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"ClientC" + "onfig\":{\"additionalProperties\":false,\"description\":\"Safe browser playback configuration only. De" + "liberately closed shape: no storage paths, integrations, private settings or credentials. go2rtc" + "_available may stay true while its lifecycle guard is busy.\",\"properties\":{\"api_key\":false,\"audi" + "o_disabled\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"default_playback_transport\":{" + "\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hls_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"str" + "ing\"},\"demo_mode\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":" + "{\"type\":\"number\"},\"go2rtc_available\":{\"type\":\"boolean\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go" + "2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"hls_disabled\":{\"t" + "ype\":\"boolean\"},\"mse_disabled\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"secret" + "\":false,\"thumbnails_per_recording\":{\"type\":\"number\"},\"token_hash\":false,\"totp_secret\":false,\"tot" + "p_token\":false,\"web_auth_enabled\":{\"type\":\"boolean\"},\"webrtc_connection_timeout_ms\":{\"type\":\"num" + "ber\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"number\"}},\"" + "required\":[\"go2rtc_enabled\",\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_disabled\",\"go2" + "rtc_available\",\"webrtc_disabled\",\"hls_disabled\",\"mse_disabled\",\"go2rtc_force_native_hls\",\"genera" + "te_thumbnails\",\"go2rtc_api_port\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\"" + ",\"thumbnails_per_recording\",\"go2rtc_base_path\",\"default_playback_transport\"],\"type\":\"object\"},\"C" + "redentialRevoked\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"bo" + "olean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"DaynightRequest\":{\"additionalProperties\":false" + ",\"anyOf\":[{\"properties\":{\"mode\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"mod" + "e\"]},{\"properties\":{\"daynight\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"dayn" + "ight\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required" + "\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"req" + "uired\":[\"ircut\"]}],\"properties\":{\"daynight\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"forc" + "e_persistence\":{\"type\":\"boolean\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}" + ",\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"mode\":{\"maxLength\":15,\"minLength\":1,\"ty" + "pe\":\"string\"}},\"required\":[],\"type\":\"object\"},\"DaynightResponse\":{\"additionalProperties\":false,\"" + "properties\":{\"ir_cut_filter\":{\"type\":\"string\"},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\",\"unknown\"]}," + "\"options_available\":{\"type\":\"boolean\"},\"source\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\":tru" + "e},\"supported_modes\":{\"items\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"type\":\"array\"},\"video_source_toke" + "n\":{\"type\":\"string\"}},\"required\":[\"success\",\"source\",\"video_source_token\",\"mode\",\"supported_mode" + "s\",\"options_available\"],\"type\":\"object\"},\"DaynightUpdated\":{\"additionalProperties\":false,\"proper" + "ties\":{\"ir_cut_filter\":{\"enum\":[\"On\",\"Off\",\"Auto\"]},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"sour" + "ce\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"source\",\"mode\",\"i" + "r_cut_filter\"],\"type\":\"object\"},\"DetectionZone\":{\"additionalProperties\":true,\"properties\":{\"colo" + "r\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\"" + ":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"min_conf" + "idence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polyg" + "on\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"num" + "ber\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxI" + "tems\":32,\"minItems\":3,\"type\":\"array\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"id\",\"name\",\"" + "enabled\",\"color\",\"polygon\",\"filter_classes\",\"min_confidence\",\"stream_name\"],\"type\":\"object\"},\"De" + "tectionZoneWrite\":{\"additionalProperties\":true,\"properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6" + "}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\":{\"maxLength\":255,\"type\":\"stri" + "ng\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\"" + ":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polygon\":{\"items\":{\"additionalPrope" + "rties\":true,\"properties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimu" + "m\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"" + "array\"}},\"required\":[\"id\",\"name\",\"polygon\"],\"type\":\"object\"},\"DeviceCommandResult\":{\"additionalP" + "roperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":" + "[\"success\",\"message\"],\"type\":\"object\"},\"DeviceEmptyRequest\":{\"additionalProperties\":false,\"prope" + "rties\":{},\"required\":[],\"type\":\"object\"},\"DeviceFloatRange\":{\"additionalProperties\":false,\"prope" + "rties\":{\"max\":{\"type\":\"number\"},\"min\":{\"type\":\"number\"}},\"required\":[\"min\",\"max\"],\"type\":\"object" + "\"},\"DeviceSuccess\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true}},\"requir" + "ed\":[\"success\"],\"type\":\"object\"},\"EventCameraScope\":{\"oneOf\":[{\"additionalProperties\":false,\"pro" + "perties\":{\"type\":{\"const\":\"all\",\"type\":\"string\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additio" + "nalProperties\":false,\"properties\":{\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"typ" + "e\":{\"const\":\"selector\",\"type\":\"string\"}},\"required\":[\"type\",\"selector\"],\"type\":\"object\"}]},\"Even" + "tCatalog\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"ev" + "ent_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"default_expiry_seconds\":{\"minim" + "um\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"dynamic_severity\":{\"type\":\"boolean\"},\"e" + "xpected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"family\":{\"type\":\"string\"},\"maxim" + "um_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"me" + "dia_policy\":{\"type\":\"string\"},\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"err" + "or\",\"critical\"],\"type\":\"string\"},\"sensitivity\":{\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"in" + "fo\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"st" + "orage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"" + "severity\",\"dynamic_severity\",\"minimum_severity\",\"maximum_severity\",\"subject_kind\",\"family\",\"desc" + "ription\",\"sensitivity\",\"media_policy\",\"default_expiry_seconds\"],\"type\":\"object\"},\"type\":\"array\"}" + ",\"schema_version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"schema_version\",\"count\",\"event_type" + "s\"],\"type\":\"object\"},\"EventDestination\":{\"additionalProperties\":false,\"properties\":{\"authenticat" + "ion\":{\"additionalProperties\":false,\"properties\":{\"password_configured\":{\"type\":\"boolean\"},\"usern" + "ame\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[\"username\",\"password_configured\"],\"type\":\"ob" + "ject\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLen" + "gth\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_second" + "s\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"int" + "eger\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511" + ",\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[" + "\"host\",\"port\",\"client_id\",\"topic_template\",\"status_topic_template\",\"keepalive_seconds\",\"qos\"],\"t" + "ype\":\"object\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"maxLength\":511,\"type\"" + ":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"ma" + "naged\":{\"const\":true,\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"r" + "evision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalProperties\"" + ":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"t" + "ype\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\"," + "\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[\"mode\",\"ca_file\",\"cert_file\",\"key_file\"],\"ty" + "pe\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"" + "},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"required\":[\"uuid\",\"key\",\"name\",\"description\",\"enabl" + "ed\",\"type\",\"broker\",\"authentication\",\"tls\",\"managed\",\"revision\",\"created_at\",\"updated_at\"],\"type" + "\":\"object\"},\"EventDestinationCreate\":{\"additionalProperties\":false,\"properties\":{\"authentication" + "\":{\"additionalProperties\":false,\"properties\":{\"password\":{\"maxLength\":255,\"type\":[\"string\",\"null" + "\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"broker\":{\"addi" + "tionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"" + "},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"mi" + "nimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum" + "\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"top" + "ic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"host\"],\"type\":\"object" + "\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLeng" + "th\":127,\"minLength\":1,\"type\":\"string\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_fil" + "e\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"" + "maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\"" + ":\"string\"}},\"required\":[],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[" + "\"name\",\"broker\"],\"type\":\"object\"},\"EventDestinationUpdate\":{\"additionalProperties\":false,\"proper" + "ties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password\":{\"maxLength\":255," + "\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"ob" + "ject\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLen" + "gth\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_second" + "s\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"int" + "eger\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511" + ",\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[" + "],\"type\":\"object\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"}," + "\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"m" + "inimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLen" + "gth\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":" + "511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}}" + ",\"required\":[],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[\"revision\"]" + ",\"type\":\"object\"},\"EventDestinations\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"maxi" + "mum\":64,\"minimum\":0,\"type\":\"integer\"},\"default_destination\":{\"additionalProperties\":false,\"prope" + "rties\":{\"configuration_source\":{\"const\":\"settings\",\"type\":\"string\"},\"key\":{\"const\":\"mqtt:default" + "\",\"type\":\"string\"},\"managed\":{\"const\":false,\"type\":\"boolean\"},\"name\":{\"type\":\"string\"}},\"require" + "d\":[\"key\",\"name\",\"configuration_source\",\"managed\"],\"type\":\"object\"},\"destinations\":{\"items\":{\"$r" + "ef\":\"#/components/schemas/EventDestination\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"default" + "_destination\",\"count\",\"destinations\"],\"type\":\"object\"},\"EventMutationAck\":{\"additionalProperties" + "\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"" + "object\"},\"EventPredicate\":{\"additionalProperties\":false,\"properties\":{\"detection\":{\"additionalPr" + "operties\":false,\"minProperties\":1,\"properties\":{\"labels_any\":{\"items\":{\"maxLength\":127,\"minLengt" + "h\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"min_confidence\":{\"maximum\":1,\"" + "minimum\":0,\"type\":\"number\"},\"zone_ids_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"strin" + "g\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"health\":{\"additi" + "onalProperties\":false,\"minProperties\":1,\"properties\":{\"condition_codes_any\":{\"items\":{\"maxLength" + "\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"severities_any\"" + ":{\"items\":{\"enum\":[\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"maxItems\":3,\"minItems\":1,\"typ" + "e\":\"array\"}},\"required\":[],\"type\":\"object\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[" + "\"version\"],\"type\":\"object\"},\"EventRoute\":{\"additionalProperties\":false,\"properties\":{\"camera_sco" + "pe\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"}" + ",\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"t" + "ype\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":" + "1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLen" + "gth\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate" + "\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/co" + "mponents/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cool" + "down_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":8640" + "0,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"in" + "teger\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"deb" + "ounce_seconds\",\"cooldown_seconds\",\"grouping_window_seconds\",\"max_events_per_minute\"],\"type\":\"obj" + "ect\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"req" + "uired\":[\"uuid\",\"name\",\"description\",\"enabled\",\"destination\",\"event_types\",\"camera_scope\",\"predic" + "ate\",\"schedule\",\"suppression\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"EventRoute" + "Create\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas" + "/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":1" + "27,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLengt" + "h\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":tr" + "ue},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/sch" + "emas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"a" + "dditionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\"" + ":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_s" + "econds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"" + "minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"name\",\"event_types\"]," + "\"type\":\"object\"},\"EventRoutePreview\":{\"additionalProperties\":false,\"properties\":{\"camera_sample\"" + ":{\"items\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"format\":\"uuid\",\"type\":\"str" + "ing\"},\"location_path\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"nam" + "e\",\"location_path\"],\"type\":\"object\"},\"maxItems\":20,\"type\":\"array\"},\"event_types\":{\"items\":{\"addi" + "tionalProperties\":false,\"properties\":{\"dynamic_severity\":{\"type\":\"boolean\"},\"expected_rate\":{\"en" + "um\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"maximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\"" + ",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"noti" + "ce\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"" + "warning\",\"error\",\"critical\"],\"type\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"storage\",\"system" + "\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"severity\",\"dyn" + "amic_severity\",\"minimum_severity\",\"maximum_severity\",\"subject_kind\"],\"type\":\"object\"},\"type\":\"ar" + "ray\"},\"matched_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_limit\":{\"const\":20,\"type\":\"i" + "nteger\"},\"would_publish\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"matched_camera_count\",\"s" + "ample_limit\",\"camera_sample\",\"event_types\",\"would_publish\"],\"type\":\"object\"},\"EventRoutePreviewR" + "equest\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas" + "/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":1" + "27,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLengt" + "h\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":tr" + "ue},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/sch" + "emas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"a" + "dditionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\"" + ":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_s" + "econds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"" + "minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"name\",\"event_types\"]," + "\"type\":\"object\"},\"EventRouteUpdate\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{" + "\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"}," + "\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"even" + "t_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"ty" + "pe\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicat" + "e\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"revision\":{\"maximum\":9007199254740991,\"minimu" + "m\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"" + "additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type" + "\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_" + "seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000," + "\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"revision\"],\"type\":\"o" + "bject\"},\"EventRoutes\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"maximum\":512,\"minimu" + "m\":0,\"type\":\"integer\"},\"routes\":{\"items\":{\"$ref\":\"#/components/schemas/EventRoute\"},\"maxItems\":5" + "12,\"type\":\"array\"}},\"required\":[\"routes\",\"count\"],\"type\":\"object\"},\"EventSchedule\":{\"additionalP" + "roperties\":false,\"properties\":{\"timezone\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"versio" + "n\":{\"const\":1,\"type\":\"integer\"},\"windows\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"" + "days\":{\"items\":{\"maximum\":6,\"minimum\":0,\"type\":\"integer\"},\"maxItems\":7,\"minItems\":1,\"type\":\"arra" + "y\",\"uniqueItems\":true},\"end\":{\"pattern\":\"^(\077:[01][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"},\"st" + "art\":{\"pattern\":\"^(\077:[01][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"}},\"required\":[\"days\",\"start\"" + ",\"end\"],\"type\":\"object\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"version\",\"timezone\",\"window" + "s\"],\"type\":\"object\"},\"EvidenceBookmark\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid" + "s\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]" + "{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"created_a" + "t\":{\"type\":\"integer\"},\"cursor_time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type" + "\":\"integer\"},\"filters\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkFilters\"},\"holds_recordings" + "\":{\"const\":false},\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[" + "0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"r" + "epresentative_result\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkResult\"},\"revision\":{\"minimu" + "m\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"min" + "Length\":1,\"type\":\"string\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0" + "-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"titl" + "e\",\"note\",\"start_time\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\",\"camera_uuids\",\"filters\",\"" + "representative_result\",\"uuid\",\"revision\",\"created_at\",\"updated_at\",\"holds_recordings\"],\"type\":\"o" + "bject\"},\"EvidenceBookmarkCreate\":{\"additionalProperties\":true,\"description\":\"Camera list, primar" + "y camera, region and saved result must agree. Cursor must lie inside the interval. The bookmark " + "is owned by the current user (also for scoped tokens), never grants access, and does not protect" + " media from retention.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-" + "fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItem" + "s\":1,\"type\":\"array\",\"uniqueItems\":true},\"cursor_time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":" + "{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkFilters\"}" + ",\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-" + "9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"representative_res" + "ult\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkResult\"},\"start_time\":{\"minimum\":1,\"type\":\"in" + "teger\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"title\",\"start_time" + "\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\",\"camera_uuids\",\"filters\"],\"type\":\"object\"},\"Evi" + "denceBookmarkDelete\":{\"additionalProperties\":true,\"properties\":{\"revision\":{\"minimum\":1,\"type\":\"" + "integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EvidenceBookmarkDeleted\":{\"additionalProper" + "ties\":false,\"properties\":{\"success\":{\"const\":true}},\"required\":[\"success\"],\"type\":\"object\"},\"Evi" + "denceBookmarkFilters\":{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"maxLength\":" + "127,\"type\":\"string\"},\"event_type\":{\"maxLength\":127,\"type\":\"string\"},\"label\":{\"maxLength\":127,\"ty" + "pe\":\"string\"},\"location\":{\"maxLength\":127,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimu" + "m\":0,\"type\":\"number\"},\"protection\":{\"maxLength\":127,\"type\":\"string\"},\"recording_tag\":{\"maxLength" + "\":127,\"type\":\"string\"},\"region\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"patt" + "ern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"str" + "ing\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"int" + "ersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maxi" + "mum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum" + "\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"cam" + "era_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"source\":{\"maxLength\":127,\"type\":\"string\"}," + "\"zone\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"EvidenceBookmarkResult" + "\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8" + "}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection_id\"" + ":{\"type\":[\"string\",\"number\"]},\"end_time\":{\"type\":[\"string\",\"number\"]},\"event_type\":{\"type\":[\"str" + "ing\",\"number\"]},\"label\":{\"type\":[\"string\",\"number\"]},\"recording_id\":{\"type\":[\"string\",\"number\"]}" + ",\"result_id\":{\"type\":[\"string\",\"number\"]},\"start_time\":{\"type\":[\"string\",\"number\"]}},\"required\":" + "[],\"type\":\"object\"},{\"type\":\"null\"}]},\"EvidenceBookmarkUpdate\":{\"additionalProperties\":true,\"pro" + "perties\":{\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"t" + "itle\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"title\",\"revision\"],\"type\":\"o" + "bject\"},\"EvidenceBookmarks\":{\"additionalProperties\":false,\"properties\":{\"bookmarks\":{\"items\":{\"$" + "ref\":\"#/components/schemas/EvidenceBookmark\"},\"maxItems\":256,\"type\":\"array\"},\"count\":{\"minimum\":" + "0,\"type\":\"integer\"}},\"required\":[\"bookmarks\",\"count\"],\"type\":\"object\"},\"EvidenceDetection\":{\"add" + "itionalProperties\":false,\"properties\":{\"confidence\":{\"type\":\"number\"},\"end_timestamp\":{\"type\":\"i" + "nteger\"},\"height\":{\"type\":\"number\"},\"label\":{\"type\":\"string\"},\"observation\":{\"$ref\":\"#/component" + "s/schemas/EvidenceDetectionObservation\"},\"timestamp\":{\"type\":\"integer\"},\"width\":{\"type\":\"number\"" + "},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"confidence\",\"x\",\"y\",\"width\",\"height\"" + ",\"label\",\"timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"EvidenceDetectionObservation\":{\"addition" + "alProperties\":false,\"description\":\"INT_01 P2 (L15). Present only when the detection links to an " + "observation of the same camera: observed in the same second \302\2611 with the same label (ASCII case-" + "insensitive), nearest second first. classification is the annotation to display \342\200\224 owner_confir" + "med[0] before automated[0] of GET /api/observations/{id} \342\200\224 or null; the detection's own label " + "and confidence are the detector's and never change.\",\"properties\":{\"classification\":{\"anyOf\":[{\"" + "$ref\":\"#/components/schemas/ObservationClassification\"},{\"type\":\"null\"}]},\"id\":{\"$ref\":\"#/compon" + "ents/schemas/ObservationUuid\"},\"owner_confirmed\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"owner_con" + "firmed\",\"classification\"],\"type\":\"object\"},\"EvidenceDetections\":{\"additionalProperties\":false,\"p" + "roperties\":{\"detections\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceDetection\"},\"type\":\"arra" + "y\"},\"stream_status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"timestamp\":{\"type\":\"string\"}}," + "\"required\":[\"detections\",\"timestamp\",\"stream_status\"],\"type\":\"object\"},\"EvidenceFileCheck\":{\"one" + "Of\":[{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":false}},\"required\":[\"exists\"]" + ",\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":true},\"mtime\":{\"" + "type\":\"integer\"},\"size\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"exists\",\"size\",\"mtime\"],\"ty" + "pe\":\"object\"}]},\"EvidenceFileDeleted\":{\"additionalProperties\":false,\"properties\":{\"existed\":{\"ty" + "pe\":\"boolean\"},\"status\":{\"enum\":[\"deleted\",\"deletion_pending\"],\"type\":\"string\"},\"success\":{\"cons" + "t\":true}},\"required\":[\"success\",\"existed\",\"status\"],\"type\":\"object\"},\"EvidenceLprDeleted\":{\"addi" + "tionalProperties\":false,\"properties\":{\"deleted\":{\"const\":true}},\"required\":[\"deleted\"],\"type\":\"o" + "bject\"},\"EvidenceLprExportRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pa" + "ttern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"s" + "tring\"},\"end_at\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":1000,\"minimum\":1,\"type\":\"inte" + "ger\"},\"match\":{\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":" + "{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"}," + "\"EvidenceLprRead\":{\"additionalProperties\":false,\"properties\":{\"attributes\":{\"additionalPropertie" + "s\":false,\"properties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"bot" + "tom\":{\"type\":\"number\"},\"left\":{\"type\":\"number\"},\"right\":{\"type\":\"number\"},\"top\":{\"type\":\"number\"" + "}},\"required\":[\"left\",\"top\",\"right\",\"bottom\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"correlation_id" + "\":{\"type\":[\"string\",\"null\"]},\"country\":{\"type\":[\"string\",\"null\"]},\"direction\":{\"type\":[\"string\"," + "\"null\"]},\"lane\":{\"type\":[\"string\",\"null\"]},\"object_id\":{\"type\":[\"string\",\"null\"]},\"plate_type\":{" + "\"type\":[\"string\",\"null\"]},\"region\":{\"type\":[\"string\",\"null\"]},\"vehicle_color\":{\"type\":[\"string\"," + "\"null\"]},\"vehicle_type\":{\"type\":[\"string\",\"null\"]},\"vendor_topic\":{\"type\":\"string\"}},\"required\":" + "[\"country\",\"region\",\"plate_type\",\"direction\",\"lane\",\"vehicle_type\",\"vehicle_color\",\"object_id\",\"" + "correlation_id\",\"vendor_topic\",\"bounding_box\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a" + "-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"confi" + "dence\":{\"anyOf\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"id\":{\"pattern\":\"^[" + "0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"o" + "bserved_at\":{\"minimum\":1,\"type\":\"integer\"},\"plate\":{\"type\":\"string\"},\"received_at\":{\"minimum\":1," + "\"type\":\"integer\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"sou" + "rce\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"type\":{\"const\":\"license_plate\"}},\"requir" + "ed\":[\"id\",\"type\",\"camera_uuid\",\"stream_name\",\"observed_at\",\"received_at\",\"source\",\"plate\",\"confi" + "dence\",\"recording_id\",\"attributes\"],\"type\":\"object\"},\"EvidenceLprReads\":{\"additionalProperties\":" + "false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"reads\":{\"items\":{\"$ref\":\"#/component" + "s/schemas/EvidenceLprRead\"},\"maxItems\":1000,\"type\":\"array\"}},\"required\":[\"reads\",\"count\"],\"type\"" + ":\"object\"},\"EvidenceLprSearchRequest\":{\"additionalProperties\":true,\"description\":\"Epoch millisec" + "onds, end at/after start within 366 days; match requires a canonicalizable plate (partial at lea" + "st 3 canonical characters). Search returns at most 100 rows.\",\"properties\":{\"camera_uuid\":{\"patt" + "ern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"str" + "ing\"},\"end_at\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer" + "\"},\"match\":{\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"m" + "inimum\":1,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"Ev" + "idenceModel\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"type\":\"string\"},\"id\":{\"" + "type\":\"string\"},\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"size\":{\"minimum\":0,\"type\":\"in" + "teger\"},\"supported\":{\"type\":\"boolean\"},\"type\":{\"type\":\"string\"},\"unsupported_reason\":{\"type\":\"st" + "ring\"}},\"required\":[\"id\",\"name\",\"path\",\"type\",\"supported\"],\"type\":\"object\"},\"EvidenceModels\":{\"a" + "dditionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"litert_availabl" + "e\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"models\":{\"items\":{\"$ref\":\"#/components/schema" + "s/EvidenceModel\"},\"type\":\"array\"},\"sod_available\":{\"type\":\"boolean\"}},\"required\":[\"models\",\"coun" + "t\",\"litert_available\",\"sod_available\"],\"type\":\"object\"},\"EvidencePreview\":{\"additionalProperties" + "\":false,\"properties\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"typ" + "e\":\"integer\"},\"permissions\":{\"additionalProperties\":false,\"properties\":{\"export\":{\"additionalPro" + "perties\":false,\"properties\":{\"all_allowed\":{\"type\":\"boolean\"},\"allowed_count\":{\"minimum\":0,\"type" + "\":\"integer\"},\"denied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_" + "count\",\"all_allowed\"],\"type\":\"object\"},\"protect\":{\"additionalProperties\":false,\"properties\":{\"ac" + "tionable_count\":{\"minimum\":0,\"type\":\"integer\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"d" + "enied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"actiona" + "ble_count\"],\"type\":\"object\"}},\"required\":[\"protect\",\"export\"],\"type\":\"object\"},\"protected_count\"" + ":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"recordings\":{\"" + "items\":{\"$ref\":\"#/components/schemas/EvidencePreviewRecording\"},\"maxItems\":200,\"type\":\"array\"},\"" + "start_time\":{\"minimum\":1,\"type\":\"integer\"},\"total_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"unprote" + "cted_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_count\",\"" + "recording_count\",\"total_bytes\",\"protected_count\",\"unprotected_count\",\"recordings\",\"permissions\"]" + ",\"type\":\"object\"},\"EvidencePreviewRecording\":{\"additionalProperties\":false,\"properties\":{\"camera" + "_name\":{\"type\":\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}" + "-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"can_export\":{\"type\":\"boolean\"},\"can_protect\"" + ":{\"type\":\"boolean\"},\"end_time\":{\"type\":\"integer\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"protected" + "\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}" + ",\"stream_name\":{\"type\":\"string\"}},\"required\":[\"id\",\"camera_uuid\",\"camera_name\",\"stream_name\",\"st" + "art_time\",\"end_time\",\"size_bytes\",\"protected\",\"can_protect\",\"can_export\"],\"type\":\"object\"},\"Evid" + "enceProtectedCount\":{\"additionalProperties\":false,\"properties\":{\"protected_count\":{\"minimum\":0,\"" + "type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"protected_count\"],\"type\":\"object\"" + "},\"EvidenceRangeRequest\":{\"additionalProperties\":true,\"description\":\"End must exceed start by no" + " more than 31 days. All explicit cameras must exist and permit recordings.replay; mixed unauthor" + "ized input is rejected.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a" + "-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minIte" + "ms\":1,\"type\":\"array\",\"uniqueItems\":true},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":" + "{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"obje" + "ct\"},\"EvidenceSearch\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"t" + "ype\":\"integer\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"complete\":{\"type\":\"boole" + "an\"},\"incomplete_reasons\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"requested_end\":{\"minimum\":" + "1,\"type\":\"integer\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"},\"searched_end\":{\"minimum\":1" + ",\"type\":\"integer\"},\"searched_start\":{\"minimum\":1,\"type\":\"integer\"},\"spatial_metadata\":{\"oneOf\":[" + "{\"additionalProperties\":false,\"properties\":{\"requested\":{\"const\":false}},\"required\":[\"requested\"" + "],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-" + "fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"comple" + "te\":{\"type\":\"boolean\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"st" + "ring\"},\"min_intersection\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"rectangle\":{\"additionalProp" + "erties\":false,\"properties\":{\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":" + "{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}" + ",\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\"],\"type\":\"ob" + "ject\"},\"requested\":{\"const\":true},\"rows_with_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"rows_without" + "_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"search_type\":{\"const\":\"metadata_search\"}},\"required\":[\"r" + "equested\",\"search_type\",\"camera_uuid\",\"match\",\"rows_with_boxes\",\"rows_without_boxes\",\"complete\"," + "\"rectangle\"],\"type\":\"object\"}]},\"unresolved_legacy_rows\":{\"minimum\":0,\"type\":\"integer\"}},\"requir" + "ed\":[\"requested_start\",\"requested_end\",\"searched_start\",\"searched_end\",\"unresolved_legacy_rows\"," + "\"spatial_metadata\",\"incomplete_reasons\",\"complete\"],\"type\":\"object\"},\"facets\":{\"additionalProper" + "ties\":false,\"properties\":{\"cameras\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\"" + ":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":" + "[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"capture_methods\":{\"items\":{\"additionalProper" + "ties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"valu" + "e\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"event_types" "\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"l" "abel\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"}" - ",\"type\":\"array\"},\"sources\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimu" - "m\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\"," - "\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"zones\":{\"items\":{\"additionalProperties\":false,\"proper" - "ties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"" - "}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"cameras\",\"labels\"" - ",\"zones\",\"sources\",\"event_types\",\"locations\",\"capture_methods\",\"recording_tags\",\"protection\"],\"t" - "ype\":\"object\"},\"histogram\":{\"additionalProperties\":false,\"properties\":{\"bucket_seconds\":{\"minimu" - "m\":0,\"type\":\"integer\"},\"buckets\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"" - "minimum\":0,\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"event_time\":{\"type\":\"integer\"},\"star" - "t_time\":{\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"event_time\",\"count\"],\"type\":\"ob" - "ject\"},\"type\":\"array\"}},\"required\":[\"bucket_seconds\",\"buckets\"],\"type\":\"object\"},\"page\":{\"additi" - "onalProperties\":false,\"properties\":{\"has_more\":{\"type\":\"boolean\"},\"limit\":{\"minimum\":1,\"type\":\"i" - "nteger\"},\"next_cursor\":{\"type\":[\"string\",\"null\"]},\"returned\":{\"minimum\":0,\"type\":\"integer\"},\"tot" - "al\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"limit\",\"returned\",\"total\",\"has_more\",\"next_curs" - "or\"],\"type\":\"object\"},\"results\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceSearchResult\"},\"t" - "ype\":\"array\"},\"selector_applied\":{\"type\":\"boolean\"},\"summary_complete\":{\"type\":\"boolean\"}},\"requ" - "ired\":[\"camera_count\",\"selector_applied\",\"summary_complete\",\"results\",\"page\",\"facets\",\"histogram" - "\",\"coverage\"],\"type\":\"object\"},\"EvidenceSearchRequest\":{\"oneOf\":[{\"additionalProperties\":true,\"p" - "roperties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0" - "-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqu" - "eItems\":true},\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"" - "type\":\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true" - ",\"properties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxIt" - "ems\":16,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"" - "maxItems\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"" - "maxItems\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"" - "},\"maxItems\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_" - "confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_" - "tags\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"so" - "urces\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"z" - "ones\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"r" - "equired\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimu" - "m\":1,\"type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"patte" - "rn\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"stri" - "ng\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"inte" - "rsects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maxim" - "um\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\"" - ":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"came" - "ra_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":false,\"start_time\":{\"minimum\":1,\"" - "type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\",\"end_time\",\"camera_" - "uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":false,\"cursor" - "\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"end_" - "time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true,\"properties\":{\"captu" - "re_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array" - "\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"" - "array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"" - "array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type" - "\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_confidence\":{\"maximum" - "\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_tags\":{\"items\":{\"maxL" - "ength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"sources\":{\"items\":{\"max" - "Length\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"zones\":{\"items\":{\"maxL" - "ength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[],\"type\":\"o" - "bject\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimum\":1,\"type\":\"integer\"" - "},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-" - "[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclu" - "siveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_inte" - "rsection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"" - "},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\"" - ":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"wid" - "th\",\"height\"],\"type\":\"object\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"start_t" - "ime\":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\"," - "\"end_time\",\"selector\"],\"type\":\"object\"}]},\"EvidenceSearchResult\":{\"additionalProperties\":false,\"" - "properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"location_name\":{\"type\":[\"stri" - "ng\",\"null\"]},\"location_path\":{\"type\":[\"string\",\"null\"]},\"location_uuid\":{\"type\":[\"string\",\"null\"" - "]},\"name\":{\"type\":\"string\"}},\"required\":[\"name\",\"location_uuid\",\"location_name\",\"location_path\"]" - ",\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" - "fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection\":{\"additionalProperties\":false,\"propertie" - "s\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"height\":{\"type\":\"numbe" - "r\"},\"normalized\":{\"const\":true},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"num" - "ber\"}},\"required\":[\"x\",\"y\",\"width\",\"height\",\"normalized\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"co" - "nfidence\":{\"type\":\"number\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"sourc" - "e\":{\"type\":\"string\"},\"track_id\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"zone" - "_uuid\":{\"type\":[\"string\",\"null\"]}},\"required\":[\"id\",\"label\",\"confidence\",\"source\",\"zone_uuid\",\"t" - "rack_id\",\"bounding_box\"],\"type\":\"object\"},\"end_time\":{\"type\":\"integer\"},\"event_type\":{\"enum\":[\"m" - "otion\",\"detection\"],\"type\":\"string\"},\"known_gap\":{\"type\":\"boolean\"},\"media_available\":{\"type\":\"b" - "oolean\"},\"recording\":{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"type\":[\"stri" - "ng\",\"null\"]},\"protected\":{\"anyOf\":[{\"type\":\"boolean\"},{\"type\":\"null\"}]}},\"required\":[\"capture_me" - "thod\",\"protected\"],\"type\":\"object\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"ty" - "pe\":\"null\"}]},\"result_id\":{\"type\":\"string\"},\"start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\"" - ":[{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"stri" - "ng\"}},\"required\":[\"status\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"" - "status\":{\"enum\":[\"unavailable\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"objec" - "t\"}]}},\"required\":[\"result_id\",\"event_type\",\"camera_uuid\",\"start_time\",\"end_time\",\"media_availab" - "le\",\"known_gap\",\"recording_id\",\"camera\",\"detection\",\"thumbnail\",\"recording\"],\"type\":\"object\"},\"E" - "videnceSegment\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"type\":\"" - "string\"},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"bo" - "olean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"protected\":{\"type\"" - ":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"requir" - "ed\":[\"id\",\"start_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_ava" - "ilable\",\"capture_method\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"coverage" - "_only\":{\"const\":true},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detection" - "\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"prote" - "cted\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"source_segment_count\":{\"mi" - "nimum\":1,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"id\",\"start_time\",\"end_t" - "ime\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_available\",\"source_segment_count" - "\",\"coverage_only\"],\"type\":\"object\"}]},\"EvidenceSegmentAt\":{\"additionalProperties\":false,\"propert" - "ies\":{\"segment\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/EvidenceSegment\"},{\"type\":\"null\"}]}},\"re" - "quired\":[\"segment\"],\"type\":\"object\"},\"EvidenceSegmentAtRequest\":{\"additionalProperties\":true,\"pr" - "operties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-" - "9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"timest" - "amp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"camera_uuids\"],\"type\":\"object\"},\"E" - "videnceSyncComplete\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"su" - "ccess\":{\"const\":true},\"updated\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"success\",\"message\"," - "\"updated\"],\"type\":\"object\"},\"EvidenceThumbnailRequest\":{\"additionalProperties\":true,\"properties\"" - ":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{" - "4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"end_time\":{\"min" - "imum\":1,\"type\":\"integer\"},\"sample_count\":{\"maximum\":12,\"minimum\":3,\"type\":\"integer\"},\"start_time" - "\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"ob" - "ject\"},\"EvidenceThumbnailSample\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"media_st" - "atus\":{\"const\":\"gap\"},\"thumbnail\":{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":" - "\"unavailable\"}},\"required\":[\"status\"],\"type\":\"object\"},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"" - "}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\"],\"type\":\"object\"},{\"additionalProperties\":" - "false,\"properties\":{\"media_status\":{\"const\":\"available\"},\"offset_ms\":{\"minimum\":0,\"type\":\"intege" - "r\"},\"recording_end_time\":{\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"recor" - "ding_start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\":[{\"additionalProperties\":false,\"propert" - "ies\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"string\"}},\"required\":[\"status\",\"url\"],\"type\"" - ":\"object\"},{\"additionalProperties\":false,\"properties\":{\"status\":{\"enum\":[\"unavailable\",\"disabled" - "\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"}]},\"timestamp\":{\"minimum\":1,\"type\":\"i" - "nteger\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\",\"recording_id\",\"recording_start_tim" - "e\",\"recording_end_time\",\"offset_ms\"],\"type\":\"object\"}]},\"EvidenceThumbnails\":{\"additionalPropert" - "ies\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pa" - "ttern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"s" - "tring\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"coverage\":" - "{\"additionalProperties\":false,\"properties\":{\"available_samples\":{\"minimum\":0,\"type\":\"integer\"},\"" - "gap_samples\":{\"minimum\":0,\"type\":\"integer\"},\"segments_truncated\":{\"const\":false}},\"required\":[\"s" - "egments_truncated\",\"available_samples\",\"gap_samples\"],\"type\":\"object\"},\"end_time\":{\"minimum\":1,\"" - "type\":\"integer\"},\"sample_count\":{\"minimum\":1,\"type\":\"integer\"},\"samples\":{\"items\":{\"$ref\":\"#/com" - "ponents/schemas/EvidenceThumbnailSample\"},\"maxItems\":12,\"type\":\"array\"},\"start_time\":{\"minimum\":" - "1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"sample_count\",\"camera\",\"samples\",\"cove" - "rage\"],\"type\":\"object\"},\"EvidenceTimeline\":{\"additionalProperties\":false,\"properties\":{\"camera_c" - "ount\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"max_active_decod" - "ers\":{\"const\":4},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"tracks\":{\"items\":{\"$ref\":\"#/compon" - "ents/schemas/EvidenceTimelineTrack\"},\"type\":\"array\"}},\"required\":[\"start_time\",\"end_time\",\"camer" - "a_count\",\"max_active_decoders\",\"tracks\"],\"type\":\"object\"},\"EvidenceTimelineTrack\":{\"additionalPr" - "operties\":false,\"properties\":{\"aggregated\":{\"type\":\"boolean\"},\"aggregation_bucket_seconds\":{\"min" - "imum\":1,\"type\":\"integer\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4" - "}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"coverage\":{\"additionalProperties\":false,\"pr" - "operties\":{\"identity_resolved\":{\"const\":true},\"requested_end\":{\"minimum\":1,\"type\":\"integer\"},\"re" - "quested_start\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"identity_resolved\",\"requested_start\"" - ",\"requested_end\"],\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"returned_interval_count\":{\"minimum\"" - ":0,\"type\":\"integer\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":" - "\"#/components/schemas/EvidenceSegment\"},\"type\":\"array\"},\"stream_name\":{\"type\":\"string\"},\"truncat" - "ed\":{\"const\":false}},\"required\":[\"camera_uuid\",\"name\",\"stream_name\",\"segment_count\",\"returned_in" - "terval_count\",\"aggregated\",\"aggregation_bucket_seconds\",\"truncated\",\"coverage\",\"segments\"],\"type" - "\":\"object\"},\"FleetCamera\":{\"additionalProperties\":false,\"properties\":{\"address\":{\"type\":\"string\"" - "},\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"patter" + ",\"type\":\"array\"},\"labels\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum" + "\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"" + "count\"],\"type\":\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"additionalProperties\":false,\"pro" + "perties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"stri" + "ng\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"protection\":{\"items\":{\"addi" + "tionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"st" + "ring\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}," + "\"recording_tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type" + "\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"" + "type\":\"object\"},\"type\":\"array\"},\"sources\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"" + "count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"requ" + "ired\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"zones\":{\"items\":{\"additionalProperties" + "\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{" + "\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"ca" + "meras\",\"labels\",\"zones\",\"sources\",\"event_types\",\"locations\",\"capture_methods\",\"recording_tags\",\"" + "protection\"],\"type\":\"object\"},\"histogram\":{\"additionalProperties\":false,\"properties\":{\"bucket_se" + "conds\":{\"minimum\":0,\"type\":\"integer\"},\"buckets\":{\"items\":{\"additionalProperties\":false,\"properti" + "es\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"event_time\":{\"type\":\"" + "integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"event_time\",\"cou" + "nt\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"bucket_seconds\",\"buckets\"],\"type\":\"object\"}," + "\"page\":{\"additionalProperties\":false,\"properties\":{\"has_more\":{\"type\":\"boolean\"},\"limit\":{\"minim" + "um\":1,\"type\":\"integer\"},\"next_cursor\":{\"type\":[\"string\",\"null\"]},\"returned\":{\"minimum\":0,\"type\":" + "\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"limit\",\"returned\",\"total\",\"has_m" + "ore\",\"next_cursor\"],\"type\":\"object\"},\"results\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceSe" + "archResult\"},\"type\":\"array\"},\"selector_applied\":{\"type\":\"boolean\"},\"summary_complete\":{\"type\":\"b" + "oolean\"}},\"required\":[\"camera_count\",\"selector_applied\",\"summary_complete\",\"results\",\"page\",\"fac" + "ets\",\"histogram\",\"coverage\"],\"type\":\"object\"},\"EvidenceSearchRequest\":{\"oneOf\":[{\"additionalProp" + "erties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0" + "-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\"" + ":\"array\",\"uniqueItems\":true},\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"typ" + "e\":\"string\"},{\"type\":\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalP" + "roperties\":true,\"properties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"" + "string\"},\"maxItems\":16,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"ty" + "pe\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"ty" + "pe\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1," + "\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":" + "\"number\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolea" + "n\"},\"recording_tags\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"typ" + "e\":\"array\"},\"sources\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"ty" + "pe\":\"array\"},\"zones\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"typ" + "e\":\"array\"}},\"required\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maxim" + "um\":500,\"minimum\":1,\"type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camer" + "a_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" + "$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":" + "[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMi" + "nimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"}" + ",\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"r" + "equired\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":false,\"start_time\"" + ":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\",\"end" + "_time\",\"camera_uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"camera_uuids" + "\":false,\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"type\":" + "\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true,\"prop" + "erties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":1" + "6,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxIte" + "ms\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxIte" + "ms\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"max" + "Items\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_confid" + "ence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_tags\":" + "{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"sources\"" + ":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"zones\":" + "{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"require" + "d\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimum\":1,\"" + "type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^" + "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"" + "height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects" + "\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1," + "\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"mi" + "nimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uui" + "d\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSele" + "ctor\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\"" + ":[\"start_time\",\"end_time\",\"selector\"],\"type\":\"object\"}]},\"EvidenceSearchResult\":{\"additionalProp" + "erties\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"location_name\"" + ":{\"type\":[\"string\",\"null\"]},\"location_path\":{\"type\":[\"string\",\"null\"]},\"location_uuid\":{\"type\":[" + "\"string\",\"null\"]},\"name\":{\"type\":\"string\"}},\"required\":[\"name\",\"location_uuid\",\"location_name\",\"" + "location_path\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-" + "fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection\":{\"additionalProperties\":f" + "alse,\"properties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"height\"" + ":{\"type\":\"number\"},\"normalized\":{\"const\":true},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"" + "y\":{\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\",\"normalized\"],\"type\":\"object\"},{\"type" + "\":\"null\"}]},\"confidence\":{\"type\":\"number\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"label\":{\"type\":\"" + "string\"},\"source\":{\"type\":\"string\"},\"track_id\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":" + "\"null\"}]},\"zone_uuid\":{\"type\":[\"string\",\"null\"]}},\"required\":[\"id\",\"label\",\"confidence\",\"source\"" + ",\"zone_uuid\",\"track_id\",\"bounding_box\"],\"type\":\"object\"},\"end_time\":{\"type\":\"integer\"},\"event_ty" + "pe\":{\"enum\":[\"motion\",\"detection\"],\"type\":\"string\"},\"known_gap\":{\"type\":\"boolean\"},\"media_availa" + "ble\":{\"type\":\"boolean\"},\"recording\":{\"additionalProperties\":false,\"properties\":{\"capture_method\"" + ":{\"type\":[\"string\",\"null\"]},\"protected\":{\"anyOf\":[{\"type\":\"boolean\"},{\"type\":\"null\"}]}},\"require" + "d\":[\"capture_method\",\"protected\"],\"type\":\"object\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":" + "\"integer\"},{\"type\":\"null\"}]},\"result_id\":{\"type\":\"string\"},\"start_time\":{\"type\":\"integer\"},\"thum" + "bnail\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url" + "\":{\"type\":\"string\"}},\"required\":[\"status\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false," + "\"properties\":{\"status\":{\"enum\":[\"unavailable\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"" + "],\"type\":\"object\"}]}},\"required\":[\"result_id\",\"event_type\",\"camera_uuid\",\"start_time\",\"end_time\"" + ",\"media_available\",\"known_gap\",\"recording_id\",\"camera\",\"detection\",\"thumbnail\",\"recording\"],\"typ" + "e\":\"object\"},\"EvidenceSegment\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"capture_me" + "thod\":{\"type\":\"string\"},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detecti" + "on\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"pro" + "tected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"int" + "eger\"}},\"required\":[\"id\",\"start_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protec" + "ted\",\"media_available\",\"capture_method\"],\"type\":\"object\"},{\"additionalProperties\":false,\"propert" + "ies\":{\"coverage_only\":{\"const\":true},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"}" + ",\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"cons" + "t\":true},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"source_segm" + "ent_count\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"id\",\"sta" + "rt_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_available\",\"sourc" + "e_segment_count\",\"coverage_only\"],\"type\":\"object\"}]},\"EvidenceSegmentAt\":{\"additionalProperties\"" + ":false,\"properties\":{\"segment\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/EvidenceSegment\"},{\"type\"" + ":\"null\"}]}},\"required\":[\"segment\"],\"type\":\"object\"},\"EvidenceSegmentAtRequest\":{\"additionalPrope" + "rties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-" + "9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"" + "array\"},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"camera_uuids\"],\"typ" + "e\":\"object\"},\"EvidenceSyncComplete\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type" + "\":\"string\"},\"success\":{\"const\":true},\"updated\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"succ" + "ess\",\"message\",\"updated\"],\"type\":\"object\"},\"EvidenceThumbnailRequest\":{\"additionalProperties\":tr" + "ue,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{" + "4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"" + "end_time\":{\"minimum\":1,\"type\":\"integer\"},\"sample_count\":{\"maximum\":12,\"minimum\":3,\"type\":\"intege" + "r\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uui" + "ds\"],\"type\":\"object\"},\"EvidenceThumbnailSample\":{\"oneOf\":[{\"additionalProperties\":false,\"propert" + "ies\":{\"media_status\":{\"const\":\"gap\"},\"thumbnail\":{\"additionalProperties\":false,\"properties\":{\"st" + "atus\":{\"const\":\"unavailable\"}},\"required\":[\"status\"],\"type\":\"object\"},\"timestamp\":{\"minimum\":1,\"" + "type\":\"integer\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\"],\"type\":\"object\"},{\"additio" + "nalProperties\":false,\"properties\":{\"media_status\":{\"const\":\"available\"},\"offset_ms\":{\"minimum\":0" + ",\"type\":\"integer\"},\"recording_end_time\":{\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"i" + "nteger\"},\"recording_start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\":[{\"additionalProperties\"" + ":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"string\"}},\"required\":[\"status" + "\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"status\":{\"enum\":[\"unavail" + "able\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"}]},\"timestamp\":{\"minim" + "um\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\",\"recording_id\",\"reco" + "rding_start_time\",\"recording_end_time\",\"offset_ms\"],\"type\":\"object\"}]},\"EvidenceThumbnails\":{\"ad" + "ditionalProperties\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"ca" + "mera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{" + "12}$\",\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"objec" + "t\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"available_samples\":{\"minimum\":0,\"typ" + "e\":\"integer\"},\"gap_samples\":{\"minimum\":0,\"type\":\"integer\"},\"segments_truncated\":{\"const\":false}}" + ",\"required\":[\"segments_truncated\",\"available_samples\",\"gap_samples\"],\"type\":\"object\"},\"end_time\"" + ":{\"minimum\":1,\"type\":\"integer\"},\"sample_count\":{\"minimum\":1,\"type\":\"integer\"},\"samples\":{\"items\"" + ":{\"$ref\":\"#/components/schemas/EvidenceThumbnailSample\"},\"maxItems\":12,\"type\":\"array\"},\"start_ti" + "me\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"sample_count\",\"camera\"," + "\"samples\",\"coverage\"],\"type\":\"object\"},\"EvidenceTimeline\":{\"additionalProperties\":false,\"propert" + "ies\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"m" + "ax_active_decoders\":{\"const\":4},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"tracks\":{\"items\":{\"" + "$ref\":\"#/components/schemas/EvidenceTimelineTrack\"},\"type\":\"array\"}},\"required\":[\"start_time\",\"e" + "nd_time\",\"camera_count\",\"max_active_decoders\",\"tracks\"],\"type\":\"object\"},\"EvidenceTimelineTrack\"" + ":{\"additionalProperties\":false,\"properties\":{\"aggregated\":{\"type\":\"boolean\"},\"aggregation_bucket" + "_seconds\":{\"minimum\":1,\"type\":\"integer\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4" + "}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"coverage\":{\"additionalProper" + "ties\":false,\"properties\":{\"identity_resolved\":{\"const\":true},\"requested_end\":{\"minimum\":1,\"type\"" + ":\"integer\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"identity_resolved\",\"r" + "equested_start\",\"requested_end\"],\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"returned_interval_co" + "unt\":{\"minimum\":0,\"type\":\"integer\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"" + "items\":{\"$ref\":\"#/components/schemas/EvidenceSegment\"},\"type\":\"array\"},\"stream_name\":{\"type\":\"st" + "ring\"},\"truncated\":{\"const\":false}},\"required\":[\"camera_uuid\",\"name\",\"stream_name\",\"segment_coun" + "t\",\"returned_interval_count\",\"aggregated\",\"aggregation_bucket_seconds\",\"truncated\",\"coverage\",\"s" + "egments\"],\"type\":\"object\"},\"FleetCamera\":{\"additionalProperties\":false,\"properties\":{\"address\":{" + "\"type\":\"string\"},\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera" + "_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + "\",\"type\":\"string\"},\"can_configure\":{\"type\":\"boolean\"},\"capabilities\":{\"items\":{\"enum\":[\"onvif\",\"" + "ptz\",\"backchannel\"]},\"type\":\"array\"},\"current_fps\":{\"type\":\"number\"},\"enabled\":{\"type\":\"boolean\"" + "},\"first_video_at\":{\"minimum\":0,\"type\":\"integer\"},\"health\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"d" + "own\",\"disabled\"]},\"health_changed_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_frame_ts\":{\"minimum\":" + "0,\"type\":\"integer\"},\"last_recording_at\":{\"minimum\":0,\"type\":\"integer\"},\"location\":{\"additionalPr" + "operties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"uuid\":{\"type\":\"" + "string\"}},\"required\":[\"uuid\",\"name\",\"path\"],\"type\":\"object\"},\"manufacturer\":{\"type\":\"string\"},\"m" + "atched_clauses\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"model\":{\"type\":\"string\"},\"name\":{\"ty" + "pe\":\"string\"},\"recording_active\":{\"type\":\"boolean\"},\"recording_mode\":{\"enum\":[\"off\",\"continuous\"" + ",\"detection\"]},\"tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"label\":{\"type\":\"stri" + "ng\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]" + "{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"label\"],\"type\":\"object\"},\"type\":\"array\"}},\"required" + "\":[\"camera_uuid\",\"name\",\"address\",\"enabled\",\"recording_mode\",\"health\",\"availability\",\"health_cha" + "nged_at\",\"last_frame_ts\",\"first_video_at\",\"last_recording_at\",\"current_fps\",\"recording_active\",\"" + "can_configure\",\"manufacturer\",\"model\",\"capabilities\",\"location\",\"tags\"],\"type\":\"object\"},\"FleetF" + "acetEntity\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"" + "label\":{\"type\":\"string\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" + "fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"count\"],\"type\":\"object\"},\"Fleet" + "Facets\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"items\":{\"additionalProperti" + "es\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"live\",\"offline\"" + ",\"never_connected\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"" + "enabled\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integ" + "er\"},\"value\":{\"type\":\"boolean\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"" + "health\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"intege" + "r\"},\"value\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]}},\"required\":[\"value\",\"count\"]" + ",\"type\":\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetE" + "ntity\"},\"type\":\"array\"},\"recording_mode\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"c" + "ount\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"off\",\"continuous\",\"detection\"]}},\"require" + "d\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"tags\":{\"items\":{\"$ref\":\"#/components/sche" + "mas/FleetFacetEntity\"},\"type\":\"array\"}},\"required\":[\"health\",\"enabled\",\"recording_mode\",\"availab" + "ility\",\"tags\",\"locations\"],\"type\":\"object\"},\"FleetLocation\":{\"additionalProperties\":false,\"prope" + "rties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"child_count\":{\"minimum\":0,\"type\":\"integer" + "\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_system\":{\"type\":\"boolean\"},\"metadata\":{\"addit" + "ionalProperties\":true,\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"patter" "n\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"strin" - "g\"},\"can_configure\":{\"type\":\"boolean\"},\"capabilities\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchann" - "el\"]},\"type\":\"array\"},\"current_fps\":{\"type\":\"number\"},\"enabled\":{\"type\":\"boolean\"},\"first_video_" - "at\":{\"minimum\":0,\"type\":\"integer\"},\"health\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"" - "]},\"health_changed_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_frame_ts\":{\"minimum\":0,\"type\":\"integ" - "er\"},\"last_recording_at\":{\"minimum\":0,\"type\":\"integer\"},\"location\":{\"additionalProperties\":false" - ",\"properties\":{\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string\"}},\"requ" - "ired\":[\"uuid\",\"name\",\"path\"],\"type\":\"object\"},\"manufacturer\":{\"type\":\"string\"},\"matched_clauses\"" - ":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"model\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"" - "recording_active\":{\"type\":\"boolean\"},\"recording_mode\":{\"enum\":[\"off\",\"continuous\",\"detection\"]}," - "\"tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"label\":{\"type\":\"string\"},\"uuid\":{\"p" + "g\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"integer\"},\"type\":{\"type\":\"string\"},\"updated_at\":{\"mi" + "nimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9" + "a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"parent_uuid\",\"name\",\"type\",\"s" + "ort_order\",\"is_system\",\"metadata\",\"child_count\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\"" + ":\"object\"},\"FleetLocationCreate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additi" + "onalProperties\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pa" + "rent_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{" + "\"type\":\"number\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\"],\"typ" + "e\":\"object\"},\"FleetLocationUpdate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"addi" + "tionalProperties\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"" + "parent_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\"" + ":{\"type\":\"number\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"" + "object\"},\"FleetLocationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\"" + ":0,\"type\":\"integer\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetLocation\"},\"type\":\"" + "array\"},\"unassigned_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{" + "4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"locations\",\"count\"],\"type\":\"object\"},\"FleetM" + "utationAck\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"" + "}},\"required\":[\"success\"],\"type\":\"object\"},\"FleetOperationalQueue\":{\"additionalProperties\":false" + ",\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"key\":{\"type\":\"string\"},\"oldest_unresolved" + "_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"remediation\":{\"type\":\"string\"}," + "\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"severity\":{\"type\":\"string\"}},\"required" + "\":[\"key\",\"severity\",\"count\",\"remediation\",\"oldest_unresolved_at\",\"selector\"],\"type\":\"object\"},\"F" + "leetPreviewRequest\":{\"additionalProperties\":false,\"description\":\"Read-only POST. Defaults: all s" + "elector, page 1, page_size 50, name/asc, facets true, explain false, availability all. Server en" + "forces UTF-8 byte length for search and bounded selector parsing; an invalid or stale selector n" + "ever grants visibility.\",\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_con" + "nected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-" + "9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-" + "9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"" + "boolean\"},\"facets\":{\"type\":\"boolean\"},\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"p" + "age_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"}" + ",\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid" + "\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]" + "}},\"required\":[],\"type\":\"object\"},\"FleetPreviewResponse\":{\"additionalProperties\":false,\"properti" + "es\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FleetCamera\"},\"maxItems\":50,\"type\":\"array\"" + "},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-" + "9a-fA-F]{12}$\",\"type\":\"string\"},\"facets\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"mi" + "nimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"c" + "onst\":true},\"queue_version\":{\"const\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOpe" + "rationalQueue\"},\"type\":\"array\"},\"selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera" + "_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"d" + "esc\"]},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"req" + "uired\":[\"selector_version\",\"queue_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"s" + "ort_by\",\"sort_order\",\"cameras\",\"queues\"],\"type\":\"object\"},\"FleetQueryRequest\":{\"additionalProper" + "ties\":false,\"description\":\"Read-only POST. Defaults: all selector, page 1, page_size 50, name/as" + "c, facets true, explain false, availability all. Server enforces UTF-8 byte length for search an" + "d bounded selector parsing; an invalid or stale selector never grants visibility.\",\"properties\":" + "{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"p" "attern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"" - "string\"}},\"required\":[\"uuid\",\"label\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"camera_uuid" - "\",\"name\",\"address\",\"enabled\",\"recording_mode\",\"health\",\"availability\",\"health_changed_at\",\"last_" - "frame_ts\",\"first_video_at\",\"last_recording_at\",\"current_fps\",\"recording_active\",\"can_configure\"," - "\"manufacturer\",\"model\",\"capabilities\",\"location\",\"tags\"],\"type\":\"object\"},\"FleetFacetEntity\":{\"a" - "dditionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":" - "\"string\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" - "fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"count\"],\"type\":\"object\"},\"FleetFacets\":{\"addit" - "ionalProperties\":false,\"properties\":{\"availability\":{\"items\":{\"additionalProperties\":false,\"prop" - "erties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"live\",\"offline\",\"never_connect" - "ed\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"enabled\":{\"item" - "s\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"" - "type\":\"boolean\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"health\":{\"items" - "\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"e" - "num\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object" - "\"},\"type\":\"array\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetEntity\"},\"type\":" - "\"array\"},\"recording_mode\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum" - "\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"off\",\"continuous\",\"detection\"]}},\"required\":[\"value\",\"co" - "unt\"],\"type\":\"object\"},\"type\":\"array\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetE" - "ntity\"},\"type\":\"array\"}},\"required\":[\"health\",\"enabled\",\"recording_mode\",\"availability\",\"tags\",\"" - "locations\"],\"type\":\"object\"},\"FleetLocation\":{\"additionalProperties\":false,\"properties\":{\"camera" - "_count\":{\"minimum\":0,\"type\":\"integer\"},\"child_count\":{\"minimum\":0,\"type\":\"integer\"},\"created_at\"" - ":{\"minimum\":0,\"type\":\"integer\"},\"is_system\":{\"type\":\"boolean\"},\"metadata\":{\"additionalProperties" - "\":true,\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F" + "string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]" + "{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"facets\":{\"type\":\"boolean\"}," + "\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200,\"minimum\":1,\"" + "type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/sch" + "emas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"reco" + "rding_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[],\"type\":\"object\"},\"Fl" + "eetQueryResponse\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/comp" + "onents/schemas/FleetCamera\"},\"maxItems\":200,\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a" + "-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"facet" + "s\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\"" + ":{\"maximum\":200,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"queue_version\":{\"con" + "st\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"},\"type\":\"array\"},\"" + "selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabl" + "ed\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":{\"minimum\":0,\"type" + "\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"selector_version\",\"queue" + "_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_order\",\"cameras\",\"q" + "ueues\"],\"type\":\"object\"},\"FleetRevisionRequest\":{\"additionalProperties\":false,\"properties\":{\"rev" + "ision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"FleetSavedView\"" + ":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F" "]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"nu" - "ll\"}]},\"sort_order\":{\"type\":\"integer\"},\"type\":{\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\"" - ":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9" - "a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"parent_uuid\",\"name\",\"type\",\"sort_order\",\"is_" - "system\",\"metadata\",\"child_count\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"Fle" - "etLocationCreate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additionalProperties\"" - ":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_uuid\":{\"an" - "yOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"number\"" - "},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\"],\"type\":\"object\"},\"F" - "leetLocationUpdate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additionalPropertie" - "s\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_uuid\":{\"" - "anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"numbe" - "r\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"Fleet" - "LocationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"inte" - "ger\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetLocation\"},\"type\":\"array\"},\"unassi" - "gned_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{" - "12}$\",\"type\":\"string\"}},\"required\":[\"locations\",\"count\"],\"type\":\"object\"},\"FleetMutationAck\":{\"a" - "dditionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[" - "\"success\"],\"type\":\"object\"},\"FleetOperationalQueue\":{\"additionalProperties\":false,\"properties\":{" - "\"count\":{\"minimum\":0,\"type\":\"integer\"},\"key\":{\"type\":\"string\"},\"oldest_unresolved_at\":{\"anyOf\":[" - "{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"remediation\":{\"type\":\"string\"},\"selector\":{\"$r" - "ef\":\"#/components/schemas/FleetSelector\"},\"severity\":{\"type\":\"string\"}},\"required\":[\"key\",\"sever" - "ity\",\"count\",\"remediation\",\"oldest_unresolved_at\",\"selector\"],\"type\":\"object\"},\"FleetPreviewRequ" - "est\":{\"additionalProperties\":false,\"description\":\"Read-only POST. Defaults: all selector, page 1" - ", page_size 50, name/asc, facets true, explain false, availability all. Server enforces UTF-8 by" - "te length for search and bounded selector parsing; an invalid or stale selector never grants vis" - "ibility.\",\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabl" - "ed\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-" - "9a-fA-F]{12}$\",\"type\":\"string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-" - "9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"face" - "ts\":{\"type\":\"boolean\"},\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"max" - "imum\":50,\"minimum\":1,\"type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$" - "ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"h" - "ealth\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[" - "],\"type\":\"object\"},\"FleetPreviewResponse\":{\"additionalProperties\":false,\"properties\":{\"cameras\":" - "{\"items\":{\"$ref\":\"#/components/schemas/FleetCamera\"},\"maxItems\":50,\"type\":\"array\"},\"collection_u" - "uid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\"," - "\"type\":\"string\"},\"facets\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\"" - ":\"integer\"},\"page_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"const\":true},\"qu" - "eue_version\":{\"const\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"}" - ",\"type\":\"array\"},\"selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"locatio" - "n\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":" - "{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"select" - "or_version\",\"queue_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_o" - "rder\",\"cameras\",\"queues\"],\"type\":\"object\"},\"FleetQueryRequest\":{\"additionalProperties\":false,\"de" - "scription\":\"Read-only POST. Defaults: all selector, page 1, page_size 50, name/asc, facets true," - " explain false, availability all. Server enforces UTF-8 byte length for search and bounded selec" - "tor parsing; an invalid or stale selector never grants visibility.\",\"properties\":{\"availability\"" - ":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a" - "-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"colle" - "ction_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]" - "{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"facets\":{\"type\":\"boolean\"},\"page\":{\"maximu" - "m\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200,\"minimum\":1,\"type\":\"integer\"" - "},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelec" - "tor\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"ad" - "dress\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[],\"type\":\"object\"},\"FleetQueryRespons" - "e\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/" - "FleetCamera\"},\"maxItems\":200,\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a" - "-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"facets\":{\"$ref\":\"#/c" - "omponents/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200" - ",\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"queue_version\":{\"const\":1},\"queues\"" - ":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"},\"type\":\"array\"},\"selector_versio" - "n\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_" - "mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"t" - "otal_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"selector_version\",\"queue_version\",\"prev" - "iew\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_order\",\"cameras\",\"queues\"],\"type\":" - "\"object\"},\"FleetRevisionRequest\":{\"additionalProperties\":false,\"properties\":{\"revision\":{\"minimu" - "m\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"FleetSavedView\":{\"additionalPr" - "operties\":false,\"properties\":{\"collection_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F" - "]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"columns" - "\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_sha" - "red\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"revision\":{\"minimum" - "\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetS" - "elector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\"" - ",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"u" - "uid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\"," - "\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"is_shared\",\"owned\",\"selector\",\"search\",\"collection_" - "uuid\",\"columns\",\"sort_by\",\"sort_order\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"F" - "leetSavedViewsResponse\":{\"additionalProperties\":false,\"properties\":{\"can_share\":{\"type\":\"boolean" - "\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"views\":{\"items\":{\"$ref\":\"#/components/schemas/FleetSa" - "vedView\"},\"maxItems\":256,\"type\":\"array\"}},\"required\":[\"views\",\"count\",\"can_share\"],\"type\":\"objec" - "t\"},\"FleetSelector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"FleetTag\":{\"additionalPropert" - "ies\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"color\":{\"type\":\"string\"}" - ",\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"label\":{\"type\":\"st" - "ring\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F" - "]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"label" - "\",\"color\",\"description\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetTagCrea" - "te\":{\"additionalProperties\":false,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\"" - ":\"string\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":" - "1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"},\"FleetTagMergeRequest\":{\"additionalPro" - "perties\":false,\"properties\":{\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required" - "\":[\"target_uuid\"],\"type\":\"object\"},\"FleetTagMergeResult\":{\"additionalProperties\":false,\"properti" - "es\":{\"source_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":true,\"type\":\"bo" - "olean\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"success\",\"source_" - "uuid\",\"target_uuid\"],\"type\":\"object\"},\"FleetTagUpdate\":{\"additionalProperties\":false,\"properties" - "\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\":\"string\"},\"description\":{\"maxLength\":511,\"ty" - "pe\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"obj" - "ect\"},\"FleetTagsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type" - "\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"array\"}},\"required" - "\":[\"tags\",\"count\"],\"type\":\"object\"},\"FleetViewCreate\":{\"additionalProperties\":false,\"properties\"" - ":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"is_s" - "hared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"search\":{\"type\":\"string\"},\"sel" - "ector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"lo" - "cation\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"r" - "equired\":[\"name\",\"selector\"],\"type\":\"object\"},\"FleetViewUpdate\":{\"additionalProperties\":false,\"p" - "roperties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"arr" - "ay\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"revision\":{\"minimum\"" - ":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSe" - "lector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\"," - "\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[\"name\",\"selector\",\"revision\"],\"typ" - "e\":\"object\"},\"FloorPlan\":{\"additionalProperties\":false,\"properties\":{\"background_mime\":{\"enum\":[" - "\"image/png\",\"image/jpeg\",null],\"type\":[\"string\",\"null\"]},\"cameras\":{\"items\":{\"$ref\":\"#/component" - "s/schemas/FloorPlanCamera\"},\"maxItems\":1000,\"type\":\"array\"},\"can_modify\":{\"type\":\"boolean\"},\"can" - "vas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"mini" - "mum\":400,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":" - "[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength" - "\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"t" - "ype\":\"null\"}]},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer" - "\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]},\"updated_at\"" - ":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"" - "name\",\"location_uuid\",\"parent_plan_uuid\",\"canvas_width\",\"canvas_height\",\"sketch\",\"cameras\",\"uuid" - "\",\"background_mime\",\"partially_authorized\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"" - "},\"FloorPlanCamera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/compone" - "nts/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotation\":{\"maximum\"" - ":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum" - "\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"rotation\",\"fov\"],\"type\":\"ob" - "ject\"},\"FloorPlanCameraRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref" - "\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotation" - "\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"" - "y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\"],\"type\":\"object\"},\"Floor" - "PlanCreate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components" - "/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canvas_height\":{\"maximum\":400" - "0,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"}" - ",\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{" - "\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/component" - "s/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorS" - "ketch\"},{\"type\":\"null\"}]}},\"required\":[\"name\"],\"type\":\"object\"},\"FloorPlanDeletion\":{\"additional" - "Properties\":false,\"properties\":{\"deleted\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"deleted\"" - "],\"type\":\"object\"},\"FloorPlanUpdate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"ite" - "ms\":{\"$ref\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canv" - "as_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minim" - "um\":400,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{" - "\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"any" - "Of\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"revision\":{\"minimum\":1,\"type\"" - ":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]}},\"r" - "equired\":[\"name\",\"cameras\",\"revision\"],\"type\":\"object\"},\"FloorPlansResponse\":{\"additionalPropert" - "ies\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"plans\":{\"items\":{\"$ref\":\"#/components/" - "schemas/FloorPlan\"},\"type\":\"array\"}},\"required\":[\"plans\",\"can_modify\"],\"type\":\"object\"},\"FloorSk" - "etch\":{\"additionalProperties\":false,\"properties\":{\"shapes\":{\"items\":{\"oneOf\":[{\"$ref\":\"#/compone" - "nts/schemas/FloorSketchRect\"},{\"$ref\":\"#/components/schemas/FloorSketchWall\"},{\"$ref\":\"#/compone" - "nts/schemas/FloorSketchArea\"},{\"$ref\":\"#/components/schemas/FloorSketchLabel\"}]},\"maxItems\":400," - "\"type\":\"array\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\",\"shapes\"],\"type\":\"" - "object\"},\"FloorSketchArea\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"min" - "Length\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"points\":{\"items\":{\"items\":f" - "alse,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximu" - "m\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"maxItems\":64,\"minItems\":3,\"type\":\"array\"},\"t" - "one\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"a" - "rea\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"points\"],\"type\":\"object\"},\"FloorSketchLabel\":{\"a" - "dditionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"si" - "ze\":{\"enum\":[\"sm\",\"md\",\"lg\"],\"type\":\"string\"},\"text\":{\"maxLength\":95,\"minLength\":1,\"type\":\"strin" - "g\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"con" - "st\":\"label\",\"type\":\"string\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"min" - "imum\":0,\"type\":\"number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"text\"],\"type\":\"object\"},\"FloorSketchR" - "ect\":{\"additionalProperties\":false,\"properties\":{\"filled\":{\"type\":\"boolean\"},\"h\":{\"maximum\":1,\"m" - "inimum\":0,\"type\":\"number\"},\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLeng" - "th\":95,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"s" - "tring\"},\"type\":{\"const\":\"rect\",\"type\":\"string\"},\"w\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"x" - "\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"requ" - "ired\":[\"id\",\"type\",\"x\",\"y\",\"w\",\"h\"],\"type\":\"object\"},\"FloorSketchWall\":{\"additionalProperties\":f" - "alse,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"" - "type\":\"string\"},\"points\":{\"items\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maxi" - "mum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"" - "maxItems\":64,\"minItems\":2,\"type\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"" - "violet\"],\"type\":\"string\"},\"type\":{\"const\":\"wall\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"poin" - "ts\"],\"type\":\"object\"},\"Go2rtcConfigurationInput\":{\"additionalProperties\":true,\"properties\":{\"ove" - "rride\":{\"maxLength\":65535,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"Go2rtcConfigurationV" - "alidation\":{\"additionalProperties\":true,\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"null\"},{\"additi" - "onalProperties\":true,\"properties\":{\"column\":{\"type\":\"number\"},\"line\":{\"type\":\"number\"},\"message\"" - ":{\"type\":\"string\"}},\"type\":\"object\"}]},\"libyaml_available\":{\"type\":\"boolean\"},\"skip_reason\":{\"ad" - "ditionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type" - "\":\"object\"},\"skipped\":{\"type\":\"boolean\"},\"valid\":{\"type\":\"boolean\"},\"warnings\":{\"items\":{\"type\":" - "\"string\"},\"type\":\"array\"}},\"required\":[\"valid\",\"libyaml_available\",\"skipped\",\"warnings\"],\"type\":" - "\"object\"},\"Go2rtcEffectiveConfiguration\":{\"additionalProperties\":true,\"properties\":{\"base\":{\"typ" - "e\":\"string\"},\"go2rtc_initialized\":{\"type\":\"boolean\"},\"managed_listener_policy\":{\"type\":\"string\"}" - ",\"merged_source_order\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"override\":{\"type\":\"string\"},\"" - "override_in_use\":{\"type\":\"boolean\"},\"redaction_available\":{\"type\":\"boolean\"},\"warnings\":{\"items\"" - ":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"go2rtc_initialized\",\"redaction_available\",\"base" - "\",\"override\",\"managed_listener_policy\",\"merged_source_order\",\"override_in_use\",\"warnings\"],\"type" - "\":\"object\"},\"Go2rtcOverrideDiagnostics\":{\"additionalProperties\":true,\"properties\":{\"content_matc" - "hes\":{\"type\":\"boolean\"},\"db_bytes\":{\"type\":\"number\"},\"expected_file_path\":{\"type\":\"string\"},\"fil" - "e_bytes\":{\"type\":\"number\"},\"file_present\":{\"type\":\"boolean\"},\"file_read_error\":{\"type\":\"string\"}" - ",\"process_pid\":{\"type\":\"number\"},\"process_running\":{\"type\":\"boolean\"},\"process_state_known\":{\"ty" - "pe\":\"boolean\"},\"quarantine_active\":{\"type\":\"boolean\"},\"quarantine_reason\":{\"type\":\"string\"},\"qua" - "rantined_copy_bytes\":{\"type\":\"integer\"},\"validated_version\":{\"type\":\"string\"}},\"required\":[\"expe" - "cted_file_path\",\"db_bytes\",\"file_present\",\"file_bytes\",\"content_matches\",\"quarantine_active\",\"pr" - "ocess_state_known\",\"process_running\",\"process_pid\",\"quarantined_copy_bytes\",\"validated_version\"]" - ",\"type\":\"object\"},\"HealthResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamHealth\"},{\"$ref" - "\":\"#/components/schemas/ProcessLiveness\"},{\"$ref\":\"#/components/schemas/StreamHealthHistory\"}]}," - "\"HlsHealth\":{\"additionalProperties\":true,\"properties\":{\"healthy\":{\"type\":\"boolean\"},\"status\":{\"t" - "ype\":\"string\"},\"timestamp\":{\"type\":\"string\"},\"watchdogRestartCount\":{\"type\":\"integer\"}},\"require" - "d\":[\"healthy\",\"status\",\"watchdogRestartCount\",\"timestamp\"],\"type\":\"object\"},\"HostHealth\":{\"addit" - "ionalProperties\":true,\"properties\":{\"active_incidents\":{\"items\":{\"additionalProperties\":true,\"pr" - "operties\":{\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"incident_id\":" - "{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalProperties\":" - "true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":{\"type\":\"string\"},\"metric\":{\"type\"" - ":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"type\":\"string\"},\"sampled_monotonic_ms" - "\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\"" - ":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"freshness\",\"" - "unit\",\"value\"],\"type\":\"object\"},\"persistence_pending\":{\"type\":\"boolean\"},\"scope\":{\"type\":\"string" - "\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"threshold" - "s\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"critical_for_second" - "s\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"string\"},\"enabl" - "ed\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"type\":\"number\"}," - "\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seconds\":{\"type\":\"n" - "umber\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\",\"overridden\",\"d" - "irection\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"warning_for_seco" - "nds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"}},\"required\":[\"incident_id\"," - "\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_observed_at_ms\",\"p" - "ersistence_pending\",\"observation\"],\"type\":\"object\"},\"type\":\"array\"},\"coverage\":{\"additionalPrope" - "rties\":true,\"properties\":{\"abandoned_helpers\":{\"type\":\"number\"},\"collection_errors\":{\"type\":\"num" - "ber\"},\"collection_timeouts\":{\"type\":\"number\"},\"complete\":{\"type\":\"boolean\"},\"coverage_overflows\"" - ":{\"type\":\"number\"},\"incomplete_count\":{\"type\":\"number\"},\"observations_dropped\":{\"type\":\"number\"}" - ",\"unavailable_observations\":{\"type\":\"number\"}},\"required\":[\"complete\",\"incomplete_count\",\"unavai" - "lable_observations\",\"observations_dropped\",\"collection_errors\",\"collection_timeouts\",\"abandoned_" - "helpers\",\"coverage_overflows\"],\"type\":\"object\"},\"evaluator\":{\"additionalProperties\":true,\"proper" - "ties\":{\"active_incidents\":{\"type\":\"integer\"},\"pending_persistence\":{\"type\":\"integer\"},\"tracked_c" - "onditions\":{\"type\":\"integer\"}},\"required\":[\"tracked_conditions\",\"active_incidents\",\"pending_pers" - "istence\"],\"type\":\"object\"},\"monitoring\":{\"additionalProperties\":true,\"properties\":{\"configured_e" - "nabled\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"initialized\":{\"type\":\"boolean\"},\"restar" - "t_required\":{\"type\":\"boolean\"},\"worker_threads\":{\"type\":\"number\"}},\"required\":[\"configured_enabl" - "ed\",\"initialized\",\"enabled\",\"worker_threads\",\"restart_required\"],\"type\":\"object\"},\"observations\"" - ":{\"items\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":" - "{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"typ" - "e\":\"string\"},\"sampled_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"" - "string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\"," - "\"scope\",\"capability\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"overall_state" - "\":{\"type\":\"string\"},\"recent_samples\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"comple" - "ted_at_ms\":{\"type\":\"number\"},\"observation_count\":{\"type\":\"number\"},\"observations_dropped\":{\"type" - "\":\"number\"},\"sequence\":{\"type\":\"number\"}},\"required\":[\"sequence\",\"completed_at_ms\",\"observation_" - "count\",\"observations_dropped\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":{\"type\":\"number" - "\"},\"self_observability\":{\"additionalProperties\":true,\"properties\":{\"collectors\":{\"items\":{\"addit" - "ionalProperties\":true,\"properties\":{\"attempts\":{\"type\":\"number\"},\"busy\":{\"type\":\"boolean\"},\"coll" - "ector\":{\"type\":\"string\"},\"completions\":{\"type\":\"number\"},\"failures\":{\"type\":\"number\"},\"last_dura" - "tion_ms\":{\"type\":\"number\"},\"maximum_duration_ms\":{\"type\":\"number\"},\"overlap_skips\":{\"type\":\"numb" - "er\"},\"scope\":{\"type\":\"string\"},\"stale\":{\"type\":\"boolean\"},\"tier\":{\"type\":\"number\"},\"timeouts\":{\"" - "type\":\"number\"}},\"required\":[\"collector\",\"scope\",\"tier\",\"attempts\",\"completions\",\"failures\",\"tim" - "eouts\",\"overlap_skips\",\"last_duration_ms\",\"maximum_duration_ms\",\"busy\",\"stale\"],\"type\":\"object\"}" - ",\"type\":\"array\"},\"coverage_overflows\":{\"type\":\"number\"},\"event_delivery\":{\"additionalProperties\"" - ":true,\"properties\":{\"all_destinations_unavailable\":{\"type\":\"boolean\"},\"circular_report_path\":{\"t" - "ype\":\"boolean\"},\"degraded\":{\"type\":\"boolean\"},\"outbox\":{\"additionalProperties\":true,\"properties\"" - ":{\"available\":{\"type\":\"boolean\"}},\"type\":\"object\"},\"outbox_full\":{\"type\":\"boolean\"}},\"type\":\"obj" - "ect\"},\"incident_persistence\":{\"additionalProperties\":true,\"properties\":{\"failures\":{\"type\":\"numb" - "er\"},\"pending\":{\"type\":\"number\"},\"retries\":{\"type\":\"number\"}},\"type\":\"object\"}},\"required\":[\"col" - "lectors\",\"coverage_overflows\",\"incident_persistence\",\"event_delivery\"],\"type\":\"object\"},\"snapsho" - "t\":{\"additionalProperties\":true,\"properties\":{\"age_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number" - "\"}]},\"available\":{\"type\":\"boolean\"},\"completed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"" - "}]},\"freshness\":{\"type\":\"string\"},\"sequence\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"req" - "uired\":[\"available\",\"sequence\",\"completed_at_ms\",\"age_ms\",\"freshness\"],\"type\":\"object\"},\"thresho" - "lds\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"critical" - "_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"stri" - "ng\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"type\"" - ":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seconds\"" - ":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\",\"ove" - "rridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"warni" - "ng_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"},\"type\":\"array\"}," - "\"visibility\":{\"additionalProperties\":true,\"properties\":{\"coverage_boundary\":{\"type\":\"string\"},\"e" - "ffective_scope\":{\"type\":\"string\"},\"host_hardware_visible\":{\"type\":\"boolean\"}},\"required\":[\"effec" - "tive_scope\",\"host_hardware_visible\",\"coverage_boundary\"],\"type\":\"object\"}},\"required\":[\"schema_v" - "ersion\",\"monitoring\",\"overall_state\",\"visibility\",\"snapshot\",\"coverage\",\"observations\",\"threshol" - "ds\",\"active_incidents\",\"recent_samples\",\"self_observability\"],\"type\":\"object\"},\"HostHealthIncide" - "nts\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"incidents\":{\"items\":{" - "\"additionalProperties\":true,\"properties\":{\"closed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"numb" - "er\"}]},\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"incident_id\":{\"ty" - "pe\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalProperties\":true" - ",\"properties\":{\"capability\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"resource\":{\"type\":\"str" - "ing\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"typ" - "e\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"unit\",\"value\"],\"type\":\"obj" - "ect\"},\"reconciliation\":{\"type\":\"string\"},\"revision\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"}," - "\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"}},\"required\":[" - "\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_obse" - "rved_at_ms\",\"closed_at_ms\",\"reconciliation\",\"revision\",\"observation\"],\"type\":\"object\"},\"type\":\"a" - "rray\"},\"next_cursor\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"schema_version\":{\"type\":\"num" - "ber\"}},\"required\":[\"schema_version\",\"count\",\"incidents\",\"next_cursor\"],\"type\":\"object\"},\"IceServ" - "ersResponse\":{\"additionalProperties\":true,\"properties\":{\"ice_servers\":{\"items\":{\"additionalPrope" - "rties\":true,\"properties\":{\"credential\":{\"type\":\"string\"},\"urls\":{\"items\":{\"type\":\"string\"},\"minI" - "tems\":1,\"type\":\"array\"},\"username\":{\"type\":\"string\"}},\"required\":[\"urls\"],\"type\":\"object\"},\"type" - "\":\"array\"}},\"required\":[\"ice_servers\"],\"type\":\"object\"},\"ImagingModeLevel\":{\"additionalPropertie" - "s\":false,\"properties\":{\"level\":{\"type\":\"number\"},\"mode\":{\"type\":\"string\"}},\"required\":[],\"type\":" - "\"object\"},\"ImagingModeLevelUpdate\":{\"oneOf\":[{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028" - "234663852886e+38,\"type\":\"number\"},{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{" - "\"level\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"mo" - "de\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"}]},\"ImagingMod" - "eOptions\":{\"additionalProperties\":false,\"properties\":{\"level\":{\"$ref\":\"#/components/schemas/Devi" - "ceFloatRange\"},\"modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"modes\"],\"type\":\"" - "object\"},\"ImagingOptions\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":{" - "\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"brightness\":{\"$ref\":\"#/components/schemas/Dev" - "iceFloatRange\"},\"color_saturation\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"contrast\":{" - "\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"defogging\":{\"$ref\":\"#/components/schemas/Imagin" - "gModeOptions\"},\"ir_cut_filter_modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"noise_reduction" - "\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"sharpness\":{\"$ref\":\"#/components/schemas/Dev" - "iceFloatRange\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"video_so" - "urce_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeOpti" - "ons\"}},\"required\":[\"video_source_token\",\"ir_cut_filter_modes\"],\"type\":\"object\"},\"ImagingOptionsR" - "esponse\":{\"additionalProperties\":false,\"properties\":{\"options\":{\"$ref\":\"#/components/schemas/Ima" - "gingOptions\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"options\"],\"type\":\"object\"},\"Imagi" - "ngSettings\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/comp" - "onents/schemas/ImagingModeLevel\"},\"brightness\":{\"type\":\"number\"},\"color_saturation\":{\"type\":\"num" - "ber\"},\"contrast\":{\"type\":\"number\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"}," - "\"ir_cut_filter\":{\"type\":\"string\"},\"noise_reduction\":{\"type\":\"number\"},\"sharpness\":{\"type\":\"numbe" - "r\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"video_source_token\":{\"" - "type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"}},\"required" - "\":[\"video_source_token\"],\"type\":\"object\"},\"ImagingSettingsRequest\":{\"additionalProperties\":false" - ",\"anyOf\":[{\"properties\":{\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.40282346638" - "52886e+38,\"type\":\"number\"}},\"required\":[\"brightness\"]},{\"properties\":{\"color_saturation\":{\"maxim" - "um\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"colo" - "r_saturation\"]},{\"properties\":{\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234" - "663852886e+38,\"type\":\"number\"}},\"required\":[\"contrast\"]},{\"properties\":{\"sharpness\":{\"maximum\":3" - ".4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"sharpness" - "\"]},{\"properties\":{\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.402823466385" - "2886e+38,\"type\":\"number\"}},\"required\":[\"noise_reduction\"]},{\"properties\":{\"saturation\":{\"maximum" - "\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"satura" - "tion\"]},{\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUp" - "date\"}},\"required\":[\"backlight_compensation\"]},{\"properties\":{\"wide_dynamic_range\":{\"$ref\":\"#/co" - "mponents/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"wide_dynamic_range\"]},{\"properties\":{\"to" - "ne_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"tone_compe" - "nsation\"]},{\"properties\":{\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"r" - "equired\":[\"defogging\"]},{\"properties\":{\"backlight\":{\"$ref\":\"#/components/schemas/ImagingModeLeve" - "lUpdate\"}},\"required\":[\"backlight\"]},{\"properties\":{\"wdr\":{\"$ref\":\"#/components/schemas/ImagingM" - "odeLevelUpdate\"}},\"required\":[\"wdr\"]},{\"properties\":{\"tone\":{\"$ref\":\"#/components/schemas/Imagin" - "gModeLevelUpdate\"}},\"required\":[\"tone\"]},{\"properties\":{\"defog\":{\"$ref\":\"#/components/schemas/Im" - "agingModeLevelUpdate\"}},\"required\":[\"defog\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLength\":15,\"mi" - "nLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"maxLength\":1" - "5,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"description\":\"Known aliases remain acc" - "epted; force_persistence alone is rejected. Numeric limits come from the device options rather t" - "han an invented global range.\",\"minProperties\":1,\"properties\":{\"backlight\":{\"$ref\":\"#/components" - "/schemas/ImagingModeLevelUpdate\"},\"backlight_compensation\":{\"$ref\":\"#/components/schemas/Imaging" - "ModeLevelUpdate\"},\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+" - "38,\"type\":\"number\"},\"color_saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663" - "852886e+38,\"type\":\"number\"},\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663" - "852886e+38,\"type\":\"number\"},\"defog\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"defo" - "gging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"force_persistence\":{\"type\":\"boole" - "an\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"maxLength\":15,\"min" - "Length\":1,\"type\":\"string\"},\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028" - "234663852886e+38,\"type\":\"number\"},\"saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.40" - "28234663852886e+38,\"type\":\"number\"},\"sharpness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4" - "028234663852886e+38,\"type\":\"number\"},\"tone\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate" - "\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wdr\":{\"$ref\":\"#/c" - "omponents/schemas/ImagingModeLevelUpdate\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/Im" - "agingModeLevelUpdate\"}},\"required\":[],\"type\":\"object\"},\"ImagingSettingsResponse\":{\"additionalPro" - "perties\":false,\"properties\":{\"settings\":{\"$ref\":\"#/components/schemas/ImagingSettings\"},\"success" - "\":{\"const\":true}},\"required\":[\"success\",\"settings\"],\"type\":\"object\"},\"LegacyApiKeyCreated\":{\"add" - "itionalProperties\":false,\"properties\":{\"api_key\":{\"minLength\":1,\"type\":\"string\"},\"success\":{\"con" - "st\":true}},\"required\":[\"success\",\"api_key\"],\"type\":\"object\"},\"LiveCameraSlot\":{\"additionalProper" - "ties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[" - "\"camera_uuid\"],\"type\":\"object\"},\"LiveLayout\":{\"additionalProperties\":false,\"properties\":{\"availa" - "bility\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_s" - "lots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"col" - "umns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is" - "_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"}" - ",{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"owned\":{\"type\":\"boole" - "an\"},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"rows\":" - "{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"" - "$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"name\",\"is_shared\",\"location_uuid\",\"availa" - "bility\",\"columns\",\"rows\",\"camera_slots\",\"uuid\",\"owned\",\"partially_authorized\",\"revision\",\"create" - "d_at\",\"updated_at\"],\"type\":\"object\"},\"LiveLayoutCreate\":{\"additionalProperties\":false,\"propertie" - "s\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"" - "},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"a" - "rray\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"loca" - "tion_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLe" - "ngth\":127,\"minLength\":1,\"type\":\"string\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"req" - "uired\":[\"name\",\"availability\",\"columns\",\"rows\",\"camera_slots\"],\"type\":\"object\"},\"LiveLayoutUpdat" - "e\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"n" - "ever_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schem" - "as/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"int" - "eger\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Ca" - "meraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{" - "\"minimum\":1,\"type\":\"integer\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"na" - "me\",\"availability\",\"columns\",\"rows\",\"camera_slots\",\"revision\"],\"type\":\"object\"},\"LiveLayoutsResp" - "onse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"can_share\":{\"" - "type\":\"boolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"layouts\":{\"items\":{\"$ref\":\"#/components" - "/schemas/LiveLayout\"},\"type\":\"array\"}},\"required\":[\"layouts\",\"count\",\"can_share\",\"can_modify\"],\"" - "type\":\"object\"},\"LiveStreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true" - ", surface=live. Uses the same filtered pagination envelope, with playback-specific row fields.\"," - "\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100" - ",\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/LiveSt" - "reamSummaryRedacted\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPrivileged\",\"x-lightnvr-vis" - "ibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"ca" - "mera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":" - "\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"p" - "age_size\",\"total_pages\"],\"type\":\"object\"},\"LiveStreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/com" - "ponents/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enab" - "led\":{\"type\":\"boolean\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"},\"can_control_privacy\":{\"" - "const\":true,\"type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"" - "},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transp" - "ort\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_en" - "abled\":{\"type\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled" - "\",\"privacy_mode\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"pl" - "ayback_transport\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":" - "\"object\"}],\"description\":\"surface=live includes playback and capability hints; admin_url is neve" - "r emitted by this surface.\"},\"LiveStreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas" - "/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"type\":\"b" - "oolean\"},\"can_configure\":{\"const\":false,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":false,\"" - "type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rtc_hls" - "_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$ref\":" - "\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{\"type" - "\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"privacy_mo" - "de\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_transp" - "ort\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"}],\"d" - "escription\":\"surface=live includes playback and capability hints; admin_url is never emitted by " - "this surface.\"},\"LoginConfig\":{\"additionalProperties\":false,\"properties\":{\"api_key\":false,\"auth_" - "absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"force_mfa_on_l" - "ogin\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"remember_device_enabled\":{\"type" - "\":\"boolean\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_d" - "evice_days\":{\"type\":\"number\"}},\"required\":[\"force_mfa_on_login\",\"remember_device_enabled\",\"auth_" - "timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"LoginLocko" - "utCleared\":{\"additionalProperties\":false,\"properties\":{\"cleared\":{\"type\":\"boolean\"},\"message\":{\"" - "type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"cleared\",\"message\"],\"type\":\"obj" - "ect\"},\"LoginMfaChallenge\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_chang" - "e_password\":false,\"password\":false,\"password_hash\":false,\"redirect\":false,\"secret\":false,\"succes" - "s\":false,\"token_hash\":false,\"totp_required\":{\"const\":true},\"totp_secret\":false,\"totp_token\":{\"de" - "scription\":\"Ephemeral MFA challenge credential. Returned only by login; never an ordinary identi" - "ty/token-list field or documentation example.\",\"minLength\":1,\"readOnly\":true,\"type\":\"string\"}},\"" - "required\":[\"totp_required\",\"totp_token\"],\"type\":\"object\"},\"LoginRequest\":{\"additionalProperties\"" - ":true,\"description\":\"JSON credentials. The C handler copies username/password into 64-byte buffe" - "rs (63-byte payload); it does not reject oversized strings before truncating. Invalid credential" - "s return401. Optional fields with wrong types are ignored by current C; this schema describes ty" - "ped client input.\",\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"remember_device\"" - ":{\"type\":\"boolean\"},\"totp_code\":{\"description\":\"Optional inline MFA code; enrollment and challen" - "ge completion endpoints remain outside this slice.\",\"type\":\"string\",\"writeOnly\":true},\"username\"" - ":{\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"LoginResponse\":{\"anyOf\"" - ":[{\"$ref\":\"#/components/schemas/LoginSuccess\"},{\"$ref\":\"#/components/schemas/LoginMfaChallenge\"}" - "]},\"LoginSuccess\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_passwo" - "rd\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"redirect\":{\"const\":\"/index.html\"}" - ",\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_required\":false,\"totp_secret\":" - "false,\"totp_token\":false},\"required\":[\"success\",\"redirect\",\"must_change_password\"],\"type\":\"objec" - "t\"},\"LogoutResponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false" - ",\"password_hash\":false,\"redirect\":{\"const\":\"/login.html\077logout=true\"},\"secret\":false,\"success\":{" - "\"const\":true},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\",\"" - "redirect\"],\"type\":\"object\"},\"LprStreamSummary\":{\"additionalProperties\":true,\"description\":\"surfa" - "ce=lpr exposes only camera identity and requires both lpr.search and lpr.read for each returned " - "camera. It does not imply live.view or configuration access.\",\"properties\":{\"camera_uuid\":{\"type" - "\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"LprStr" - "eamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=lpr. Empty res" - "ults remain a valid response even with no LPR grants.\",\"properties\":{\"page\":{\"maximum\":1000000,\"" - "minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":" - "{\"items\":{\"$ref\":\"#/components/schemas/LprStreamSummary\"},\"type\":\"array\"},\"total\":{\"minimum\":0,\"" - "type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"pa" - "ge\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"MotionTrigger\":{\"additionalProperties\":false,\"p" - "roperties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"confidence\":{\"maximum\":1" - ",\"minimum\":0,\"type\":\"number\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"},\"lab" - "el\":{\"minLength\":1,\"type\":\"string\"},\"objects\":{\"items\":{\"oneOf\":[{\"minLength\":1,\"type\":\"string\"}" - ",{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"numbe" - "r\"},\"label\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"}]},\"type\":\"arr" - "ay\"},\"stream\":{\"minLength\":1,\"type\":\"string\"},\"tags\":{\"items\":{\"minLength\":1,\"type\":\"string\"},\"t" - "ype\":\"array\"}},\"required\":[\"stream\",\"action\"],\"type\":\"object\"},\"MotionTriggered\":{\"additionalPro" - "perties\":false,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"detecti" - "ons_stored\":{\"minimum\":0,\"type\":\"integer\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"i" - "nteger\"},\"recording_triggered\":{\"type\":\"boolean\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":" - "true,\"type\":\"boolean\"},\"tags_applied\":{\"minimum\":0,\"type\":\"integer\"},\"warning\":{\"type\":\"string\"}" - "},\"required\":[\"success\",\"stream\",\"action\",\"recording_triggered\",\"detections_stored\",\"tags_applie" - "d\"],\"type\":\"object\"},\"OnvifAddRequest\":{\"additionalProperties\":false,\"properties\":{\"device_url\":" - "{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"password\":{\"maxLength\":63,\"type\":\"string\"},\"pro" - "file_token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLe" - "ngth\":1,\"type\":\"string\"},\"username\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[\"device_url\",\"" - "profile_token\",\"stream_name\"],\"type\":\"object\"},\"OnvifAddResult\":{\"additionalProperties\":false,\"p" - "roperties\":{\"message\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"success\":{\"const\":true}" - "},\"required\":[\"success\",\"message\",\"stream_name\"],\"type\":\"object\"},\"OnvifClaimRequest\":{\"addition" - "alProperties\":false,\"properties\":{\"inventory_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"stream_nam" - "e\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"inventory_uuid\",\"stream_name\"]," - "\"type\":\"object\"},\"OnvifDevice\":{\"additionalProperties\":false,\"properties\":{\"addresses\":{\"items\":" - "{\"type\":\"string\"},\"type\":\"array\"},\"claim_state\":{\"type\":\"string\"},\"claimed_camera_uuid\":{\"type\":" - "\"string\"},\"device_service\":{\"type\":\"string\"},\"discovery_time\":{\"type\":\"integer\"},\"duplicate_susp" - "ected\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"firmware_version\":{\"type\":\"string\"},\"fir" - "st_seen_at\":{\"type\":\"integer\"},\"hardware_id\":{\"type\":\"string\"},\"imaging_service\":{\"type\":\"string" - "\"},\"inventory_uuid\":{\"type\":\"string\"},\"ip_address\":{\"type\":\"string\"},\"last_scan_network\":{\"type\"" - ":\"string\"},\"last_seen_at\":{\"type\":\"integer\"},\"mac_address\":{\"type\":\"string\"},\"manufacturer\":{\"ty" - "pe\":\"string\"},\"media_service\":{\"type\":\"string\"},\"model\":{\"type\":\"string\"},\"online\":{\"type\":\"bool" - "ean\"},\"ptz_service\":{\"type\":\"string\"},\"serial_number\":{\"type\":\"string\"}},\"required\":[\"endpoint\"," - "\"device_service\",\"media_service\",\"ptz_service\",\"imaging_service\",\"manufacturer\",\"model\",\"firmwar" - "e_version\",\"serial_number\",\"hardware_id\",\"ip_address\",\"mac_address\",\"inventory_uuid\",\"last_scan_" - "network\",\"claim_state\",\"claimed_camera_uuid\",\"discovery_time\",\"first_seen_at\",\"last_seen_at\",\"on" - "line\",\"duplicate_suspected\",\"addresses\"],\"type\":\"object\"},\"OnvifDevicesResponse\":{\"additionalPro" - "perties\":false,\"properties\":{\"devices\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifDevice\"},\"typ" - "e\":\"array\"}},\"required\":[\"devices\"],\"type\":\"object\"},\"OnvifDiscoveryRequest\":{\"additionalPropert" - "ies\":false,\"properties\":{\"network\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[],\"type\":\"obje" - "ct\"},\"OnvifDiscoveryStatus\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boole" - "an\"},\"interval\":{\"type\":\"integer\"},\"network\":{\"type\":\"string\"}},\"required\":[\"enabled\",\"network\"," - "\"interval\"],\"type\":\"object\"},\"OnvifProfile\":{\"additionalProperties\":false,\"properties\":{\"bitrate" - "\":{\"type\":\"integer\"},\"encoding\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"int" - "eger\"},\"name\":{\"type\":\"string\"},\"ptz_configuration_token\":{\"type\":\"string\"},\"ptz_supported\":{\"ty" - "pe\":\"boolean\"},\"snapshot_uri\":{\"type\":\"string\"},\"stream_uri\":{\"type\":\"string\"},\"token\":{\"type\":\"" - "string\"},\"video_source_token\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"token\"," - "\"name\",\"video_source_token\",\"ptz_configuration_token\",\"snapshot_uri\",\"stream_uri\",\"encoding\",\"wi" - "dth\",\"height\",\"fps\",\"bitrate\",\"ptz_supported\"],\"type\":\"object\"},\"OnvifProfilesResponse\":{\"additi" - "onalProperties\":false,\"properties\":{\"profiles\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifProfi" - "le\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"profiles\"],\"type\":\"object\"},\"OnvifTestRequest\":" - "{\"additionalProperties\":false,\"properties\":{\"insecure\":{\"type\":\"boolean\"},\"password\":{\"maxLength" - "\":63,\"type\":\"string\"},\"url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"username\":{\"maxLeng" - "th\":63,\"type\":\"string\"}},\"required\":[\"url\"],\"type\":\"object\"},\"OpenApiCoverage\":{\"additionalPrope" - "rties\":true,\"description\":\"Describes the canonical pilot coverage, not user or camera identities" - ". Omission of an unspecified API is not a denial of access.\",\"properties\":{\"canonical_revision\":" - "{\"type\":\"string\"},\"covered_operations\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"description\":" - "{\"type\":\"string\"},\"partial\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"partial\",\"canonical_re" - "vision\",\"covered_operations\"],\"type\":\"object\"},\"OpenApiDocument\":{\"additionalProperties\":true,\"d" - "escription\":\"A valid OpenAPI 3.1.1 document projected for the actual authenticated principal. Th" - "is response schema checks its envelope; independent OpenAPI lint checks document validity. It ca" - "n contain no camera-specific eligible operations while retaining authenticated discovery/list op" - "erations.\",\"properties\":{\"components\":{\"additionalProperties\":true,\"type\":\"object\"},\"info\":{\"add" - "itionalProperties\":true,\"properties\":{\"title\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"re" - "quired\":[\"title\",\"version\"],\"type\":\"object\"},\"openapi\":{\"const\":\"3.1.1\",\"type\":\"string\"},\"paths\"" - ":{\"additionalProperties\":true,\"type\":\"object\"},\"x-lightnvr-coverage\":{\"$ref\":\"#/components/schem" - "as/OpenApiCoverage\"}},\"required\":[\"openapi\",\"info\",\"paths\",\"x-lightnvr-coverage\"],\"type\":\"object" - "\"},\"PasswordAdminReset\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"stri" - "ng\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"new_passwo" - "rd\"],\"type\":\"object\"},\"PasswordChangeRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/PasswordSe" - "lfChange\"},{\"$ref\":\"#/components/schemas/PasswordAdminReset\",\"x-lightnvr-visibility\":{\"kind\":\"gl" - "obal\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}],\"description\":\"" - "New password must contain at least eight UTF-8 bytes. Self-service requires the current password" - "; effective users.manage can reset passwords except its own forced first-login change. Locked/de" - "fault-password rules are enforced by the server.\"},\"PasswordChangedResponse\":{\"additionalPropert" - "ies\":false,\"properties\":{\"must_change_password\":{\"const\":false},\"success\":{\"const\":true}},\"requi" - "red\":[\"success\",\"must_change_password\"],\"type\":\"object\"},\"PasswordLockRequest\":{\"additionalPrope" - "rties\":true,\"properties\":{\"locked\":{\"type\":\"boolean\"}},\"required\":[\"locked\"],\"type\":\"object\"},\"P" - "asswordSelfChange\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"string\",\"" - "writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"old_password\",\"" - "new_password\"],\"type\":\"object\"},\"PlaybackTransport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hl" - "s_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"PlayerTelemetry\":{\"additionalPropert" - "ies\":true,\"properties\":{\"rebuffer_count\":{\"type\":\"integer\"},\"rebuffer_duration_ms\":{\"type\":\"numb" - "er\"},\"resolution_switches\":{\"type\":\"integer\"},\"session_id\":{\"type\":\"string\"},\"stream_name\":{\"typ" - "e\":\"string\"},\"transport\":{\"type\":\"string\"},\"ttff_ms\":{\"type\":\"number\"},\"webrtc_rtt_ms\":{\"type\":\"" - "number\"}},\"required\":[\"stream_name\"],\"type\":\"object\"},\"ProcessLiveness\":{\"additionalProperties\":" - "true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"liveness\":{" - "\"const\":true,\"type\":\"boolean\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"integer\"}," - "\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"tim" - "estamp\",\"liveness\"],\"type\":\"object\"},\"PtzAxesRequest\":{\"additionalProperties\":false,\"description" - "\":\"Finite float-representable coordinates in the camera default space. Camera-advertised ranges " - "vary; the server does not assume normalized position units. Omitted axes retain existing native " - "zero/axis-presence semantics.\",\"minProperties\":1,\"properties\":{\"pan\":{\"maximum\":3.40282346638528" - "86e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tilt\":{\"maximum\":3.4028234663852886e+" - "38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"zoom\":{\"maximum\":3.4028234663852886e+38,\"" - "minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[],\"type\":\"object\"},\"PtzCapabiliti" - "es\":{\"additionalProperties\":false,\"properties\":{\"has_absolute_move\":{\"type\":\"boolean\"},\"has_cont" - "inuous_move\":{\"type\":\"boolean\"},\"has_home\":{\"type\":\"boolean\"},\"has_home_position\":{\"type\":\"boole" - "an\"},\"has_pan_tilt\":{\"type\":\"boolean\"},\"has_presets\":{\"type\":\"boolean\"},\"has_relative_move\":{\"ty" - "pe\":\"boolean\"},\"has_zoom\":{\"type\":\"boolean\"},\"max_presets\":{\"type\":\"integer\"},\"max_x\":{\"type\":\"n" - "umber\"},\"max_y\":{\"type\":\"number\"},\"max_z\":{\"type\":\"number\"},\"onvif_queried\":{\"type\":\"boolean\"},\"" - "pan_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"pt" - "z_service_url\":{\"type\":\"string\"},\"tilt_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"" - "zoom_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"}},\"required\":[\"ptz_enabled\",\"onvif_q" - "ueried\",\"has_pan_tilt\",\"has_zoom\",\"has_continuous_move\",\"has_absolute_move\",\"has_relative_move\"," - "\"has_home_position\",\"has_presets\",\"has_home\",\"pan_range\",\"tilt_range\",\"zoom_range\",\"max_x\",\"max_" - "y\",\"max_z\",\"max_presets\",\"ptz_service_url\"],\"type\":\"object\"},\"PtzGotoPresetRequest\":{\"additional" - "Properties\":false,\"properties\":{\"token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"require" - "d\":[\"token\"],\"type\":\"object\"},\"PtzPresetRequest\":{\"additionalProperties\":false,\"properties\":{\"na" - "me\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"PtzPresetResult\":{\"additio" - "nalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true},\"token\":" - "{\"type\":\"string\"}},\"required\":[\"success\",\"message\",\"token\"],\"type\":\"object\"},\"PtzPresets\":{\"addi" - "tionalProperties\":false,\"properties\":{\"count\":{\"type\":\"integer\"},\"presets\":{\"items\":{\"additional" - "Properties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"}},\"required\":[" - "\"token\",\"name\"],\"type\":\"object\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"presets\",\"count\"],\"" - "type\":\"object\"},\"RecordingBatchProtectionRequest\":{\"additionalProperties\":true,\"description\":\"Em" - "pty arrays are accepted. Invalid or missing IDs count as failures; an existing forbidden member " - "rejects the entire request before writes.\",\"properties\":{\"ids\":{\"items\":{},\"type\":\"array\"},\"prot" - "ected\":{\"type\":\"boolean\"}},\"required\":[\"ids\",\"protected\"],\"type\":\"object\"},\"RecordingBatchProtec" - "tionResult\":{\"additionalProperties\":true,\"properties\":{\"fail_count\":{\"minimum\":0,\"type\":\"integer" - "\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"success_count\":{\"minimum\":0,\"type" - "\":\"integer\"}},\"required\":[\"success_count\",\"fail_count\",\"protected\",\"message\"],\"type\":\"object\"},\"" - "RecordingBatchTagsRequest\":{\"additionalProperties\":true,\"description\":\"Add/remove are optional; " - "wrong-typed arrays and non-string or empty elements are ignored by the handler. All IDs are auth" - "orized before writes. Database failures may follow earlier successful tag writes.\",\"properties\":" - "{\"add\":{\"items\":{},\"type\":\"array\"},\"ids\":{\"items\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"maxIte" - "ms\":10000,\"minItems\":1,\"type\":\"array\"},\"remove\":{\"items\":{},\"type\":\"array\"}},\"required\":[\"ids\"]," - "\"type\":\"object\"},\"RecordingBatchTagsResult\":{\"additionalProperties\":true,\"properties\":{\"recordin" - "gs_count\":{\"description\":\"Number of authorized input IDs processed, including duplicate IDs.\",\"m" - "inimum\":0,\"type\":\"integer\"},\"tags_added\":{\"description\":\"Successful per-recording add statement " - "executions, including INSERT OR IGNORE no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"}," - "\"tags_removed\":{\"description\":\"Successful per-recording remove statement executions, including a" - "bsent-tag no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"recordings_coun" - "t\",\"tags_added\",\"tags_removed\"],\"type\":\"object\"},\"RecordingDeletionFilter\":{\"additionalPropertie" - "s\":false,\"description\":\"stream_name (or stream fallback) selects comma-separated current camera " - "names. Without a nonempty stream filter, all-fleet recording.delete authority is required. Autho" - "rized camera UUIDs and collection membership are captured before dispatch; caller-supplied camer" - "a UUID fields do not grant scope. Supplied start/end must be valid UTC YYYY-MM-DDTHH:mm:ss strin" - "gs, optionally followed by fractional seconds and/or Z; fractions are truncated to whole seconds" - ". Bounds must be after the Unix epoch and start must not exceed end. Invalid types, calendar dat" - "es, suffixes, or ranges return400 before job creation. Omitted bounds are unbounded. detection/p" - "rotected must be -1, 0, or 1. Unknown keys, duplicate keys (including date-case aliases), simult" - "aneous stream and stream_name, invalid types, and empty selectors return400. Date key spelling i" - "s case-insensitive; other keys are case-sensitive. Caller-supplied camera/stream UUID selectors " - "are unsupported and rejected. In-progress rows are excluded.\",\"patternProperties\":{\"^(\077!start$|e" - "nd$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"}},\"prope" - "rties\":{\"capture_method\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"collection_uuid\":{\"max" - "Length\":36,\"minLength\":1,\"type\":\"string\"},\"detection\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"detect" - "ion_label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"end\":{\"maxLength\":64,\"minLength\":1,\"" - "type\":\"string\"},\"protected\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"start\":{\"maxLength\":64,\"minLengt" - "h\":1,\"type\":\"string\"},\"stream\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"m" - "axLength\":255,\"minLength\":1,\"type\":\"string\"},\"tag\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string" - "\"}},\"required\":[],\"type\":\"object\"},\"RecordingDeletionJobAccepted\":{\"additionalProperties\":true,\"" - "properties\":{\"job_id\":{\"type\":\"string\"},\"status\":{\"const\":\"started\"}},\"required\":[\"job_id\",\"stat" - "us\"],\"type\":\"object\"},\"RecordingDeletionProgress\":{\"additionalProperties\":true,\"description\":\"co" - "mplete is true for both complete and error terminal states. A complete job can have failed membe" - "rs or queued pending deletions; it does not imply every file is already removed. SQL failure is " - "error; an empty successful filter completes with zero counts. No cancellation endpoint/state exi" - "sts.\",\"properties\":{\"complete\":{\"type\":\"boolean\"},\"current\":{\"minimum\":0,\"type\":\"integer\"},\"erro" - "r_message\":{\"type\":\"string\"},\"failed\":{\"minimum\":0,\"type\":\"integer\"},\"job_id\":{\"type\":\"string\"}," - "\"pending_deletions\":{\"minimum\":0,\"type\":\"integer\"},\"status\":{\"enum\":[\"pending\",\"running\",\"comple" - "te\",\"error\",\"unknown\"],\"type\":\"string\"},\"status_message\":{\"type\":\"string\"},\"succeeded\":{\"minimum" - "\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"job_id\",\"status\",\"tot" - "al\",\"current\",\"succeeded\",\"pending_deletions\",\"failed\",\"status_message\",\"complete\"],\"type\":\"obje" - "ct\"},\"RecordingDeletionRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"description\":\"IDs take p" - "recedence over filter. IDs accept safe positive integer JSON numbers or exact positive decimal u" - "int64 strings. Invalid, unsafe numeric, or missing members become failed counts; a forbidden exi" - "sting member rejects the whole request before job creation.\",\"properties\":{\"ids\":{\"items\":{},\"mi" - "nItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"pro" - "perties\":{\"filter\":{\"$ref\":\"#/components/schemas/RecordingDeletionFilter\"},\"ids\":{\"anyOf\":[{\"ite" - "ms\":{},\"minItems\":1,\"type\":\"array\"},{\"type\":[\"null\",\"boolean\",\"object\",\"number\",\"string\"]}],\"des" - "cription\":\"Array-valued ids take precedence over filter and must be nonempty. Other ids values f" - "all through to the filter handler.\"}},\"required\":[\"filter\"],\"type\":\"object\"}]},\"RecordingDeletio" - "nResult\":{\"additionalProperties\":true,\"description\":\"Immediate200 has success/message. Queued202" - " also carries status deletion_pending; neither outcome promises deletion of unrelated recordings" - ".\",\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"deletion_pending\"},\"success\":{\"c" - "onst\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"RecordingDetail\":{\"additionalPro" - "perties\":true,\"description\":\"Authorized recording metadata. id is durable database identity, not" - " list position. Times are UTC display strings with separate Unix seconds. In-progress rows have " - "null end_time/end_time_unix and changing effective duration. playback_issue is empty for no save" - "d issue; invalid_media/no_video/decode_error cause playback422. Other diagnostic values do not c" - "onclusively prohibit playback. Optional storage fields are omitted if their lookup fails. file_p" - "ath is existing authorized metadata, not a client-selected playback path. A finalized row or tim" - "eline presence does not guarantee playable media.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9" - "a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pending\":{" - "\"type\":\"boolean\"},\"detection_labels\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\"" - ":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"" - "object\"},\"type\":\"array\"},\"durable_copy_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"e" - "nd_time\":{\"type\":[\"string\",\"null\"]},\"end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source" - "\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"min" - "imum\":1,\"type\":\"integer\"},\"in_progress\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"p" - "rotected\":{\"type\":\"boolean\"},\"retention_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedu" - "le_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string" - "\"},\"start_time_unix\":{\"type\":\"integer\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_" - "version\":{\"type\":\"integer\"},\"storage_state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable" - "\",\"archiving\",\"archived\",\"hot_and_archive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stre" - "am\":{\"type\":\"string\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"end_tim" - "e\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"capture_m" - "ethod\",\"schedule_restricted\",\"has_detection\",\"protected\"],\"type\":\"object\"},\"RecordingDetectionLa" - "bels\":{\"additionalProperties\":true,\"properties\":{\"labels\":{\"items\":{\"type\":\"string\"},\"type\":\"arr" - "ay\"}},\"required\":[\"labels\"],\"type\":\"object\"},\"RecordingExportAccepted\":{\"additionalProperties\":t" - "rue,\"properties\":{\"token\":{\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[" - "\"token\",\"total\"],\"type\":\"object\"},\"RecordingExportRequest\":{\"additionalProperties\":true,\"descrip" - "tion\":\"Positive numeric IDs are converted to uint64. Filename is sanitized for attachment delive" - "ry. Every member is authorized before a job is created; existing operational missing/incomplete " - "errors can abort earlier in input order.\",\"properties\":{\"filename\":{\"type\":\"string\"},\"ids\":{\"ite" - "ms\":{\"description\":\"Recording ID: a number through 9007199254740991 (JavaScript maximum safe int" - "eger), otherwise an exact decimal string. Treat IDs as opaque values; do not convert strings to " - "Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0" - "-9]{0,19}$\",\"type\":\"string\"}]},\"maxItems\":200,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"" - "type\":\"object\"},\"RecordingExportStatus\":{\"additionalProperties\":true,\"description\":\"error is alw" - "ays present, usually empty until failure. Detected unavailable sources or ZIP32 member/size-limi" - "t failures put the job in error and remove its temporary result. The current writer does not con" - "sistently check filesystem read/write/close errors: complete is not a guarantee against undetect" - "ed I/O failure or a valid intact archive. No cancellation endpoint/state exists.\",\"properties\":{" - "\"current\":{\"minimum\":0,\"type\":\"integer\"},\"error\":{\"type\":\"string\"},\"status\":{\"enum\":[\"pending\",\"" - "running\",\"complete\",\"error\"],\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\"" - ":[\"status\",\"current\",\"total\",\"error\"],\"type\":\"object\"},\"RecordingListItem\":{\"additionalPropertie" - "s\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"c" - "apture_method\":{\"type\":\"string\"},\"deletion_pending\":{\"type\":\"boolean\"},\"detection_labels\":{\"item" - "s\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"t" - "ype\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"durable_copy_coun" - "t\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"end_ti" - "me_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"s" - "tring\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"in_progress\":{\"t" - "ype\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"retention_mod" - "e\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]}," - "\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_time_unix\":{\"type\":\"integer\"},\"st" - "orage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_version\":{\"type\":\"integer\"},\"storage_state\"" - ":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable\",\"archiving\",\"archived\",\"hot_and_archive\"," - "\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"tags\":{\"items\":{\"typ" - "e\":\"string\"},\"type\":\"array\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"" - "end_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"ca" - "pture_method\",\"schedule_restricted\",\"has_detection\",\"protected\",\"tags\"],\"type\":\"object\"},\"Record" - "ingPagination\":{\"additionalProperties\":true,\"description\":\"Counts use the same authorized predic" - "ate as rows. Ordinary totals may use a ten-second cache invalidated by supported database mutati" - "ons; limit=all recounts and resets page1. No top-level total is emitted. When replay scope is em" - "pty, the early empty envelope retains the normalized requested page and ordinary limit (all is t" - "reated as20).\",\"properties\":{\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"page\":{\"minimum\":1,\"type\":\"" - "integer\"},\"pages\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"requir" - "ed\":[\"page\",\"pages\",\"total\",\"limit\"],\"type\":\"object\"},\"RecordingPlaybackPreparing\":{\"additionalP" - "roperties\":true,\"properties\":{\"error\":{\"type\":\"string\"},\"source\":{\"const\":\"archive\"},\"status\":{\"" - "const\":\"preparing\"}},\"required\":[\"status\"],\"type\":\"object\"},\"RecordingPlaybackReady\":{\"additiona" - "lProperties\":true,\"properties\":{\"source\":{\"const\":\"archive\"},\"status\":{\"const\":\"ready\"}},\"requir" - "ed\":[\"status\"],\"type\":\"object\"},\"RecordingProtectionRequest\":{\"additionalProperties\":true,\"prope" - "rties\":{\"protected\":{\"type\":\"boolean\"}},\"required\":[\"protected\"],\"type\":\"object\"},\"RecordingProt" - "ectionResult\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"me" - "ssage\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"protected\",\"message\"]" - ",\"type\":\"object\"},\"RecordingRetentionRequest\":{\"additionalProperties\":true,\"description\":\"Numeri" - "c input is truncated toward zero through the C JSON integer accessor. Any negative value clears " - "the override; zero means indefinite; positive values are days.\",\"properties\":{\"retention_days\":{" - "\"type\":\"number\"}},\"required\":[\"retention_days\"],\"type\":\"object\"},\"RecordingRetentionResult\":{\"ad" - "ditionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"message\":{\"type\":\"st" - "ring\"},\"retention_days\":{\"type\":\"integer\"}},\"required\":[\"id\",\"retention_days\",\"message\"],\"type\":" - "\"object\"},\"RecordingTags\":{\"additionalProperties\":true,\"properties\":{\"tags\":{\"items\":{\"type\":\"st" - "ring\"},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTagsById\":{\"additionalPro" - "perties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"tags\":{\"items\":{\"type\":\"string\"" - "},\"type\":\"array\"}},\"required\":[\"id\",\"tags\"],\"type\":\"object\"},\"RecordingTagsRequest\":{\"additional" - "Properties\":true,\"description\":\"Only nonempty string elements are used; other elements are ignor" - "ed. The database trims tag text, truncates to its fixed UTF-8 byte buffer, and deduplicates. Thi" - "s is replacement, including clearing with an empty array.\",\"properties\":{\"tags\":{\"items\":{},\"typ" - "e\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTimeline\":{\"additionalProperties\":tr" - "ue,\"description\":\"One authorized camera and time window, capped at8640 segments. Optional exact " - "external-motion intervals may be absent on allocation failure.\",\"properties\":{\"detection_interva" - "l_count\":{\"minimum\":0,\"type\":\"integer\"},\"detection_intervals\":{\"items\":{\"additionalProperties\":t" - "rue,\"properties\":{\"end_timestamp\":{\"type\":\"integer\"},\"start_timestamp\":{\"type\":\"integer\"}},\"requ" - "ired\":[\"start_timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"type\":\"array\"},\"end_time\":{\"type\":\"s" - "tring\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components" - "/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"string\"},\"stream\":{\"ty" - "pe\":\"string\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\",\"segment_count\"],\"type\":\"" - "object\"},\"RecordingTimelineByIds\":{\"additionalProperties\":true,\"description\":\"Requested IDs are " - "processed in request order. Missing, deleted-camera and denied recordings are omitted. Counts an" - "d overall range reflect retained segments only; an empty result has empty start_time/end_time. D" - "uplicate input IDs can produce duplicate segments.\",\"properties\":{\"end_time\":{\"type\":\"string\"},\"" - "multi_stream\":{\"const\":true},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":" - "{\"$ref\":\"#/components/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"s" - "tring\"},\"stream\":{\"const\":\"(selected)\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\"" - ",\"segment_count\",\"multi_stream\"],\"type\":\"object\"},\"RecordingTimelineSegment\":{\"additionalPropert" - "ies\":true,\"description\":\"Timeline timestamps are Unix seconds; display strings use server local " - "YYYY-MM-DD HH:MM:SS. local_* fields currently equal the Unix timestamp fields. In-progress segme" - "nts use an effective end for display. Timeline segments omit playback_issue and protected/storag" - "e metadata: use detail/playback before treating a finalized segment as playable.\",\"properties\":{" - "\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\"" - ":\"string\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":\"string\"},\"end_timestamp\":{\"type\":\"in" - "teger\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"description\":\"Recording ID: a number through 9" - "007199254740991 (JavaScript maximum safe integer), otherwise an exact decimal string. Treat IDs " - "as opaque values; do not convert strings to Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minim" - "um\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"in_progress\":{\"type\"" - ":\"boolean\"},\"local_end_timestamp\":{\"type\":\"integer\"},\"local_start_timestamp\":{\"type\":\"integer\"}," - "\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":" - "\"string\"},\"start_timestamp\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"id\",\"str" - "eam\",\"camera_uuid\",\"start_time\",\"in_progress\",\"duration\",\"size\",\"capture_method\",\"schedule_restr" - "icted\",\"has_detection\",\"end_time\",\"start_timestamp\",\"end_timestamp\",\"local_start_timestamp\",\"loc" - "al_end_timestamp\"],\"type\":\"object\"},\"RecordingsPage\":{\"additionalProperties\":true,\"properties\":{" - "\"pagination\":{\"$ref\":\"#/components/schemas/RecordingPagination\"},\"recordings\":{\"items\":{\"$ref\":\"" - "#/components/schemas/RecordingListItem\"},\"type\":\"array\"}},\"required\":[\"recordings\",\"pagination\"]" - ",\"type\":\"object\"},\"SetupStatus\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\":\"b" - "oolean\"},\"setup_completed_at\":{\"type\":\"number\"}},\"required\":[\"complete\",\"setup_completed_at\"],\"t" - "ype\":\"object\"},\"SetupUpdate\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\":\"bool" - "ean\"}},\"required\":[],\"type\":\"object\"},\"SetupUpdated\":{\"additionalProperties\":true,\"properties\":{" - "\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"StorageArchi" - "veDeletion\":{\"additionalProperties\":false,\"properties\":{\"attempts\":{\"minimum\":0,\"type\":\"integer\"" - "},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"next_attempt_at\":{" - "\"minimum\":0,\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"remaining_objects\":" - "{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"u" - "uid\",\"recording_id\",\"created_at\",\"remaining_objects\",\"attempts\",\"next_attempt_at\",\"last_error\"]," - "\"type\":\"object\"},\"StorageArchiveResponse\":{\"additionalProperties\":false,\"properties\":{\"archive_b" - "ytes\":{\"minimum\":0,\"type\":\"integer\"},\"pending_deletion_count\":{\"minimum\":0,\"type\":\"integer\"},\"pe" - "nding_deletions\":{\"items\":{\"$ref\":\"#/components/schemas/StorageArchiveDeletion\"},\"maxItems\":100," - "\"type\":\"array\"},\"recovery_read_only\":{\"type\":\"boolean\"},\"retrieval_cache_bytes\":{\"minimum\":0,\"ty" - "pe\":\"integer\"},\"retrieval_jobs\":{\"minimum\":0,\"type\":\"integer\"},\"s3_enabled\":{\"type\":\"boolean\"},\"" - "transfer_backlog_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"s3_enabled\",\"recovery_read_" - "only\",\"archive_bytes\",\"transfer_backlog_bytes\",\"retrieval_cache_bytes\",\"retrieval_jobs\",\"pending" - "_deletion_count\",\"pending_deletions\"],\"type\":\"object\"},\"StorageArchiveRetryRequest\":{\"additional" - "Properties\":false,\"properties\":{\"deletion_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"req" - "uired\":[\"deletion_uuid\"],\"type\":\"object\"},\"StorageArchiveRetryResponse\":{\"additionalProperties\":" - "false,\"properties\":{\"status\":{\"const\":\"retry_queued\",\"type\":\"string\"}},\"required\":[\"status\"],\"ty" - "pe\":\"object\"},\"StorageCleanupRequest\":{\"additionalProperties\":false,\"properties\":{\"aggressive\":{" - "\"type\":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"StorageCleanupResponse\":{\"additionalPropertie" - "s\":false,\"properties\":{\"aggressive\":{\"type\":\"boolean\"},\"message\":{\"const\":\"Cleanup triggered suc" - "cessfully\",\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"ag" - "gressive\",\"message\"],\"type\":\"object\"},\"StorageCompliancePolicy\":{\"additionalProperties\":false,\"p" - "roperties\":{\"achieved_retention_days\":{\"type\":\"number\"},\"active_violation_count\":{\"minimum\":0,\"t" - "ype\":\"integer\"},\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"copy_deficit_reco" - "rdings\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"" - "expected_retention_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"maximum_rete" - "ntion_days\":{\"minimum\":0,\"type\":\"integer\"},\"minimum_forecast_met\":{\"type\":\"boolean\"},\"minimum_re" - "tention_days\":{\"minimum\":0,\"type\":\"integer\"},\"observed_daily_bytes\":{\"type\":\"number\"},\"policy_na" - "me\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_bytes\"" - ":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"required_copy_" - "count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_window_days\":{\"type\":\"number\"}},\"required\":[\"polic" - "y_uuid\",\"policy_name\",\"recording_count\",\"recording_bytes\",\"minimum_retention_days\",\"desired_rete" - "ntion_days\",\"maximum_retention_days\",\"required_copy_count\",\"copy_deficit_recordings\",\"active_vio" - "lation_count\",\"sample_window_days\",\"observed_daily_bytes\",\"achieved_retention_days\",\"confidence\"" - ",\"expected_retention_days\",\"minimum_forecast_met\"],\"type\":\"object\"},\"StorageComplianceResponse\":" - "{\"additionalProperties\":false,\"properties\":{\"active_violations\":{\"items\":{\"$ref\":\"#/components/s" - "chemas/StorageComplianceViolation\"},\"type\":\"array\"},\"forecast_note\":{\"type\":\"string\"},\"forecast_" - "window_days\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/schemas/St" - "orageCompliancePolicy\"},\"type\":\"array\"},\"targets\":{\"items\":{\"$ref\":\"#/components/schemas/Storage" - "ComplianceTarget\"},\"type\":\"array\"}},\"required\":[\"forecast_window_days\",\"forecast_note\",\"targets\"" - ",\"policies\",\"active_violations\"],\"type\":\"object\"},\"StorageComplianceTarget\":{\"additionalProperti" - "es\":false,\"properties\":{\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"days_to_h" - "igh_watermark\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"expected_retention_day" - "s\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"observed_bytes\":{\"minimum\":0,\"type" - "\":\"integer\"},\"observed_daily_bytes\":{\"minimum\":0,\"type\":\"number\"},\"sample_count\":{\"minimum\":0,\"t" - "ype\":\"integer\"},\"sample_window_days\":{\"minimum\":0,\"type\":\"number\"},\"target_name\":{\"type\":\"string" - "\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"target_uuid\",\"target_n" - "ame\",\"sample_window_days\",\"observed_daily_bytes\",\"sample_count\",\"observed_bytes\",\"confidence\",\"d" - "ays_to_high_watermark\",\"expected_retention_days\"],\"type\":\"object\"},\"StorageComplianceViolation\":" - "{\"additionalProperties\":false,\"properties\":{\"details\":{\"type\":\"string\"},\"first_seen_at\":{\"minimu" - "m\":0,\"type\":\"integer\"},\"last_seen_at\":{\"minimum\":0,\"type\":\"integer\"},\"policy_name\":{\"type\":\"stri" - "ng\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_id\":{\"anyOf\":[{\"minimum" - "\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"type\":{\"type\":\"string\"},\"uuid\":{\"$ref\":\"#/components/sc" - "hemas/CameraUuid\"}},\"required\":[\"uuid\",\"policy_uuid\",\"policy_name\",\"recording_id\",\"type\",\"detail" - "s\",\"first_seen_at\",\"last_seen_at\"],\"type\":\"object\"},\"StorageHealthResponse\":{\"additionalProperti" - "es\":false,\"properties\":{\"capacity_target_free_pct\":{\"minimum\":0,\"type\":\"integer\"},\"fill_rate_byt" - "es_per_day\":{\"type\":\"number\"},\"free_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"free_space_pct\"" - ":{\"type\":\"number\"},\"last_check_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_deleted\":{\"min" - "imum\":0,\"type\":\"integer\"},\"last_cleanup_freed\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_time" - "\":{\"minimum\":0,\"type\":\"integer\"},\"last_deep_time\":{\"minimum\":0,\"type\":\"integer\"},\"pressure_level" - "\":{\"type\":\"string\"},\"pressure_level_num\":{\"minimum\":0,\"type\":\"integer\"},\"projected_seconds_to_ta" - "rget\":{\"type\":\"number\"},\"total_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_space_bytes\":{\"" - "minimum\":0,\"type\":\"integer\"}},\"required\":[\"pressure_level\",\"pressure_level_num\",\"free_space_pct\"" - ",\"fill_rate_bytes_per_day\",\"projected_seconds_to_target\",\"free_space_bytes\",\"total_space_bytes\"," - "\"used_space_bytes\",\"last_check_time\",\"last_cleanup_time\",\"last_deep_time\",\"last_cleanup_deleted\"" - ",\"last_cleanup_freed\",\"capacity_target_free_pct\"],\"type\":\"object\"},\"StorageMigration\":{\"addition" - "alProperties\":false,\"properties\":{\"attempt_count\":{\"minimum\":0,\"type\":\"integer\"},\"bandwidth_limi" - "t_bps\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_copied\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_total" - "\":{\"minimum\":0,\"type\":\"integer\"},\"cancel_requested\":{\"type\":\"boolean\"},\"checksum\":{\"anyOf\":[{\"pa" - "ttern\":\"^[0-9a-f]{64}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"checksum_mode\":{\"const\":\"sha256\",\"ty" - "pe\":\"string\"},\"completed_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"created" - "_at\":{\"minimum\":0,\"type\":\"integer\"},\"destination_object_key\":{\"type\":\"string\"},\"destination_targ" - "et_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"last_error\":{\"type\":\"string\"},\"max_attempts" - "\":{\"minimum\":0,\"type\":\"integer\"},\"next_attempt_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"ty" - "pe\":\"null\"}]},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"progress\":{\"minimum\":0,\"type" - "\":\"number\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991" - ",\"minimum\":1,\"type\":\"integer\"},\"source_object_key\":{\"type\":\"string\"},\"source_target_uuid\":{\"$ref" - "\":\"#/components/schemas/CameraUuid\"},\"started_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"typ" - "e\":\"null\"}]},\"state\":{\"enum\":[\"queued\",\"copying\",\"verifying\",\"committing\",\"retry_wait\",\"cleanup_" - "pending\",\"completed\",\"failed\",\"cancelled\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"in" - "teger\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"window_end_minute\":{\"maximum\":1439,\"m" - "inimum\":0,\"type\":\"integer\"},\"window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"}}" - ",\"required\":[\"uuid\",\"source_target_uuid\",\"destination_target_uuid\",\"recording_id\",\"operation\",\"s" - "ource_object_key\",\"destination_object_key\",\"last_error\",\"state\",\"checksum_mode\",\"checksum\",\"byte" - "s_total\",\"bytes_copied\",\"attempt_count\",\"max_attempts\",\"bandwidth_limit_bps\",\"progress\",\"next_at" - "tempt_at\",\"cancel_requested\",\"window_start_minute\",\"window_end_minute\",\"revision\",\"created_at\",\"" - "updated_at\",\"started_at\",\"completed_at\"],\"type\":\"object\"},\"StorageMigrationCreate\":{\"additionalP" - "roperties\":false,\"properties\":{\"destination_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUui" - "d\"},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"recording_id\":{\"maximum\":9007199254740" - "991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"recording_id\",\"destination_target_uuid\"],\"type\":" - "\"object\"},\"StorageMigrationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"mini" - "mum\":0,\"type\":\"integer\"},\"jobs\":{\"items\":{\"$ref\":\"#/components/schemas/StorageMigration\"},\"type\"" - ":\"array\"},\"worker_concurrency\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"jobs\",\"count\",\"worker_" - "concurrency\"],\"type\":\"object\"},\"StoragePoliciesResponse\":{\"additionalProperties\":false,\"properti" - "es\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/schemas/St" - "oragePolicy\"},\"type\":\"array\"}},\"required\":[\"policies\",\"count\"],\"type\":\"object\"},\"StoragePolicy\":" - "{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minim" - "um\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"b" - "oolean\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"maximum\":36500,\"" - "minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"ta" - "rget\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/sc" - "hemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1," - "\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migra" - "tion_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\"" - ":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximu" - "m\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"p" - "ressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"" - "anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$re" - "f\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"i" - "nteger\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"n" - "ull\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9" - "007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSele" - "ctor\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUu" - "id\"}},\"required\":[\"uuid\",\"name\",\"enabled\",\"priority\",\"selector\",\"primary_target_uuid\",\"primary_p" - "ool_uuid\",\"fallback_target_uuid\",\"replication_pool_uuid\",\"migration_target_uuid\",\"fallback_mode\"" - ",\"minimum_retention_days\",\"desired_retention_days\",\"maximum_retention_days\",\"migration_after_day" - "s\",\"required_copy_count\",\"pressure_priority\",\"archive_after_seconds\",\"hot_residency_seconds\",\"ar" - "chive_protected\",\"archive_on_pressure\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"S" - "toragePolicyApplyRequest\":{\"additionalProperties\":false,\"properties\":{\"preview\":{\"type\":\"boolean" - "\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"," - "\"preview\"],\"type\":\"object\"},\"StoragePolicyApplyResponse\":{\"additionalProperties\":false,\"properti" - "es\":{\"bytes\":{\"minimum\":0,\"type\":\"integer\"},\"past_new_retention_limit\":{\"minimum\":0,\"type\":\"inte" - "ger\"},\"preview\":{\"type\":\"boolean\"},\"recordings\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maxi" - "mum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"unfinished_transfers\":{\"minimum\":0,\"type\":\"" - "integer\"}},\"required\":[\"revision\",\"preview\",\"recordings\",\"bytes\",\"past_new_retention_limit\",\"unf" - "inished_transfers\"],\"type\":\"object\"},\"StoragePolicyConflict\":{\"additionalProperties\":false,\"prop" - "erties\":{\"draft_precedes\":{\"type\":\"boolean\"},\"overlap_camera_count\":{\"minimum\":0,\"type\":\"integer" - "\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"pri" - "ority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"policy_uuid\",\"policy_name\",\"priority\"" - ",\"overlap_camera_count\",\"draft_precedes\"],\"type\":\"object\"},\"StoragePolicyCreate\":{\"additionalPro" - "perties\":false,\"anyOf\":[{\"properties\":{\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/Camer" - "aUuid\"}},\"required\":[\"primary_target_uuid\"]},{\"properties\":{\"primary_pool_uuid\":{\"anyOf\":[{\"$ref" - "\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]}},\"required\":[\"primary_pool_uuid\"]}],\"prop" - "erties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_o" - "n_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{" - "\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enu" - "m\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\"" - ":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483" - "647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":" - "\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_targ" - "et_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retenti" - "on_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"t" - "ype\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"prim" - "ary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_t" - "arget_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-" - "1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Camera" - "Uuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"selec" - "tor\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\"],\"type\":\"objec" - "t\"},\"StoragePolicyPreview\":{\"additionalProperties\":false,\"properties\":{\"conflict_camera_count\":{" - "\"minimum\":0,\"type\":\"integer\"},\"conflict_policy_count\":{\"minimum\":0,\"type\":\"integer\"},\"conflicts\"" - ":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyConflict\"},\"type\":\"array\"},\"draft_enabled\":" - "{\"type\":\"boolean\"},\"effective_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"matched_camera_count" - "\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyPr" - "eviewSample\"},\"maxItems\":50,\"type\":\"array\"},\"sample_truncated\":{\"type\":\"boolean\"},\"shadowed_came" - "ra_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"draft_enabled\",\"sample_truncated\",\"matche" - "d_camera_count\",\"effective_camera_count\",\"shadowed_camera_count\",\"conflict_camera_count\",\"confli" - "ct_policy_count\",\"sample\",\"conflicts\"],\"type\":\"object\"},\"StoragePolicyPreviewRequest\":{\"addition" - "alProperties\":false,\"anyOf\":[{\"properties\":{\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"" - "type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"revision" - "\"]},{\"properties\":{\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"primary_target_uuid\":" - "{\"$ref\":\"#/components/schemas/CameraUuid\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelecto" - "r\"}},\"required\":[\"name\",\"selector\",\"primary_target_uuid\"]},{\"properties\":{\"name\":{\"maxLength\":12" - "7,\"minLength\":1,\"type\":\"string\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Cam" - "eraUuid\"},{\"type\":\"null\"}]},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\"" - ":[\"name\",\"selector\",\"primary_pool_uuid\"]}],\"properties\":{\"archive_after_seconds\":{\"maximum\":2147" - "483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protecte" - "d\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"e" - "nabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"s" - "tring\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"nul" - "l\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_rete" - "ntion_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":365" - "00,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas" - "/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"in" - "teger\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":10" - "00000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/s" - "chemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/Camera" - "Uuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid" - "\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{" - "\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"ty" - "pe\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"uuid\":{\"$ref\":\"#/compon" - "ents/schemas/CameraUuid\"}},\"required\":[],\"type\":\"object\"},\"StoragePolicyPreviewSample\":{\"additio" - "nalProperties\":false,\"properties\":{\"camera_name\":{\"type\":\"string\"},\"camera_uuid\":{\"$ref\":\"#/comp" - "onents/schemas/CameraUuid\"},\"effective_policy\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StoragePo" - "licyWinner\"},{\"type\":\"null\"}]},\"overlapping_policy_count\":{\"minimum\":0,\"type\":\"integer\"}},\"requi" - "red\":[\"camera_uuid\",\"camera_name\",\"overlapping_policy_count\",\"effective_policy\"],\"type\":\"object\"" - "},\"StoragePolicyUpdate\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"ma" - "ximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archi" - "ve_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"i" - "nteger\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"" - "],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{" - "\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"m" - "aximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"m" - "aximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/compone" - "nts/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0" - ",\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"" - "maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/c" - "omponents/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/sch" - "emas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replicatio" - "n_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_co" - "py_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"min" - "imum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[" - "\"revision\"],\"type\":\"object\"},\"StoragePolicyWinner\":{\"additionalProperties\":false,\"properties\":{\"" - "draft\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"st" - "ring\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null" - "\"}]},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Camer" - "aUuid\"},{\"type\":\"null\"}]},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid" - "\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\"" - ":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"draft\",\"policy_uuid\",\"policy_name\",\"priorit" - "y\",\"primary_target_uuid\",\"primary_pool_uuid\",\"fallback_mode\",\"fallback_target_uuid\"],\"type\":\"obj" - "ect\"},\"StoragePool\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\"" - ":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/Storag" - "ePoolMember\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1," - "\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"strategy\"" - ":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type" - "\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"strat" - "egy\",\"enabled\",\"revision\",\"created_at\",\"updated_at\",\"members\"],\"type\":\"object\"},\"StoragePoolCrea" - "te\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":" - "{\"$ref\":\"#/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"arr" - "ay\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"strategy\":{\"enum\":[\"most_free\",\"rou" - "nd_robin\",\"priority\"],\"type\":\"string\"}},\"required\":[\"name\",\"members\"],\"type\":\"object\"},\"StorageP" - "oolMember\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"integer\"}" - ",\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimum\":1,\"" - "type\":\"integer\"}},\"required\":[\"target_uuid\",\"position\",\"weight\"],\"type\":\"object\"},\"StoragePoolMe" - "mberRequest\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"integer" - "\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimum\":1" - ",\"type\":\"integer\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"StoragePoolUpdate\":{\"additional" - "Properties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/compo" - "nents/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"ma" - "xLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"" - "type\":\"integer\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"}},\"re" - "quired\":[\"revision\"],\"type\":\"object\"},\"StoragePoolsResponse\":{\"additionalProperties\":false,\"prop" - "erties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"pools\":{\"items\":{\"$ref\":\"#/components/schemas/S" - "toragePool\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"pools\",\"count\"],\"type\":\"object\"},\"Stora" - "geTarget\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":14" - "39,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"typ" - "e\":\"integer\"},\"archive_budget_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"archive_only\":{\"type\":\"bool" - "ean\"},\"bucket\":{\"type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"credential_ref\":{\"" - "type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"health\":{\"$ref\":\"#/co" - "mponents/schemas/StorageTargetHealth\"},\"high_watermark_pct\":{\"type\":\"number\"},\"is_default\":{\"typ" - "e\":\"boolean\"},\"low_watermark_pct\":{\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type" - "\":\"integer\"},\"mount_guard_path\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"null\"}]},\"mount_required\":{" - "\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"recording_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"rec" - "ording_count\":{\"minimum\":0,\"type\":\"integer\"},\"region\":{\"type\":\"string\"},\"replica_bytes\":{\"minimu" - "m\":0,\"type\":\"integer\"},\"replica_count\":{\"minimum\":0,\"type\":\"integer\"},\"reserve_bytes\":{\"minimum\"" - ":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_" + "ll\"}]},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"created_at\":{\"minimum\":0,\"type\":\"in" + "teger\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"revi" + "sion\":{\"minimum\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components" + "/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"" + "recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"updated_at\":{\"minimum\":0,\"type" + "\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-" + "9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"is_shared\",\"owned\",\"selector\",\"searc" + "h\",\"collection_uuid\",\"columns\",\"sort_by\",\"sort_order\",\"revision\",\"created_at\",\"updated_at\"],\"typ" + "e\":\"object\"},\"FleetSavedViewsResponse\":{\"additionalProperties\":false,\"properties\":{\"can_share\":{" + "\"type\":\"boolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"views\":{\"items\":{\"$ref\":\"#/components/" + "schemas/FleetSavedView\"},\"maxItems\":256,\"type\":\"array\"}},\"required\":[\"views\",\"count\",\"can_share\"" + "],\"type\":\"object\"},\"FleetSelector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"FleetTag\":{\"ad" + "ditionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"color\":{\"" + "type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"lab" + "el\":{\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F" + "]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\"" + ":[\"uuid\",\"label\",\"color\",\"description\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"" + "},\"FleetTagCreate\":{\"additionalProperties\":false,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-" + "F]{6})$\",\"type\":\"string\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"label\":{\"maxLength\":2" + "55,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"},\"FleetTagMergeRequest\":" + "{\"additionalProperties\":false,\"properties\":{\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUu" + "id\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"FleetTagMergeResult\":{\"additionalProperties\":" + "false,\"properties\":{\"source_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":" + "true,\"type\":\"boolean\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"su" + "ccess\",\"source_uuid\",\"target_uuid\"],\"type\":\"object\"},\"FleetTagUpdate\":{\"additionalProperties\":fa" + "lse,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\":\"string\"},\"description\":{\"max" + "Length\":511,\"type\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\"" + ":[],\"type\":\"object\"},\"FleetTagsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"m" + "inimum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"arr" + "ay\"}},\"required\":[\"tags\",\"count\"],\"type\":\"object\"},\"FleetViewCreate\":{\"additionalProperties\":fal" + "se,\"properties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\"" + ":\"array\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"search\":{\"type\"" + ":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"c" + "amera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"as" + "c\",\"desc\"]}},\"required\":[\"name\",\"selector\"],\"type\":\"object\"},\"FleetViewUpdate\":{\"additionalPrope" + "rties\":false,\"properties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"strin" + "g\"},\"type\":\"array\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"revis" + "ion\":{\"minimum\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/" + "schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"r" + "ecording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[\"name\",\"selector\",\"" + "revision\"],\"type\":\"object\"},\"FloorPlan\":{\"additionalProperties\":false,\"properties\":{\"background_" + "mime\":{\"enum\":[\"image/png\",\"image/jpeg\",null],\"type\":[\"string\",\"null\"]},\"cameras\":{\"items\":{\"$re" + "f\":\"#/components/schemas/FloorPlanCamera\"},\"maxItems\":1000,\"type\":\"array\"},\"can_modify\":{\"type\":" + "\"boolean\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maxi" + "mum\":4000,\"minimum\":400,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"location_" + "uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\"" + ":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/C" + "ameraUuid\"},{\"type\":\"null\"}]},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1," + "\"type\":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}" + "]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}" + "},\"required\":[\"name\",\"location_uuid\",\"parent_plan_uuid\",\"canvas_width\",\"canvas_height\",\"sketch\"," + "\"cameras\",\"uuid\",\"background_mime\",\"partially_authorized\",\"revision\",\"created_at\",\"updated_at\"]," + "\"type\":\"object\"},\"FloorPlanCamera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$" + "ref\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotat" + "ion\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"" + "},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"rotation\",\"f" + "ov\"],\"type\":\"object\"},\"FloorPlanCameraRequest\":{\"additionalProperties\":false,\"properties\":{\"came" + "ra_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"num" + "ber\"},\"rotation\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"ty" + "pe\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\"],\"type\":\"" + "object\"},\"FloorPlanCreate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref" + "\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canvas_height\"" + ":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"t" + "ype\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"nu" + "ll\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$re" + "f\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components" + "/schemas/FloorSketch\"},{\"type\":\"null\"}]}},\"required\":[\"name\"],\"type\":\"object\"},\"FloorPlanDeletio" + "n\":{\"additionalProperties\":false,\"properties\":{\"deleted\":{\"const\":true,\"type\":\"boolean\"}},\"requi" + "red\":[\"deleted\"],\"type\":\"object\"},\"FloorPlanUpdate\":{\"additionalProperties\":false,\"properties\":{" + "\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\"" + ":\"array\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maxim" + "um\":4000,\"minimum\":400,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas" + "/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_p" + "lan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"revision\":{\"mi" + "nimum\":1,\"type\":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type" + "\":\"null\"}]}},\"required\":[\"name\",\"cameras\",\"revision\"],\"type\":\"object\"},\"FloorPlansResponse\":{\"ad" + "ditionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"plans\":{\"items\":{\"$ref\"" + ":\"#/components/schemas/FloorPlan\"},\"type\":\"array\"}},\"required\":[\"plans\",\"can_modify\"],\"type\":\"ob" + "ject\"},\"FloorSketch\":{\"additionalProperties\":false,\"properties\":{\"shapes\":{\"items\":{\"oneOf\":[{\"$" + "ref\":\"#/components/schemas/FloorSketchRect\"},{\"$ref\":\"#/components/schemas/FloorSketchWall\"},{\"$" + "ref\":\"#/components/schemas/FloorSketchArea\"},{\"$ref\":\"#/components/schemas/FloorSketchLabel\"}]}," + "\"maxItems\":400,\"type\":\"array\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\",\"sh" + "apes\"],\"type\":\"object\"},\"FloorSketchArea\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"max" + "Length\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"points\":{\"it" + "ems\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"nu" + "mber\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"maxItems\":64,\"minItems\":3,\"ty" + "pe\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"ty" + "pe\":{\"const\":\"area\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"points\"],\"type\":\"object\"},\"FloorS" + "ketchLabel\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type" + "\":\"string\"},\"size\":{\"enum\":[\"sm\",\"md\",\"lg\"],\"type\":\"string\"},\"text\":{\"maxLength\":95,\"minLength\":" + "1,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string" + "\"},\"type\":{\"const\":\"label\",\"type\":\"string\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"" + "maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"text\"],\"type\":\"object\"" + "},\"FloorSketchRect\":{\"additionalProperties\":false,\"properties\":{\"filled\":{\"type\":\"boolean\"},\"h\":" + "{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"l" + "abel\":{\"maxLength\":95,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"vio" + "let\"],\"type\":\"string\"},\"type\":{\"const\":\"rect\",\"type\":\"string\"},\"w\":{\"maximum\":1,\"minimum\":0,\"typ" + "e\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"" + "number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"w\",\"h\"],\"type\":\"object\"},\"FloorSketchWall\":{\"addition" + "alProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"" + "maxLength\":95,\"type\":\"string\"},\"points\":{\"items\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefi" + "xItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"t" + "ype\":\"array\"},\"maxItems\":64,\"minItems\":2,\"type\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\"," + "\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"wall\",\"type\":\"string\"}},\"required\":[\"i" + "d\",\"type\",\"points\"],\"type\":\"object\"},\"Go2rtcConfigurationInput\":{\"additionalProperties\":true,\"pr" + "operties\":{\"override\":{\"maxLength\":65535,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"Go2rt" + "cConfigurationValidation\":{\"additionalProperties\":true,\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"" + "null\"},{\"additionalProperties\":true,\"properties\":{\"column\":{\"type\":\"number\"},\"line\":{\"type\":\"num" + "ber\"},\"message\":{\"type\":\"string\"}},\"type\":\"object\"}]},\"libyaml_available\":{\"type\":\"boolean\"},\"sk" + "ip_reason\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"" + "message\"],\"type\":\"object\"},\"skipped\":{\"type\":\"boolean\"},\"valid\":{\"type\":\"boolean\"},\"warnings\":{\"" + "items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"valid\",\"libyaml_available\",\"skipped\",\"war" + "nings\"],\"type\":\"object\"},\"Go2rtcEffectiveConfiguration\":{\"additionalProperties\":true,\"properties" + "\":{\"base\":{\"type\":\"string\"},\"go2rtc_initialized\":{\"type\":\"boolean\"},\"managed_listener_policy\":{\"" + "type\":\"string\"},\"merged_source_order\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"override\":{\"ty" + "pe\":\"string\"},\"override_in_use\":{\"type\":\"boolean\"},\"redaction_available\":{\"type\":\"boolean\"},\"war" + "nings\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"go2rtc_initialized\",\"redaction_a" + "vailable\",\"base\",\"override\",\"managed_listener_policy\",\"merged_source_order\",\"override_in_use\",\"w" + "arnings\"],\"type\":\"object\"},\"Go2rtcOverrideDiagnostics\":{\"additionalProperties\":true,\"properties\"" + ":{\"content_matches\":{\"type\":\"boolean\"},\"db_bytes\":{\"type\":\"number\"},\"expected_file_path\":{\"type\"" + ":\"string\"},\"file_bytes\":{\"type\":\"number\"},\"file_present\":{\"type\":\"boolean\"},\"file_read_error\":{\"" + "type\":\"string\"},\"process_pid\":{\"type\":\"number\"},\"process_running\":{\"type\":\"boolean\"},\"process_st" + "ate_known\":{\"type\":\"boolean\"},\"quarantine_active\":{\"type\":\"boolean\"},\"quarantine_reason\":{\"type\"" + ":\"string\"},\"quarantined_copy_bytes\":{\"type\":\"integer\"},\"validated_version\":{\"type\":\"string\"}},\"r" + "equired\":[\"expected_file_path\",\"db_bytes\",\"file_present\",\"file_bytes\",\"content_matches\",\"quarant" + "ine_active\",\"process_state_known\",\"process_running\",\"process_pid\",\"quarantined_copy_bytes\",\"vali" + "dated_version\"],\"type\":\"object\"},\"HealthResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Stream" + "Health\"},{\"$ref\":\"#/components/schemas/ProcessLiveness\"},{\"$ref\":\"#/components/schemas/StreamHea" + "lthHistory\"}]},\"HlsHealth\":{\"additionalProperties\":true,\"properties\":{\"healthy\":{\"type\":\"boolean" + "\"},\"status\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"},\"watchdogRestartCount\":{\"type\":\"inte" + "ger\"}},\"required\":[\"healthy\",\"status\",\"watchdogRestartCount\",\"timestamp\"],\"type\":\"object\"},\"Host" + "Health\":{\"additionalProperties\":true,\"properties\":{\"active_incidents\":{\"items\":{\"additionalPrope" + "rties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"}" + ",\"incident_id\":{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additio" + "nalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":{\"type\":\"string\"},\"" + "metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"type\":\"string\"},\"sampl" + "ed_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"" + "anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability" + "\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"persistence_pending\":{\"type\":\"boolean\"},\"scope\":" + "{\"type\":\"string\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"stri" + "ng\"},\"thresholds\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"crit" + "ical_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"" + "string\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"t" + "ype\":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seco" + "nds\":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\"," + "\"overridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"w" + "arning_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"}},\"required\":" + "[\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_obs" + "erved_at_ms\",\"persistence_pending\",\"observation\"],\"type\":\"object\"},\"type\":\"array\"},\"coverage\":{\"" + "additionalProperties\":true,\"properties\":{\"abandoned_helpers\":{\"type\":\"number\"},\"collection_error" + "s\":{\"type\":\"number\"},\"collection_timeouts\":{\"type\":\"number\"},\"complete\":{\"type\":\"boolean\"},\"cove" + "rage_overflows\":{\"type\":\"number\"},\"incomplete_count\":{\"type\":\"number\"},\"observations_dropped\":{\"" + "type\":\"number\"},\"unavailable_observations\":{\"type\":\"number\"}},\"required\":[\"complete\",\"incomplete" + "_count\",\"unavailable_observations\",\"observations_dropped\",\"collection_errors\",\"collection_timeou" + "ts\",\"abandoned_helpers\",\"coverage_overflows\"],\"type\":\"object\"},\"evaluator\":{\"additionalPropertie" + "s\":true,\"properties\":{\"active_incidents\":{\"type\":\"integer\"},\"pending_persistence\":{\"type\":\"integ" + "er\"},\"tracked_conditions\":{\"type\":\"integer\"}},\"required\":[\"tracked_conditions\",\"active_incidents" + "\",\"pending_persistence\"],\"type\":\"object\"},\"monitoring\":{\"additionalProperties\":true,\"properties\"" + ":{\"configured_enabled\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"initialized\":{\"type\":\"bo" + "olean\"},\"restart_required\":{\"type\":\"boolean\"},\"worker_threads\":{\"type\":\"number\"}},\"required\":[\"c" + "onfigured_enabled\",\"initialized\",\"enabled\",\"worker_threads\",\"restart_required\"],\"type\":\"object\"}" + ",\"observations\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string" + "\"},\"freshness\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"" + "resource\":{\"type\":\"string\"},\"sampled_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"" + "unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metr" + "ic\",\"resource\",\"scope\",\"capability\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"type\":\"array\"}" + ",\"overall_state\":{\"type\":\"string\"},\"recent_samples\":{\"items\":{\"additionalProperties\":true,\"prope" + "rties\":{\"completed_at_ms\":{\"type\":\"number\"},\"observation_count\":{\"type\":\"number\"},\"observations_" + "dropped\":{\"type\":\"number\"},\"sequence\":{\"type\":\"number\"}},\"required\":[\"sequence\",\"completed_at_ms" + "\",\"observation_count\",\"observations_dropped\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":" + "{\"type\":\"number\"},\"self_observability\":{\"additionalProperties\":true,\"properties\":{\"collectors\":{" + "\"items\":{\"additionalProperties\":true,\"properties\":{\"attempts\":{\"type\":\"number\"},\"busy\":{\"type\":\"" + "boolean\"},\"collector\":{\"type\":\"string\"},\"completions\":{\"type\":\"number\"},\"failures\":{\"type\":\"numb" + "er\"},\"last_duration_ms\":{\"type\":\"number\"},\"maximum_duration_ms\":{\"type\":\"number\"},\"overlap_skips" + "\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"stale\":{\"type\":\"boolean\"},\"tier\":{\"type\":\"number\"" + "},\"timeouts\":{\"type\":\"number\"}},\"required\":[\"collector\",\"scope\",\"tier\",\"attempts\",\"completions\"," + "\"failures\",\"timeouts\",\"overlap_skips\",\"last_duration_ms\",\"maximum_duration_ms\",\"busy\",\"stale\"],\"" + "type\":\"object\"},\"type\":\"array\"},\"coverage_overflows\":{\"type\":\"number\"},\"event_delivery\":{\"additi" + "onalProperties\":true,\"properties\":{\"all_destinations_unavailable\":{\"type\":\"boolean\"},\"circular_r" + "eport_path\":{\"type\":\"boolean\"},\"degraded\":{\"type\":\"boolean\"},\"outbox\":{\"additionalProperties\":tr" + "ue,\"properties\":{\"available\":{\"type\":\"boolean\"}},\"type\":\"object\"},\"outbox_full\":{\"type\":\"boolean" + "\"}},\"type\":\"object\"},\"incident_persistence\":{\"additionalProperties\":true,\"properties\":{\"failures" + "\":{\"type\":\"number\"},\"pending\":{\"type\":\"number\"},\"retries\":{\"type\":\"number\"}},\"type\":\"object\"}},\"" + "required\":[\"collectors\",\"coverage_overflows\",\"incident_persistence\",\"event_delivery\"],\"type\":\"ob" + "ject\"},\"snapshot\":{\"additionalProperties\":true,\"properties\":{\"age_ms\":{\"anyOf\":[{\"type\":\"null\"}," + "{\"type\":\"number\"}]},\"available\":{\"type\":\"boolean\"},\"completed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{" + "\"type\":\"number\"}]},\"freshness\":{\"type\":\"string\"},\"sequence\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"n" + "umber\"}]}},\"required\":[\"available\",\"sequence\",\"completed_at_ms\",\"age_ms\",\"freshness\"],\"type\":\"ob" + "ject\"},\"thresholds\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"str" + "ing\"},\"critical_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction" + "\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_s" + "econds\":{\"type\":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warni" + "ng_for_seconds\":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\"" + ",\"enabled\",\"overridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_th" + "reshold\",\"warning_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"},\"" + "type\":\"array\"},\"visibility\":{\"additionalProperties\":true,\"properties\":{\"coverage_boundary\":{\"typ" + "e\":\"string\"},\"effective_scope\":{\"type\":\"string\"},\"host_hardware_visible\":{\"type\":\"boolean\"}},\"re" + "quired\":[\"effective_scope\",\"host_hardware_visible\",\"coverage_boundary\"],\"type\":\"object\"}},\"requi" + "red\":[\"schema_version\",\"monitoring\",\"overall_state\",\"visibility\",\"snapshot\",\"coverage\",\"observat" + "ions\",\"thresholds\",\"active_incidents\",\"recent_samples\",\"self_observability\"],\"type\":\"object\"},\"H" + "ostHealthIncidents\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"incide" + "nts\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"closed_at_ms\":{\"anyOf\":[{\"type\":\"null\"" + "},{\"type\":\"number\"}]},\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"in" + "cident_id\":{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalP" + "roperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"resourc" + "e\":{\"type\":\"string\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type" + "\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"unit\",\"valu" + "e\"],\"type\":\"object\"},\"reconciliation\":{\"type\":\"string\"},\"revision\":{\"type\":\"number\"},\"scope\":{\"t" + "ype\":\"string\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"" + "}},\"required\":[\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at" + "_ms\",\"last_observed_at_ms\",\"closed_at_ms\",\"reconciliation\",\"revision\",\"observation\"],\"type\":\"obj" + "ect\"},\"type\":\"array\"},\"next_cursor\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"schema_versio" + "n\":{\"type\":\"number\"}},\"required\":[\"schema_version\",\"count\",\"incidents\",\"next_cursor\"],\"type\":\"ob" + "ject\"},\"IceServersResponse\":{\"additionalProperties\":true,\"properties\":{\"ice_servers\":{\"items\":{\"" + "additionalProperties\":true,\"properties\":{\"credential\":{\"type\":\"string\"},\"urls\":{\"items\":{\"type\":" + "\"string\"},\"minItems\":1,\"type\":\"array\"},\"username\":{\"type\":\"string\"}},\"required\":[\"urls\"],\"type\":" + "\"object\"},\"type\":\"array\"}},\"required\":[\"ice_servers\"],\"type\":\"object\"},\"ImagingModeLevel\":{\"addi" + "tionalProperties\":false,\"properties\":{\"level\":{\"type\":\"number\"},\"mode\":{\"type\":\"string\"}},\"requi" + "red\":[],\"type\":\"object\"},\"ImagingModeLevelUpdate\":{\"oneOf\":[{\"maximum\":3.4028234663852886e+38,\"m" + "inimum\":-3.4028234663852886e+38,\"type\":\"number\"},{\"additionalProperties\":false,\"minProperties\":1" + ",\"properties\":{\"level\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type" + "\":\"number\"},\"mode\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"" + "}]},\"ImagingModeOptions\":{\"additionalProperties\":false,\"properties\":{\"level\":{\"$ref\":\"#/componen" + "ts/schemas/DeviceFloatRange\"},\"modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"m" + "odes\"],\"type\":\"object\"},\"ImagingOptions\":{\"additionalProperties\":false,\"properties\":{\"backlight_" + "compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"brightness\":{\"$ref\":\"#/compone" + "nts/schemas/DeviceFloatRange\"},\"color_saturation\":{\"$ref\":\"#/components/schemas/DeviceFloatRange" + "\"},\"contrast\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"defogging\":{\"$ref\":\"#/components" + "/schemas/ImagingModeOptions\"},\"ir_cut_filter_modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"" + "noise_reduction\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"sharpness\":{\"$ref\":\"#/compone" + "nts/schemas/DeviceFloatRange\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOpti" + "ons\"},\"video_source_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/" + "ImagingModeOptions\"}},\"required\":[\"video_source_token\",\"ir_cut_filter_modes\"],\"type\":\"object\"},\"" + "ImagingOptionsResponse\":{\"additionalProperties\":false,\"properties\":{\"options\":{\"$ref\":\"#/compone" + "nts/schemas/ImagingOptions\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"options\"],\"type\":\"" + "object\"},\"ImagingSettings\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":" + "{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"brightness\":{\"type\":\"number\"},\"color_saturatio" + "n\":{\"type\":\"number\"},\"contrast\":{\"type\":\"number\"},\"defogging\":{\"$ref\":\"#/components/schemas/Imag" + "ingModeLevel\"},\"ir_cut_filter\":{\"type\":\"string\"},\"noise_reduction\":{\"type\":\"number\"},\"sharpness\"" + ":{\"type\":\"number\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"video_s" + "ource_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLev" + "el\"}},\"required\":[\"video_source_token\"],\"type\":\"object\"},\"ImagingSettingsRequest\":{\"additionalPr" + "operties\":false,\"anyOf\":[{\"properties\":{\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\"" + ":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"brightness\"]},{\"properties\":{\"color_satu" + "ration\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"r" + "equired\":[\"color_saturation\"]},{\"properties\":{\"contrast\":{\"maximum\":3.4028234663852886e+38,\"mini" + "mum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"contrast\"]},{\"properties\":{\"sharpnes" + "s\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"requir" + "ed\":[\"sharpness\"]},{\"properties\":{\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":" + "-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"noise_reduction\"]},{\"properties\":{\"satura" + "tion\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"req" + "uired\":[\"saturation\"]},{\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/Ima" + "gingModeLevelUpdate\"}},\"required\":[\"backlight_compensation\"]},{\"properties\":{\"wide_dynamic_range" + "\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"wide_dynamic_range\"]},{\"p" + "roperties\":{\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"require" + "d\":[\"tone_compensation\"]},{\"properties\":{\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLe" + "velUpdate\"}},\"required\":[\"defogging\"]},{\"properties\":{\"backlight\":{\"$ref\":\"#/components/schemas/" + "ImagingModeLevelUpdate\"}},\"required\":[\"backlight\"]},{\"properties\":{\"wdr\":{\"$ref\":\"#/components/s" + "chemas/ImagingModeLevelUpdate\"}},\"required\":[\"wdr\"]},{\"properties\":{\"tone\":{\"$ref\":\"#/components" + "/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"tone\"]},{\"properties\":{\"defog\":{\"$ref\":\"#/compon" + "ents/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"defog\"]},{\"properties\":{\"ir_cut_filter\":{\"ma" + "xLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\"" + ":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"description\":\"Known ali" + "ases remain accepted; force_persistence alone is rejected. Numeric limits come from the device o" + "ptions rather than an invented global range.\",\"minProperties\":1,\"properties\":{\"backlight\":{\"$ref" + "\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"backlight_compensation\":{\"$ref\":\"#/components/" + "schemas/ImagingModeLevelUpdate\"},\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.402" + "8234663852886e+38,\"type\":\"number\"},\"color_saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum" + "\":-3.4028234663852886e+38,\"type\":\"number\"},\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum" + "\":-3.4028234663852886e+38,\"type\":\"number\"},\"defog\":{\"$ref\":\"#/components/schemas/ImagingModeLeve" + "lUpdate\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"force_persistence\"" + ":{\"type\":\"boolean\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"max" + "Length\":15,\"minLength\":1,\"type\":\"string\"},\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"m" + "inimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"saturation\":{\"maximum\":3.4028234663852886e+38," + "\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"sharpness\":{\"maximum\":3.4028234663852886e+38" + ",\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tone\":{\"$ref\":\"#/components/schemas/Imaging" + "ModeLevelUpdate\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wd" + "r\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wide_dynamic_range\":{\"$ref\":\"#/compon" + "ents/schemas/ImagingModeLevelUpdate\"}},\"required\":[],\"type\":\"object\"},\"ImagingSettingsResponse\":" + "{\"additionalProperties\":false,\"properties\":{\"settings\":{\"$ref\":\"#/components/schemas/ImagingSett" + "ings\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"settings\"],\"type\":\"object\"},\"Integration" + "Capabilities\":{\"additionalProperties\":true,\"description\":\"Capability document scoped to the call" + "ing principal (contract \302\2479).\",\"properties\":{\"cameras\":{\"items\":{\"additionalProperties\":true,\"pr" + "operties\":{\"actions\":{\"items\":{\"enum\":[\"live.view\",\"snapshot.create\",\"observations.annotate\"],\"t" + "ype\":\"string\"},\"type\":\"array\",\"uniqueItems\":true},\"camera_uuid\":{\"$ref\":\"#/components/schemas/Ob" + "servationUuid\"},\"evidence_enabled\":{\"type\":\"boolean\"}},\"required\":[\"camera_uuid\",\"evidence_enabl" + "ed\",\"actions\"],\"type\":\"object\"},\"maxItems\":1024,\"type\":\"array\"},\"contract_versions\":{\"additional" + "Properties\":true,\"properties\":{\"classifications\":{\"minimum\":1,\"type\":\"integer\"},\"observation_eve" + "nt\":{\"minLength\":1,\"type\":\"string\"},\"observations\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"" + "observations\",\"classifications\",\"observation_event\"],\"type\":\"object\"},\"delivery\":{\"additionalPro" + "perties\":true,\"properties\":{\"mqtt_enabled\":{\"type\":\"boolean\"},\"topic\":{\"description\":\"Configured" + " topic with a literal {camera_uuid} placeholder.\",\"type\":[\"string\",\"null\"]}},\"required\":[\"mqtt_e" + "nabled\",\"topic\"],\"type\":\"object\"},\"history\":{\"additionalProperties\":true,\"properties\":{\"oldest_c" + "ursor\":{\"maxLength\":256,\"type\":[\"string\",\"null\"]},\"oldest_observed_at\":{\"anyOf\":[{\"$ref\":\"#/comp" + "onents/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"retention_days\":{\"minimum\":1,\"type\":\"integer" + "\"}},\"required\":[\"retention_days\",\"oldest_cursor\",\"oldest_observed_at\"],\"type\":\"object\"},\"limits\"" + ":{\"additionalProperties\":true,\"properties\":{\"annotations_per_observation_max\":{\"minimum\":1,\"type" + "\":\"integer\"},\"history_filter_cameras_max\":{\"minimum\":1,\"type\":\"integer\"},\"history_filter_labels_" + "max\":{\"minimum\":1,\"type\":\"integer\"},\"history_page_default\":{\"minimum\":1,\"type\":\"integer\"},\"histo" + "ry_page_max\":{\"minimum\":1,\"type\":\"integer\"},\"label_max_bytes\":{\"minimum\":1,\"type\":\"integer\"},\"ma" + "x_objects_per_batch\":{\"minimum\":1,\"type\":\"integer\"},\"snapshot_retry_after_max_seconds\":{\"minimum" + "\":1,\"type\":\"integer\"}},\"required\":[\"history_page_default\",\"history_page_max\",\"history_filter_cam" + "eras_max\",\"history_filter_labels_max\",\"max_objects_per_batch\",\"label_max_bytes\",\"snapshot_retry_" + "after_max_seconds\",\"annotations_per_observation_max\"],\"type\":\"object\"},\"operations\":{\"additional" + "Properties\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"description\":\"Unsupp" + "orted features are declared explicitly; never infer support from an error.\",\"properties\":{\"event" + "_clip\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"observations.annotate\":{\"" + "$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"observations.history\":{\"$ref\":\"#/c" + "omponents/schemas/IntegrationCapabilityOperation\"},\"observations.snapshot\":{\"$ref\":\"#/components" + "/schemas/IntegrationCapabilityOperation\"},\"range_clip\":{\"$ref\":\"#/components/schemas/Integration" + "CapabilityOperation\"},\"recorded_frame\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperat" + "ion\"},\"track_lifecycle\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"}},\"require" + "d\":[\"observations.history\",\"observations.snapshot\",\"observations.annotate\",\"track_lifecycle\",\"re" + "corded_frame\",\"event_clip\",\"range_clip\"],\"type\":\"object\"},\"principal\":{\"additionalProperties\":tr" + "ue,\"properties\":{\"writer_namespace\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":[\"string\",\"n" + "ull\"]}},\"required\":[\"writer_namespace\"],\"type\":\"object\"},\"source\":{\"additionalProperties\":true,\"" + "properties\":{\"id\":{\"pattern\":\"^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9" + "a-f]{12}$\",\"type\":\"string\"},\"installation_uuid\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}," + "\"kind\":{\"const\":\"lightnvr\"},\"product_version\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"id\"," + "\"kind\",\"installation_uuid\",\"product_version\"],\"type\":\"object\"}},\"required\":[\"contract_versions\"," + "\"source\",\"principal\",\"operations\",\"limits\",\"history\",\"delivery\",\"cameras\"],\"type\":\"object\"},\"Int" + "egrationCapabilityOperation\":{\"additionalProperties\":true,\"properties\":{\"authorized\":{\"type\":\"bo" + "olean\"},\"reason\":{\"enum\":[\"not_implemented\",\"not_available_in_this_version\",\"not_authorized\",\"in" + "valid_writer_namespace\",\"reserved_writer_namespace\"],\"type\":\"string\"},\"supported\":{\"type\":\"boole" + "an\"}},\"required\":[\"supported\",\"authorized\"],\"type\":\"object\"},\"LegacyApiKeyCreated\":{\"additionalP" + "roperties\":false,\"properties\":{\"api_key\":{\"minLength\":1,\"type\":\"string\"},\"success\":{\"const\":true" + "}},\"required\":[\"success\",\"api_key\"],\"type\":\"object\"},\"LiveCameraSlot\":{\"additionalProperties\":fa" + "lse,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"camera_" + "uuid\"],\"type\":\"object\"},\"LiveLayout\":{\"additionalProperties\":false,\"properties\":{\"availability\":" + "{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"" + "items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"" + "maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_shared\"" + ":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\"" + ":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"pa" + "rtially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"rows\":{\"maximu" + "m\":9,\"minimum\":1,\"type\":\"integer\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#" + "/components/schemas/CameraUuid\"}},\"required\":[\"name\",\"is_shared\",\"location_uuid\",\"availability\"," + "\"columns\",\"rows\",\"camera_slots\",\"uuid\",\"owned\",\"partially_authorized\",\"revision\",\"created_at\",\"u" + "pdated_at\"],\"type\":\"object\"},\"LiveLayoutCreate\":{\"additionalProperties\":false,\"properties\":{\"ava" + "ilability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camer" + "a_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"" + "columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uui" + "d\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":12" + "7,\"minLength\":1,\"type\":\"string\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[" + "\"name\",\"availability\",\"columns\",\"rows\",\"camera_slots\"],\"type\":\"object\"},\"LiveLayoutUpdate\":{\"add" + "itionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_con" + "nected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveC" + "ameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"" + "is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid" + "\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"minimum" + "\":1,\"type\":\"integer\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"name\",\"ava" + "ilability\",\"columns\",\"rows\",\"camera_slots\",\"revision\"],\"type\":\"object\"},\"LiveLayoutsResponse\":{\"" + "additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"can_share\":{\"type\":\"b" + "oolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"layouts\":{\"items\":{\"$ref\":\"#/components/schemas" + "/LiveLayout\"},\"type\":\"array\"}},\"required\":[\"layouts\",\"count\",\"can_share\",\"can_modify\"],\"type\":\"o" + "bject\"},\"LiveStreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surfac" + "e=live. Uses the same filtered pagination envelope, with playback-specific row fields.\",\"propert" + "ies\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimu" + "m\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/LiveStreamSumm" + "aryRedacted\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPrivileged\",\"x-lightnvr-visibility\"" + ":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.con" + "figure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer" + "\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size" + "\",\"total_pages\"],\"type\":\"object\"},\"LiveStreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/" + "schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"t" + "ype\":\"boolean\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":t" + "rue,\"type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rt" + "c_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$" + "ref\":\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{" + "\"type\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"priva" + "cy_mode\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_t" + "ransport\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"" + "}],\"description\":\"surface=live includes playback and capability hints; admin_url is never emitte" + "d by this surface.\"},\"LiveStreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamS" + "ummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"type\":\"boolean\"}" + ",\"can_configure\":{\"const\":false,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":false,\"type\":\"b" + "oolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rtc_hls_availab" + "le\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$ref\":\"#/compo" + "nents/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{\"type\":\"boole" + "an\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"privacy_mode\",\"has" + "_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_transport\",\"ep" + "tz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"}],\"descripti" + "on\":\"surface=live includes playback and capability hints; admin_url is never emitted by this sur" + "face.\"},\"LoginConfig\":{\"additionalProperties\":false,\"properties\":{\"api_key\":false,\"auth_absolute" + "_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"" + "type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"remember_device_enabled\":{\"type\":\"boole" + "an\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_da" + "ys\":{\"type\":\"number\"}},\"required\":[\"force_mfa_on_login\",\"remember_device_enabled\",\"auth_timeout_" + "hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"LoginLockoutCleare" + "d\":{\"additionalProperties\":false,\"properties\":{\"cleared\":{\"type\":\"boolean\"},\"message\":{\"type\":\"s" + "tring\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"cleared\",\"message\"],\"type\":\"object\"},\"L" + "oginMfaChallenge\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_passwo" + "rd\":false,\"password\":false,\"password_hash\":false,\"redirect\":false,\"secret\":false,\"success\":false" + ",\"token_hash\":false,\"totp_required\":{\"const\":true},\"totp_secret\":false,\"totp_token\":{\"descriptio" + "n\":\"Ephemeral MFA challenge credential. Returned only by login; never an ordinary identity/token" + "-list field or documentation example.\",\"minLength\":1,\"readOnly\":true,\"type\":\"string\"}},\"required" + "\":[\"totp_required\",\"totp_token\"],\"type\":\"object\"},\"LoginRequest\":{\"additionalProperties\":true,\"d" + "escription\":\"JSON credentials. The C handler copies username/password into 64-byte buffers (63-b" + "yte payload); it does not reject oversized strings before truncating. Invalid credentials return" + "401. Optional fields with wrong types are ignored by current C; this schema describes typed clie" + "nt input.\",\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"remember_device\":{\"type\"" + ":\"boolean\"},\"totp_code\":{\"description\":\"Optional inline MFA code; enrollment and challenge compl" + "etion endpoints remain outside this slice.\",\"type\":\"string\",\"writeOnly\":true},\"username\":{\"type\"" + ":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"LoginResponse\":{\"anyOf\":[{\"$ref" + "\":\"#/components/schemas/LoginSuccess\"},{\"$ref\":\"#/components/schemas/LoginMfaChallenge\"}]},\"Logi" + "nSuccess\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_password\":{\"ty" + "pe\":\"boolean\"},\"password\":false,\"password_hash\":false,\"redirect\":{\"const\":\"/index.html\"},\"secret" + "\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_required\":false,\"totp_secret\":false,\"t" + "otp_token\":false},\"required\":[\"success\",\"redirect\",\"must_change_password\"],\"type\":\"object\"},\"Log" + "outResponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false,\"passwo" + "rd_hash\":false,\"redirect\":{\"const\":\"/login.html\077logout=true\"},\"secret\":false,\"success\":{\"const\":" + "true},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\",\"redirect" + "\"],\"type\":\"object\"},\"LprStreamSummary\":{\"additionalProperties\":true,\"description\":\"surface=lpr e" + "xposes only camera identity and requires both lpr.search and lpr.read for each returned camera. " + "It does not imply live.view or configuration access.\",\"properties\":{\"camera_uuid\":{\"type\":\"strin" + "g\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"LprStreamSumma" + "ryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=lpr. Empty results rem" + "ain a valid response even with no LPR grants.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\"" + ":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\"" + ":{\"$ref\":\"#/components/schemas/LprStreamSummary\"},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"i" + "nteger\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"pag" + "e_size\",\"total_pages\"],\"type\":\"object\"},\"MotionTrigger\":{\"additionalProperties\":false,\"propertie" + "s\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"confidence\":{\"maximum\":1,\"minimu" + "m\":0,\"type\":\"number\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"},\"label\":{\"mi" + "nLength\":1,\"type\":\"string\"},\"objects\":{\"items\":{\"oneOf\":[{\"minLength\":1,\"type\":\"string\"},{\"addit" + "ionalProperties\":false,\"properties\":{\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"lab" + "el\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"}]},\"type\":\"array\"},\"st" + "ream\":{\"minLength\":1,\"type\":\"string\"},\"tags\":{\"items\":{\"minLength\":1,\"type\":\"string\"},\"type\":\"ar" + "ray\"}},\"required\":[\"stream\",\"action\"],\"type\":\"object\"},\"MotionTriggered\":{\"additionalProperties\"" + ":false,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"detections_stor" + "ed\":{\"minimum\":0,\"type\":\"integer\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"}" + ",\"recording_triggered\":{\"type\":\"boolean\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"ty" + "pe\":\"boolean\"},\"tags_applied\":{\"minimum\":0,\"type\":\"integer\"},\"warning\":{\"type\":\"string\"}},\"requi" + "red\":[\"success\",\"stream\",\"action\",\"recording_triggered\",\"detections_stored\",\"tags_applied\"],\"typ" + "e\":\"object\"},\"Observation\":{\"additionalProperties\":true,\"description\":\"One detected object in on" + "e accepted inference result (contract \302\2473). Readers ignore unknown fields.\",\"properties\":{\"batch" + "_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"box\":{\"anyOf\":[{\"$ref\":\"#/components/schem" + "as/ObservationBox\"},{\"type\":\"null\"}]},\"camera_uuid\":{\"$ref\":\"#/components/schemas/ObservationUui" + "d\"},\"classifications\":{\"$ref\":\"#/components/schemas/ObservationClassifications\"},\"confidence\":{\"" + "maximum\":1,\"minimum\":0,\"type\":\"number\"},\"created_at\":{\"$ref\":\"#/components/schemas/ObservationTi" + "me\"},\"detector\":{\"$ref\":\"#/components/schemas/ObservationDetector\"},\"evidence\":{\"$ref\":\"#/compon" + "ents/schemas/ObservationEvidence\"},\"id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"label\":" + "{\"description\":\"Original detector label (1..31 bytes); never modified by classification.\",\"maxLe" + "ngth\":31,\"minLength\":1,\"type\":\"string\"},\"object_index\":{\"maximum\":19,\"minimum\":0,\"type\":\"integer" + "\"},\"observed_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"recording_reference\":{\"anyOf\":" + "[{\"$ref\":\"#/components/schemas/ObservationRecordingReference\"},{\"type\":\"null\"}]},\"revision\":{\"mi" + "nimum\":1,\"type\":\"integer\"},\"source\":{\"pattern\":\"^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4" + "}-[0-9a-f]{4}-[0-9a-f]{12}$\",\"type\":\"string\"},\"stream_name_at_capture\":{\"description\":\"Display o" + "nly.\",\"maxLength\":63,\"type\":\"string\"},\"track\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Observatio" + "nTrack\"},{\"type\":\"null\"}]}},\"required\":[\"id\",\"source\",\"camera_uuid\",\"stream_name_at_capture\",\"ba" + "tch_id\",\"object_index\",\"observed_at\",\"created_at\",\"detector\",\"label\",\"confidence\",\"box\",\"evidenc" + "e\",\"track\",\"recording_reference\",\"revision\",\"classifications\"],\"type\":\"object\"},\"ObservationBox\"" + ":{\"additionalProperties\":true,\"description\":\"Top-left corner and size normalized to the stored e" + "vidence image (contract \302\2473.2). Consumers never re-apply transform.\",\"properties\":{\"format\":{\"co" + "nst\":\"xywh_normalized\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"ex" + "clusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\"" + ":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"format\",\"x\",\"y\",\"width\",\"height\"],\"type" + "\":\"object\"},\"ObservationChangeEvent\":{\"additionalProperties\":true,\"allOf\":[{\"else\":{\"properties\"" + ":{\"evidence_reason\":{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"}},\"type\":\"object\"}," + "\"if\":{\"properties\":{\"evidence_state\":{\"enum\":[\"pending\",\"ready\"]}},\"type\":\"object\"},\"then\":{\"pro" + "perties\":{\"evidence_reason\":{\"type\":\"null\"}},\"type\":\"object\"}},{\"else\":{\"not\":{\"properties\":{\"sn" + "apshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"},\"type\":\"object\"},\"if" + "\":{\"properties\":{\"evidence_state\":{\"const\":\"ready\"}},\"type\":\"object\"},\"then\":{\"properties\":{\"sna" + "pshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"}}],\"description\":\"Data" + " payload of io.lightnvr.detection.observation_changed.v1 (contract \302\2476.3). Consumers skip classi" + "fication-only changes for re-classification.\",\"properties\":{\"asset_id\":{\"$ref\":\"#/components/sch" + "emas/ObservationUuid\"},\"camera_uuid\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"change_cau" + "se\":{\"enum\":[\"created\",\"evidence\",\"classification\"],\"type\":\"string\"},\"evidence_reason\":{\"anyOf\":" + "[{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"},{\"type\":\"null\"}]},\"evidence_state\":{\"" + "$ref\":\"#/components/schemas/ObservationEvidenceState\"},\"label\":{\"maxLength\":31,\"minLength\":1,\"ty" + "pe\":\"string\"},\"observation_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"observed_at\":{\"$" + "ref\":\"#/components/schemas/ObservationTime\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"snapshot" + "_url\":{\"pattern\":\"^/api/observations/[0-9a-f-]{36}/snapshot$\",\"type\":\"string\"}},\"required\":[\"obs" + "ervation_id\",\"revision\",\"camera_uuid\",\"label\",\"observed_at\",\"change_cause\",\"evidence_state\",\"evi" + "dence_reason\",\"asset_id\"],\"type\":\"object\"},\"ObservationClassification\":{\"additionalProperties\":t" + "rue,\"description\":\"Stored classification annotation (contract \302\24710.3). P2.\",\"properties\":{\"annot" + "ation_revision\":{\"minimum\":1,\"type\":\"integer\"},\"classified_at\":{\"$ref\":\"#/components/schemas/Obs" + "ervationTime\"},\"classifier\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"confidence\":{\"maximu" + "m\":1,\"minimum\":0,\"type\":\"number\"},\"created_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"" + "input_asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"input_revision\":{\"minimum\":1,\"t" + "ype\":\"integer\"},\"key\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":" + "\"string\"},\"kind\":{\"enum\":[\"automated\",\"owner_confirmed\"],\"type\":\"string\"},\"label\":{\"maxLength\":1" + "28,\"minLength\":1,\"type\":\"string\"},\"model_version\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"tax" + "onomy_id\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"updated_at\":{\"$ref\":\"#/components/schemas/O" + "bservationTime\"},\"writer_namespace\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":\"string\"}},\"" + "required\":[\"key\",\"writer_namespace\",\"classifier\",\"model_version\",\"label\",\"taxonomy_id\",\"confiden" + "ce\",\"classified_at\",\"input_asset_id\",\"input_revision\",\"kind\",\"annotation_revision\",\"created_at\"," + "\"updated_at\"],\"type\":\"object\"},\"ObservationClassificationResult\":{\"additionalProperties\":true,\"p" + "roperties\":{\"annotation\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"changed\":{\"t" + "ype\":\"boolean\"},\"observation_revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"changed\",\"ob" + "servation_revision\",\"annotation\"],\"type\":\"object\"},\"ObservationClassificationWrite\":{\"additional" + "Properties\":false,\"description\":\"PUT body (contract \302\24710.3). Closed: unknown fields return 400. " + "The writer namespace is derived by the server from the authenticated user, never from this body." + "\",\"properties\":{\"classified_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"classifier\":{\"m" + "axLength\":64,\"minLength\":1,\"type\":\"string\"},\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number" + "\"},\"expected_revision\":{\"description\":\"0 or absent means the key must not exist yet.\",\"minimum\":" + "0,\"type\":\"integer\"},\"input_asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"input_revi" + "sion\":{\"minimum\":1,\"type\":\"integer\"},\"kind\":{\"enum\":[\"automated\",\"owner_confirmed\"],\"type\":\"stri" + "ng\"},\"label\":{\"maxLength\":128,\"minLength\":1,\"type\":\"string\"},\"model_version\":{\"maxLength\":64,\"ty" + "pe\":[\"string\",\"null\"]},\"taxonomy_id\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"writer\":{\"descri" + "ption\":\"Same rule as writer_namespace.\",\"type\":\"string\"},\"writer_namespace\":{\"description\":\"Opti" + "onal. Accepted and ignored when it names the principal's derived namespace; any other value retu" + "rns 403 namespace_forbidden.\",\"type\":\"string\"}},\"required\":[\"classifier\",\"label\",\"confidence\",\"c" + "lassified_at\",\"input_asset_id\",\"input_revision\",\"kind\"],\"type\":\"object\"},\"ObservationClassificat" + "ions\":{\"additionalProperties\":true,\"description\":\"Annotations split by kind, each ordered by upd" + "ated_at descending. Both lists are empty in P1.\",\"properties\":{\"automated\":{\"items\":{\"$ref\":\"#/c" + "omponents/schemas/ObservationClassification\"},\"maxItems\":8,\"type\":\"array\"},\"owner_confirmed\":{\"i" + "tems\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"maxItems\":8,\"type\":\"array\"}},\"r" + "equired\":[\"automated\",\"owner_confirmed\"],\"type\":\"object\"},\"ObservationDetector\":{\"additionalProp" + "erties\":true,\"properties\":{\"engine_key\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"engine_t" + "ype\":{\"enum\":[\"object\",\"api\",\"onvif\",\"external\"],\"type\":\"string\"},\"model\":{\"description\":\"Model " + "basename only, never a path.\",\"maxLength\":128,\"type\":[\"string\",\"null\"]},\"model_version\":{\"maxLen" + "gth\":64,\"type\":[\"string\",\"null\"]}},\"required\":[\"engine_key\",\"engine_type\",\"model\",\"model_version" + "\"],\"type\":\"object\"},\"ObservationError\":{\"additionalProperties\":true,\"description\":\"Coded error (" + "contract \302\24711, D2): a valid ApiError with a string error and a stable code. Clients branch on st" + "atus and code, never on the message.\",\"properties\":{\"code\":{\"enum\":[\"invalid_request\",\"invalid_c" + "ursor\",\"forbidden\",\"namespace_forbidden\",\"observation_not_found\",\"evidence_unavailable\",\"evidenc" + "e_pending\",\"evidence_expired\",\"cursor_expired\",\"revision_conflict\",\"owner_decision_locked\",\"anno" + "tation_limit\",\"evidence_mismatch\",\"not_implemented\",\"service_unavailable\",\"internal_error\"],\"typ" + "e\":\"string\"},\"current\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"error\":{\"type\"" + ":\"string\"},\"evidence_state\":{\"$ref\":\"#/components/schemas/ObservationEvidenceState\"},\"expired_at" + "\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"field\":{\"type\":\"s" + "tring\"},\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"observation_asset_id\":{\"$ref\":\"#/components/sche" + "mas/ObservationUuid\"},\"oldest_cursor\":{\"maxLength\":256,\"minLength\":1,\"type\":\"string\"},\"oldest_ob" + "served_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"reason\":" + "{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"},\"retry_after_seconds\":{\"maximum\":30,\"m" + "inimum\":1,\"type\":\"integer\"}},\"required\":[\"error\",\"code\"],\"type\":\"object\"},\"ObservationEvidence\":" + "{\"additionalProperties\":true,\"allOf\":[{\"else\":{\"properties\":{\"reason\":{\"$ref\":\"#/components/sche" + "mas/ObservationEvidenceReason\"}},\"type\":\"object\"},\"if\":{\"properties\":{\"state\":{\"enum\":[\"pending\"" + ",\"ready\"]}},\"type\":\"object\"},\"then\":{\"properties\":{\"reason\":{\"type\":\"null\"}},\"type\":\"object\"}},{" + "\"else\":{\"not\":{\"properties\":{\"snapshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type" + "\":\"object\"},\"type\":\"object\"},\"if\":{\"properties\":{\"state\":{\"const\":\"ready\"}},\"type\":\"object\"},\"th" + "en\":{\"properties\":{\"snapshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"" + "}}],\"description\":\"Evidence asset (contract \302\2474). Every observation has one; siblings in a batch" + " share asset_id.\",\"properties\":{\"asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"byte" + "s\":{\"minimum\":0,\"type\":[\"integer\",\"null\"]},\"captured_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas" + "/ObservationTime\"},{\"type\":\"null\"}]},\"expires_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Observ" + "ationTime\"},{\"type\":\"null\"}]},\"height\":{\"minimum\":1,\"type\":[\"integer\",\"null\"]},\"media_type\":{\"en" + "um\":[\"image/jpeg\",null],\"type\":[\"string\",\"null\"]},\"origin\":{\"enum\":[\"inference_input\",\"submitted" + "_image\",null],\"type\":[\"string\",\"null\"]},\"reason\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Observa" + "tionEvidenceReason\"},{\"type\":\"null\"}]},\"snapshot_url\":{\"description\":\"Present only when state is" + " ready. Relative and authenticated.\",\"pattern\":\"^/api/observations/[0-9a-f-]{36}/snapshot$\",\"typ" + "e\":\"string\"},\"state\":{\"$ref\":\"#/components/schemas/ObservationEvidenceState\"},\"transform\":{\"anyO" + "f\":[{\"$ref\":\"#/components/schemas/ObservationTransform\"},{\"type\":\"null\"}]},\"width\":{\"minimum\":1," + "\"type\":[\"integer\",\"null\"]}},\"required\":[\"asset_id\",\"state\",\"reason\",\"origin\",\"media_type\",\"width" + "\",\"height\",\"bytes\",\"captured_at\",\"expires_at\",\"transform\"],\"type\":\"object\"},\"ObservationEvidence" + "Reason\":{\"description\":\"Bounded reason (contract \302\2474.3). Consumers treat an unknown reason by it" + "s state.\",\"enum\":[\"queue_full\",\"budget_exhausted\",\"encode_failed\",\"write_failed\",\"interrupted\",\"" + "no_matching_media\",\"disabled\",\"retention_expired\",\"deleted\"],\"type\":\"string\"},\"ObservationEviden" + "ceState\":{\"enum\":[\"pending\",\"ready\",\"unavailable\",\"expired\"],\"type\":\"string\"},\"ObservationPage\":" + "{\"additionalProperties\":true,\"description\":\"History page (contract \302\2477.2-7.3). Empty items with " + "has_more=true is valid; continue from next_cursor.\",\"properties\":{\"has_more\":{\"type\":\"boolean\"}," + "\"items\":{\"items\":{\"$ref\":\"#/components/schemas/Observation\"},\"maxItems\":500,\"type\":\"array\"},\"nex" + "t_cursor\":{\"maxLength\":256,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"items\",\"next_cursor\",\"ha" + "s_more\"],\"type\":\"object\"},\"ObservationPipelineDiagnostics\":{\"additionalProperties\":true,\"descrip" + "tion\":\"INT_01 observation pipeline diagnostics (observation_diagnostics_to_json(), contract sect" + "ion 15). Counters are per process run unless noted; per-camera rows come from the last retention" + " pass and may be up to 5 minutes old.\",\"properties\":{\"accepted\":{\"minimum\":0,\"type\":\"integer\"},\"" + "active\":{\"description\":\"False when evidence is unused: no queue exists, counters are zero and no" + " observations are retained.\",\"type\":\"boolean\"},\"budget_exhausted_cameras\":{\"minimum\":0,\"type\":\"i" + "nteger\"},\"cameras\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"budget_exhausted\":{\"type" + "\":\"boolean\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]" + "{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"max_age_days\":{\"minimum\":0,\"type\":\"integer\"},\"max_bytes\"" + ":{\"minimum\":0,\"type\":\"integer\"},\"retained_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"ca" + "mera_uuid\",\"retained_bytes\",\"max_bytes\",\"max_age_days\",\"budget_exhausted\"],\"type\":\"object\"},\"typ" + "e\":\"array\"},\"dropped\":{\"additionalProperties\":true,\"description\":\"Observations dropped at admiss" + "ion, by reason.\",\"properties\":{\"budget_exhausted\":{\"minimum\":0,\"type\":\"integer\"},\"disabled\":{\"mi" + "nimum\":0,\"type\":\"integer\"},\"queue_full\":{\"minimum\":0,\"type\":\"integer\"},\"untracked\":{\"minimum\":0," + "\"type\":\"integer\"}},\"required\":[\"queue_full\",\"budget_exhausted\",\"disabled\",\"untracked\"],\"type\":\"o" + "bject\"},\"evidence\":{\"additionalProperties\":true,\"properties\":{\"backlog_bytes\":{\"minimum\":0,\"type" + "\":\"integer\"},\"backlog_items\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_written\":{\"minimum\":0,\"type\":" + "\"integer\"},\"drop_ring_depth\":{\"minimum\":0,\"type\":\"integer\"},\"failures\":{\"additionalProperties\":t" + "rue,\"properties\":{\"db_error\":{\"minimum\":0,\"type\":\"integer\"},\"disabled\":{\"minimum\":0,\"type\":\"inte" + "ger\"},\"encode_failed\":{\"minimum\":0,\"type\":\"integer\"},\"interrupted\":{\"minimum\":0,\"type\":\"integer\"" + "},\"write_failed\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"encode_failed\",\"write_failed\",\"db_" + "error\",\"disabled\",\"interrupted\"],\"type\":\"object\"},\"high_water_bytes\":{\"minimum\":0,\"type\":\"intege" + "r\"},\"high_water_items\":{\"minimum\":0,\"type\":\"integer\"},\"queue_allocated\":{\"type\":\"boolean\"},\"read" + "y\":{\"minimum\":0,\"type\":\"integer\"},\"unavailable_submitted\":{\"minimum\":0,\"type\":\"integer\"},\"worker" + "_running\":{\"type\":\"boolean\"}},\"required\":[\"queue_allocated\",\"worker_running\",\"backlog_items\",\"ba" + "cklog_bytes\",\"high_water_items\",\"high_water_bytes\",\"drop_ring_depth\",\"ready\",\"unavailable_submit" + "ted\",\"bytes_written\",\"failures\"],\"type\":\"object\"},\"history\":{\"additionalProperties\":true,\"proper" + "ties\":{\"available\":{\"description\":\"False when the observation tables could not be read; the DB-d" + "erived values are then zero.\",\"type\":\"boolean\"},\"epoch_regenerations\":{\"minimum\":0,\"type\":\"integ" + "er\"},\"head_seq\":{\"minimum\":0,\"type\":\"integer\"},\"observations\":{\"minimum\":0,\"type\":\"integer\"},\"ol" + "dest_observed_at\":{\"anyOf\":[{\"type\":\"null\"},{\"$ref\":\"#/components/schemas/ObservationTime\"}]},\"o" + "ldest_seq\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"available\",\"observations\",\"oldest_seq\",\"" + "head_seq\",\"oldest_observed_at\",\"epoch_regenerations\"],\"type\":\"object\"},\"publications\":{\"addition" + "alProperties\":true,\"properties\":{\"expired\":{\"minimum\":0,\"type\":\"integer\"},\"failed\":{\"minimum\":0," + "\"type\":\"integer\"},\"pending\":{\"minimum\":0,\"type\":\"integer\"},\"relay_lag_seconds\":{\"description\":\"A" + "ge of the oldest pending publication; 0 when none is pending.\",\"minimum\":0,\"type\":\"number\"},\"rel" + "ay_running\":{\"type\":\"boolean\"},\"relayed\":{\"minimum\":0,\"type\":\"integer\"},\"relayed_no_route\":{\"min" + "imum\":0,\"type\":\"integer\"}},\"required\":[\"pending\",\"failed\",\"expired\",\"relay_lag_seconds\",\"relay_r" + "unning\",\"relayed\",\"relayed_no_route\"],\"type\":\"object\"}},\"required\":[\"active\",\"accepted\",\"dropped" + "\",\"evidence\",\"history\",\"publications\",\"budget_exhausted_cameras\",\"cameras\"],\"type\":\"object\"},\"Ob" + "servationRecordingReference\":{\"additionalProperties\":true,\"properties\":{\"recording_id\":{\"minimum" + "\":1,\"type\":\"integer\"}},\"required\":[\"recording_id\"],\"type\":\"object\"},\"ObservationTime\":{\"descript" + "ion\":\"UTC RFC 3339 with millisecond precision and a Z suffix (contract \302\2472).\",\"format\":\"date-tim" + "e\",\"type\":\"string\"},\"ObservationTrack\":{\"additionalProperties\":true,\"description\":\"Track hint on" + "ly; compare id, namespace and session together. No lifecycle is implied (contract \302\2473.4).\",\"prop" + "erties\":{\"id\":{\"pattern\":\"^[0-9]{1,64}$\",\"type\":\"string\"},\"namespace\":{\"maxLength\":64,\"minLength" + "\":1,\"type\":\"string\"},\"session\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}},\"required\":[\"id\"" + ",\"namespace\",\"session\"],\"type\":\"object\"},\"ObservationTransform\":{\"additionalProperties\":true,\"de" + "scription\":\"Audit record of the inference-to-stored-image mapping: x = (u - pad_x) / scale_x, y " + "= (v - pad_y) / scale_y (contract \302\2474.4).\",\"properties\":{\"inference_height\":{\"minimum\":1,\"type\":" + "\"integer\"},\"inference_width\":{\"minimum\":1,\"type\":\"integer\"},\"mode\":{\"enum\":[\"none\",\"resize\",\"let" + "terbox\"],\"type\":\"string\"},\"pad_x\":{\"minimum\":0,\"type\":\"number\"},\"pad_y\":{\"minimum\":0,\"type\":\"num" + "ber\"},\"scale_x\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"scale_y\":{\"exclusiveMinimum\":0,\"type\":\"n" + "umber\"},\"source_height\":{\"minimum\":1,\"type\":\"integer\"},\"source_width\":{\"minimum\":1,\"type\":\"integ" + "er\"}},\"required\":[\"mode\",\"source_width\",\"source_height\",\"inference_width\",\"inference_height\",\"sc" + "ale_x\",\"scale_y\",\"pad_x\",\"pad_y\"],\"type\":\"object\"},\"ObservationUuid\":{\"description\":\"Lowercase c" + "anonical UUID (contract \302\2472).\",\"pattern\":\"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a" + "-f]{12}$\",\"type\":\"string\"},\"OnvifAddRequest\":{\"additionalProperties\":false,\"properties\":{\"device" + "_url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"password\":{\"maxLength\":63,\"type\":\"string\"" + "},\"profile_token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"maxLength\":255," + "\"minLength\":1,\"type\":\"string\"},\"username\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[\"device_" + "url\",\"profile_token\",\"stream_name\"],\"type\":\"object\"},\"OnvifAddResult\":{\"additionalProperties\":fa" + "lse,\"properties\":{\"message\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"success\":{\"const\"" + ":true}},\"required\":[\"success\",\"message\",\"stream_name\"],\"type\":\"object\"},\"OnvifClaimRequest\":{\"ad" + "ditionalProperties\":false,\"properties\":{\"inventory_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"stre" + "am_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"inventory_uuid\",\"stream_n" + "ame\"],\"type\":\"object\"},\"OnvifDevice\":{\"additionalProperties\":false,\"properties\":{\"addresses\":{\"i" + "tems\":{\"type\":\"string\"},\"type\":\"array\"},\"claim_state\":{\"type\":\"string\"},\"claimed_camera_uuid\":{\"" + "type\":\"string\"},\"device_service\":{\"type\":\"string\"},\"discovery_time\":{\"type\":\"integer\"},\"duplicat" + "e_suspected\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"firmware_version\":{\"type\":\"string\"" + "},\"first_seen_at\":{\"type\":\"integer\"},\"hardware_id\":{\"type\":\"string\"},\"imaging_service\":{\"type\":\"" + "string\"},\"inventory_uuid\":{\"type\":\"string\"},\"ip_address\":{\"type\":\"string\"},\"last_scan_network\":{" + "\"type\":\"string\"},\"last_seen_at\":{\"type\":\"integer\"},\"mac_address\":{\"type\":\"string\"},\"manufacturer" + "\":{\"type\":\"string\"},\"media_service\":{\"type\":\"string\"},\"model\":{\"type\":\"string\"},\"online\":{\"type\"" + ":\"boolean\"},\"ptz_service\":{\"type\":\"string\"},\"serial_number\":{\"type\":\"string\"}},\"required\":[\"endp" + "oint\",\"device_service\",\"media_service\",\"ptz_service\",\"imaging_service\",\"manufacturer\",\"model\",\"f" + "irmware_version\",\"serial_number\",\"hardware_id\",\"ip_address\",\"mac_address\",\"inventory_uuid\",\"last" + "_scan_network\",\"claim_state\",\"claimed_camera_uuid\",\"discovery_time\",\"first_seen_at\",\"last_seen_a" + "t\",\"online\",\"duplicate_suspected\",\"addresses\"],\"type\":\"object\"},\"OnvifDevicesResponse\":{\"additio" + "nalProperties\":false,\"properties\":{\"devices\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifDevice\"" + "},\"type\":\"array\"}},\"required\":[\"devices\"],\"type\":\"object\"},\"OnvifDiscoveryRequest\":{\"additionalP" + "roperties\":false,\"properties\":{\"network\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[],\"type\"" + ":\"object\"},\"OnvifDiscoveryStatus\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":" + "\"boolean\"},\"interval\":{\"type\":\"integer\"},\"network\":{\"type\":\"string\"}},\"required\":[\"enabled\",\"net" + "work\",\"interval\"],\"type\":\"object\"},\"OnvifProfile\":{\"additionalProperties\":false,\"properties\":{\"b" + "itrate\":{\"type\":\"integer\"},\"encoding\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type" + "\":\"integer\"},\"name\":{\"type\":\"string\"},\"ptz_configuration_token\":{\"type\":\"string\"},\"ptz_supported" + "\":{\"type\":\"boolean\"},\"snapshot_uri\":{\"type\":\"string\"},\"stream_uri\":{\"type\":\"string\"},\"token\":{\"t" + "ype\":\"string\"},\"video_source_token\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"t" + "oken\",\"name\",\"video_source_token\",\"ptz_configuration_token\",\"snapshot_uri\",\"stream_uri\",\"encodin" + "g\",\"width\",\"height\",\"fps\",\"bitrate\",\"ptz_supported\"],\"type\":\"object\"},\"OnvifProfilesResponse\":{\"" + "additionalProperties\":false,\"properties\":{\"profiles\":{\"items\":{\"$ref\":\"#/components/schemas/Onvi" + "fProfile\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"profiles\"],\"type\":\"object\"},\"OnvifTestReq" + "uest\":{\"additionalProperties\":false,\"properties\":{\"insecure\":{\"type\":\"boolean\"},\"password\":{\"max" + "Length\":63,\"type\":\"string\"},\"url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"username\":{\"m" + "axLength\":63,\"type\":\"string\"}},\"required\":[\"url\"],\"type\":\"object\"},\"OpenApiCoverage\":{\"additiona" + "lProperties\":true,\"description\":\"Describes the canonical pilot coverage, not user or camera iden" + "tities. Omission of an unspecified API is not a denial of access.\",\"properties\":{\"canonical_revi" + "sion\":{\"type\":\"string\"},\"covered_operations\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"descrip" + "tion\":{\"type\":\"string\"},\"partial\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"partial\",\"canoni" + "cal_revision\",\"covered_operations\"],\"type\":\"object\"},\"OpenApiDocument\":{\"additionalProperties\":t" + "rue,\"description\":\"A valid OpenAPI 3.1.1 document projected for the actual authenticated princip" + "al. This response schema checks its envelope; independent OpenAPI lint checks document validity." + " It can contain no camera-specific eligible operations while retaining authenticated discovery/l" + "ist operations.\",\"properties\":{\"components\":{\"additionalProperties\":true,\"type\":\"object\"},\"info\"" + ":{\"additionalProperties\":true,\"properties\":{\"title\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"" + "}},\"required\":[\"title\",\"version\"],\"type\":\"object\"},\"openapi\":{\"const\":\"3.1.1\",\"type\":\"string\"},\"" + "paths\":{\"additionalProperties\":true,\"type\":\"object\"},\"x-lightnvr-coverage\":{\"$ref\":\"#/components" + "/schemas/OpenApiCoverage\"}},\"required\":[\"openapi\",\"info\",\"paths\",\"x-lightnvr-coverage\"],\"type\":\"" + "object\"},\"PasswordAdminReset\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\"" + ":\"string\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"new_" + "password\"],\"type\":\"object\"},\"PasswordChangeRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Pass" + "wordSelfChange\"},{\"$ref\":\"#/components/schemas/PasswordAdminReset\",\"x-lightnvr-visibility\":{\"kin" + "d\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}],\"descript" + "ion\":\"New password must contain at least eight UTF-8 bytes. Self-service requires the current pa" + "ssword; effective users.manage can reset passwords except its own forced first-login change. Loc" + "ked/default-password rules are enforced by the server.\"},\"PasswordChangedResponse\":{\"additionalP" + "roperties\":false,\"properties\":{\"must_change_password\":{\"const\":false},\"success\":{\"const\":true}}," + "\"required\":[\"success\",\"must_change_password\"],\"type\":\"object\"},\"PasswordLockRequest\":{\"additiona" + "lProperties\":true,\"properties\":{\"locked\":{\"type\":\"boolean\"}},\"required\":[\"locked\"],\"type\":\"objec" + "t\"},\"PasswordSelfChange\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"str" + "ing\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"old_passw" + "ord\",\"new_password\"],\"type\":\"object\"},\"PlaybackTransport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_onl" + "y\",\"hls_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"PlayerTelemetry\":{\"additionalP" + "roperties\":true,\"properties\":{\"rebuffer_count\":{\"type\":\"integer\"},\"rebuffer_duration_ms\":{\"type\"" + ":\"number\"},\"resolution_switches\":{\"type\":\"integer\"},\"session_id\":{\"type\":\"string\"},\"stream_name\"" + ":{\"type\":\"string\"},\"transport\":{\"type\":\"string\"},\"ttff_ms\":{\"type\":\"number\"},\"webrtc_rtt_ms\":{\"t" + "ype\":\"number\"}},\"required\":[\"stream_name\"],\"type\":\"object\"},\"ProcessLiveness\":{\"additionalProper" + "ties\":true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"liven" + "ess\":{\"const\":true,\"type\":\"boolean\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"inte" + "ger\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests" + "\",\"timestamp\",\"liveness\"],\"type\":\"object\"},\"PtzAxesRequest\":{\"additionalProperties\":false,\"descr" + "iption\":\"Finite float-representable coordinates in the camera default space. Camera-advertised r" + "anges vary; the server does not assume normalized position units. Omitted axes retain existing n" + "ative zero/axis-presence semantics.\",\"minProperties\":1,\"properties\":{\"pan\":{\"maximum\":3.40282346" + "63852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tilt\":{\"maximum\":3.402823466385" + "2886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"zoom\":{\"maximum\":3.4028234663852886" + "e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[],\"type\":\"object\"},\"PtzCapa" + "bilities\":{\"additionalProperties\":false,\"properties\":{\"has_absolute_move\":{\"type\":\"boolean\"},\"ha" + "s_continuous_move\":{\"type\":\"boolean\"},\"has_home\":{\"type\":\"boolean\"},\"has_home_position\":{\"type\":" + "\"boolean\"},\"has_pan_tilt\":{\"type\":\"boolean\"},\"has_presets\":{\"type\":\"boolean\"},\"has_relative_move" + "\":{\"type\":\"boolean\"},\"has_zoom\":{\"type\":\"boolean\"},\"max_presets\":{\"type\":\"integer\"},\"max_x\":{\"ty" + "pe\":\"number\"},\"max_y\":{\"type\":\"number\"},\"max_z\":{\"type\":\"number\"},\"onvif_queried\":{\"type\":\"boole" + "an\"},\"pan_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"ptz_enabled\":{\"type\":\"boolean" + "\"},\"ptz_service_url\":{\"type\":\"string\"},\"tilt_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRan" + "ge\"},\"zoom_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"}},\"required\":[\"ptz_enabled\",\"o" + "nvif_queried\",\"has_pan_tilt\",\"has_zoom\",\"has_continuous_move\",\"has_absolute_move\",\"has_relative_" + "move\",\"has_home_position\",\"has_presets\",\"has_home\",\"pan_range\",\"tilt_range\",\"zoom_range\",\"max_x\"" + ",\"max_y\",\"max_z\",\"max_presets\",\"ptz_service_url\"],\"type\":\"object\"},\"PtzGotoPresetRequest\":{\"addi" + "tionalProperties\":false,\"properties\":{\"token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"r" + "equired\":[\"token\"],\"type\":\"object\"},\"PtzPresetRequest\":{\"additionalProperties\":false,\"properties" + "\":{\"name\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"PtzPresetResult\":{\"a" + "dditionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true},\"t" + "oken\":{\"type\":\"string\"}},\"required\":[\"success\",\"message\",\"token\"],\"type\":\"object\"},\"PtzPresets\":" + "{\"additionalProperties\":false,\"properties\":{\"count\":{\"type\":\"integer\"},\"presets\":{\"items\":{\"addi" + "tionalProperties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"}},\"requi" + "red\":[\"token\",\"name\"],\"type\":\"object\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"presets\",\"cou" + "nt\"],\"type\":\"object\"},\"RecordingBatchProtectionRequest\":{\"additionalProperties\":true,\"descriptio" + "n\":\"Empty arrays are accepted. Invalid or missing IDs count as failures; an existing forbidden m" + "ember rejects the entire request before writes.\",\"properties\":{\"ids\":{\"items\":{},\"type\":\"array\"}" + ",\"protected\":{\"type\":\"boolean\"}},\"required\":[\"ids\",\"protected\"],\"type\":\"object\"},\"RecordingBatch" + "ProtectionResult\":{\"additionalProperties\":true,\"properties\":{\"fail_count\":{\"minimum\":0,\"type\":\"i" + "nteger\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"success_count\":{\"minimum\":0" + ",\"type\":\"integer\"}},\"required\":[\"success_count\",\"fail_count\",\"protected\",\"message\"],\"type\":\"obje" + "ct\"},\"RecordingBatchTagsRequest\":{\"additionalProperties\":true,\"description\":\"Add/remove are opti" + "onal; wrong-typed arrays and non-string or empty elements are ignored by the handler. All IDs ar" + "e authorized before writes. Database failures may follow earlier successful tag writes.\",\"proper" + "ties\":{\"add\":{\"items\":{},\"type\":\"array\"},\"ids\":{\"items\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"" + "maxItems\":10000,\"minItems\":1,\"type\":\"array\"},\"remove\":{\"items\":{},\"type\":\"array\"}},\"required\":[\"" + "ids\"],\"type\":\"object\"},\"RecordingBatchTagsResult\":{\"additionalProperties\":true,\"properties\":{\"re" + "cordings_count\":{\"description\":\"Number of authorized input IDs processed, including duplicate ID" + "s.\",\"minimum\":0,\"type\":\"integer\"},\"tags_added\":{\"description\":\"Successful per-recording add stat" + "ement executions, including INSERT OR IGNORE no-ops; not changed rows.\",\"minimum\":0,\"type\":\"inte" + "ger\"},\"tags_removed\":{\"description\":\"Successful per-recording remove statement executions, inclu" + "ding absent-tag no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"recording" + "s_count\",\"tags_added\",\"tags_removed\"],\"type\":\"object\"},\"RecordingDeletionFilter\":{\"additionalPro" + "perties\":false,\"description\":\"stream_name (or stream fallback) selects comma-separated current c" + "amera names. Without a nonempty stream filter, all-fleet recording.delete authority is required." + " Authorized camera UUIDs and collection membership are captured before dispatch; caller-supplied" + " camera UUID fields do not grant scope. Supplied start/end must be valid UTC YYYY-MM-DDTHH:mm:ss" + " strings, optionally followed by fractional seconds and/or Z; fractions are truncated to whole s" + "econds. Bounds must be after the Unix epoch and start must not exceed end. Invalid types, calend" + "ar dates, suffixes, or ranges return400 before job creation. Omitted bounds are unbounded. detec" + "tion/protected must be -1, 0, or 1. Unknown keys, duplicate keys (including date-case aliases), " + "simultaneous stream and stream_name, invalid types, and empty selectors return400. Date key spel" + "ling is case-insensitive; other keys are case-sensitive. Caller-supplied camera/stream UUID sele" + "ctors are unsupported and rejected. In-progress rows are excluded.\",\"patternProperties\":{\"^(\077!st" + "art$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"}}," + "\"properties\":{\"capture_method\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"collection_uuid\"" + ":{\"maxLength\":36,\"minLength\":1,\"type\":\"string\"},\"detection\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"" + "detection_label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"end\":{\"maxLength\":64,\"minLengt" + "h\":1,\"type\":\"string\"},\"protected\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"start\":{\"maxLength\":64,\"mi" + "nLength\":1,\"type\":\"string\"},\"stream\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"stream_nam" + "e\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"tag\":{\"maxLength\":511,\"minLength\":1,\"type\":\"" + "string\"}},\"required\":[],\"type\":\"object\"},\"RecordingDeletionJobAccepted\":{\"additionalProperties\":" + "true,\"properties\":{\"job_id\":{\"type\":\"string\"},\"status\":{\"const\":\"started\"}},\"required\":[\"job_id\"" + ",\"status\"],\"type\":\"object\"},\"RecordingDeletionProgress\":{\"additionalProperties\":true,\"descriptio" + "n\":\"complete is true for both complete and error terminal states. A complete job can have failed" + " members or queued pending deletions; it does not imply every file is already removed. SQL failu" + "re is error; an empty successful filter completes with zero counts. No cancellation endpoint/sta" + "te exists.\",\"properties\":{\"complete\":{\"type\":\"boolean\"},\"current\":{\"minimum\":0,\"type\":\"integer\"}" + ",\"error_message\":{\"type\":\"string\"},\"failed\":{\"minimum\":0,\"type\":\"integer\"},\"job_id\":{\"type\":\"str" + "ing\"},\"pending_deletions\":{\"minimum\":0,\"type\":\"integer\"},\"status\":{\"enum\":[\"pending\",\"running\",\"" + "complete\",\"error\",\"unknown\"],\"type\":\"string\"},\"status_message\":{\"type\":\"string\"},\"succeeded\":{\"m" + "inimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"job_id\",\"status" + "\",\"total\",\"current\",\"succeeded\",\"pending_deletions\",\"failed\",\"status_message\",\"complete\"],\"type\"" + ":\"object\"},\"RecordingDeletionRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"description\":\"IDs " + "take precedence over filter. IDs accept safe positive integer JSON numbers or exact positive dec" + "imal uint64 strings. Invalid, unsafe numeric, or missing members become failed counts; a forbidd" + "en existing member rejects the whole request before job creation.\",\"properties\":{\"ids\":{\"items\":" + "{},\"minItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},{\"additionalProperties\":tru" + "e,\"properties\":{\"filter\":{\"$ref\":\"#/components/schemas/RecordingDeletionFilter\"},\"ids\":{\"anyOf\":" + "[{\"items\":{},\"minItems\":1,\"type\":\"array\"},{\"type\":[\"null\",\"boolean\",\"object\",\"number\",\"string\"]}" + "],\"description\":\"Array-valued ids take precedence over filter and must be nonempty. Other ids va" + "lues fall through to the filter handler.\"}},\"required\":[\"filter\"],\"type\":\"object\"}]},\"RecordingD" + "eletionResult\":{\"additionalProperties\":true,\"description\":\"Immediate200 has success/message. Que" + "ued202 also carries status deletion_pending; neither outcome promises deletion of unrelated reco" + "rdings.\",\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"deletion_pending\"},\"succes" + "s\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"RecordingDetail\":{\"additio" + "nalProperties\":true,\"description\":\"Authorized recording metadata. id is durable database identit" + "y, not list position. Times are UTC display strings with separate Unix seconds. In-progress rows" + " have null end_time/end_time_unix and changing effective duration. playback_issue is empty for n" + "o saved issue; invalid_media/no_video/decode_error cause playback422. Other diagnostic values do" + " not conclusively prohibit playback. Optional storage fields are omitted if their lookup fails. " + "file_path is existing authorized metadata, not a client-selected playback path. A finalized row " + "or timeline presence does not guarantee playable media.\",\"properties\":{\"camera_uuid\":{\"pattern\":" + "\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pend" + "ing\":{\"type\":\"boolean\"},\"detection_labels\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"" + "count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"t" + "ype\":\"object\"},\"type\":\"array\"},\"durable_copy_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"numbe" + "r\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_" + "source\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\"" + ":{\"minimum\":1,\"type\":\"integer\"},\"in_progress\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"strin" + "g\"},\"protected\":{\"type\":\"boolean\"},\"retention_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"" + "schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"" + "string\"},\"start_time_unix\":{\"type\":\"integer\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_p" + "olicy_version\":{\"type\":\"integer\"},\"storage_state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unava" + "ilable\",\"archiving\",\"archived\",\"hot_and_archive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"}" + ",\"stream\":{\"type\":\"string\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"e" + "nd_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"cap" + "ture_method\",\"schedule_restricted\",\"has_detection\",\"protected\"],\"type\":\"object\"},\"RecordingDetec" + "tionLabels\":{\"additionalProperties\":true,\"properties\":{\"labels\":{\"items\":{\"type\":\"string\"},\"type" + "\":\"array\"}},\"required\":[\"labels\"],\"type\":\"object\"},\"RecordingExportAccepted\":{\"additionalPropert" + "ies\":true,\"properties\":{\"token\":{\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"requi" + "red\":[\"token\",\"total\"],\"type\":\"object\"},\"RecordingExportRequest\":{\"additionalProperties\":true,\"d" + "escription\":\"Positive numeric IDs are converted to uint64. Filename is sanitized for attachment " + "delivery. Every member is authorized before a job is created; existing operational missing/incom" + "plete errors can abort earlier in input order.\",\"properties\":{\"filename\":{\"type\":\"string\"},\"ids\"" + ":{\"items\":{\"description\":\"Recording ID: a number through 9007199254740991 (JavaScript maximum sa" + "fe integer), otherwise an exact decimal string. Treat IDs as opaque values; do not convert strin" + "gs to Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[" + "1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"maxItems\":200,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"i" + "ds\"],\"type\":\"object\"},\"RecordingExportStatus\":{\"additionalProperties\":true,\"description\":\"error " + "is always present, usually empty until failure. Detected unavailable sources or ZIP32 member/siz" + "e-limit failures put the job in error and remove its temporary result. The current writer does n" + "ot consistently check filesystem read/write/close errors: complete is not a guarantee against un" + "detected I/O failure or a valid intact archive. No cancellation endpoint/state exists.\",\"propert" + "ies\":{\"current\":{\"minimum\":0,\"type\":\"integer\"},\"error\":{\"type\":\"string\"},\"status\":{\"enum\":[\"pend" + "ing\",\"running\",\"complete\",\"error\"],\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"req" + "uired\":[\"status\",\"current\",\"total\",\"error\"],\"type\":\"object\"},\"RecordingListItem\":{\"additionalPro" + "perties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null" + "\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pending\":{\"type\":\"boolean\"},\"detection_labels\":" + "{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"labe" + "l\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"durable_cop" + "y_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"" + "end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source\":{\"type\":\"boolean\"},\"file_path\":{\"ty" + "pe\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"in_progres" + "s\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"retenti" + "on_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedule_restricted\":{\"type\":[\"boolean\",\"nu" + "ll\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_time_unix\":{\"type\":\"integer" + "\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_version\":{\"type\":\"integer\"},\"storage_" + "state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable\",\"archiving\",\"archived\",\"hot_and_arc" + "hive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"tags\":{\"items\"" + ":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_t" + "ime\",\"end_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"siz" + "e\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"protected\",\"tags\"],\"type\":\"object\"},\"" + "RecordingPagination\":{\"additionalProperties\":true,\"description\":\"Counts use the same authorized " + "predicate as rows. Ordinary totals may use a ten-second cache invalidated by supported database " + "mutations; limit=all recounts and resets page1. No top-level total is emitted. When replay scope" + " is empty, the early empty envelope retains the normalized requested page and ordinary limit (al" + "l is treated as20).\",\"properties\":{\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"page\":{\"minimum\":1,\"t" + "ype\":\"integer\"},\"pages\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"" + "required\":[\"page\",\"pages\",\"total\",\"limit\"],\"type\":\"object\"},\"RecordingPlaybackPreparing\":{\"addit" + "ionalProperties\":true,\"properties\":{\"error\":{\"type\":\"string\"},\"source\":{\"const\":\"archive\"},\"stat" + "us\":{\"const\":\"preparing\"}},\"required\":[\"status\"],\"type\":\"object\"},\"RecordingPlaybackReady\":{\"add" + "itionalProperties\":true,\"properties\":{\"source\":{\"const\":\"archive\"},\"status\":{\"const\":\"ready\"}},\"" + "required\":[\"status\"],\"type\":\"object\"},\"RecordingProtectionRequest\":{\"additionalProperties\":true," + "\"properties\":{\"protected\":{\"type\":\"boolean\"}},\"required\":[\"protected\"],\"type\":\"object\"},\"Recordi" + "ngProtectionResult\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer" + "\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"protected\",\"mes" + "sage\"],\"type\":\"object\"},\"RecordingRetentionRequest\":{\"additionalProperties\":true,\"description\":\"" + "Numeric input is truncated toward zero through the C JSON integer accessor. Any negative value c" + "lears the override; zero means indefinite; positive values are days.\",\"properties\":{\"retention_d" + "ays\":{\"type\":\"number\"}},\"required\":[\"retention_days\"],\"type\":\"object\"},\"RecordingRetentionResult" + "\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"message\":{\"typ" + "e\":\"string\"},\"retention_days\":{\"type\":\"integer\"}},\"required\":[\"id\",\"retention_days\",\"message\"],\"" + "type\":\"object\"},\"RecordingTags\":{\"additionalProperties\":true,\"properties\":{\"tags\":{\"items\":{\"typ" + "e\":\"string\"},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTagsById\":{\"additio" + "nalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"tags\":{\"items\":{\"type\":\"s" + "tring\"},\"type\":\"array\"}},\"required\":[\"id\",\"tags\"],\"type\":\"object\"},\"RecordingTagsRequest\":{\"addi" + "tionalProperties\":true,\"description\":\"Only nonempty string elements are used; other elements are" + " ignored. The database trims tag text, truncates to its fixed UTF-8 byte buffer, and deduplicate" + "s. This is replacement, including clearing with an empty array.\",\"properties\":{\"tags\":{\"items\":{" + "},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTimeline\":{\"additionalProperti" + "es\":true,\"description\":\"One authorized camera and time window, capped at8640 segments. Optional " + "exact external-motion intervals may be absent on allocation failure.\",\"properties\":{\"detection_i" + "nterval_count\":{\"minimum\":0,\"type\":\"integer\"},\"detection_intervals\":{\"items\":{\"additionalPropert" + "ies\":true,\"properties\":{\"end_timestamp\":{\"type\":\"integer\"},\"start_timestamp\":{\"type\":\"integer\"}}" + ",\"required\":[\"start_timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"type\":\"array\"},\"end_time\":{\"ty" + "pe\":\"string\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/comp" + "onents/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"string\"},\"stream" + "\":{\"type\":\"string\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\",\"segment_count\"],\"t" + "ype\":\"object\"},\"RecordingTimelineByIds\":{\"additionalProperties\":true,\"description\":\"Requested ID" + "s are processed in request order. Missing, deleted-camera and denied recordings are omitted. Cou" + "nts and overall range reflect retained segments only; an empty result has empty start_time/end_t" + "ime. Duplicate input IDs can produce duplicate segments.\",\"properties\":{\"end_time\":{\"type\":\"stri" + "ng\"},\"multi_stream\":{\"const\":true},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"i" + "tems\":{\"$ref\":\"#/components/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"ty" + "pe\":\"string\"},\"stream\":{\"const\":\"(selected)\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end" + "_time\",\"segment_count\",\"multi_stream\"],\"type\":\"object\"},\"RecordingTimelineSegment\":{\"additionalP" + "roperties\":true,\"description\":\"Timeline timestamps are Unix seconds; display strings use server " + "local YYYY-MM-DD HH:MM:SS. local_* fields currently equal the Unix timestamp fields. In-progress" + " segments use an effective end for display. Timeline segments omit playback_issue and protected/" + "storage metadata: use detail/playback before treating a finalized segment as playable.\",\"propert" + "ies\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{" + "\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":\"string\"},\"end_timestamp\":{\"typ" + "e\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"description\":\"Recording ID: a number thr" + "ough 9007199254740991 (JavaScript maximum safe integer), otherwise an exact decimal string. Trea" + "t IDs as opaque values; do not convert strings to Number.\",\"oneOf\":[{\"maximum\":9007199254740991," + "\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"in_progress\":{" + "\"type\":\"boolean\"},\"local_end_timestamp\":{\"type\":\"integer\"},\"local_start_timestamp\":{\"type\":\"inte" + "ger\"},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"" + "type\":\"string\"},\"start_timestamp\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"id" + "\",\"stream\",\"camera_uuid\",\"start_time\",\"in_progress\",\"duration\",\"size\",\"capture_method\",\"schedule" + "_restricted\",\"has_detection\",\"end_time\",\"start_timestamp\",\"end_timestamp\",\"local_start_timestamp" + "\",\"local_end_timestamp\"],\"type\":\"object\"},\"RecordingsPage\":{\"additionalProperties\":true,\"propert" + "ies\":{\"pagination\":{\"$ref\":\"#/components/schemas/RecordingPagination\"},\"recordings\":{\"items\":{\"$" + "ref\":\"#/components/schemas/RecordingListItem\"},\"type\":\"array\"}},\"required\":[\"recordings\",\"pagina" + "tion\"],\"type\":\"object\"},\"SetupStatus\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"ty" + "pe\":\"boolean\"},\"setup_completed_at\":{\"type\":\"number\"}},\"required\":[\"complete\",\"setup_completed_a" + "t\"],\"type\":\"object\"},\"SetupUpdate\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\"" + ":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"SetupUpdated\":{\"additionalProperties\":true,\"propert" + "ies\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"Storag" + "eArchiveDeletion\":{\"additionalProperties\":false,\"properties\":{\"attempts\":{\"minimum\":0,\"type\":\"in" + "teger\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"next_attempt" + "_at\":{\"minimum\":0,\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"remaining_obj" + "ects\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"require" + "d\":[\"uuid\",\"recording_id\",\"created_at\",\"remaining_objects\",\"attempts\",\"next_attempt_at\",\"last_er" + "ror\"],\"type\":\"object\"},\"StorageArchiveResponse\":{\"additionalProperties\":false,\"properties\":{\"arc" + "hive_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"pending_deletion_count\":{\"minimum\":0,\"type\":\"integer" + "\"},\"pending_deletions\":{\"items\":{\"$ref\":\"#/components/schemas/StorageArchiveDeletion\"},\"maxItems" + "\":100,\"type\":\"array\"},\"recovery_read_only\":{\"type\":\"boolean\"},\"retrieval_cache_bytes\":{\"minimum\"" + ":0,\"type\":\"integer\"},\"retrieval_jobs\":{\"minimum\":0,\"type\":\"integer\"},\"s3_enabled\":{\"type\":\"boole" + "an\"},\"transfer_backlog_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"s3_enabled\",\"recovery" + "_read_only\",\"archive_bytes\",\"transfer_backlog_bytes\",\"retrieval_cache_bytes\",\"retrieval_jobs\",\"p" + "ending_deletion_count\",\"pending_deletions\"],\"type\":\"object\"},\"StorageArchiveRetryRequest\":{\"addi" + "tionalProperties\":false,\"properties\":{\"deletion_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}" + "},\"required\":[\"deletion_uuid\"],\"type\":\"object\"},\"StorageArchiveRetryResponse\":{\"additionalProper" + "ties\":false,\"properties\":{\"status\":{\"const\":\"retry_queued\",\"type\":\"string\"}},\"required\":[\"status" + "\"],\"type\":\"object\"},\"StorageCleanupRequest\":{\"additionalProperties\":false,\"properties\":{\"aggress" + "ive\":{\"type\":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"StorageCleanupResponse\":{\"additionalPro" + "perties\":false,\"properties\":{\"aggressive\":{\"type\":\"boolean\"},\"message\":{\"const\":\"Cleanup trigger" + "ed successfully\",\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"succes" + "s\",\"aggressive\",\"message\"],\"type\":\"object\"},\"StorageCompliancePolicy\":{\"additionalProperties\":fa" + "lse,\"properties\":{\"achieved_retention_days\":{\"type\":\"number\"},\"active_violation_count\":{\"minimum" + "\":0,\"type\":\"integer\"},\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"copy_defici" + "t_recordings\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"minimum\":0,\"type\":\"integ" + "er\"},\"expected_retention_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"maximu" + "m_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"minimum_forecast_met\":{\"type\":\"boolean\"},\"mini" + "mum_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"observed_daily_bytes\":{\"type\":\"number\"},\"pol" + "icy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_" + "bytes\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"required" + "_copy_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_window_days\":{\"type\":\"number\"}},\"required\":[" + "\"policy_uuid\",\"policy_name\",\"recording_count\",\"recording_bytes\",\"minimum_retention_days\",\"desire" + "d_retention_days\",\"maximum_retention_days\",\"required_copy_count\",\"copy_deficit_recordings\",\"acti" + "ve_violation_count\",\"sample_window_days\",\"observed_daily_bytes\",\"achieved_retention_days\",\"confi" + "dence\",\"expected_retention_days\",\"minimum_forecast_met\"],\"type\":\"object\"},\"StorageComplianceResp" + "onse\":{\"additionalProperties\":false,\"properties\":{\"active_violations\":{\"items\":{\"$ref\":\"#/compon" + "ents/schemas/StorageComplianceViolation\"},\"type\":\"array\"},\"forecast_note\":{\"type\":\"string\"},\"for" + "ecast_window_days\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/sche" + "mas/StorageCompliancePolicy\"},\"type\":\"array\"},\"targets\":{\"items\":{\"$ref\":\"#/components/schemas/S" + "torageComplianceTarget\"},\"type\":\"array\"}},\"required\":[\"forecast_window_days\",\"forecast_note\",\"ta" + "rgets\",\"policies\",\"active_violations\"],\"type\":\"object\"},\"StorageComplianceTarget\":{\"additionalPr" + "operties\":false,\"properties\":{\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"day" + "s_to_high_watermark\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"expected_retenti" + "on_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"observed_bytes\":{\"minimum\":0" + ",\"type\":\"integer\"},\"observed_daily_bytes\":{\"minimum\":0,\"type\":\"number\"},\"sample_count\":{\"minimum" + "\":0,\"type\":\"integer\"},\"sample_window_days\":{\"minimum\":0,\"type\":\"number\"},\"target_name\":{\"type\":\"" + "string\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"target_uuid\",\"ta" + "rget_name\",\"sample_window_days\",\"observed_daily_bytes\",\"sample_count\",\"observed_bytes\",\"confiden" + "ce\",\"days_to_high_watermark\",\"expected_retention_days\"],\"type\":\"object\"},\"StorageComplianceViola" + "tion\":{\"additionalProperties\":false,\"properties\":{\"details\":{\"type\":\"string\"},\"first_seen_at\":{\"" + "minimum\":0,\"type\":\"integer\"},\"last_seen_at\":{\"minimum\":0,\"type\":\"integer\"},\"policy_name\":{\"type\"" + ":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_id\":{\"anyOf\":[{\"m" + "inimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"type\":{\"type\":\"string\"},\"uuid\":{\"$ref\":\"#/compone" + "nts/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"policy_uuid\",\"policy_name\",\"recording_id\",\"type\",\"" + "details\",\"first_seen_at\",\"last_seen_at\"],\"type\":\"object\"},\"StorageHealthResponse\":{\"additionalPr" + "operties\":false,\"properties\":{\"capacity_target_free_pct\":{\"minimum\":0,\"type\":\"integer\"},\"fill_ra" + "te_bytes_per_day\":{\"type\":\"number\"},\"free_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"free_spac" + "e_pct\":{\"type\":\"number\"},\"last_check_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_deleted\"" + ":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_freed\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanu" + "p_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_deep_time\":{\"minimum\":0,\"type\":\"integer\"},\"pressure" + "_level\":{\"type\":\"string\"},\"pressure_level_num\":{\"minimum\":0,\"type\":\"integer\"},\"projected_seconds" + "_to_target\":{\"type\":\"number\"},\"total_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_space_byt" + "es\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"pressure_level\",\"pressure_level_num\",\"free_spac" + "e_pct\",\"fill_rate_bytes_per_day\",\"projected_seconds_to_target\",\"free_space_bytes\",\"total_space_b" + "ytes\",\"used_space_bytes\",\"last_check_time\",\"last_cleanup_time\",\"last_deep_time\",\"last_cleanup_de" + "leted\",\"last_cleanup_freed\",\"capacity_target_free_pct\"],\"type\":\"object\"},\"StorageMigration\":{\"ad" + "ditionalProperties\":false,\"properties\":{\"attempt_count\":{\"minimum\":0,\"type\":\"integer\"},\"bandwidt" + "h_limit_bps\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_copied\":{\"minimum\":0,\"type\":\"integer\"},\"bytes" + "_total\":{\"minimum\":0,\"type\":\"integer\"},\"cancel_requested\":{\"type\":\"boolean\"},\"checksum\":{\"anyOf\"" + ":[{\"pattern\":\"^[0-9a-f]{64}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"checksum_mode\":{\"const\":\"sha25" + "6\",\"type\":\"string\"},\"completed_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"c" + "reated_at\":{\"minimum\":0,\"type\":\"integer\"},\"destination_object_key\":{\"type\":\"string\"},\"destinatio" + "n_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"last_error\":{\"type\":\"string\"},\"max_at" + "tempts\":{\"minimum\":0,\"type\":\"integer\"},\"next_attempt_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"" + "},{\"type\":\"null\"}]},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"progress\":{\"minimum\":0" + ",\"type\":\"number\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254" + "740991,\"minimum\":1,\"type\":\"integer\"},\"source_object_key\":{\"type\":\"string\"},\"source_target_uuid\":" + "{\"$ref\":\"#/components/schemas/CameraUuid\"},\"started_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"}" + ",{\"type\":\"null\"}]},\"state\":{\"enum\":[\"queued\",\"copying\",\"verifying\",\"committing\",\"retry_wait\",\"cl" + "eanup_pending\",\"completed\",\"failed\",\"cancelled\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"typ" + "e\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"window_end_minute\":{\"maximum\":1" + "439,\"minimum\":0,\"type\":\"integer\"},\"window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"inte" + "ger\"}},\"required\":[\"uuid\",\"source_target_uuid\",\"destination_target_uuid\",\"recording_id\",\"operati" + "on\",\"source_object_key\",\"destination_object_key\",\"last_error\",\"state\",\"checksum_mode\",\"checksum\"" + ",\"bytes_total\",\"bytes_copied\",\"attempt_count\",\"max_attempts\",\"bandwidth_limit_bps\",\"progress\",\"n" + "ext_attempt_at\",\"cancel_requested\",\"window_start_minute\",\"window_end_minute\",\"revision\",\"created" + "_at\",\"updated_at\",\"started_at\",\"completed_at\"],\"type\":\"object\"},\"StorageMigrationCreate\":{\"addit" + "ionalProperties\":false,\"properties\":{\"destination_target_uuid\":{\"$ref\":\"#/components/schemas/Cam" + "eraUuid\"},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"recording_id\":{\"maximum\":9007199" + "254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"recording_id\",\"destination_target_uuid\"],\"" + "type\":\"object\"},\"StorageMigrationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":" + "{\"minimum\":0,\"type\":\"integer\"},\"jobs\":{\"items\":{\"$ref\":\"#/components/schemas/StorageMigration\"}," + "\"type\":\"array\"},\"worker_concurrency\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"jobs\",\"count\",\"w" + "orker_concurrency\"],\"type\":\"object\"},\"StoragePoliciesResponse\":{\"additionalProperties\":false,\"pr" + "operties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/sche" + "mas/StoragePolicy\"},\"type\":\"array\"}},\"required\":[\"policies\",\"count\"],\"type\":\"object\"},\"StoragePo" + "licy\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647," + "\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"ty" + "pe\":\"boolean\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"maximum\":3" + "6500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"defaul" + "t\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/compone" + "nts/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimu" + "m\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"}," + "\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"" + "anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"" + "maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"strin" + "g\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uu" + "id\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\"" + ":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"ty" + "pe\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"ty" + "pe\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maxi" + "mum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/Fle" + "etSelector\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/Ca" + "meraUuid\"}},\"required\":[\"uuid\",\"name\",\"enabled\",\"priority\",\"selector\",\"primary_target_uuid\",\"pri" + "mary_pool_uuid\",\"fallback_target_uuid\",\"replication_pool_uuid\",\"migration_target_uuid\",\"fallback" + "_mode\",\"minimum_retention_days\",\"desired_retention_days\",\"maximum_retention_days\",\"migration_aft" + "er_days\",\"required_copy_count\",\"pressure_priority\",\"archive_after_seconds\",\"hot_residency_second" + "s\",\"archive_protected\",\"archive_on_pressure\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"objec" + "t\"},\"StoragePolicyApplyRequest\":{\"additionalProperties\":false,\"properties\":{\"preview\":{\"type\":\"b" + "oolean\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revi" + "sion\",\"preview\"],\"type\":\"object\"},\"StoragePolicyApplyResponse\":{\"additionalProperties\":false,\"pr" + "operties\":{\"bytes\":{\"minimum\":0,\"type\":\"integer\"},\"past_new_retention_limit\":{\"minimum\":0,\"type\"" + ":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"recordings\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":" + "{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"unfinished_transfers\":{\"minimum\":0,\"t" + "ype\":\"integer\"}},\"required\":[\"revision\",\"preview\",\"recordings\",\"bytes\",\"past_new_retention_limit" + "\",\"unfinished_transfers\"],\"type\":\"object\"},\"StoragePolicyConflict\":{\"additionalProperties\":false" + ",\"properties\":{\"draft_precedes\":{\"type\":\"boolean\"},\"overlap_camera_count\":{\"minimum\":0,\"type\":\"i" + "nteger\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"" + "},\"priority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"policy_uuid\",\"policy_name\",\"pri" + "ority\",\"overlap_camera_count\",\"draft_precedes\"],\"type\":\"object\"},\"StoragePolicyCreate\":{\"additio" + "nalProperties\":false,\"anyOf\":[{\"properties\":{\"primary_target_uuid\":{\"$ref\":\"#/components/schemas" + "/CameraUuid\"}},\"required\":[\"primary_target_uuid\"]},{\"properties\":{\"primary_pool_uuid\":{\"anyOf\":[" + "{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]}},\"required\":[\"primary_pool_uuid\"]}]" + ",\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"arc" + "hive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_d" + "ays\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\"" + ":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{" + "\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2" + "147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"" + "type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migratio" + "n_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_r" + "etention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength" + "\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"}" + ",\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"pri" + "mary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"mini" + "mum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/" + "CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"}," + "\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\"],\"type\":" + "\"object\"},\"StoragePolicyPreview\":{\"additionalProperties\":false,\"properties\":{\"conflict_camera_co" + "unt\":{\"minimum\":0,\"type\":\"integer\"},\"conflict_policy_count\":{\"minimum\":0,\"type\":\"integer\"},\"conf" + "licts\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyConflict\"},\"type\":\"array\"},\"draft_ena" + "bled\":{\"type\":\"boolean\"},\"effective_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"matched_camera" + "_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePo" + "licyPreviewSample\"},\"maxItems\":50,\"type\":\"array\"},\"sample_truncated\":{\"type\":\"boolean\"},\"shadowe" + "d_camera_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"draft_enabled\",\"sample_truncated\",\"" + "matched_camera_count\",\"effective_camera_count\",\"shadowed_camera_count\",\"conflict_camera_count\",\"" + "conflict_policy_count\",\"sample\",\"conflicts\"],\"type\":\"object\"},\"StoragePolicyPreviewRequest\":{\"ad" + "ditionalProperties\":false,\"anyOf\":[{\"properties\":{\"revision\":{\"maximum\":9007199254740991,\"minimu" + "m\":1,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"re" + "vision\"]},{\"properties\":{\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"primary_target_" + "uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetS" + "elector\"}},\"required\":[\"name\",\"selector\",\"primary_target_uuid\"]},{\"properties\":{\"name\":{\"maxLeng" + "th\":127,\"minLength\":1,\"type\":\"string\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schem" + "as/CameraUuid\"},{\"type\":\"null\"}]},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"req" + "uired\":[\"name\",\"selector\",\"primary_pool_uuid\"]}],\"properties\":{\"archive_after_seconds\":{\"maximum" + "\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_pr" + "otected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"intege" + "r\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"ty" + "pe\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type" + "\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximu" + "m_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximu" + "m\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/s" + "chemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"typ" + "e\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maxim" + "um\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/compon" + "ents/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/" + "CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_poo" + "l_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_co" + "unt\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\"" + ":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"uuid\":{\"$ref\":\"#/" + "components/schemas/CameraUuid\"}},\"required\":[],\"type\":\"object\"},\"StoragePolicyPreviewSample\":{\"a" + "dditionalProperties\":false,\"properties\":{\"camera_name\":{\"type\":\"string\"},\"camera_uuid\":{\"$ref\":\"" + "#/components/schemas/CameraUuid\"},\"effective_policy\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Sto" + "ragePolicyWinner\"},{\"type\":\"null\"}]},\"overlapping_policy_count\":{\"minimum\":0,\"type\":\"integer\"}}," + "\"required\":[\"camera_uuid\",\"camera_name\",\"overlapping_policy_count\",\"effective_policy\"],\"type\":\"o" + "bject\"},\"StoragePolicyUpdate\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds" + "\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"}," + "\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"ty" + "pe\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\"," + "\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUu" + "id\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"intege" + "r\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_day" + "s\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/c" + "omponents/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"mini" + "mum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priori" + "ty\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref" + "\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/componen" + "ts/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"repl" + "ication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"requi" + "red_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":900719925474099" + "1,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"requi" + "red\":[\"revision\"],\"type\":\"object\"},\"StoragePolicyWinner\":{\"additionalProperties\":false,\"properti" + "es\":{\"draft\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"typ" + "e\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\"" + ":\"null\"}]},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas" + "/CameraUuid\"},{\"type\":\"null\"}]},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Came" + "raUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"pri" + "ority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"draft\",\"policy_uuid\",\"policy_name\",\"p" + "riority\",\"primary_target_uuid\",\"primary_pool_uuid\",\"fallback_mode\",\"fallback_target_uuid\"],\"type" + "\":\"object\"},\"StoragePool\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0," + "\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/" + "StoragePoolMember\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLeng" + "th\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"str" + "ategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0" + ",\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\"," + "\"strategy\",\"enabled\",\"revision\",\"created_at\",\"updated_at\",\"members\"],\"type\":\"object\"},\"StoragePo" + "olCreate\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"i" + "tems\":{\"$ref\":\"#/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type" + "\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"strategy\":{\"enum\":[\"most_free" + "\",\"round_robin\",\"priority\"],\"type\":\"string\"}},\"required\":[\"name\",\"members\"],\"type\":\"object\"},\"St" + "oragePoolMember\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"int" + "eger\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimu" + "m\":1,\"type\":\"integer\"}},\"required\":[\"target_uuid\",\"position\",\"weight\"],\"type\":\"object\"},\"Storage" + "PoolMemberRequest\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"i" + "nteger\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"mini" + "mum\":1,\"type\":\"integer\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"StoragePoolUpdate\":{\"addi" + "tionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#" + "/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name" + "\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimu" + "m\":1,\"type\":\"integer\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"" + "}},\"required\":[\"revision\"],\"type\":\"object\"},\"StoragePoolsResponse\":{\"additionalProperties\":false" + ",\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"pools\":{\"items\":{\"$ref\":\"#/components/sch" + "emas/StoragePool\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"pools\",\"count\"],\"type\":\"object\"}," + "\"StorageTarget\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maxim" + "um\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":" + "0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"archive_only\":{\"type\"" + ":\"boolean\"},\"bucket\":{\"type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"credential_r" + "ef\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"health\":{\"$ref\"" + ":\"#/components/schemas/StorageTargetHealth\"},\"high_watermark_pct\":{\"type\":\"number\"},\"is_default\"" + ":{\"type\":\"boolean\"},\"low_watermark_pct\":{\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0" + ",\"type\":\"integer\"},\"mount_guard_path\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"null\"}]},\"mount_requi" + "red\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"recording_bytes\":{\"minimum\":0,\"type\":\"integer\"" + "},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"region\":{\"type\":\"string\"},\"replica_bytes\":{\"" + "minimum\":0,\"type\":\"integer\"},\"replica_count\":{\"minimum\":0,\"type\":\"integer\"},\"reserve_bytes\":{\"mi" + "nimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}," + "\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"ta" + "rget_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"intege" + "r\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"root_path\",\"en" + "dpoint\",\"region\",\"bucket\",\"credential_ref\",\"target_type\",\"archive_only\",\"enabled\",\"is_default\",\"" + "mount_required\",\"mount_guard_path\",\"storage_class\",\"archive_budget_bytes\",\"reserve_bytes\",\"recor" + "ding_count\",\"recording_bytes\",\"replica_count\",\"replica_bytes\",\"migration_bandwidth_bps\",\"high_wa" + "termark_pct\",\"low_watermark_pct\",\"archival_window_start_minute\",\"archival_window_end_minute\",\"re" + "vision\",\"created_at\",\"updated_at\",\"health\"],\"type\":\"object\"},\"StorageTargetCreate\":{\"additionalP" + "roperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"" + "integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_" + "budget_bytes\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string" + "\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"}," + "\"high_watermark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermar" + "k_pct\":{\"exclusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\"" + ":0,\"type\":\"integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"" + "type\":\"string\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"root_" "path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_t" - "ype\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"u" - "uid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"root_path\",\"endpoint" - "\",\"region\",\"bucket\",\"credential_ref\",\"target_type\",\"archive_only\",\"enabled\",\"is_default\",\"mount_" - "required\",\"mount_guard_path\",\"storage_class\",\"archive_budget_bytes\",\"reserve_bytes\",\"recording_c" - "ount\",\"recording_bytes\",\"replica_count\",\"replica_bytes\",\"migration_bandwidth_bps\",\"high_watermar" - "k_pct\",\"low_watermark_pct\",\"archival_window_start_minute\",\"archival_window_end_minute\",\"revision" - "\",\"created_at\",\"updated_at\",\"health\"],\"type\":\"object\"},\"StorageTargetCreate\":{\"additionalPropert" - "ies\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"intege" - "r\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget" - "_bytes\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"cr" - "edential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_" - "watermark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\"" - ":{\"exclusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"ty" - "pe\":\"integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":" - "\"string\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"root_path\":" - "{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{" - "\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"}},\"required\":[\"name\",\"root_path\"],\"type\":\"object\"},\"S" - "torageTargetHealth\":{\"additionalProperties\":false,\"properties\":{\"available_bytes\":{\"minimum\":0,\"" - "type\":\"integer\"},\"capacity_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"capacity_known\":{\"type\":\"boole" - "an\"},\"cleanup_target_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"duplicate_filesystem\":{\"type\":\"boole" - "an\"},\"filesystem_device\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"last_pro" - "be_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"last_success_at\":{\"anyOf\":[{\"" - "minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"pressure\":{\"type\":\"string\"},\"status\":{\"type\":\"st" - "ring\"},\"used_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_pct\":{\"minimum\":0,\"type\":\"number\"}},\"re" - "quired\":[\"status\",\"pressure\",\"last_error\",\"capacity_known\",\"duplicate_filesystem\",\"capacity_byte" - "s\",\"available_bytes\",\"used_bytes\",\"cleanup_target_bytes\",\"filesystem_device\",\"used_pct\",\"last_pr" - "obe_at\",\"last_success_at\"],\"type\":\"object\"},\"StorageTargetUpdate\":{\"additionalProperties\":false," - "\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archiv" - "al_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"m" - "aximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"credential_re" - "f\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_watermark_p" - "ct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\":{\"exclusiv" - "eMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"intege" - "r\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"" - "region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9" - "007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"en" - "um\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"str" - "ing\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StorageTargetsResponse\":{\"additionalProperties\"" - ":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"targets\":{\"items\":{\"$ref\":\"#/compon" - "ents/schemas/StorageTarget\"},\"maxItems\":128,\"type\":\"array\"}},\"required\":[\"targets\",\"count\"],\"typ" - "e\":\"object\"},\"StreamCompatibilityList\":{\"description\":\"Default GET /api/streams response. An emp" - "ty array is valid. Each row is filtered and redacted using runtime plus go2rtc health observatio" - "ns.\",\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/components/sche" - "mas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.v" - "iew\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":" - "\"array\"},\"StreamConfigurationUpdate\":{\"additionalProperties\":true,\"description\":\"Partial ordinar" - "y update. All fields are optional and omission preserves existing state. Unknown fields (includi" - "ng detected width/height/fps) are currently ignored. This typed contract describes intended valu" - "es; existing handlers can ignore wrong types instead of returning 400. It does not promise every" - " malformed value is rejected by C.\",\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabl" - "ed\":{\"const\":false}},\"required\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"ad" - "min_url\":{\"description\":\"Null or empty string clears this override; omission preserves the curre" - "nt value.\",\"type\":[\"string\",\"null\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or null " - "preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"O" - "mission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":" - "\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"st" - "ring\",\"null\"]},\"detection_based_recording\":{\"description\":\"Omission or null preserves the curren" - "t value.\",\"type\":[\"boolean\",\"null\"]},\"detection_interval\":{\"description\":\"Omission or null prese" - "rves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"numb" - "er\",\"null\"]},\"detection_model\":{\"description\":\"Omission preserves the current value. Null is ign" - "ored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"descrip" + "ype\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"}},\"required\":[\"name\",\"root_path\"],\"type\":\"objec" + "t\"},\"StorageTargetHealth\":{\"additionalProperties\":false,\"properties\":{\"available_bytes\":{\"minimu" + "m\":0,\"type\":\"integer\"},\"capacity_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"capacity_known\":{\"type\":" + "\"boolean\"},\"cleanup_target_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"duplicate_filesystem\":{\"type\":" + "\"boolean\"},\"filesystem_device\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"la" + "st_probe_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"last_success_at\":{\"anyO" + "f\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"pressure\":{\"type\":\"string\"},\"status\":{\"typ" + "e\":\"string\"},\"used_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_pct\":{\"minimum\":0,\"type\":\"number\"" + "}},\"required\":[\"status\",\"pressure\",\"last_error\",\"capacity_known\",\"duplicate_filesystem\",\"capacit" + "y_bytes\",\"available_bytes\",\"used_bytes\",\"cleanup_target_bytes\",\"filesystem_device\",\"used_pct\",\"l" + "ast_probe_at\",\"last_success_at\"],\"type\":\"object\"},\"StorageTargetUpdate\":{\"additionalProperties\":" + "false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"" + "archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_byte" + "s\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"credent" + "ial_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_water" + "mark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\":{\"ex" + "clusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"" + "integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"stri" + "ng\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maxi" + "mum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class" + "\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type" + "\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StorageTargetsResponse\":{\"additionalPrope" + "rties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"targets\":{\"items\":{\"$ref\":\"#/" + "components/schemas/StorageTarget\"},\"maxItems\":128,\"type\":\"array\"}},\"required\":[\"targets\",\"count\"" + "],\"type\":\"object\"},\"StreamCompatibilityList\":{\"description\":\"Default GET /api/streams response. " + "An empty array is valid. Each row is filtered and redacted using runtime plus go2rtc health obse" + "rvations.\",\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/component" + "s/schemas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"" + "live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"" + "type\":\"array\"},\"StreamConfigurationUpdate\":{\"additionalProperties\":true,\"description\":\"Partial o" + "rdinary update. All fields are optional and omission preserves existing state. Unknown fields (i" + "ncluding detected width/height/fps) are currently ignored. This typed contract describes intende" + "d values; existing handlers can ignore wrong types instead of returning 400. It does not promise" + " every malformed value is rejected by C.\",\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true}," + "\"enabled\":{\"const\":false}},\"required\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties" + "\":{\"admin_url\":{\"description\":\"Null or empty string clears this override; omission preserves the" + " current value.\",\"type\":[\"string\",\"null\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or" + " null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"descripti" + "on\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"descrip" "tion\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type" - "\":[\"string\",\"null\"]},\"detection_object_filter_list\":{\"description\":\"Omission preserves the curre" - "nt value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_rec" - "ord_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean" - "\",\"null\"]},\"detection_recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule" - "\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The curren" - "t handler ignores wrong-length arrays and treats each non-true element as false, rather than rej" - "ecting all malformed schedule input.\"},\"detection_retention_days\":{\"description\":\"Numeric input " - "is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days." - " Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exc" - "lusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"detection_threshold\":{\"description\":\"Percentage con" - "fidence, including zero; divided by 100 for storage. Omission/null preserves the current value. " - "The handler does not clamp this numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"desc" - "ription\":\"Null or empty string clears this override; omission preserves the current value. Nonem" - "pty values must start with http://, https://, rtsp:// or rtsps:// (case-insensitive), at most 51" - "1 UTF-8 bytes.\",\"type\":[\"string\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or null pre" - "serves the current value.\",\"type\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null" - " preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty st" - "ring disables; otherwise at most 1023 UTF-8 bytes containing version=1, projection=equidistant, " - "mount=ceiling and bounded centerX/centerY [0,1], radius [.05,1], fov [160,360], rotation/default" - "Yaw [-360,360], defaultTilt [-90,30], defaultViewFov [20,120]. Null is rejected. Unknown documen" - "t fields are tolerated.\",\"maxLength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"descriptio" - "n\":\"Null or empty string clears this override; omission preserves the current value. Whitespace-" - "only also clears. Maximum 8191 UTF-8 bytes. Nonblank multiline values are YAML-validated when YA" - "ML support is enabled; malformed YAML can return a structured error. Single-line values are not " - "YAML-checked.\",\"type\":[\"string\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preserves th" - "e current value.\",\"type\":[\"boolean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or null p" - "reserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"" - "number\",\"null\"]},\"motion_trigger_source\":{\"description\":\"Null or empty string clears this overri" - "de; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"Opti" - "onal identifying metadata; ignored. This operation does not rename the stream.\",\"type\":\"string\"}" - ",\"onvif_password\":{\"description\":\"Omission preserves the current value. Null is ignored by this " - "typed field handler. Processed only when the resulting isOnvif value is true; empty string clear" - "s this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_port" - "\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate n" - "umeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"description\":\"Omission pr" - "eserves the current value. Null is ignored by this typed field handler. Processed only when the " - "resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 2" - "02 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omission preserves the " - "current value. Null is ignored by this typed field handler. Processed only when the resulting is" - "Onvif value is true; empty string clears this field. ONVIF probing may precede the 202 response." - "\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport" - "\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer" - "-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"pre_detection_buf" - "fer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncat" - "e numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"description\":\"Omission or n" - "ull preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"typ" - "e\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves the current value. I" - "nteger-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_enabled" - "\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz" - "_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"nul" - "l\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current value. Integer-backed fi" - "elds truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_y\":{\"description\":\"" - "Omission or null preserves the current value. Integer-backed fields truncate numeric input towar" - "d zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or null preserves the cu" - "rrent value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]" - "},\"publish_url\":{\"description\":\"Null or empty string clears this override; omission preserves th" - "e current value. Nonempty values are a supported RTMP/RTMPS destination list, at most 511 UTF-8 " - "bytes; each destination must pass the publish URL validator.\",\"type\":[\"string\",\"null\"]},\"record\"" - ":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"reco" - "rd_audio\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null" - "\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[" - "\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"}" - ",{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current " - "handler ignores wrong-length arrays and treats each non-true element as false, rather than rejec" - "ting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeric input is truncated" - " toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omissi" - "on preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimu" - "m\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission or null preserves th" - "e current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"nu" - "ll\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate privacy-only form; null " - "is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{\"description\":\"Omiss" - "ion or null preserves the current value. Integer-backed fields truncate numeric input toward zer" - "o.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission or null preserves the" - " current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":\"Null or empty strin" - "g clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tags\":" + "\":[\"string\",\"null\"]},\"detection_based_recording\":{\"description\":\"Omission or null preserves the " + "current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_interval\":{\"description\":\"Omission or null" + " preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":" + "[\"number\",\"null\"]},\"detection_model\":{\"description\":\"Omission preserves the current value. Null " + "is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"d" + "escription\":\"Omission preserves the current value. Null is ignored by this typed field handler.\"" + ",\"type\":[\"string\",\"null\"]},\"detection_object_filter_list\":{\"description\":\"Omission preserves the" + " current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detecti" + "on_record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"b" + "oolean\",\"null\"]},\"detection_recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySc" + "hedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The " + "current handler ignores wrong-length arrays and treats each non-true element as false, rather th" + "an rejecting all malformed schedule input.\"},\"detection_retention_days\":{\"description\":\"Numeric " + "input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive" + " days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":36" + "6,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"detection_threshold\":{\"description\":\"Percenta" + "ge confidence, including zero; divided by 100 for storage. Omission/null preserves the current v" + "alue. The handler does not clamp this numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":" "{\"description\":\"Null or empty string clears this override; omission preserves the current value." - "\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omission or null preserve" - "s the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\"" - ",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null preserves the current val" - "ue. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_i" - "mportant_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backe" - "d fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url\":{\"description\":\"Om" - "ission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"strin" - "g\",\"null\"]}},\"required\":[],\"type\":\"object\"},\"StreamCreateRequest\":{\"additionalProperties\":true,\"" - "not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabled\":{\"const\":false}},\"required\":[\"ena" - "ble_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"admin_url\":{\"description\":\"Null or empt" - "y string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]}," - "\"audio_voice_enhancement\":{\"description\":\"Omission or null preserves the current value.\",\"type\":" - "[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"Omission or null preserves the current" - " value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":\"Omission preserves the current value" - ". Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_based_recor" - "ding\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]}," - "\"detection_interval\":{\"description\":\"Omission or null preserves the current value. Integer-backe" - "d fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"detection_model\":{\"desc" - "ription\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"t" - "ype\":[\"string\",\"null\"]},\"detection_object_filter\":{\"description\":\"Omission preserves the current" - " value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_objec" - "t_filter_list\":{\"description\":\"Omission preserves the current value. Null is ignored by this typ" - "ed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_record_on_schedule\":{\"description\":\"Omis" - "sion or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_recording_sched" - "ule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Om" - "ission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays" - " and treats each non-true element as false, rather than rejecting all malformed schedule input.\"" - "},\"detection_retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yie" - "ld -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predict" - "able clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"n" - "ull\"]},\"detection_threshold\":{\"description\":\"Percentage confidence, including zero; divided by 1" - "00 for storage. Omission/null preserves the current value. The handler does not clamp this numer" - "ic field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"description\":\"Null or empty string clears" - " this override; omission preserves the current value. Nonempty values must start with http://, h" - "ttps://, rtsp:// or rtsps:// (case-insensitive), at most 511 UTF-8 bytes.\",\"type\":[\"string\",\"nul" - "l\"]},\"enable_disabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"b" - "oolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\"" - ":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty string disables; otherwise at most 1023" - " UTF-8 bytes containing version=1, projection=equidistant, mount=ceiling and bounded centerX/cen" - "terY [0,1], radius [.05,1], fov [160,360], rotation/defaultYaw [-360,360], defaultTilt [-90,30]," - " defaultViewFov [20,120]. Null is rejected. Unknown document fields are tolerated.\",\"maxLength\":" - "1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Null or empty string clears this " - "override; omission preserves the current value. Whitespace-only also clears. Maximum 8191 UTF-8 " - "bytes. Nonblank multiline values are YAML-validated when YAML support is enabled; malformed YAML" - " can return a structured error. Single-line values are not YAML-checked.\",\"type\":[\"string\",\"null" - "\"]},\"isOnvif\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"" - "null\"]},\"max_storage_mb\":{\"description\":\"Omission or null preserves the current value. Integer-b" - "acked fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"motion_trigger_sour" - "ce\":{\"description\":\"Null or empty string clears this override; omission preserves the current va" - "lue.\",\"type\":[\"string\",\"null\"]},\"name\":{\"maxLength\":255,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_.-]" - "+$\",\"type\":\"string\"},\"onvif_password\":{\"description\":\"Omission preserves the current value. Null" - " is ignored by this typed field handler. Processed only when the resulting isOnvif value is true" - "; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\"," - "\"null\"]},\"onvif_port\":{\"description\":\"Omission or null preserves the current value. Integer-back" - "ed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"descr" - "iption\":\"Omission preserves the current value. Null is ignored by this typed field handler. Proc" - "essed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probi" - "ng may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omi" - "ssion preserves the current value. Null is ignored by this typed field handler. Processed only w" - "hen the resulting isOnvif value is true; empty string clears this field. ONVIF probing may prece" - "de the 202 response.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schem" - "as/PlaybackTransport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the cu" - "rrent value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]" - "},\"pre_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-b" - "acked fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"descrip" + " Nonempty values must start with http://, https://, rtsp:// or rtsps:// (case-insensitive), at m" + "ost 511 UTF-8 bytes.\",\"type\":[\"string\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or nu" + "ll preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission o" + "r null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Em" + "pty string disables; otherwise at most 1023 UTF-8 bytes containing version=1, projection=equidis" + "tant, mount=ceiling and bounded centerX/centerY [0,1], radius [.05,1], fov [160,360], rotation/d" + "efaultYaw [-360,360], defaultTilt [-90,30], defaultViewFov [20,120]. Null is rejected. Unknown d" + "ocument fields are tolerated.\",\"maxLength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"desc" + "ription\":\"Null or empty string clears this override; omission preserves the current value. White" + "space-only also clears. Maximum 8191 UTF-8 bytes. Nonblank multiline values are YAML-validated w" + "hen YAML support is enabled; malformed YAML can return a structured error. Single-line values ar" + "e not YAML-checked.\",\"type\":[\"string\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preser" + "ves the current value.\",\"type\":[\"boolean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or " + "null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"ty" + "pe\":[\"number\",\"null\"]},\"motion_trigger_source\":{\"description\":\"Null or empty string clears this " + "override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\"" + ":\"Optional identifying metadata; ignored. This operation does not rename the stream.\",\"type\":\"st" + "ring\"},\"observation_evidence_enabled\":{\"description\":\"INT_01 per-camera opt-in for observation e" + "vidence for integrations (contract section 13). Off by default. Requires a bird-capable object d" + "etector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong" + " type is rejected with 400; omission preserves the current value. Takes effect without a restart" + ".\",\"type\":\"boolean\"},\"observation_labels\":{\"description\":\"Comma-separated detector labels eligib" + "le for observation evidence; empty means every object label. At most 16 labels, each 1..31 print" + "able ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is " + "ASCII case-insensitive on the original detector label (contract section 13). Requires camera.con" + "figure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the" + " current value. Takes effect without a restart.\",\"maxLength\":511,\"type\":\"string\"},\"observation_m" + "ax_age_days\":{\"description\":\"Evidence media maximum age in days; must be an integer 1..90 (defau" + "lt 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 4" + "00; omission preserves the current value. Takes effect without a restart.\",\"maximum\":90,\"minimum" + "\":1,\"type\":\"integer\"},\"observation_max_bytes\":{\"description\":\"Evidence media byte budget; must b" + "e an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure" + " (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the curre" + "nt value. Takes effect without a restart.\",\"maximum\":1099511627776,\"minimum\":67108864,\"type\":\"in" + "teger\"},\"onvif_password\":{\"description\":\"Omission preserves the current value. Null is ignored b" + "y this typed field handler. Processed only when the resulting isOnvif value is true; empty strin" + "g clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onv" + "if_port\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields tru" + "ncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"description\":\"Omis" + "sion preserves the current value. Null is ignored by this typed field handler. Processed only wh" + "en the resulting isOnvif value is true; empty string clears this field. ONVIF probing may preced" + "e the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omission preserv" + "es the current value. Null is ignored by this typed field handler. Processed only when the resul" + "ting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 re" + "sponse.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTr" + "ansport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the current value. " + "Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"pre_detect" + "ion_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields " + "truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"description\":\"Omissi" + "on or null preserves the current value. Integer-backed fields truncate numeric input toward zero" + ".\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves the current v" + "alue. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_" + "enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"" + "]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolea" + "n\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current value. Integer-ba" + "cked fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_y\":{\"descrip" "tion\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric inpu" - "t toward zero.\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves " - "the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"" - "null\"]},\"ptz_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"bo" - "olean\",\"null\"]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"t" - "ype\":[\"boolean\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current valu" - "e. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max" - "_y\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate" - " numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or n" - "ull preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"typ" - "e\":[\"number\",\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this override; o" - "mission preserves the current value. Nonempty values are a supported RTMP/RTMPS destination list" - ", at most 511 UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\":[\"strin" - "g\",\"null\"]},\"record\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boo" - "lean\",\"null\"]},\"record_audio\":{\"description\":\"Omission or null preserves the current value.\",\"ty" - "pe\":[\"boolean\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the curr" - "ent value.\",\"type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/sche" - "mas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 bo" - "oleans. The current handler ignores wrong-length arrays and treats each non-true element as fals" - "e, rather than rejecting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeri" - "c input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positi" - "ve days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":" - "366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission " - "or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\"," - "\"type\":[\"number\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate priv" - "acy-only form; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{" + "t toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or null preserves" + " the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\"," + "\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this override; omission prese" + "rves the current value. Nonempty values are a supported RTMP/RTMPS destination list, at most 511" + " UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\":[\"string\",\"null\"]},\"" + "record\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]" + "},\"record_audio\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean" + "\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"" + "type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySch" + "edule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The c" + "urrent handler ignores wrong-length arrays and treats each non-true element as false, rather tha" + "n rejecting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeric input is tr" + "uncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null" + "/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiv" + "eMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission or null prese" + "rves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"numb" + "er\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate privacy-only form" + "; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{\"description\"" + ":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input tow" + "ard zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission or null preser" + "ves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":\"Null or empt" + "y string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]}," + "\"tags\":{\"description\":\"Null or empty string clears this override; omission preserves the current" + " value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omission or null p" + "reserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"" + "number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null preserves the curr" + "ent value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]}," + "\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the current value. Intege" + "r-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url\":{\"descripti" + "on\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":" + "[\"string\",\"null\"]}},\"required\":[],\"type\":\"object\"},\"StreamCreateRequest\":{\"additionalProperties\"" + ":true,\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabled\":{\"const\":false}},\"required" + "\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"admin_url\":{\"description\":\"Null " + "or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"n" + "ull\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or null preserves the current value.\"," + "\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"Omission or null preserves the " + "current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":\"Omission preserves the curren" + "t value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_base" + "d_recording\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"n" + "ull\"]},\"detection_interval\":{\"description\":\"Omission or null preserves the current value. Intege" + "r-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"detection_model\"" + ":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handl" + "er.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"description\":\"Omission preserves the " + "current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detectio" + "n_object_filter_list\":{\"description\":\"Omission preserves the current value. Null is ignored by t" + "his typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_record_on_schedule\":{\"description" + "\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_recordin" + "g_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"descripti" + "on\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length" + " arrays and treats each non-true element as false, rather than rejecting all malformed schedule " + "input.\"},\"detection_retention_days\":{\"description\":\"Numeric input is truncated toward zero and m" + "ust yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For " + "predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"num" + "ber\",\"null\"]},\"detection_threshold\":{\"description\":\"Percentage confidence, including zero; divid" + "ed by 100 for storage. Omission/null preserves the current value. The handler does not clamp thi" + "s numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"description\":\"Null or empty string" + " clears this override; omission preserves the current value. Nonempty values must start with htt" + "p://, https://, rtsp:// or rtsps:// (case-insensitive), at most 511 UTF-8 bytes.\",\"type\":[\"strin" + "g\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or null preserves the current value.\",\"ty" + "pe\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null preserves the current value.\"" + ",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty string disables; otherwise at mo" + "st 1023 UTF-8 bytes containing version=1, projection=equidistant, mount=ceiling and bounded cent" + "erX/centerY [0,1], radius [.05,1], fov [160,360], rotation/defaultYaw [-360,360], defaultTilt [-" + "90,30], defaultViewFov [20,120]. Null is rejected. Unknown document fields are tolerated.\",\"maxL" + "ength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Null or empty string clear" + "s this override; omission preserves the current value. Whitespace-only also clears. Maximum 8191" + " UTF-8 bytes. Nonblank multiline values are YAML-validated when YAML support is enabled; malform" + "ed YAML can return a structured error. Single-line values are not YAML-checked.\",\"type\":[\"string" + "\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boo" + "lean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or null preserves the current value. In" + "teger-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"motion_trigg" + "er_source\":{\"description\":\"Null or empty string clears this override; omission preserves the cur" + "rent value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"maxLength\":255,\"minLength\":1,\"pattern\":\"^[A-Za-z" + "0-9_.-]+$\",\"type\":\"string\"},\"observation_evidence_enabled\":{\"description\":\"INT_01 per-camera opt" + "-in for observation evidence for integrations (contract section 13). Off by default. Requires a " + "bird-capable object detector for bird integrations. Requires camera.configure (all stream writes" + " do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes eff" + "ect without a restart.\",\"type\":\"boolean\"},\"observation_labels\":{\"description\":\"Comma-separated d" + "etector labels eligible for observation evidence; empty means every object label. At most 16 lab" + "els, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are r" + "ejected. Matching is ASCII case-insensitive on the original detector label (contract section 13)" + ". Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; o" + "mission preserves the current value. Takes effect without a restart.\",\"maxLength\":511,\"type\":\"st" + "ring\"},\"observation_max_age_days\":{\"description\":\"Evidence media maximum age in days; must be an" + " integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong ty" + "pe is rejected with 400; omission preserves the current value. Takes effect without a restart.\"," + "\"maximum\":90,\"minimum\":1,\"type\":\"integer\"},\"observation_max_bytes\":{\"description\":\"Evidence medi" + "a byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requ" + "ires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omissio" + "n preserves the current value. Takes effect without a restart.\",\"maximum\":1099511627776,\"minimum" + "\":67108864,\"type\":\"integer\"},\"onvif_password\":{\"description\":\"Omission preserves the current val" + "ue. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value" + " is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"" + "string\",\"null\"]},\"onvif_port\":{\"description\":\"Omission or null preserves the current value. Inte" + "ger-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\"" + ":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handl" + "er. Processed only when the resulting isOnvif value is true; empty string clears this field. ONV" + "IF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"descripti" + "on\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processe" + "d only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing m" + "ay precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/componen" + "ts/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserve" + "s the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\"" + ",\"null\"]},\"pre_detection_buffer\":{\"description\":\"Omission or null preserves the current value. I" + "nteger-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{" "\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate nume" - "ric input toward zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission o" - "r null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":" - "\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"stri" - "ng\",\"null\"]},\"tags\":{\"description\":\"Null or empty string clears this override; omission preserve" - "s the current value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omiss" + "ric input toward zero.\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null pr" + "eserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"n" + "umber\",\"null\"]},\"ptz_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"ty" + "pe\":[\"boolean\",\"null\"]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current va" + "lue.\",\"type\":[\"boolean\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the curr" + "ent value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]}," + "\"ptz_max_y\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields " + "truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omiss" "ion or null preserves the current value. Integer-backed fields truncate numeric input toward zer" - "o.\",\"type\":[\"number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null prese" - "rves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"numb" - "er\",\"null\"]},\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the current " - "value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url" - "\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\",\"url\"],\"type\":\"object\"},\"StreamCreated\":{\"" - "additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"type\":\"string\"},\"location_uuid\":{\"type\"" - ":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"],\"type\":\"" - "string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\",\"locatio" - "n_uuid\"],\"type\":\"object\"},\"StreamDeleteAccepted\":{\"additionalProperties\":true,\"properties\":{\"per" - "manent\":{\"type\":\"boolean\"},\"status\":{\"const\":\"accepted\",\"type\":\"string\"},\"success\":{\"const\":true" - ",\"type\":\"boolean\"}},\"required\":[\"success\",\"permanent\",\"status\"],\"type\":\"object\"},\"StreamDetailsB" - "ase\":{\"additionalProperties\":true,\"description\":\"Fields shared by compatibility-list rows and fu" - "ll detail. Strings use empty values rather than null; additional fields are preserved for additi" - "ve compatibility.\",\"properties\":{\"audio_voice_enhancement\":{\"type\":\"boolean\"},\"backchannel_enabl" - "ed\":{\"type\":\"boolean\"},\"camera_uuid\":{\"description\":\"Stable camera UUID. Older migrated rows may" - " have an empty string; no instance identifiers are embedded in this document.\",\"type\":\"string\"}," - "\"codec\":{\"type\":\"string\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"detection_interval\":{\"" - "type\":\"integer\"},\"detection_model\":{\"type\":\"string\"},\"detection_object_filter\":{\"type\":\"string\"}" - ",\"detection_object_filter_list\":{\"type\":\"string\"},\"detection_record_on_schedule\":{\"type\":\"boolea" - "n\"},\"detection_recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"detection_ret" - "ention_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive values a" - "re days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"detection_th" - "reshold\":{\"description\":\"Integer percentage emitted from stored float confidence, including zero" - ". The existing update handler does not clamp this to 0\342\200\223100.\",\"type\":\"integer\"},\"enabled\":{\"typ" - "e\":\"boolean\"},\"eptz_config\":{\"description\":\"Empty string disables browser dewarping; otherwise a" - " serialized version-1 JSON document. It is a string on the wire, not an object.\",\"maxLength\":102" - "3,\"type\":\"string\"},\"error_message\":{\"description\":\"Present only when an Error state has a record" - "ed reason.\",\"type\":\"string\"},\"fps\":{\"description\":\"Detected metadata; zero is valid when unavail" - "able.\",\"type\":\"integer\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boo" - "lean\"},\"height\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"inte" - "ger\"},\"isOnvif\":{\"type\":\"boolean\"},\"location_uuid\":{\"description\":\"Primary location UUID, or an " - "empty string when unset.\",\"type\":\"string\"},\"max_storage_mb\":{\"description\":\"Storage quota in MB;" - " zero means unlimited.\",\"type\":\"integer\"},\"motion_trigger_source\":{\"type\":\"string\"},\"name\":{\"des" - "cription\":\"Current stream name; lookup is by name, not camera_uuid.\",\"type\":\"string\"},\"onvif_por" - "t\":{\"description\":\"Zero requests automatic port detection.\",\"type\":\"integer\"},\"onvif_profile\":{\"" - "type\":\"string\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_det" - "ection_buffer\":{\"type\":\"integer\"},\"pre_detection_buffer\":{\"type\":\"integer\"},\"priority\":{\"type\":\"" - "integer\"},\"privacy_mode\":{\"type\":\"boolean\"},\"protocol\":{\"description\":\"Stored transport code: 0 " - "TCP, 1 UDP. Existing configuration paths may retain other numeric values.\",\"type\":\"integer\"},\"pt" - "z_enabled\":{\"type\":\"boolean\"},\"ptz_has_home\":{\"type\":\"boolean\"},\"ptz_max_x\":{\"type\":\"integer\"},\"" - "ptz_max_y\":{\"type\":\"integer\"},\"ptz_max_z\":{\"type\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record" - "_audio\":{\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boolean\"},\"recording_schedule\":{\"$ref\":" - "\"#/components/schemas/WeeklySchedule\"},\"retention_days\":{\"description\":\"-1 inherits the global p" - "olicy; 0 means unlimited; positive values are days. PUT validates the truncated integer to -1 th" - "rough 365.\",\"type\":\"integer\"},\"segment_duration\":{\"type\":\"integer\"},\"status\":{\"$ref\":\"#/componen" - "ts/schemas/StreamStatus\"},\"storage_priority\":{\"type\":\"integer\"},\"streaming_enabled\":{\"type\":\"boo" - "lean\"},\"tags\":{\"type\":\"string\"},\"tier_critical_multiplier\":{\"description\":\"Stored tier multiplie" - "r. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"tier_ephemeral_multiplie" - "r\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"t" - "ype\":\"number\"},\"tier_important_multiplier\":{\"description\":\"Stored tier multiplier. Zero is prese" - "rved; no client default is inserted.\",\"type\":\"number\"},\"url\":{\"description\":\"Configured input UR" - "L. For readers without camera.configure, URL userinfo is stripped; unsupported URL formats may b" - "ecome an empty string. For configurators the legacy/custom form may remain, while ONVIF/separate" - "-credential configurations still strip URL userinfo.\",\"type\":\"string\"},\"width\":{\"description\":\"D" - "etected metadata; zero is valid when unavailable.\",\"type\":\"integer\"}},\"required\":[\"camera_uuid\"," - "\"location_uuid\",\"name\",\"url\",\"eptz_config\",\"codec\",\"detection_model\",\"detection_object_filter\",\"" - "detection_object_filter_list\",\"onvif_profile\",\"tags\",\"motion_trigger_source\",\"enabled\",\"streamin" - "g_enabled\",\"record\",\"detection_based_recording\",\"record_audio\",\"audio_voice_enhancement\",\"isOnvi" - "f\",\"backchannel_enabled\",\"ptz_enabled\",\"ptz_has_home\",\"record_on_schedule\",\"detection_record_on_" - "schedule\",\"privacy_mode\",\"has_sub_stream\",\"go2rtc_hls_available\",\"width\",\"height\",\"fps\",\"priorit" - "y\",\"segment_duration\",\"detection_threshold\",\"detection_interval\",\"pre_detection_buffer\",\"post_de" - "tection_buffer\",\"protocol\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\",\"storage" - "_priority\",\"ptz_max_x\",\"ptz_max_y\",\"ptz_max_z\",\"onvif_port\",\"tier_critical_multiplier\",\"tier_imp" - "ortant_multiplier\",\"tier_ephemeral_multiplier\",\"playback_transport\",\"recording_schedule\",\"detect" - "ion_recording_schedule\",\"status\"],\"type\":\"object\"},\"StreamDetectionEngine\":{\"additionalPropertie" - "s\":true,\"properties\":{\"config\":{\"additionalProperties\":true,\"description\":\"Engine-specific confi" - "guration; current engine plugins own its keys. Serialized JSON must fit the native configuration" - " buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"min" - "imum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"" - "string\"},\"managed_by_legacy_stream_fields\":{\"type\":\"boolean\"},\"model_path\":{\"maxLength\":511,\"typ" - "e\":\"string\"},\"sort_order\":{\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"thresho" - "ld\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"e" - "xternal\"],\"type\":\"string\"}},\"required\":[\"key\",\"type\",\"model_path\",\"enabled\",\"threshold\",\"interva" - "l_seconds\",\"sort_order\",\"config\",\"managed_by_legacy_stream_fields\"],\"type\":\"object\"},\"StreamDete" - "ctionEngineWrite\":{\"additionalProperties\":true,\"properties\":{\"config\":{\"additionalProperties\":tr" - "ue,\"description\":\"Engine-specific configuration; current engine plugins own its keys. Serialized" - " JSON must fit the native configuration buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"" - "interval_seconds\":{\"maximum\":86400,\"minimum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLengt" - "h\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"" - "sort_order\":{\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\"" - ":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"typ" - "e\":\"string\"}},\"required\":[\"key\",\"type\",\"threshold\",\"interval_seconds\"],\"type\":\"object\"},\"StreamD" - "etectionEngines\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"integer\"},\"engines\"" - ":{\"items\":{\"$ref\":\"#/components/schemas/StreamDetectionEngine\"},\"type\":\"array\"},\"restart_require" - "d\":{\"type\":\"boolean\"},\"stream_name\":{\"type\":\"string\"},\"trigger_policy\":{\"const\":\"any_of\",\"type\":" - "\"string\"}},\"required\":[\"stream_name\",\"engines\",\"count\",\"trigger_policy\",\"restart_required\"],\"typ" - "e\":\"object\"},\"StreamDetectionEnginesRequest\":{\"additionalProperties\":true,\"properties\":{\"engines" - "\":{\"items\":{\"$ref\":\"#/components/schemas/StreamDetectionEngineWrite\"},\"maxItems\":7,\"type\":\"array" - "\"}},\"required\":[\"engines\"],\"type\":\"object\"},\"StreamFullPrivilegedResponse\":{\"additionalPropertie" - "s\":true,\"properties\":{\"stream\":{\"$ref\":\"#/components/schemas/StreamPrivileged\"}},\"required\":[\"st" - "ream\"],\"type\":\"object\"},\"StreamFullRedactedResponse\":{\"additionalProperties\":true,\"properties\":{" - "\"stream\":{\"$ref\":\"#/components/schemas/StreamRedacted\"}},\"required\":[\"stream\"],\"type\":\"object\"}," - "\"StreamFullResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamFullRedactedResponse\"},{\"$ref" - "\":\"#/components/schemas/StreamFullPrivilegedResponse\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"" - "requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"" - "runtime\"}],\"version\":1}}],\"description\":\"Full details are wrapped in stream. Read authorization " - "uses raw camera facts; privileged fields additionally use runtime-enriched facts on the same cam" - "era.\"},\"StreamHealth\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"numbe" - "r\"},\"healthy\":{\"type\":\"boolean\"},\"status\":{\"type\":\"string\"},\"storage\":{\"additionalProperties\":tr" - "ue,\"properties\":{\"available_bytes\":{\"type\":\"number\"},\"used_bytes\":{\"type\":\"number\"}},\"required\":" - "[\"used_bytes\",\"available_bytes\"],\"type\":\"object\"},\"streams\":{\"additionalProperties\":true,\"proper" - "ties\":{\"degraded\":{\"type\":\"number\"},\"down\":{\"type\":\"number\"},\"total\":{\"type\":\"number\"},\"up\":{\"ty" - "pe\":\"number\"}},\"required\":[\"total\",\"up\",\"degraded\",\"down\"],\"type\":\"object\"},\"streams_detail\":{\"i" - "tems\":{\"additionalProperties\":true,\"properties\":{\"bitrate_bps\":{\"type\":\"number\"},\"configured_fps" - "\":{\"type\":\"number\"},\"connection_latency_ms\":{\"type\":\"number\"},\"current_fps\":{\"type\":\"number\"},\"d" - "etector_operating\":{\"type\":\"boolean\"},\"expected_fps\":{\"type\":\"number\"},\"fps\":{\"type\":\"number\"},\"" - "frames_dropped\":{\"type\":\"number\"},\"frames_total\":{\"type\":\"number\"},\"last_completed_segment_ts\":{" - "\"type\":\"number\"},\"last_frame_ts\":{\"type\":\"number\"},\"last_packet_ts\":{\"type\":\"number\"},\"name\":{\"t" - "ype\":\"string\"},\"observed_fps\":{\"type\":\"number\"},\"reconnects\":{\"type\":\"number\"},\"recorder_session" - "_active\":{\"type\":\"boolean\"},\"recorder_writing\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"bo" - "olean\"},\"recording_gaps\":{\"type\":\"number\"},\"source_connected\":{\"type\":\"boolean\"},\"sparkline_bitr" - "ate\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type" - "\":\"array\"},\"status\":{\"type\":\"string\"},\"uptime_seconds\":{\"type\":\"number\"},\"video_advancing\":{\"typ" - "e\":\"boolean\"}},\"required\":[\"name\",\"status\",\"observed_fps\",\"current_fps\",\"expected_fps\",\"fps\",\"co" - "nfigured_fps\",\"bitrate_bps\",\"uptime_seconds\",\"reconnects\",\"last_packet_ts\",\"last_frame_ts\",\"last" - "_completed_segment_ts\",\"frames_total\",\"frames_dropped\",\"connection_latency_ms\",\"source_connected" - "\",\"video_advancing\",\"recorder_writing\",\"detector_operating\",\"recorder_session_active\",\"recording" - "_active\",\"recording_gaps\"],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"total" - "Requests\":{\"type\":\"number\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalReq" - "uests\",\"failedRequests\",\"timestamp\",\"status\",\"streams\",\"storage\",\"streams_detail\"],\"type\":\"objec" - "t\"},\"StreamHealthHistory\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"i" - "nteger\"},\"healthy\":{\"type\":\"boolean\"},\"streams_detail\":{\"items\":{\"additionalProperties\":true,\"pr" - "operties\":{\"name\":{\"type\":\"string\"},\"sparkline_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array" - "\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"}},\"required\":[\"name\"],\"type\":\"objec" - "t\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"integer\"},\"uptime\":{\"" - "type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"st" - "reams_detail\"],\"type\":\"object\"},\"StreamListResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Str" - "eamCompatibilityList\"},{\"$ref\":\"#/components/schemas/StreamSummaryPage\"},{\"$ref\":\"#/components/s" - "chemas/LiveStreamSummaryPage\"},{\"$ref\":\"#/components/schemas/LprStreamSummaryPage\"}],\"descriptio" - "n\":\"Select the response with the request query. Consumers validating an admin or live summary mu" - "st use its specific page schema: the identity-only LPR variant is deliberately minimal.\"},\"Strea" - "mPrivacyUpdate\":{\"additionalProperties\":false,\"description\":\"Synchronous lightweight privacy tog" - "gle. No other update fields may accompany it; unknown fields are rejected in this form.\",\"proper" - "ties\":{\"name\":{\"description\":\"Optional ignored identifying metadata.\",\"type\":\"string\"},\"set_priv" - "acy_mode\":{\"type\":\"boolean\"}},\"required\":[\"set_privacy_mode\"],\"type\":\"object\"},\"StreamPrivacyUpd" - "ated\":{\"additionalProperties\":true,\"description\":\"HTTP 200 privacy result. A disabled stream can" - " succeed with stream_registered=false; this is not evidence of a live stream.\",\"properties\":{\"pr" - "ivacy_mode\":{\"type\":\"boolean\"},\"stream_registered\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"t" - "ype\":\"boolean\"}},\"required\":[\"success\",\"privacy_mode\",\"stream_registered\"],\"type\":\"object\"},\"Str" - "eamPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase\"},{\"additionalPropertie" - "s\":true,\"properties\":{\"admin_url\":{\"type\":\"string\"},\"can_control_privacy\":{\"const\":true,\"type\":\"" - "boolean\"},\"detection_url\":{\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Single sour" - "ce string or multiline YAML source list; empty means generated configuration.\",\"type\":\"string\"}," - "\"onvif_password\":{\"description\":\"Configuration credential, conditionally visible for this resour" - "ce. Empty is valid when unset.\",\"type\":\"string\"},\"onvif_username\":{\"type\":\"string\"},\"publish_url" - "\":{\"type\":\"string\"},\"sub_stream_url\":{\"type\":\"string\"}},\"required\":[\"onvif_username\",\"onvif_pass" - "word\",\"admin_url\",\"go2rtc_source_override\",\"sub_stream_url\",\"detection_url\",\"publish_url\",\"can_c" - "ontrol_privacy\"],\"type\":\"object\"}],\"description\":\"This camera is both readable and configurable " - "for the current principal. Privileged values may still be empty; authority over another camera i" - "s insufficient.\"},\"StreamProbeRequest\":{\"additionalProperties\":true,\"properties\":{\"onvif_passwor" - "d\":{\"type\":\"string\"},\"onvif_username\":{\"type\":\"string\"},\"protocol\":{\"type\":\"integer\"},\"url\":{\"ty" - "pe\":\"string\"}},\"required\":[\"url\",\"protocol\"],\"type\":\"object\"},\"StreamProbeResponse\":{\"oneOf\":[{\"" - "additionalProperties\":true,\"properties\":{\"info\":{\"additionalProperties\":true,\"properties\":{\"code" - "c\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"width\":{\"type\":\"integ" - "er\"}},\"required\":[\"width\",\"height\",\"fps\",\"codec\"],\"type\":\"object\"},\"success\":{\"const\":true,\"type" - "\":\"boolean\"}},\"required\":[\"success\",\"info\"],\"type\":\"object\"},{\"additionalProperties\":true,\"prope" - "rties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"suc" - "cess\",\"message\"],\"type\":\"object\"}]},\"StreamRecordingChanged\":{\"additionalProperties\":true,\"prope" - "rties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"type\":\"string\"},\"state\":{\"enum\":[\"starting\",\"idle\"],\"" - "type\":\"string\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\"" - ":[\"success\",\"stream\",\"action\",\"state\"],\"type\":\"object\"},\"StreamRecordingRequest\":{\"additionalPro" - "perties\":true,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"type\":\"string\"}},\"required\":[\"act" - "ion\"],\"type\":\"object\"},\"StreamRecordingState\":{\"additionalProperties\":true,\"properties\":{\"captur" - "e_method\":{\"type\":[\"string\",\"null\"]},\"manual_control_allowed\":{\"type\":\"boolean\"},\"manual_control" - "_reason\":{\"enum\":[\"read_only\",\"continuous_recording\",null],\"type\":[\"string\",\"null\"]},\"recording_" - "active\":{\"type\":\"boolean\"},\"recording_id\":{\"type\":[\"integer\",\"null\"]},\"state\":{\"enum\":[\"starting" - "\",\"recording\",\"idle\"],\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"stream\",\"state\"," - "\"recording_active\",\"capture_method\",\"recording_id\",\"manual_control_allowed\",\"manual_control_reas" - "on\"],\"type\":\"object\"},\"StreamRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase" - "\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"const\":\"\",\"type\":\"string\"},\"can_cont" - "rol_privacy\":{\"const\":false,\"type\":\"boolean\"},\"detection_url\":{\"const\":\"\",\"type\":\"string\"},\"go2r" - "tc_source_override\":{\"const\":\"\",\"type\":\"string\"},\"onvif_password\":{\"const\":\"\",\"type\":\"string\"},\"" - "onvif_username\":{\"const\":\"\",\"type\":\"string\"},\"publish_url\":{\"const\":\"\",\"type\":\"string\"},\"sub_str" - "eam_url\":{\"const\":\"\",\"type\":\"string\"}},\"required\":[\"onvif_username\",\"onvif_password\",\"admin_url\"" - ",\"go2rtc_source_override\",\"sub_stream_url\",\"detection_url\",\"publish_url\",\"can_control_privacy\"]," - "\"type\":\"object\"}],\"description\":\"This camera is readable but not configurable. Sensitive fields " - "remain present as empty strings; has_sub_stream can still be true.\"},\"StreamRefreshAccepted\":{\"a" - "dditionalProperties\":true,\"properties\":{\"coalesced\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string" - "\"},\"retry_after_seconds\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"" - "type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"stream\"],\"type\":\"object\"},\"StreamRetention\":{" - "\"additionalProperties\":true,\"properties\":{\"detection_retention_days\":{\"maximum\":365,\"minimum\":-1" - ",\"type\":\"integer\"},\"max_storage_mb\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"m" - "essage\":{\"type\":\"string\"},\"retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"stream" - "_name\":{\"type\":\"string\"}},\"required\":[\"stream_name\",\"retention_days\",\"detection_retention_days\"," - "\"max_storage_mb\"],\"type\":\"object\"},\"StreamRetentionRequest\":{\"additionalProperties\":true,\"proper" - "ties\":{\"detection_retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\"" - ":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"retention_days\":{\"maximum\":365,\"mini" - "mum\":-1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"StreamSingleResponse\":{\"anyOf\":[{\"$re" - "f\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/components/schemas/StreamPrivileged\",\"x-lig" - "htnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"act" - "ion\":\"camera.configure\",\"resource\":\"runtime\"}],\"version\":1}}],\"description\":\"Single legacy strea" - "m object. URL and credential fields are redacted unless this same camera is configurable.\"},\"Str" - "eamStatus\":{\"enum\":[\"Stopped\",\"Starting\",\"Running\",\"Stopping\",\"Error\",\"Reconnecting\",\"Degraded\"," - "\"Unknown\"],\"type\":\"string\"},\"StreamSummaryBase\":{\"additionalProperties\":true,\"description\":\"Admi" - "nistrative and live summary fields. Unlike detail, summary rows contain no input URL, credential" - "s or error_message.\",\"properties\":{\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"d" - "isabled\"],\"type\":\"string\"},\"camera_uuid\":{\"type\":\"string\"},\"codec\":{\"type\":\"string\"},\"current_fp" - "s\":{\"type\":\"number\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"}" - ",\"first_video_at\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"fps" - "\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"isOnvif\":{\"type\":\"boolean\"},\"last_frame_ts\":{\"" - "description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"last_recording_at\":{\"de" - "scription\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"location_name\":{\"type\":\"s" - "tring\"},\"location_path\":{\"type\":\"string\"},\"location_uuid\":{\"type\":\"string\"},\"name\":{\"type\":\"stri" - "ng\"},\"protocol\":{\"type\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boo" - "lean\"},\"recording_active\":{\"type\":\"boolean\"},\"status\":{\"$ref\":\"#/components/schemas/StreamStatus" - "\"},\"tags\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"name\",\"locati" - "on_uuid\",\"location_name\",\"location_path\",\"codec\",\"tags\",\"enabled\",\"record\",\"record_on_schedule\"," - "\"detection_based_recording\",\"isOnvif\",\"recording_active\",\"width\",\"height\",\"fps\",\"protocol\",\"firs" - "t_video_at\",\"last_frame_ts\",\"last_recording_at\",\"current_fps\",\"status\",\"availability\"],\"type\":\"o" - "bject\"},\"StreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=ad" - "min (default). Totals count only authorized, searched and availability-filtered rows before pagi" - "nation. A page beyond the end has an empty streams array; total_pages is zero when total is zero" - ".\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":" - "100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/Str" - "eamSummaryRedacted\"},{\"$ref\":\"#/components/schemas/StreamSummaryPrivileged\",\"x-lightnvr-visibili" - "ty\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera." - "configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"inte" - "ger\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_s" - "ize\",\"total_pages\"],\"type\":\"object\"},\"StreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/s" - "chemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"description\"" - ":\"Only emitted for surface=admin with include_admin_url=true or 1. Empty when not configurable.\"" - ",\"type\":\"string\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"can_configure\"]," - "\"type\":\"object\"}]},\"StreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummary" - "Base\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"const\":\"\",\"description\":\"Only em" - "itted for surface=admin with include_admin_url=true or 1. Empty when not configurable.\",\"type\":\"" - "string\"},\"can_configure\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"" - "object\"}]},\"StreamUpdateAccepted\":{\"additionalProperties\":true,\"description\":\"HTTP 202 acknowled" - "ges a queued update, not successful durable application. ONVIF results are optional. Re-fetch de" - "tail to observe eventual state; a request requiring ONVIF work may perform that probe before acc" - "eptance.\",\"properties\":{\"message\":{\"type\":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_sta" - "tus\":{\"enum\":[\"success\",\"error\"],\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"re" - "quired\":[\"success\",\"message\"],\"type\":\"object\"},\"StreamUpdateRequest\":{\"anyOf\":[{\"$ref\":\"#/compon" - "ents/schemas/StreamConfigurationUpdate\"},{\"$ref\":\"#/components/schemas/StreamPrivacyUpdate\"}]},\"" - "StreamZones\":{\"additionalProperties\":true,\"properties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/s" - "chemas/DetectionZone\"},\"type\":\"array\"}},\"required\":[\"zones\"],\"type\":\"object\"},\"StreamZonesDelete" - "d\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":tru" - "e,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"StreamZonesRequest\":{\"ad" - "ditionalProperties\":true,\"properties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZ" - "oneWrite\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"zones\"],\"type\":\"object\"},\"StreamZonesSave" - "d\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"maximum\":16,\"minimum\":0,\"type\":\"integer\"" - "},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"" - "message\",\"count\"],\"type\":\"object\"},\"SuccessResponse\":{\"additionalProperties\":true,\"properties\":{" - "\"api_key\":false,\"password\":false,\"password_hash\":false,\"secret\":false,\"success\":{\"const\":true},\"" - "token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\"],\"type\":\"object\"" - "},\"SystemActionAccepted\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}," - "\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"Sy" - "stemBackupCreated\":{\"additionalProperties\":true,\"properties\":{\"backupUrl\":{\"type\":\"string\"},\"fil" - "ename\":{\"type\":\"string\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}}," - "\"required\":[\"success\",\"message\",\"backupUrl\",\"filename\"],\"type\":\"object\"},\"SystemInformation\":{\"a" - "dditionalProperties\":true,\"properties\":{\"cpu\":{\"additionalProperties\":true,\"properties\":{\"cores\"" - ":{\"type\":\"number\"},\"model\":{\"type\":\"string\"},\"usage\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}" - "]},\"usageCapability\":{\"type\":\"string\"}},\"type\":\"object\"},\"detectorMemory\":{\"additionalProperties" - "\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"nu" - "mber\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"}," - "{\"type\":\"number\"}]}},\"type\":\"object\"},\"disk\":{\"additionalProperties\":true,\"properties\":{\"capabil" - "ity\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"" - "type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"" - "object\"},\"git_commit\":{\"type\":\"string\"},\"global_retention_days\":{\"type\":\"number\"},\"go2rtcMemory\"" - ":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"ty" - "pe\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"a" - "nyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"memory\":{\"additionalProperties\":tr" - "ue,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number" - "\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"ty" - "pe\":\"number\"}]}},\"type\":\"object\"},\"network\":{\"additionalProperties\":true,\"properties\":{\"interfac" - "es\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"address\":{\"anyOf\":[{\"type\":\"null\"},{\"ty" - "pe\":\"string\"}]},\"capability\":{\"type\":\"string\"},\"ipv6\":{\"type\":\"string\"},\"mac\":{\"type\":\"string\"}," - "\"name\":{\"type\":\"string\"},\"up\":{\"type\":\"boolean\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"a" - "rray\"}},\"type\":\"object\"},\"recordings\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\"" - ":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"count\",\"size\"],\"type\":\"object\"},\"streamStorage" - "\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"detection_reten" - "tion_days\":{\"type\":\"number\"},\"max_storage_mb\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"retent" - "ion_days\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"name\",\"size\",\"count\",\"retenti" - "on_days\",\"detection_retention_days\",\"max_storage_mb\"],\"type\":\"object\"},\"type\":\"array\"},\"streams\"" - ":{\"additionalProperties\":true,\"properties\":{\"active\":{\"type\":\"number\"},\"total\":{\"type\":\"number\"}" - "},\"required\":[\"active\",\"total\"],\"type\":\"object\"},\"systemDisk\":{\"additionalProperties\":true,\"prop" - "erties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"to" - "tal\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"num" - "ber\"}]}},\"type\":\"object\"},\"systemMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\"" - ":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type" - "\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"obje" - "ct\"},\"threads\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"uptime\":{\"type\":\"number\"},\"uptimeC" - "apability\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"},\"versions\":{\"additionalProperties\":true" - ",\"properties\":{\"items\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"category\":{\"type\":\"s" - "tring\"},\"details\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"requi" - "red\":[\"name\",\"category\",\"version\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"items\"],\"type\"" - ":\"object\"},\"webThreadPoolSize\":{\"type\":\"number\"}},\"required\":[\"version\",\"git_commit\",\"cpu\",\"memo" - "ry\",\"threads\",\"webThreadPoolSize\",\"uptime\",\"disk\",\"systemDisk\",\"global_retention_days\",\"versions" - "\",\"network\",\"streams\",\"recordings\",\"streamStorage\"],\"type\":\"object\"},\"SystemLogs\":{\"additionalPr" - "operties\":true,\"properties\":{\"file\":{\"type\":\"string\"},\"level\":{\"type\":\"string\"},\"logs\":{\"items\":" - "{\"additionalProperties\":true,\"properties\":{\"level\":{\"type\":\"string\"},\"message\":{\"type\":\"string\"}" - ",\"timestamp\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"logs\",\"file\",\"lev" - "el\"],\"type\":\"object\"},\"SystemSettings\":{\"additionalProperties\":true,\"properties\":{\"api_detection" - "_backend\":{\"type\":\"string\"},\"api_detection_detector_name\":{\"type\":\"string\"},\"api_detection_forma" - "t\":{\"type\":\"string\"},\"api_detection_url\":{\"type\":\"string\"},\"audio_disabled\":{\"type\":\"boolean\"},\"" - "auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"auto_dele" - "te_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"buffer_size\":{\"type\":\"number\"}" - ",\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_backup_retention_count\":{\"type\":\"number\"},\"d" - "b_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"string\"},\"db_post_backup_script\":{\"type\":\"" - "string\"},\"default_detection_threshold\":{\"type\":\"number\"},\"default_playback_transport\":{\"type\":\"s" - "tring\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engine_delegate\":{\"type\":\"string\"},\"detection_" - "engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_threads\":{\"type\":\"number\"},\"detection_grace" - "_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\"" - ":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go2rtc_bin" - "ary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc_config_override\":{\"typ" - "e\":\"string\"},\"go2rtc_config_override_disabled_reason\":{\"type\":\"string\"},\"go2rtc_config_override_" - "quarantined\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"type\":" - "\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string\"},\"g" - "o2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_server\"" - ":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{\"type" - "\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true," - "\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"s" - "tring\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"type\":" - "\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"" - "string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"" - "],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_devic" - "e_interval_seconds\":{\"type\":\"number\"},\"health_effective_policy\":{\"additionalProperties\":true,\"pr" - "operties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"stri" - "ng\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"recovery\":{\"type\":\"n" - "umber\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"num" - "ber\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"arra" - "y\"}},\"type\":\"object\"},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_interval_seconds\":{\"type\"" - ":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"},\"health_incident_retention_days\":{\"type\"" - ":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"number\"},\"health_presence_interval_seconds\"" - ":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"health_slow_interval_seconds\":{\"type\":\"nu" - "mber\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"log_fi" - "le\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabled\":{\"type\":\"boolean\"" - "},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"login_rate_limit_window_seconds\":{\"type\":\"n" - "umber\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":\"number\"},\"max_stream" - "s\":{\"type\":\"number\"},\"max_streams_ceiling\":{\"type\":\"number\"},\"models_path\":{\"type\":\"string\"},\"mp" - "4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{\"type\":\"boolean\"},\"mqtt_broker_host\":{\"ty" - "pe\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqtt_client_id\":{\"type\":\"string\"},\"mqtt_enab" - "led\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"boolean\"},\"mqtt_ha_discovery_prefix\":{\"type" - "\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mq" - "tt_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\"" - ":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean\"},\"mqtt_topic_prefix\":{\"type\":\"string\"}," - "\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type\":\"boolean\"},\"onvif_discovery_enabled\":{\"" - "type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"number\"},\"onvif_discovery_network\":{\"type\":" - "\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_buffer\":{\"type\":\"number\"},\"pre_detection_" - "buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"number\"},\"storage_min_free_pct\":{\"type\":\"num" - "ber\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hls\":{\"type\":\"string\"},\"storage_pressure_cr" - "itical_pct\":{\"type\":\"number\"},\"storage_pressure_emergency_pct\":{\"type\":\"number\"},\"storage_pressu" - "re_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"number\"},\"syslog_enabled\":{\"type\":\"boolea" - "n\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":{\"type\":\"string\"},\"thumbnails_per_recordi" - "ng\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"number\"},\"trusted_proxy_cidrs\":{\"type\":\"str" - "ing\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"}" - ",\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"string\"},\"use_swap\":{\"type\":\"boole" - "an\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":\"string\"},\"web_port\":{\"type\":\"n" - "umber\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"type\":\"number\"},\"webrtc_connection" - "_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeou" - "t_ms\":{\"type\":\"integer\"}},\"required\":[\"log_level\",\"syslog_enabled\",\"syslog_ident\",\"syslog_facili" - "ty\",\"storage_path\",\"storage_path_hls\",\"mp4_directory_format\",\"max_storage_size\",\"retention_days\"" - ",\"auto_delete_oldest\",\"storage_min_free_pct\",\"storage_pressure_warning_pct\",\"storage_pressure_cr" - "itical_pct\",\"storage_pressure_emergency_pct\",\"generate_thumbnails\",\"thumbnails_per_recording\",\"d" - "b_path\",\"db_backup_interval_minutes\",\"db_backup_retention_count\",\"db_backup_verify\",\"db_post_bac" - "kup_script\",\"web_port\",\"web_bind_ip\",\"web_thread_pool_size\",\"max_streams\",\"max_streams_ceiling\"," - "\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_disabled\",\"webrtc_disabled\",\"hls_disabled\"" - ",\"mse_disabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\",\"truste" - "d_proxy_cidrs\",\"force_mfa_on_login\",\"login_rate_limit_enabled\",\"login_rate_limit_max_attempts\",\"" - "login_rate_limit_window_seconds\",\"buffer_size\",\"use_swap\",\"swap_size\",\"models_path\",\"api_detecti" - "on_url\",\"api_detection_backend\",\"api_detection_format\",\"api_detection_detector_name\",\"default_de" - "tection_threshold\",\"pre_detection_buffer\",\"post_detection_buffer\",\"default_playback_transport\",\"" - "detection_grace_period\",\"detection_engine_enabled\",\"detection_engine_threads\",\"detection_engine_" - "delegate\",\"go2rtc_enabled\",\"go2rtc_binary_path\",\"go2rtc_config_dir\",\"go2rtc_api_port\",\"go2rtc_rt" - "sp_port\",\"go2rtc_webrtc_enabled\",\"go2rtc_webrtc_listen_port\",\"go2rtc_stun_enabled\",\"go2rtc_stun_" - "server\",\"go2rtc_external_ip\",\"go2rtc_ice_servers\",\"go2rtc_force_native_hls\",\"go2rtc_config_overr" - "ide\",\"go2rtc_config_override_disabled_reason\",\"go2rtc_config_override_quarantined\",\"mqtt_enabled" - "\",\"mqtt_broker_host\",\"mqtt_broker_port\",\"mqtt_username\",\"mqtt_password\",\"mqtt_client_id\",\"mqtt_t" - "opic_prefix\",\"mqtt_tls_enabled\",\"mqtt_keepalive\",\"mqtt_qos\",\"mqtt_retain\",\"mqtt_ha_discovery\",\"m" - "qtt_ha_discovery_prefix\",\"mqtt_ha_snapshot_interval\",\"turn_enabled\",\"turn_server_url\",\"turn_user" - "name\",\"turn_password\",\"onvif_discovery_enabled\",\"onvif_discovery_interval\",\"onvif_discovery_netw" - "ork\",\"health_enabled\",\"health_profile\",\"health_fast_interval_seconds\",\"health_normal_interval_se" - "conds\",\"health_slow_interval_seconds\",\"health_device_interval_seconds\",\"health_write_probe_enabl" - "ed\",\"health_hardware_provider\",\"health_presence_interval_seconds\",\"health_incident_retention_day" - "s\",\"health_condition_overrides\",\"web_root\",\"log_file\",\"pid_file\",\"go2rtc_base_path\",\"lpr_retenti" - "on_days\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\",\"mp4_faststart\"],\"type\"" - ":\"object\"},\"SystemSettingsSaved\":{\"additionalProperties\":true,\"properties\":{\"restart_required\":{" - "\"type\":\"boolean\"},\"restart_required_message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"b" - "oolean\"}},\"required\":[\"success\",\"restart_required\"],\"type\":\"object\"},\"SystemSettingsUpdate\":{\"ad" - "ditionalProperties\":true,\"description\":\"Partial settings update; absent fields are retained. Sec" - "ret mask values preserve the stored password.\",\"properties\":{\"api_detection_backend\":{\"type\":\"st" - "ring\"},\"api_detection_detector_name\":{\"type\":\"string\"},\"api_detection_format\":{\"enum\":[\"light-ob" - "ject-detect\",\"doods2\"],\"type\":\"string\"},\"api_detection_url\":{\"type\":\"string\"},\"audio_disabled\":{" - "\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"" - "number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"buffer_size" - "\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_backup_retention_count\":{" - "\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"string\"},\"db_post_backu" - "p_script\":{\"type\":\"string\"},\"default_detection_threshold\":{\"type\":\"number\"},\"default_playback_tr" - "ansport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engine_delegate\":{\"type\":\"s" - "tring\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_threads\":{\"type\":\"number" - "\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"generate_" - "thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_binary_path\":{\"type\":" - "\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc_config_override\":{\"type\":\"string\"},\"go2r" - "tc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"type\":\"string\"},\"go2rtc_force_native_hls\":" - "{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"" - "go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_server\":{\"type\":\"string\"},\"go2rtc_webrtc_en" - "abled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{\"type\":\"number\"},\"health_condition_overri" - "des\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":" - "{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"}" - ",\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"type\":\"string\"},\"recovery\":{\"type\":\"number" - "\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"}" - ",\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"},\"" - "version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_device_interval_seconds\":{\"type\":\"number\"" - "},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_interval_seconds\":{\"type\":\"number\"},\"health_h" - "ardware_provider\":{\"type\":\"string\"},\"health_incident_retention_days\":{\"type\":\"number\"},\"health_n" - "ormal_interval_seconds\":{\"type\":\"number\"},\"health_presence_interval_seconds\":{\"type\":\"number\"},\"" - "health_profile\":{\"type\":\"string\"},\"health_slow_interval_seconds\":{\"type\":\"number\"},\"health_write" - "_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"log_file\":{\"type\":\"string\"" - "},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabled\":{\"type\":\"boolean\"},\"login_rate_limit_" - "max_attempts\":{\"type\":\"number\"},\"login_rate_limit_window_seconds\":{\"type\":\"number\"},\"lpr_retenti" - "on_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":\"number\"},\"max_streams\":{\"type\":\"number\"}" - ",\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{\"type" - "\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqtt_cli" - "ent_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"boolean" - "\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number\"},\"m" - "qtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"type\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"}," - "\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean\"},\"mqtt_topic_prefix\":{\"typ" - "e\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type\":\"boolean\"},\"onvif_discover" - "y_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"number\"},\"onvif_discovery_netw" - "ork\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_buffer\":{\"type\":\"number\"},\"p" - "re_detection_buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"number\"},\"storage_min_free_pct\"" - ":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hls\":{\"type\":\"string\"},\"storag" - "e_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pressure_emergency_pct\":{\"type\":\"number\"},\"s" - "torage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"number\"},\"syslog_enabled\":{\"" - "type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":{\"type\":\"string\"},\"thumbnail" - "s_per_recording\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"number\"},\"trusted_proxy_cidrs\"" - ":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\":{\"type\":\"string\"},\"turn_ser" - "ver_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"string\"},\"use_swap\":{\"type\":\"boolean\"},\"web_" - "auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":\"string\"},\"web_port\":{\"type\":\"number\"},\"w" - "eb_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"type\":\"number\"},\"webrtc_connection_timeout_m" - "s\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"ty" - "pe\":\"integer\"}},\"type\":\"object\"},\"SystemStatus\":{\"additionalProperties\":true,\"properties\":{\"mess" - "age\":{\"type\":\"string\"},\"status\":{\"const\":\"ok\",\"type\":\"string\"}},\"required\":[\"status\",\"message\"]," - "\"type\":\"object\"},\"TokenSelector\":{\"additionalProperties\":true,\"description\":\"Existing fleet sele" - "ctor grammar. Server additionally enforces depth\342\211\2448, total nodes\342\211\24464, UTF-8 byte limits and se" - "rialized selector<8192 bytes. Evaluation and delegation containment remain server decisions.\",\"p" - "roperties\":{\"expression\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"version\":{\"con" - "st\":1}},\"required\":[\"version\",\"expression\"],\"type\":\"object\"},\"TokenSelectorExpression\":{\"anyOf\":" - "[{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"all\"}},\"required\":[\"op\"],\"type\":\"obje" - "ct\"},{\"additionalProperties\":true,\"properties\":{\"children\":{\"items\":{\"$ref\":\"#/components/schema" - "s/TokenSelectorExpression\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"op\":{\"enum\":[\"and\",\"or\"]" - "}},\"required\":[\"op\",\"children\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"chi" - "ld\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"op\":{\"const\":\"not\"}},\"required\":[\"o" - "p\",\"child\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"location_" - "subtree\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" - "fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"op\",\"uuid\"],\"type\":\"object\"},{\"additionalProperties\":" - "true,\"properties\":{\"op\":{\"const\":\"enabled\"},\"value\":{\"type\":\"boolean\"}},\"required\":[\"op\",\"value\"" - "],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"camera_uuid\"]},\"val" - "ues\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-" - "F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"]" - ",\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"tag_any\",\"tag_all\",\"" - "tag_none\"]},\"uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-" - "F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":" - "[\"op\",\"uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"record" - "ing_mode\"]},\"values\":{\"items\":{\"enum\":[\"off\",\"continuous\",\"detection\"]},\"maxItems\":64,\"minItems\"" - ":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"pr" - "operties\":{\"op\":{\"enum\":[\"vendor\",\"model\"]},\"values\":{\"items\":{\"maxLength\":255,\"minLength\":1,\"ty" - "pe\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"obj" - "ect\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"capability_any\",\"capability_all\"" - "]},\"values\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchannel\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"a" - "rray\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"" - "op\":{\"enum\":[\"health\"]},\"values\":{\"items\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]}" - ",\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"}]},\"Totp" - "ChangedResponse\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"succes" - "s\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"TotpCodeRequest\":{\"additio" - "nalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":tr" - "ue}},\"required\":[\"code\"],\"type\":\"object\"},\"TotpLoginRequest\":{\"additionalProperties\":false,\"prop" - "erties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":true},\"remember_device\":{\"ty" - "pe\":\"boolean\"},\"totp_token\":{\"minLength\":1,\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"totp_" - "token\",\"code\"],\"type\":\"object\"},\"TotpLoginResponse\":{\"additionalProperties\":false,\"properties\":{" - "\"redirect\":{\"const\":\"/index.html\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"redirect\"],\"" - "type\":\"object\"},\"TotpSetupResponse\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type" - "\":\"string\"},\"otpauth_uri\":{\"format\":\"uri\",\"pattern\":\"^otpauth://totp/\",\"readOnly\":true,\"type\":\"s" - "tring\"},\"secret\":{\"pattern\":\"^[A-Z2-7]{32}$\",\"readOnly\":true,\"type\":\"string\"},\"success\":{\"const\"" - ":true}},\"required\":[\"success\",\"secret\",\"otpauth_uri\",\"message\"],\"type\":\"object\"},\"TotpStatusResp" - "onse\":{\"additionalProperties\":false,\"properties\":{\"totp_configured\":{\"type\":\"boolean\"},\"totp_ena" - "bled\":{\"type\":\"boolean\"}},\"required\":[\"totp_enabled\",\"totp_configured\"],\"type\":\"object\"},\"Truste" - "dDevice\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"}" - ",\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"id\":{\"maximum\":900719" - "9254740991,\"minimum\":1,\"type\":\"integer\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_use" - "d_at\":{\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":" - "[\"id\",\"created_at\",\"expires_at\",\"ip_address\",\"user_agent\",\"current\",\"last_used_at\"],\"type\":\"obje" - "ct\"},\"TrustedDevicesResponse\":{\"additionalProperties\":false,\"properties\":{\"trusted_devices\":{\"it" - "ems\":{\"$ref\":\"#/components/schemas/TrustedDevice\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"t" - "rusted_devices\"],\"type\":\"object\"},\"WeeklySchedule\":{\"description\":\"Seven days \303\227 24 hours, index" - "ed day*24+hour. true permits recording in that slot; interpreted using the configured recorder s" - "chedule timezone.\",\"items\":{\"type\":\"boolean\"},\"maxItems\":168,\"minItems\":168,\"type\":\"array\"},\"Wor" - "kspacesResponse\":{\"additionalProperties\":true,\"properties\":{\"configurable\":{\"type\":\"boolean\"},\"s" - "cope\":{\"enum\":[\"user\",\"installation\"],\"type\":\"string\"},\"workspaces\":{\"items\":{\"additionalPropert" - "ies\":true,\"properties\":{\"authorization_unchanged\":{\"const\":true,\"type\":\"boolean\"},\"configurable\"" - ":{\"type\":\"boolean\"},\"description\":{\"type\":\"string\"},\"key\":{\"enum\":[\"live.navigator\",\"investigati" - "on\"],\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"visible\":{\"type\":\"boolean\"}},\"required\":[\"key\"," - "\"label\",\"description\",\"visible\",\"configurable\",\"authorization_unchanged\"],\"type\":\"object\"},\"type" - "\":\"array\"}},\"required\":[\"workspaces\",\"configurable\",\"scope\"],\"type\":\"object\"},\"WorkspacesUpdate\"" - ":{\"additionalProperties\":true,\"properties\":{\"workspaces\":{\"additionalProperties\":false,\"minPrope" - "rties\":1,\"properties\":{\"investigation\":{\"type\":\"boolean\"},\"live.navigator\":{\"type\":\"boolean\"}},\"" - "type\":\"object\"}},\"required\":[\"workspaces\"],\"type\":\"object\"}},\"securitySchemes\":{\"apiKeyHeader\":{" - "\"description\":\"Legacy API key or scoped token. Scope intersects the active owner\342\200\231s authority.\"" - ",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"basicAuth\":{\"description\":\"Existing database" - " username/password authentication; restricted when a required password change is pending.\",\"sche" - "me\":\"basic\",\"type\":\"http\"},\"bearerToken\":{\"description\":\"Legacy API key or scoped token through " - "Authorization. Use a cookie-free client when discovering token-specific permissions.\",\"scheme\":\"" - "bearer\",\"type\":\"http\"},\"legacyApiKeyHeader\":{\"description\":\"Legacy API key only. This operation " - "rejects scoped API tokens even when their owner could call it.\",\"in\":\"header\",\"name\":\"X-API-Key\"" - ",\"type\":\"apiKey\"},\"legacyBearer\":{\"description\":\"Legacy API key through Authorization: Bearer. S" - "coped tokens are not accepted on this operation. A valid session cookie still takes precedence.\"" - ",\"scheme\":\"bearer\",\"type\":\"http\"},\"sessionCookie\":{\"description\":\"Existing authenticated session" - " cookie; checked before other credentials. The explorer uses this method only.\",\"in\":\"cookie\",\"n" - "ame\":\"session\",\"type\":\"apiKey\"}}},\"info\":{\"description\":\"Canonical contract for the current C ba" - "ckend: authentication and administration, fleet and workspaces, streams and devices, recordings " - "and investigations, storage, events, system settings and bootstrap. The authenticated discovery " - "endpoint projects operations and response variants for the current principal. Eight media-protoc" - "ol surfaces and one internal callback are documented separately in the coverage inventory. Disco" - "very is descriptive and does not grant access.\",\"title\":\"LightNVR API \342\200\224 permission-aware contr" - "acts\",\"version\":\"0.46.3-contracts.3\"},\"openapi\":\"3.1.1\",\"paths\":{\"/api/audit/events\":{\"get\":{\"de" - "scription\":\"Discovery is an operation-level capability. The server independently checks the conc" - "rete target and current effective authority. Basic credentials, session cookies and legacy keys " - "retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"lis" - "tAuditEvents\",\"parameters\":[{\"description\":\"One-based page.\",\"in\":\"query\",\"name\":\"page\",\"schema\"" - ":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Page size, includ" - "ing CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"schema\":{\"default\":100,\"maximum\":1000,\"minimu" - "m\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed" - " a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"descri" - "ption\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\"," - "\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bi" - "t integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"principal_user_id\",\"sche" - "ma\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Trimmed exact filter; native field capacity " - "applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact" - " filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"" - "}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"ta" - "rget_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capaci" - "ty applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimme" - "d exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"event_type\",\"schema\":{\"type" - "\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/AuditEventsPage\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or " - "body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority i" - "s insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"T" - "arget does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-" - "use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read" - " current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHea" - "der\":[]},{\"bearerToken\":[]}],\"summary\":\"List audit events\",\"tags\":[\"Audit\"],\"x-lightnvr-authoriz" - "ation\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}" - "}},\"/api/audit/events/export\":{\"get\":{\"description\":\"Discovery is an operation-level capability." + "o.\",\"type\":[\"number\",\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this ove" + "rride; omission preserves the current value. Nonempty values are a supported RTMP/RTMPS destinat" + "ion list, at most 511 UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\"" + ":[\"string\",\"null\"]},\"record\":{\"description\":\"Omission or null preserves the current value.\",\"typ" + "e\":[\"boolean\",\"null\"]},\"record_audio\":{\"description\":\"Omission or null preserves the current val" + "ue.\",\"type\":[\"boolean\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves " + "the current value.\",\"type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/compone" + "nts/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactl" + "y 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element" + " as false, rather than rejecting all malformed schedule input.\"},\"retention_days\":{\"description\"" + ":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited" + ", positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveM" + "aximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"O" + "mission or null preserves the current value. Integer-backed fields truncate numeric input toward" + " zero.\",\"type\":[\"number\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separ" + "ate privacy-only form; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_pri" + "ority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields trunc" + "ate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Om" + "ission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"descr" + "iption\":\"Null or empty string clears this override; omission preserves the current value.\",\"type" + "\":[\"string\",\"null\"]},\"tags\":{\"description\":\"Null or empty string clears this override; omission " + "preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description" + "\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input to" + "ward zero.\",\"type\":[\"number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or nu" + "ll preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type" + "\":[\"number\",\"null\"]},\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the " + "current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null" + "\"]},\"url\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\",\"url\"],\"type\":\"object\"},\"StreamCre" + "ated\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"type\":\"string\"},\"location_uuid\"" + ":{\"type\":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"]," + "\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\"," + "\"location_uuid\"],\"type\":\"object\"},\"StreamDeleteAccepted\":{\"additionalProperties\":true,\"propertie" + "s\":{\"permanent\":{\"type\":\"boolean\"},\"status\":{\"const\":\"accepted\",\"type\":\"string\"},\"success\":{\"con" + "st\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"permanent\",\"status\"],\"type\":\"object\"},\"Stream" + "DetailsBase\":{\"additionalProperties\":true,\"description\":\"Fields shared by compatibility-list row" + "s and full detail. Strings use empty values rather than null; additional fields are preserved fo" + "r additive compatibility.\",\"properties\":{\"audio_voice_enhancement\":{\"type\":\"boolean\"},\"backchann" + "el_enabled\":{\"type\":\"boolean\"},\"camera_uuid\":{\"description\":\"Stable camera UUID. Older migrated " + "rows may have an empty string; no instance identifiers are embedded in this document.\",\"type\":\"s" + "tring\"},\"codec\":{\"type\":\"string\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"detection_inte" + "rval\":{\"type\":\"integer\"},\"detection_model\":{\"type\":\"string\"},\"detection_object_filter\":{\"type\":\"" + "string\"},\"detection_object_filter_list\":{\"type\":\"string\"},\"detection_record_on_schedule\":{\"type\"" + ":\"boolean\"},\"detection_recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"detec" + "tion_retention_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive " + "values are days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"dete" + "ction_threshold\":{\"description\":\"Integer percentage emitted from stored float confidence, includ" + "ing zero. The existing update handler does not clamp this to 0\342\200\223100.\",\"type\":\"integer\"},\"enable" + "d\":{\"type\":\"boolean\"},\"eptz_config\":{\"description\":\"Empty string disables browser dewarping; oth" + "erwise a serialized version-1 JSON document. It is a string on the wire, not an object.\",\"maxLen" + "gth\":1023,\"type\":\"string\"},\"error_message\":{\"description\":\"Present only when an Error state has " + "a recorded reason.\",\"type\":\"string\"},\"fps\":{\"description\":\"Detected metadata; zero is valid when" + " unavailable.\",\"type\":\"integer\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"ty" + "pe\":\"boolean\"},\"height\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"typ" + "e\":\"integer\"},\"isOnvif\":{\"type\":\"boolean\"},\"location_uuid\":{\"description\":\"Primary location UUID" + ", or an empty string when unset.\",\"type\":\"string\"},\"max_storage_mb\":{\"description\":\"Storage quot" + "a in MB; zero means unlimited.\",\"type\":\"integer\"},\"motion_trigger_source\":{\"type\":\"string\"},\"nam" + "e\":{\"description\":\"Current stream name; lookup is by name, not camera_uuid.\",\"type\":\"string\"},\"o" + "bservation_evidence_enabled\":{\"default\":false,\"description\":\"INT_01 per-camera opt-in for observ" + "ation evidence for integrations (contract section 13). Off by default. Requires a bird-capable o" + "bject detector for bird integrations.\",\"type\":\"boolean\"},\"observation_labels\":{\"default\":\"\",\"des" + "cription\":\"Comma-separated detector labels eligible for observation evidence; empty means every " + "object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whi" + "tespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector" + " label (contract section 13).\",\"maxLength\":511,\"type\":\"string\"},\"observation_max_age_days\":{\"def" + "ault\":7,\"description\":\"Evidence media maximum age in days (contract section 13).\",\"maximum\":90,\"" + "minimum\":1,\"type\":\"integer\"},\"observation_max_bytes\":{\"default\":2147483648,\"description\":\"Eviden" + "ce media byte budget for this camera, 64 MiB..1 TiB (contract section 13).\",\"maximum\":1099511627" + "776,\"minimum\":67108864,\"type\":\"integer\"},\"onvif_port\":{\"description\":\"Zero requests automatic po" + "rt detection.\",\"type\":\"integer\"},\"onvif_profile\":{\"type\":\"string\"},\"playback_transport\":{\"$ref\":" + "\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"type\":\"integer\"},\"pre_detect" + "ion_buffer\":{\"type\":\"integer\"},\"priority\":{\"type\":\"integer\"},\"privacy_mode\":{\"type\":\"boolean\"},\"" + "protocol\":{\"description\":\"Stored transport code: 0 TCP, 1 UDP. Existing configuration paths may " + "retain other numeric values.\",\"type\":\"integer\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"ptz_has_home\":" + "{\"type\":\"boolean\"},\"ptz_max_x\":{\"type\":\"integer\"},\"ptz_max_y\":{\"type\":\"integer\"},\"ptz_max_z\":{\"t" + "ype\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record_audio\":{\"type\":\"boolean\"},\"record_on_schedul" + "e\":{\"type\":\"boolean\"},\"recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"reten" + "tion_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive values are" + " days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"segment_durati" + "on\":{\"type\":\"integer\"},\"status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"storage_priority\":" + "{\"type\":\"integer\"},\"streaming_enabled\":{\"type\":\"boolean\"},\"tags\":{\"type\":\"string\"},\"tier_critica" + "l_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is in" + "serted.\",\"type\":\"number\"},\"tier_ephemeral_multiplier\":{\"description\":\"Stored tier multiplier. Ze" + "ro is preserved; no client default is inserted.\",\"type\":\"number\"},\"tier_important_multiplier\":{\"" + "description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":" + "\"number\"},\"url\":{\"description\":\"Configured input URL. For readers without camera.configure, URL " + "userinfo is stripped; unsupported URL formats may become an empty string. For configurators the " + "legacy/custom form may remain, while ONVIF/separate-credential configurations still strip URL us" + "erinfo.\",\"type\":\"string\"},\"width\":{\"description\":\"Detected metadata; zero is valid when unavaila" + "ble.\",\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"location_uuid\",\"name\",\"url\",\"eptz_config\",\"c" + "odec\",\"detection_model\",\"detection_object_filter\",\"detection_object_filter_list\",\"onvif_profile\"" + ",\"tags\",\"motion_trigger_source\",\"enabled\",\"streaming_enabled\",\"record\",\"detection_based_recordin" + "g\",\"record_audio\",\"audio_voice_enhancement\",\"isOnvif\",\"backchannel_enabled\",\"ptz_enabled\",\"ptz_h" + "as_home\",\"record_on_schedule\",\"detection_record_on_schedule\",\"privacy_mode\",\"has_sub_stream\",\"go" + "2rtc_hls_available\",\"width\",\"height\",\"fps\",\"priority\",\"segment_duration\",\"detection_threshold\",\"" + "detection_interval\",\"pre_detection_buffer\",\"post_detection_buffer\",\"protocol\",\"retention_days\",\"" + "detection_retention_days\",\"max_storage_mb\",\"storage_priority\",\"ptz_max_x\",\"ptz_max_y\",\"ptz_max_z" + "\",\"onvif_port\",\"tier_critical_multiplier\",\"tier_important_multiplier\",\"tier_ephemeral_multiplier" + "\",\"playback_transport\",\"recording_schedule\",\"detection_recording_schedule\",\"status\"],\"type\":\"obj" + "ect\"},\"StreamDetectionEngine\":{\"additionalProperties\":true,\"properties\":{\"config\":{\"additionalPr" + "operties\":true,\"description\":\"Engine-specific configuration; current engine plugins own its keys" + ". Serialized JSON must fit the native configuration buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":" + "\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"minimum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":" + "63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string\"},\"managed_by_legacy_stream_fields\":{\"" + "type\":\"boolean\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"sort_order\":{\"maximum\":21474836" + "47,\"minimum\":-2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}" + ",\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"type\":\"string\"}},\"required\":[\"key\"" + ",\"type\",\"model_path\",\"enabled\",\"threshold\",\"interval_seconds\",\"sort_order\",\"config\",\"managed_by_" + "legacy_stream_fields\"],\"type\":\"object\"},\"StreamDetectionEngineWrite\":{\"additionalProperties\":tru" + "e,\"properties\":{\"config\":{\"additionalProperties\":true,\"description\":\"Engine-specific configurati" + "on; current engine plugins own its keys. Serialized JSON must fit the native configuration buffe" + "r.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"minimum\":" + "1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string" + "\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"sort_order\":{\"maximum\":2147483647,\"minimum\":-" + "2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum" + "\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"type\":\"string\"}},\"required\":[\"key\",\"type\",\"thres" + "hold\",\"interval_seconds\"],\"type\":\"object\"},\"StreamDetectionEngines\":{\"additionalProperties\":true" + ",\"properties\":{\"count\":{\"type\":\"integer\"},\"engines\":{\"items\":{\"$ref\":\"#/components/schemas/Strea" + "mDetectionEngine\"},\"type\":\"array\"},\"restart_required\":{\"type\":\"boolean\"},\"stream_name\":{\"type\":\"" + "string\"},\"trigger_policy\":{\"const\":\"any_of\",\"type\":\"string\"}},\"required\":[\"stream_name\",\"engines" + "\",\"count\",\"trigger_policy\",\"restart_required\"],\"type\":\"object\"},\"StreamDetectionEnginesRequest\":" + "{\"additionalProperties\":true,\"properties\":{\"engines\":{\"items\":{\"$ref\":\"#/components/schemas/Stre" + "amDetectionEngineWrite\"},\"maxItems\":7,\"type\":\"array\"}},\"required\":[\"engines\"],\"type\":\"object\"},\"" + "StreamFullPrivilegedResponse\":{\"additionalProperties\":true,\"properties\":{\"stream\":{\"$ref\":\"#/com" + "ponents/schemas/StreamPrivileged\"}},\"required\":[\"stream\"],\"type\":\"object\"},\"StreamFullRedactedRe" + "sponse\":{\"additionalProperties\":true,\"properties\":{\"stream\":{\"$ref\":\"#/components/schemas/Stream" + "Redacted\"}},\"required\":[\"stream\"],\"type\":\"object\"},\"StreamFullResponse\":{\"anyOf\":[{\"$ref\":\"#/com" + "ponents/schemas/StreamFullRedactedResponse\"},{\"$ref\":\"#/components/schemas/StreamFullPrivilegedR" + "esponse\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resourc" + "e\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"runtime\"}],\"version\":1}}],\"description\":\"Full" + " details are wrapped in stream. Read authorization uses raw camera facts; privileged fields addi" + "tionally use runtime-enriched facts on the same camera.\"},\"StreamHealth\":{\"additionalProperties\"" + ":true,\"properties\":{\"failedRequests\":{\"type\":\"number\"},\"healthy\":{\"type\":\"boolean\"},\"status\":{\"t" + "ype\":\"string\"},\"storage\":{\"additionalProperties\":true,\"properties\":{\"available_bytes\":{\"type\":\"n" + "umber\"},\"used_bytes\":{\"type\":\"number\"}},\"required\":[\"used_bytes\",\"available_bytes\"],\"type\":\"obje" + "ct\"},\"streams\":{\"additionalProperties\":true,\"properties\":{\"degraded\":{\"type\":\"number\"},\"down\":{\"" + "type\":\"number\"},\"total\":{\"type\":\"number\"},\"up\":{\"type\":\"number\"}},\"required\":[\"total\",\"up\",\"degr" + "aded\",\"down\"],\"type\":\"object\"},\"streams_detail\":{\"items\":{\"additionalProperties\":true,\"propertie" + "s\":{\"bitrate_bps\":{\"type\":\"number\"},\"configured_fps\":{\"type\":\"number\"},\"connection_latency_ms\":{" + "\"type\":\"number\"},\"current_fps\":{\"type\":\"number\"},\"detector_operating\":{\"type\":\"boolean\"},\"expect" + "ed_fps\":{\"type\":\"number\"},\"fps\":{\"type\":\"number\"},\"frames_dropped\":{\"type\":\"number\"},\"frames_tot" + "al\":{\"type\":\"number\"},\"last_completed_segment_ts\":{\"type\":\"number\"},\"last_frame_ts\":{\"type\":\"num" + "ber\"},\"last_packet_ts\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"observed_fps\":{\"type\":\"number" + "\"},\"reconnects\":{\"type\":\"number\"},\"recorder_session_active\":{\"type\":\"boolean\"},\"recorder_writing" + "\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"boolean\"},\"recording_gaps\":{\"type\":\"number\"},\"s" + "ource_connected\":{\"type\":\"boolean\"},\"sparkline_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array" + "\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"status\":{\"type\":\"string\"},\"uptime" + "_seconds\":{\"type\":\"number\"},\"video_advancing\":{\"type\":\"boolean\"}},\"required\":[\"name\",\"status\",\"o" + "bserved_fps\",\"current_fps\",\"expected_fps\",\"fps\",\"configured_fps\",\"bitrate_bps\",\"uptime_seconds\"," + "\"reconnects\",\"last_packet_ts\",\"last_frame_ts\",\"last_completed_segment_ts\",\"frames_total\",\"frames" + "_dropped\",\"connection_latency_ms\",\"source_connected\",\"video_advancing\",\"recorder_writing\",\"detec" + "tor_operating\",\"recorder_session_active\",\"recording_active\",\"recording_gaps\"],\"type\":\"object\"},\"" + "type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"number\"},\"uptime\":{\"type\":" + "\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"status\"," + "\"streams\",\"storage\",\"streams_detail\"],\"type\":\"object\"},\"StreamHealthHistory\":{\"additionalPropert" + "ies\":true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"stream" + "s_detail\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"name\":{\"type\":\"string\"},\"sparklin" + "e_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"}" + ",\"type\":\"array\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"stri" + "ng\"},\"totalRequests\":{\"type\":\"integer\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptim" + "e\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"streams_detail\"],\"type\":\"object\"},\"StreamListRe" + "sponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamCompatibilityList\"},{\"$ref\":\"#/components/" + "schemas/StreamSummaryPage\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPage\"},{\"$ref\":\"#/com" + "ponents/schemas/LprStreamSummaryPage\"}],\"description\":\"Select the response with the request quer" + "y. Consumers validating an admin or live summary must use its specific page schema: the identity" + "-only LPR variant is deliberately minimal.\"},\"StreamPrivacyUpdate\":{\"additionalProperties\":false" + ",\"description\":\"Synchronous lightweight privacy toggle. No other update fields may accompany it;" + " unknown fields are rejected in this form.\",\"properties\":{\"name\":{\"description\":\"Optional ignore" + "d identifying metadata.\",\"type\":\"string\"},\"set_privacy_mode\":{\"type\":\"boolean\"}},\"required\":[\"se" + "t_privacy_mode\"],\"type\":\"object\"},\"StreamPrivacyUpdated\":{\"additionalProperties\":true,\"descripti" + "on\":\"HTTP 200 privacy result. A disabled stream can succeed with stream_registered=false; this i" + "s not evidence of a live stream.\",\"properties\":{\"privacy_mode\":{\"type\":\"boolean\"},\"stream_regist" + "ered\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"priva" + "cy_mode\",\"stream_registered\"],\"type\":\"object\"},\"StreamPrivileged\":{\"allOf\":[{\"$ref\":\"#/component" + "s/schemas/StreamDetailsBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"type\":\"st" + "ring\"},\"can_control_privacy\":{\"const\":true,\"type\":\"boolean\"},\"detection_url\":{\"type\":\"string\"},\"" + "go2rtc_source_override\":{\"description\":\"Single source string or multiline YAML source list; empt" + "y means generated configuration.\",\"type\":\"string\"},\"onvif_password\":{\"description\":\"Configuratio" + "n credential, conditionally visible for this resource. Empty is valid when unset.\",\"type\":\"strin" + "g\"},\"onvif_username\":{\"type\":\"string\"},\"publish_url\":{\"type\":\"string\"},\"sub_stream_url\":{\"type\":" + "\"string\"}},\"required\":[\"onvif_username\",\"onvif_password\",\"admin_url\",\"go2rtc_source_override\",\"s" + "ub_stream_url\",\"detection_url\",\"publish_url\",\"can_control_privacy\"],\"type\":\"object\"}],\"descripti" + "on\":\"This camera is both readable and configurable for the current principal. Privileged values " + "may still be empty; authority over another camera is insufficient.\"},\"StreamProbeRequest\":{\"addi" + "tionalProperties\":true,\"properties\":{\"onvif_password\":{\"type\":\"string\"},\"onvif_username\":{\"type\"" + ":\"string\"},\"protocol\":{\"type\":\"integer\"},\"url\":{\"type\":\"string\"}},\"required\":[\"url\",\"protocol\"]," + "\"type\":\"object\"},\"StreamProbeResponse\":{\"oneOf\":[{\"additionalProperties\":true,\"properties\":{\"inf" + "o\":{\"additionalProperties\":true,\"properties\":{\"codec\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"}" + ",\"height\":{\"type\":\"integer\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"width\",\"height\",\"fps\",\"cod" + "ec\"],\"type\":\"object\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"info\"],\"" + "type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\"" + ":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"}]},\"StreamRe" + "cordingChanged\":{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"ty" + "pe\":\"string\"},\"state\":{\"enum\":[\"starting\",\"idle\"],\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"s" + "uccess\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"stream\",\"action\",\"state\"],\"type\"" + ":\"object\"},\"StreamRecordingRequest\":{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":" + "[\"start\",\"stop\"],\"type\":\"string\"}},\"required\":[\"action\"],\"type\":\"object\"},\"StreamRecordingState\"" + ":{\"additionalProperties\":true,\"properties\":{\"capture_method\":{\"type\":[\"string\",\"null\"]},\"manual_" + "control_allowed\":{\"type\":\"boolean\"},\"manual_control_reason\":{\"enum\":[\"read_only\",\"continuous_rec" + "ording\",null],\"type\":[\"string\",\"null\"]},\"recording_active\":{\"type\":\"boolean\"},\"recording_id\":{\"t" + "ype\":[\"integer\",\"null\"]},\"state\":{\"enum\":[\"starting\",\"recording\",\"idle\"],\"type\":\"string\"},\"strea" + "m\":{\"type\":\"string\"}},\"required\":[\"stream\",\"state\",\"recording_active\",\"capture_method\",\"recordin" + "g_id\",\"manual_control_allowed\",\"manual_control_reason\"],\"type\":\"object\"},\"StreamRedacted\":{\"allO" + "f\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase\"},{\"additionalProperties\":true,\"properties\":" + "{\"admin_url\":{\"const\":\"\",\"type\":\"string\"},\"can_control_privacy\":{\"const\":false,\"type\":\"boolean\"}" + ",\"detection_url\":{\"const\":\"\",\"type\":\"string\"},\"go2rtc_source_override\":{\"const\":\"\",\"type\":\"strin" + "g\"},\"onvif_password\":{\"const\":\"\",\"type\":\"string\"},\"onvif_username\":{\"const\":\"\",\"type\":\"string\"}," + "\"publish_url\":{\"const\":\"\",\"type\":\"string\"},\"sub_stream_url\":{\"const\":\"\",\"type\":\"string\"}},\"requi" + "red\":[\"onvif_username\",\"onvif_password\",\"admin_url\",\"go2rtc_source_override\",\"sub_stream_url\",\"d" + "etection_url\",\"publish_url\",\"can_control_privacy\"],\"type\":\"object\"}],\"description\":\"This camera " + "is readable but not configurable. Sensitive fields remain present as empty strings; has_sub_stre" + "am can still be true.\"},\"StreamRefreshAccepted\":{\"additionalProperties\":true,\"properties\":{\"coal" + "esced\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"retry_after_seconds\":{\"type\":\"integer\"},\"" + "stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"mess" + "age\",\"stream\"],\"type\":\"object\"},\"StreamRetention\":{\"additionalProperties\":true,\"properties\":{\"de" + "tection_retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\":{\"maximum" + "\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"retention_days\":{\"" + "maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"stream" + "_name\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\"],\"type\":\"object\"},\"StreamRet" + "entionRequest\":{\"additionalProperties\":true,\"properties\":{\"detection_retention_days\":{\"maximum\":" + "365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\":{\"maximum\":9007199254740991,\"minimum\":0,\"typ" + "e\":\"integer\"},\"retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"}},\"required\":[],\"typ" + "e\":\"object\"},\"StreamSingleResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$" + "ref\":\"#/components/schemas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requireme" + "nts\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"runtime\"}" + "],\"version\":1}}],\"description\":\"Single legacy stream object. URL and credential fields are redac" + "ted unless this same camera is configurable.\"},\"StreamStatus\":{\"enum\":[\"Stopped\",\"Starting\",\"Run" + "ning\",\"Stopping\",\"Error\",\"Reconnecting\",\"Degraded\",\"Unknown\"],\"type\":\"string\"},\"StreamSummaryBas" + "e\":{\"additionalProperties\":true,\"description\":\"Administrative and live summary fields. Unlike de" + "tail, summary rows contain no input URL, credentials or error_message.\",\"properties\":{\"availabil" + "ity\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_uuid\":{\"ty" + "pe\":\"string\"},\"codec\":{\"type\":\"string\"},\"current_fps\":{\"type\":\"number\"},\"detection_based_recordi" + "ng\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"first_video_at\":{\"description\":\"Unix second" + "s; zero means not observed.\",\"type\":\"integer\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"intege" + "r\"},\"isOnvif\":{\"type\":\"boolean\"},\"last_frame_ts\":{\"description\":\"Unix seconds; zero means not ob" + "served.\",\"type\":\"integer\"},\"last_recording_at\":{\"description\":\"Unix seconds; zero means not obse" + "rved.\",\"type\":\"integer\"},\"location_name\":{\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"lo" + "cation_uuid\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"protocol\":{\"type\":\"integer\"},\"record\":{" + "\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"boolean\"}," + "\"status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"tags\":{\"type\":\"string\"},\"width\":{\"type\":\"" + "integer\"}},\"required\":[\"camera_uuid\",\"name\",\"location_uuid\",\"location_name\",\"location_path\",\"cod" + "ec\",\"tags\",\"enabled\",\"record\",\"record_on_schedule\",\"detection_based_recording\",\"isOnvif\",\"record" + "ing_active\",\"width\",\"height\",\"fps\",\"protocol\",\"first_video_at\",\"last_frame_ts\",\"last_recording_a" + "t\",\"current_fps\",\"status\",\"availability\"],\"type\":\"object\"},\"StreamSummaryPage\":{\"additionalPrope" + "rties\":true,\"description\":\"summary=true, surface=admin (default). Totals count only authorized, " + "searched and availability-filtered rows before pagination. A page beyond the end has an empty st" + "reams array; total_pages is zero when total is zero.\",\"properties\":{\"page\":{\"maximum\":1000000,\"m" + "inimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{" + "\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryRedacted\"},{\"$ref\":\"#/components/sc" + "hemas/StreamSummaryPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action" + "\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]" + "},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"inte" + "ger\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"StreamS" + "ummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProper" + "ties\":true,\"properties\":{\"admin_url\":{\"description\":\"Only emitted for surface=admin with include" + "_admin_url=true or 1. Empty when not configurable.\",\"type\":\"string\"},\"can_configure\":{\"const\":tr" + "ue,\"type\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"object\"}]},\"StreamSummaryRedacted\":{\"" + "allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properti" + "es\":{\"admin_url\":{\"const\":\"\",\"description\":\"Only emitted for surface=admin with include_admin_ur" + "l=true or 1. Empty when not configurable.\",\"type\":\"string\"},\"can_configure\":{\"const\":false,\"type" + "\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"object\"}]},\"StreamUpdateAccepted\":{\"additiona" + "lProperties\":true,\"description\":\"HTTP 202 acknowledges a queued update, not successful durable a" + "pplication. ONVIF results are optional. Re-fetch detail to observe eventual state; a request req" + "uiring ONVIF work may perform that probe before acceptance.\",\"properties\":{\"message\":{\"type\":\"st" + "ring\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"],\"type\":\"stri" + "ng\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"" + "},\"StreamUpdateRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamConfigurationUpdate\"},{\"$r" + "ef\":\"#/components/schemas/StreamPrivacyUpdate\"}]},\"StreamZones\":{\"additionalProperties\":true,\"pr" + "operties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZone\"},\"type\":\"array\"}},\"requ" + "ired\":[\"zones\"],\"type\":\"object\"},\"StreamZonesDeleted\":{\"additionalProperties\":true,\"properties\":" + "{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"m" + "essage\"],\"type\":\"object\"},\"StreamZonesRequest\":{\"additionalProperties\":true,\"properties\":{\"zones" + "\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZoneWrite\"},\"maxItems\":16,\"type\":\"array\"}},\"re" + "quired\":[\"zones\"],\"type\":\"object\"},\"StreamZonesSaved\":{\"additionalProperties\":true,\"properties\":" + "{\"count\":{\"maximum\":16,\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"success\":{\"con" + "st\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"count\"],\"type\":\"object\"},\"SuccessRe" + "sponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false,\"password_ha" + "sh\":false,\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_secret\":false,\"totp_t" + "oken\":false},\"required\":[\"success\"],\"type\":\"object\"},\"SystemActionAccepted\":{\"additionalProperti" + "es\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"r" + "equired\":[\"success\",\"message\"],\"type\":\"object\"},\"SystemBackupCreated\":{\"additionalProperties\":tr" + "ue,\"properties\":{\"backupUrl\":{\"type\":\"string\"},\"filename\":{\"type\":\"string\"},\"message\":{\"type\":\"s" + "tring\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"backupUrl\",\"" + "filename\"],\"type\":\"object\"},\"SystemInformation\":{\"additionalProperties\":true,\"properties\":{\"cpu\"" + ":{\"additionalProperties\":true,\"properties\":{\"cores\":{\"type\":\"number\"},\"model\":{\"type\":\"string\"}," + "\"usage\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"usageCapability\":{\"type\":\"string\"}},\"type" + "\":\"object\"},\"detectorMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"st" + "ring\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"" + "type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"disk\":" + "{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"typ" + "e\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"an" + "yOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"git_commit\":{\"type\":\"string\"},\"glob" + "al_retention_days\":{\"type\":\"number\"},\"go2rtcMemory\":{\"additionalProperties\":true,\"properties\":{\"" + "capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"any" + "Of\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"" + "type\":\"object\"},\"memory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string" + "\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type" + "\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"network\":{" + "\"additionalProperties\":true,\"properties\":{\"interfaces\":{\"items\":{\"additionalProperties\":true,\"pr" + "operties\":{\"address\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"capability\":{\"type\":\"string\"" + "},\"ipv6\":{\"type\":\"string\"},\"mac\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"up\":{\"type\":\"boolea" + "n\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"array\"}},\"type\":\"object\"},\"observation_pipelin" + "e\":{\"$ref\":\"#/components/schemas/ObservationPipelineDiagnostics\"},\"recordings\":{\"additionalPrope" + "rties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"count" + "\",\"size\"],\"type\":\"object\"},\"streamStorage\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"" + "count\":{\"type\":\"number\"},\"detection_retention_days\":{\"type\":\"number\"},\"max_storage_mb\":{\"type\":\"" + "number\"},\"name\":{\"type\":\"string\"},\"retention_days\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"" + "required\":[\"name\",\"size\",\"count\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\"],\"" + "type\":\"object\"},\"type\":\"array\"},\"streams\":{\"additionalProperties\":true,\"properties\":{\"active\":{\"" + "type\":\"number\"},\"total\":{\"type\":\"number\"}},\"required\":[\"active\",\"total\"],\"type\":\"object\"},\"syste" + "mDisk\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\"" + ":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"use" + "d\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"systemMemory\":{\"additionalPr" + "operties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"t" + "ype\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":" + "\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"threads\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"numbe" + "r\"}]},\"uptime\":{\"type\":\"number\"},\"uptimeCapability\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"" + "},\"versions\":{\"additionalProperties\":true,\"properties\":{\"items\":{\"items\":{\"additionalProperties\"" + ":true,\"properties\":{\"category\":{\"type\":\"string\"},\"details\":{\"type\":\"string\"},\"name\":{\"type\":\"str" + "ing\"},\"version\":{\"type\":\"string\"}},\"required\":[\"name\",\"category\",\"version\"],\"type\":\"object\"},\"ty" + "pe\":\"array\"}},\"required\":[\"items\"],\"type\":\"object\"},\"webThreadPoolSize\":{\"type\":\"number\"}},\"requ" + "ired\":[\"version\",\"git_commit\",\"cpu\",\"memory\",\"threads\",\"webThreadPoolSize\",\"uptime\",\"disk\",\"syst" + "emDisk\",\"global_retention_days\",\"versions\",\"network\",\"streams\",\"recordings\",\"streamStorage\"],\"ty" + "pe\":\"object\"},\"SystemLogs\":{\"additionalProperties\":true,\"properties\":{\"file\":{\"type\":\"string\"},\"" + "level\":{\"type\":\"string\"},\"logs\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"level\":{\"ty" + "pe\":\"string\"},\"message\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\"" + ":\"array\"}},\"required\":[\"logs\",\"file\",\"level\"],\"type\":\"object\"},\"SystemSettings\":{\"additionalProp" + "erties\":true,\"properties\":{\"api_detection_backend\":{\"type\":\"string\"},\"api_detection_detector_nam" + "e\":{\"type\":\"string\"},\"api_detection_format\":{\"type\":\"string\"},\"api_detection_url\":{\"type\":\"strin" + "g\"},\"audio_disabled\":{\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_ti" + "meout_hours\":{\"type\":\"number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"" + "boolean\"},\"buffer_size\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_bac" + "kup_retention_count\":{\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"s" + "tring\"},\"db_post_backup_script\":{\"type\":\"string\"},\"default_detection_threshold\":{\"type\":\"number\"" + "},\"default_playback_transport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engin" + "e_delegate\":{\"type\":\"string\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_th" + "reads\":{\"type\":\"number\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\"" + ":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc" + "_base_path\":{\"type\":\"string\"},\"go2rtc_binary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\"" + ":\"string\"},\"go2rtc_config_override\":{\"type\":\"string\"},\"go2rtc_config_override_disabled_reason\":{" + "\"type\":\"string\"},\"go2rtc_config_override_quarantined\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\"" + ":\"boolean\"},\"go2rtc_external_ip\":{\"type\":\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"}," + "\"go2rtc_ice_servers\":{\"type\":\"string\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled" + "\":{\"type\":\"boolean\"},\"go2rtc_stun_server\":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boo" + "lean\"},\"go2rtc_webrtc_listen_port\":{\"type\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"ty" + "pe\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalPropert" + "ies\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seco" + "nds\":{\"type\":\"number\"},\"profile\":{\"type\":\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_se" + "conds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_secon" + "ds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"n" + "umber\"}},\"type\":\"object\"}]},\"health_device_interval_seconds\":{\"type\":\"number\"},\"health_effective" + "_policy\":{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties" + "\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds" + "\":{\"type\":\"number\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\"" + ":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"require" + "d\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"}},\"type\":\"object\"},\"health_enabled\":{\"type\":\"boolean" + "\"},\"health_fast_interval_seconds\":{\"type\":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"}" + ",\"health_incident_retention_days\":{\"type\":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"nu" + "mber\"},\"health_presence_interval_seconds\":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"" + "health_slow_interval_seconds\":{\"type\":\"number\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"}," + "\"hls_disabled\":{\"type\":\"boolean\"},\"log_file\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"lo" + "gin_rate_limit_enabled\":{\"type\":\"boolean\"},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"lo" + "gin_rate_limit_window_seconds\":{\"type\":\"number\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_st" + "orage_size\":{\"type\":\"number\"},\"max_streams\":{\"type\":\"number\"},\"max_streams_ceiling\":{\"type\":\"num" + "ber\"},\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{" + "\"type\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqt" + "t_client_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"bo" + "olean\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number" + "\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"" + "mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean" + "\"},\"mqtt_topic_prefix\":{\"type\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type" + "\":\"boolean\"},\"onvif_discovery_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"nu" + "mber\"},\"onvif_discovery_network\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_" + "buffer\":{\"type\":\"number\"},\"pre_detection_buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"num" + "ber\"},\"storage_min_free_pct\":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hl" + "s\":{\"type\":\"string\"},\"storage_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pressure_emergen" + "cy_pct\":{\"type\":\"number\"},\"storage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"" + "number\"},\"syslog_enabled\":{\"type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":" + "{\"type\":\"string\"},\"thumbnails_per_recording\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"nu" + "mber\"},\"trusted_proxy_cidrs\":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\"" + ":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"" + "type\":\"string\"},\"use_swap\":{\"type\":\"boolean\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip" + "\":{\"type\":\"string\"},\"web_port\":{\"type\":\"number\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_s" + "ize\":{\"type\":\"number\"},\"webrtc_connection_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"typ" + "e\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"integer\"}},\"required\":[\"log_level\",\"sysl" + "og_enabled\",\"syslog_ident\",\"syslog_facility\",\"storage_path\",\"storage_path_hls\",\"mp4_directory_fo" + "rmat\",\"max_storage_size\",\"retention_days\",\"auto_delete_oldest\",\"storage_min_free_pct\",\"storage_p" + "ressure_warning_pct\",\"storage_pressure_critical_pct\",\"storage_pressure_emergency_pct\",\"generate_" + "thumbnails\",\"thumbnails_per_recording\",\"db_path\",\"db_backup_interval_minutes\",\"db_backup_retenti" + "on_count\",\"db_backup_verify\",\"db_post_backup_script\",\"web_port\",\"web_bind_ip\",\"web_thread_pool_s" + "ize\",\"max_streams\",\"max_streams_ceiling\",\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_d" + "isabled\",\"webrtc_disabled\",\"hls_disabled\",\"mse_disabled\",\"auth_timeout_hours\",\"auth_absolute_tim" + "eout_hours\",\"trusted_device_days\",\"trusted_proxy_cidrs\",\"force_mfa_on_login\",\"login_rate_limit_e" + "nabled\",\"login_rate_limit_max_attempts\",\"login_rate_limit_window_seconds\",\"buffer_size\",\"use_swa" + "p\",\"swap_size\",\"models_path\",\"api_detection_url\",\"api_detection_backend\",\"api_detection_format\"," + "\"api_detection_detector_name\",\"default_detection_threshold\",\"pre_detection_buffer\",\"post_detecti" + "on_buffer\",\"default_playback_transport\",\"detection_grace_period\",\"detection_engine_enabled\",\"det" + "ection_engine_threads\",\"detection_engine_delegate\",\"go2rtc_enabled\",\"go2rtc_binary_path\",\"go2rtc" + "_config_dir\",\"go2rtc_api_port\",\"go2rtc_rtsp_port\",\"go2rtc_webrtc_enabled\",\"go2rtc_webrtc_listen_" + "port\",\"go2rtc_stun_enabled\",\"go2rtc_stun_server\",\"go2rtc_external_ip\",\"go2rtc_ice_servers\",\"go2r" + "tc_force_native_hls\",\"go2rtc_config_override\",\"go2rtc_config_override_disabled_reason\",\"go2rtc_c" + "onfig_override_quarantined\",\"mqtt_enabled\",\"mqtt_broker_host\",\"mqtt_broker_port\",\"mqtt_username\"" + ",\"mqtt_password\",\"mqtt_client_id\",\"mqtt_topic_prefix\",\"mqtt_tls_enabled\",\"mqtt_keepalive\",\"mqtt_" + "qos\",\"mqtt_retain\",\"mqtt_ha_discovery\",\"mqtt_ha_discovery_prefix\",\"mqtt_ha_snapshot_interval\",\"t" + "urn_enabled\",\"turn_server_url\",\"turn_username\",\"turn_password\",\"onvif_discovery_enabled\",\"onvif_" + "discovery_interval\",\"onvif_discovery_network\",\"health_enabled\",\"health_profile\",\"health_fast_int" + "erval_seconds\",\"health_normal_interval_seconds\",\"health_slow_interval_seconds\",\"health_device_in" + "terval_seconds\",\"health_write_probe_enabled\",\"health_hardware_provider\",\"health_presence_interva" + "l_seconds\",\"health_incident_retention_days\",\"health_condition_overrides\",\"web_root\",\"log_file\",\"" + "pid_file\",\"go2rtc_base_path\",\"lpr_retention_days\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_rec" + "overy_timeout_ms\",\"mp4_faststart\"],\"type\":\"object\"},\"SystemSettingsSaved\":{\"additionalProperties" + "\":true,\"properties\":{\"restart_required\":{\"type\":\"boolean\"},\"restart_required_message\":{\"type\":\"s" + "tring\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"restart_required\"],\"ty" + "pe\":\"object\"},\"SystemSettingsUpdate\":{\"additionalProperties\":true,\"description\":\"Partial setting" + "s update; absent fields are retained. Secret mask values preserve the stored password.\",\"propert" + "ies\":{\"api_detection_backend\":{\"type\":\"string\"},\"api_detection_detector_name\":{\"type\":\"string\"}," + "\"api_detection_format\":{\"enum\":[\"light-object-detect\",\"doods2\"],\"type\":\"string\"},\"api_detection_" + "url\":{\"type\":\"string\"},\"audio_disabled\":{\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\"" + ":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_" + "disabled\":{\"type\":\"boolean\"},\"buffer_size\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type" + "\":\"number\"},\"db_backup_retention_count\":{\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"" + "db_path\":{\"type\":\"string\"},\"db_post_backup_script\":{\"type\":\"string\"},\"default_detection_threshol" + "d\":{\"type\":\"number\"},\"default_playback_transport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean" + "\"},\"detection_engine_delegate\":{\"type\":\"string\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"" + "detection_engine_threads\":{\"type\":\"number\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mf" + "a_on_login\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type" + "\":\"number\"},\"go2rtc_binary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc" + "_config_override\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"t" + "ype\":\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string" + "\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_se" + "rver\":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{" + "\"type\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":" + "true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"typ" + "e\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"t" + "ype\":\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"ty" + "pe\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"" + "code\"],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_" + "device_interval_seconds\":{\"type\":\"number\"},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_inte" + "rval_seconds\":{\"type\":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"},\"health_incident_re" + "tention_days\":{\"type\":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"number\"},\"health_prese" + "nce_interval_seconds\":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"health_slow_interval" + "_seconds\":{\"type\":\"number\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"typ" + "e\":\"boolean\"},\"log_file\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabl" + "ed\":{\"type\":\"boolean\"},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"login_rate_limit_windo" + "w_seconds\":{\"type\":\"number\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":" + "\"number\"},\"max_streams\":{\"type\":\"number\"},\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\"" + ":{\"type\":\"string\"},\"mp4_faststart\":{\"type\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt" + "_broker_port\":{\"type\":\"number\"},\"mqtt_client_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boole" + "an\"},\"mqtt_ha_discovery\":{\"type\":\"boolean\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_h" + "a_snapshot_interval\":{\"type\":\"number\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"type" + "\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"t" + "ype\":\"boolean\"},\"mqtt_topic_prefix\":{\"type\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_dis" + "abled\":{\"type\":\"boolean\"},\"onvif_discovery_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval" + "\":{\"type\":\"number\"},\"onvif_discovery_network\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"po" + "st_detection_buffer\":{\"type\":\"number\"},\"pre_detection_buffer\":{\"type\":\"number\"},\"retention_days\"" + ":{\"type\":\"number\"},\"storage_min_free_pct\":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"st" + "orage_path_hls\":{\"type\":\"string\"},\"storage_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pre" + "ssure_emergency_pct\":{\"type\":\"number\"},\"storage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_si" + "ze\":{\"type\":\"number\"},\"syslog_enabled\":{\"type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"s" + "yslog_ident\":{\"type\":\"string\"},\"thumbnails_per_recording\":{\"type\":\"number\"},\"trusted_device_days" + "\":{\"type\":\"number\"},\"trusted_proxy_cidrs\":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"t" + "urn_password\":{\"type\":\"string\"},\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"str" + "ing\"},\"use_swap\":{\"type\":\"boolean\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":" + "\"string\"},\"web_port\":{\"type\":\"number\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"typ" + "e\":\"number\"},\"webrtc_connection_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolea" + "n\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"integer\"}},\"type\":\"object\"},\"SystemStatus\":{\"addit" + "ionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"ok\",\"type\":\"s" + "tring\"}},\"required\":[\"status\",\"message\"],\"type\":\"object\"},\"TokenSelector\":{\"additionalProperties" + "\":true,\"description\":\"Existing fleet selector grammar. Server additionally enforces depth\342\211\2448, t" + "otal nodes\342\211\24464, UTF-8 byte limits and serialized selector<8192 bytes. Evaluation and delegation" + " containment remain server decisions.\",\"properties\":{\"expression\":{\"$ref\":\"#/components/schemas/" + "TokenSelectorExpression\"},\"version\":{\"const\":1}},\"required\":[\"version\",\"expression\"],\"type\":\"obj" + "ect\"},\"TokenSelectorExpression\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"op\":{\"cons" + "t\":\"all\"}},\"required\":[\"op\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"childr" + "en\":{\"items\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"maxItems\":64,\"minItems\":1," + "\"type\":\"array\"},\"op\":{\"enum\":[\"and\",\"or\"]}},\"required\":[\"op\",\"children\"],\"type\":\"object\"},{\"addi" + "tionalProperties\":true,\"properties\":{\"child\":{\"$ref\":\"#/components/schemas/TokenSelectorExpressi" + "on\"},\"op\":{\"const\":\"not\"}},\"required\":[\"op\",\"child\"],\"type\":\"object\"},{\"additionalProperties\":tr" + "ue,\"properties\":{\"op\":{\"const\":\"location_subtree\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F" + "]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"op\",\"uuid\"]," + "\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"enabled\"},\"value\":{\"t" + "ype\":\"boolean\"}},\"required\":[\"op\",\"value\"],\"type\":\"object\"},{\"additionalProperties\":true,\"proper" + "ties\":{\"op\":{\"enum\":[\"camera_uuid\"]},\"values\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4" + "}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"t" + "ype\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"propert" + "ies\":{\"op\":{\"enum\":[\"tag_any\",\"tag_all\",\"tag_none\"]},\"uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8" + "}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64," + "\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"uuids\"],\"type\":\"object\"},{\"additionalProperties\"" + ":true,\"properties\":{\"op\":{\"enum\":[\"recording_mode\"]},\"values\":{\"items\":{\"enum\":[\"off\",\"continuou" + "s\",\"detection\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"" + "object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"vendor\",\"model\"]},\"values\":{\"" + "items\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"" + "}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{" + "\"enum\":[\"capability_any\",\"capability_all\"]},\"values\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchanne" + "l\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"a" + "dditionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"health\"]},\"values\":{\"items\":{\"enum\":[\"unk" + "nown\",\"up\",\"degraded\",\"down\",\"disabled\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\"" + ":[\"op\",\"values\"],\"type\":\"object\"}]},\"TotpChangedResponse\":{\"additionalProperties\":false,\"propert" + "ies\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"ty" + "pe\":\"object\"},\"TotpCodeRequest\":{\"additionalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^" + "[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"code\"],\"type\":\"object\"},\"TotpLoginReq" + "uest\":{\"additionalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\"" + ",\"writeOnly\":true},\"remember_device\":{\"type\":\"boolean\"},\"totp_token\":{\"minLength\":1,\"type\":\"stri" + "ng\",\"writeOnly\":true}},\"required\":[\"totp_token\",\"code\"],\"type\":\"object\"},\"TotpLoginResponse\":{\"a" + "dditionalProperties\":false,\"properties\":{\"redirect\":{\"const\":\"/index.html\"},\"success\":{\"const\":t" + "rue}},\"required\":[\"success\",\"redirect\"],\"type\":\"object\"},\"TotpSetupResponse\":{\"additionalPropert" + "ies\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"otpauth_uri\":{\"format\":\"uri\",\"pattern\":\"^o" + "tpauth://totp/\",\"readOnly\":true,\"type\":\"string\"},\"secret\":{\"pattern\":\"^[A-Z2-7]{32}$\",\"readOnly\"" + ":true,\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"secret\",\"otpauth_uri\",\"m" + "essage\"],\"type\":\"object\"},\"TotpStatusResponse\":{\"additionalProperties\":false,\"properties\":{\"totp" + "_configured\":{\"type\":\"boolean\"},\"totp_enabled\":{\"type\":\"boolean\"}},\"required\":[\"totp_enabled\",\"t" + "otp_configured\"],\"type\":\"object\"},\"TrustedDevice\":{\"additionalProperties\":false,\"properties\":{\"c" + "reated_at\":{\"minimum\":0,\"type\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0" + ",\"type\":\"integer\"},\"id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"ip_address\":{" + "\"maxLength\":45,\"type\":\"string\"},\"last_used_at\":{\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"max" + "Length\":255,\"type\":\"string\"}},\"required\":[\"id\",\"created_at\",\"expires_at\",\"ip_address\",\"user_agen" + "t\",\"current\",\"last_used_at\"],\"type\":\"object\"},\"TrustedDevicesResponse\":{\"additionalProperties\":f" + "alse,\"properties\":{\"trusted_devices\":{\"items\":{\"$ref\":\"#/components/schemas/TrustedDevice\"},\"max" + "Items\":32,\"type\":\"array\"}},\"required\":[\"trusted_devices\"],\"type\":\"object\"},\"WeeklySchedule\":{\"de" + "scription\":\"Seven days \303\227 24 hours, indexed day*24+hour. true permits recording in that slot; in" + "terpreted using the configured recorder schedule timezone.\",\"items\":{\"type\":\"boolean\"},\"maxItems" + "\":168,\"minItems\":168,\"type\":\"array\"},\"WorkspacesResponse\":{\"additionalProperties\":true,\"properti" + "es\":{\"configurable\":{\"type\":\"boolean\"},\"scope\":{\"enum\":[\"user\",\"installation\"],\"type\":\"string\"}," + "\"workspaces\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"authorization_unchanged\":{\"con" + "st\":true,\"type\":\"boolean\"},\"configurable\":{\"type\":\"boolean\"},\"description\":{\"type\":\"string\"},\"ke" + "y\":{\"enum\":[\"live.navigator\",\"investigation\"],\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"visibl" + "e\":{\"type\":\"boolean\"}},\"required\":[\"key\",\"label\",\"description\",\"visible\",\"configurable\",\"authori" + "zation_unchanged\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"workspaces\",\"configurable\",\"sc" + "ope\"],\"type\":\"object\"},\"WorkspacesUpdate\":{\"additionalProperties\":true,\"properties\":{\"workspaces" + "\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"investigation\":{\"type\":\"boolean" + "\"},\"live.navigator\":{\"type\":\"boolean\"}},\"type\":\"object\"}},\"required\":[\"workspaces\"],\"type\":\"obje" + "ct\"}},\"securitySchemes\":{\"apiKeyHeader\":{\"description\":\"Legacy API key or scoped token. Scope in" + "tersects the active owner\342\200\231s authority.\",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"bas" + "icAuth\":{\"description\":\"Existing database username/password authentication; restricted when a re" + "quired password change is pending.\",\"scheme\":\"basic\",\"type\":\"http\"},\"bearerToken\":{\"description\"" + ":\"Legacy API key or scoped token through Authorization. Use a cookie-free client when discoverin" + "g token-specific permissions.\",\"scheme\":\"bearer\",\"type\":\"http\"},\"legacyApiKeyHeader\":{\"descripti" + "on\":\"Legacy API key only. This operation rejects scoped API tokens even when their owner could c" + "all it.\",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"legacyBearer\":{\"description\":\"Legacy" + " API key through Authorization: Bearer. Scoped tokens are not accepted on this operation. A vali" + "d session cookie still takes precedence.\",\"scheme\":\"bearer\",\"type\":\"http\"},\"sessionCookie\":{\"des" + "cription\":\"Existing authenticated session cookie; checked before other credentials. The explorer" + " uses this method only.\",\"in\":\"cookie\",\"name\":\"session\",\"type\":\"apiKey\"}}},\"info\":{\"description\"" + ":\"Canonical contract for the current C backend: authentication and administration, fleet and wor" + "kspaces, streams and devices, recordings and investigations, storage, events, system settings an" + "d bootstrap. The authenticated discovery endpoint projects operations and response variants for " + "the current principal. Eight media-protocol surfaces and one internal callback are documented se" + "parately in the coverage inventory. Discovery is descriptive and does not grant access.\",\"title\"" + ":\"LightNVR API \342\200\224 permission-aware contracts\",\"version\":\"0.46.3-contracts.3\"},\"openapi\":\"3.1.1\"" + ",\"paths\":{\"/api/audit/events\":{\"get\":{\"description\":\"Discovery is an operation-level capability." " The server independently checks the concrete target and current effective authority. Basic cred" "entials, session cookies and legacy keys retain their existing behavior. Mutations must not be a" - "utomatically retried.\",\"operationId\":\"exportAuditEvents\",\"parameters\":[{\"description\":\"One-based" - " page.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"i" - "nteger\"}},{\"description\":\"Page size, including CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"sc" - "hema\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative s" - "igned 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schem" - "a\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must" - " not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"" - "}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"i" - "n\":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"T" - "rimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"typ" - "e\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\"" - ",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field" - " capacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\"" - ":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema" - "\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":" - "\"query\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"text/cs" - "v\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"One filtered page as UTF-8 CSV, quoted cells and" - " neutralized spreadsheet formula prefixes. This is not an export of all pages.\",\"headers\":{\"Cont" - "ent-Disposition\":{\"schema\":{\"const\":\"attachment; filename=lightnvr-audit.csv\",\"type\":\"string\"}}," - "\"X-Total-Count\":{\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query" - " or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authori" - "ty is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or" - " in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. " - "Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" - "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export audit events\",\"tags\":[\"Audit\"],\"x-lightnvr-au" - "thorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"versi" - "on\":1}}},\"/api/audit/settings\":{\"get\":{\"description\":\"Discovery is an operation-level capability" - ". The server independently checks the concrete target and current effective authority. Basic cre" - "dentials, session cookies and legacy keys retain their existing behavior. Mutations must not be " - "automatically retried.\",\"operationId\":\"getAuditSettings\",\"responses\":{\"200\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettings\"}}},\"description\":\"Validated su" + "utomatically retried.\",\"operationId\":\"listAuditEvents\",\"parameters\":[{\"description\":\"One-based p" + "age.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"int" + "eger\"}},{\"description\":\"Page size, including CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"sche" + "ma\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative sig" + "ned 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schema\"" + ":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must n" + "ot exceed a nonzero until.\",\"in\":\"query\",\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}}" + ",{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\"" + ":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Tri" + "mmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"type\"" + ":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"" + "name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field c" + "apacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\":\"" + "Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema\":" + "{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"q" + "uery\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditEventsPage\"}}},\"description\":\"Validated su" "ccess.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" "}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential " @@ -2633,237 +2854,272 @@ static const unsigned char lightnvr_openapi_document[] = "lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"" "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; " "a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessio" - "nCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get audit setti" - "ngs\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"sys" - "tem.admin\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires system.admin. All sub" - "mitted fields are validated before writes. Retention saves and pruning occur before decision mod" - "es: on internal failure some work may have applied. Read settings before deciding to retry. Succ" - "essful retention changes include pruned_events.\",\"operationId\":\"updateAuditSettings\",\"requestBod" - "y\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettingsWrite\"}}}" - ",\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/AuditSettings\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query o" - "r body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority" - " is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or i" - "n-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Re" - "ad current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyH" - "eader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update audit settings\",\"tags\":[\"Audit\"],\"x-lightnvr-au" - "thorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"versi" - "on\":1}}},\"/api/auth/login\":{\"post\":{\"description\":\"Public credential submission. Successful JSON" - " login sets the HttpOnly session cookie; MFA challenge returns totp_required/totp_token without " - "a cookie. Optional inline TOTP is described; MFA completion and enrollment have separate identit" - "y contracts. Form submissions redirect for success, credential rejection and rate limit. Form pa" - "rsing uses username/password only; JSON optional fields are not form inputs.\",\"operationId\":\"log" - "in\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginRe" - "quest\"}},\"application/x-www-form-urlencoded\":{\"schema\":{\"additionalProperties\":true,\"properties\"" - ":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"username\":{\"type\":\"string\"}},\"required\":[\"usern" - "ame\",\"password\"],\"type\":\"object\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginResponse\"}}},\"description\":\"Successful respo" - "nse\",\"headers\":{\"Set-Cookie\":{\"description\":\"Session cookie on full login success; absent for MF" - "A challenge. Additional trusted-device cookie may be set.\",\"schema\":{\"type\":\"string\"}}}},\"302\":{" - "\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is requ" - "ired.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"strin" - "g\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"429\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Login rate lim" - "ited.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[],\"summary\":\"Start an aut" - "henticated session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}" - "}},\"/api/auth/login/config\":{\"get\":{\"description\":\"Public booleans and timeout values needed bef" - "ore sign-in. No personalized discovery is exposed.\",\"operationId\":\"getLoginConfig\",\"responses\":{" - "\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginConfig\"}}},\"de" - "scription\":\"Successful response\"}},\"security\":[],\"summary\":\"Get public login configuration\",\"tag" - "s\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/login/totp" - "\":{\"post\":{\"description\":\"Consumes an unexpired password-verified challenge bound to the issuing" - " IP/user agent. Six-digit codes are single-use per account time step; account throttling applies" - ". Success sets an HttpOnly session cookie, and optionally a trusted-device cookie. Never automat" - "ically retry a completion after an ambiguous response.\",\"operationId\":\"completeTotpLogin\",\"reque" - "stBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpLoginRequest\"" - "}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/TotpLoginResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Contro" - "l\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":" - "\"string\"}},\"Set-Cookie\":{\"description\":\"Authenticated session; optional trusted device if enable" - "d and requested.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d credential, current password, challenge or code (including replay).\"},\"409\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expi" - "red or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not aut" - "omatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[],\"summary\":\"Complete an " - "MFA login challenge\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1" - "}}},\"/api/auth/logout\":{\"post\":{\"description\":\"Public and idempotent. Deletes the session from t" - "he supplied cookie and clears session/trusted-device cookies. Does not revoke Basic credentials," - " legacy API keys or scoped API tokens. JSON200 requires Accept containing application/json or X-" - "Requested-With; otherwise302 redirects to login.\",\"operationId\":\"logout\",\"parameters\":[{\"descrip" - "tion\":\"Use application/json for the JSON success envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schem" - "a\":{\"type\":\"string\"}},{\"description\":\"Presence also selects the JSON success envelope.\",\"in\":\"he" - "ader\",\"name\":\"X-Requested-With\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LogoutResponse\"}}},\"description\":\"Success" - "ful response\"},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page an" - "d no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"" - "schema\":{\"type\":\"string\"}}}}},\"security\":[],\"summary\":\"End the current browser session\",\"tags\":[" - "\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/sessions\":{\"ge" - "t\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, inclu" - "ding users.manage tokens, cannot resolve a principal here; a valid session cookie retains existi" - "ng precedence. Ownership is always the authenticated account, including administrators. Syntheti" - "c auth-disabled principals are rejected. Responses are private, no-store; credential material is" - " never returned. Returns at most 32 unexpired entries ordered by most recent activity, then desc" - "ending ID. There is no pagination in this bounded contract. Pending MFA challenges are excluded " - "from sessions.\",\"operationId\":\"listAuthSessions\",\"responses\":{\"200\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthSessionsResponse\"}}},\"description\":\"Successful re" - "sponse\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSO" - "N response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}" - "}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-re" - "stricted credentials or a pending password change for session/Basic authentication. Legacy keys " - "retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes p" - "rivate and no-store. The common JSON response helper also appends no-cache, no-store, must-reval" - "idate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal" - " credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store" - ". The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"" - "type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"descript" - "ion\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-s" - "tore, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicA" - "uth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List own sessions\",\"tags\":[\"I" - "dentity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/ses" - "sions/{id}\":{\"delete\":{\"description\":\"Session, Basic or legacy key personal self-service only. S" - "coped tokens, including users.manage tokens, cannot resolve a principal here; a valid session co" - "okie retains existing precedence. Ownership is always the authenticated account, including admin" - "istrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; cre" - "dential material is never returned. The ID must be a canonical positive decimal safe integer wit" - "hout signs, leading zeros, escapes or trailing path segments. Revocation is independent of the 3" - "2-entry list limit, can remove expired records, and excludes pending MFA sessions. Current-cooki" - "e clearing uses the exact removed credential. Missing or another owner\342\200\231s ID returns the same 4" - "04; database failure is 500. A successful revoke invalidates future use, but does not cancel alr" - "eady-authorized in-flight requests.\",\"operationId\":\"revokeAuthSession\",\"parameters\":[{\"descripti" - "on\":\"Canonical positive decimal safe integer; no leading zeros or encoded aliases.\",\"in\":\"path\"," - "\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}]" - ",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Crede" - "ntialRevoked\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"" - "Includes private and no-store. The common JSON response helper also appends no-cache, no-store, " - "must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Clears session or tru" - "sted_device only when the exact current credential was removed.\",\"schema\":{\"type\":\"string\"}}}},\"" - "400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" - "ption\":\"Invalid credential ID.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and " - "no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"sc" - "hema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, " - "revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic auth" - "entication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"" - "description\":\"Includes private and no-store. The common JSON response helper also appends no-cac" - "he, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled p" - "rincipal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Inclu" - "des private and no-store. The common JSON response helper also appends no-cache, no-store, must-" - "revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"No matching owned credential.\",\"headers\":{\"C" - "ache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper als" - "o appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database " - "or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. " - "The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"ty" - "pe\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"" - "legacyBearer\":[]}],\"summary\":\"Revoke own session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\"" - ":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/trusted-devices\":{\"get\":{\"description\":" - "\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage " - "tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Own" - "ership is always the authenticated account, including administrators. Synthetic auth-disabled pr" - "incipals are rejected. Responses are private, no-store; credential material is never returned. R" - "eturns at most 32 unexpired entries ordered by most recent activity, then descending ID. There i" - "s no pagination in this bounded contract. Pending MFA challenges are excluded from sessions.\",\"o" - "perationId\":\"listTrustedDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/TrustedDevicesResponse\"}}},\"description\":\"Successful response\",\"heade" - "rs\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response hel" - "per also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No" - " usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted crede" - "ntials or a pending password change for session/Basic authentication. Legacy keys retain their e" - "xisting independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-" - "store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schem" - "a\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential in" - "ventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common J" - "SON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"" - "}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes" - " private and no-store. The common JSON response helper also appends no-cache, no-store, must-rev" - "alidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"leg" - "acyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List own trusted devices\",\"tags\":[\"Identity" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/trusted-de" - "vices/{id}\":{\"delete\":{\"description\":\"Session, Basic or legacy key personal self-service only. S" - "coped tokens, including users.manage tokens, cannot resolve a principal here; a valid session co" - "okie retains existing precedence. Ownership is always the authenticated account, including admin" - "istrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; cre" - "dential material is never returned. The ID must be a canonical positive decimal safe integer wit" - "hout signs, leading zeros, escapes or trailing path segments. Revocation is independent of the 3" - "2-entry list limit, can remove expired records, and excludes pending MFA sessions. Current-cooki" - "e clearing uses the exact removed credential. Missing or another owner\342\200\231s ID returns the same 4" - "04; database failure is 500. A successful revoke invalidates future use, but does not cancel alr" - "eady-authorized in-flight requests.\",\"operationId\":\"revokeTrustedDevice\",\"parameters\":[{\"descrip" - "tion\":\"Canonical positive decimal safe integer; no leading zeros or encoded aliases.\",\"in\":\"path" - "\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}" - "}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Cre" - "dentialRevoked\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\"" - ":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store" - ", must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Clears session or t" - "rusted_device only when the exact current credential was removed.\",\"schema\":{\"type\":\"string\"}}}}" - ",\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid credential ID.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private an" - "d no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"" - "schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only" - ", revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic au" - "thentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":" - "{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-c" - "ache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled" - " principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Inc" - "ludes private and no-store. The common JSON response helper also appends no-cache, no-store, mus" - "t-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"No matching owned credential.\",\"headers\":{" - "\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper a" - "lso appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databas" - "e or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store" - ". The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"" - "type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]}," - "{\"legacyBearer\":[]}],\"summary\":\"Revoke own trusted device\",\"tags\":[\"Identity\"],\"x-lightnvr-autho" - "rization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/users\":{\"get\":{\"description\":\"" - "Discovery is an operation-level capability. The server independently checks the concrete target " - "and current effective authority. Basic credentials, session cookies and legacy keys retain their" - " existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAdminUsers\"" - ",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Admin" - "Users\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"A" - "uthentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; sc" - "oped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exi" - "st.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"5" - "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Database or internal failure; a mutation may already have applied. Read current state bef" - "ore retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerT" - "oken\":[]}],\"summary\":\"List admin users\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authoriza" - "tion\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}" - ",\"post\":{\"description\":\"Requires a non-scoped users.manage principal. Initial legacy role is con" - "verted to built-in policy grants; the caller may only delegate actions it holds. Returns 200. Ne" - "w users are provisioned inactive until policy and CIDR rules have been applied.\",\"operationId\":\"" - "createAdminUser\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/AdminUserCreate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"c" + "nCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List audit even" + "ts\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"syst" + "em.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/audit/events/export\":{\"get\":{\"description\":\"Di" + "scovery is an operation-level capability. The server independently checks the concrete target an" + "d current effective authority. Basic credentials, session cookies and legacy keys retain their e" + "xisting behavior. Mutations must not be automatically retried.\",\"operationId\":\"exportAuditEvents" + "\",\"parameters\":[{\"description\":\"One-based page.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":" + "1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Page size, including CSV expo" + "rts.\",\"in\":\"query\",\"name\":\"page_size\",\"schema\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":" + "\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero u" + "ntil.\",\"in\":\"query\",\"name\":\"since\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonn" + "egative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"unti" + "l\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; s" + "ince must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimu" + "m\":0,\"type\":\"integer\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"i" + "n\":\"query\",\"name\":\"action\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; nat" + "ive field capacity applies.\",\"in\":\"query\",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"descrip" + "tion\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"" + "schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\"" + ",\"in\":\"query\",\"name\":\"request_id\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filt" + "er; native field capacity applies.\",\"in\":\"query\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}" + "],\"responses\":{\"200\":{\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"One fil" + "tered page as UTF-8 CSV, quoted cells and neutralized spreadsheet formula prefixes. This is not " + "an export of all pages.\",\"headers\":{\"Content-Disposition\":{\"schema\":{\"const\":\"attachment; filena" + "me=lightnvr-audit.csv\",\"type\":\"string\"}},\"X-Total-Count\":{\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":" + "\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credent" + "ial invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform n" + "on-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, s" + "elf-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failu" + "re; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"se" + "ssionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export audi" + "t events\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\"" + ":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/audit/settings\":{\"get\":{\"description\":\"D" + "iscovery is an operation-level capability. The server independently checks the concrete target a" + "nd current effective authority. Basic credentials, session cookies and legacy keys retain their " + "existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getAuditSettings" + "\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Audi" + "tSettings\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient" + "; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not" + " exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"" + "},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Database or internal failure; a mutation may already have applied. Read current state" + " before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bea" + "rerToken\":[]}],\"summary\":\"Get audit settings\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind" + "\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"des" + "cription\":\"Requires system.admin. All submitted fields are validated before writes. Retention sa" + "ves and pruning occur before decision modes: on internal failure some work may have applied. Rea" + "d settings before deciding to retry. Successful retention changes include pruned_events.\",\"opera" + "tionId\":\"updateAuditSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/AuditSettingsWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettings\"}}},\"description\":\"Validated " + "success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credentia" + "l invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non" + "-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, sel" + "f-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure" + "; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sess" + "ionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update audit " + "settings\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\"" + ":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/login\":{\"post\":{\"description\":\"Publ" + "ic credential submission. Successful JSON login sets the HttpOnly session cookie; MFA challenge " + "returns totp_required/totp_token without a cookie. Optional inline TOTP is described; MFA comple" + "tion and enrollment have separate identity contracts. Form submissions redirect for success, cre" + "dential rejection and rate limit. Form parsing uses username/password only; JSON optional fields" + " are not form inputs.\",\"operationId\":\"login\",\"requestBody\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/LoginRequest\"}},\"application/x-www-form-urlencoded\":{\"schema\":" + "{\"additionalProperties\":true,\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"userna" + "me\":{\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"}}},\"required\":true},\"r" + "esponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginRes" + "ponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Set-Cookie\":{\"description\":\"Session co" + "okie on full login success; absent for MFA challenge. Additional trusted-device cookie may be se" + "t.\",\"schema\":{\"type\":\"string\"}}}},\"302\":{\"description\":\"Browser/form redirect; Location identifi" + "es the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative a" + "pplication page.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usa" + "ble credential.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Login rate limited.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure." + "\"}},\"security\":[],\"summary\":\"Start an authenticated session\",\"tags\":[\"Identity\"],\"x-lightnvr-aut" + "horization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/login/config\":{\"get\":{\"description\":\"Publ" + "ic booleans and timeout values needed before sign-in. No personalized discovery is exposed.\",\"op" + "erationId\":\"getLoginConfig\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/LoginConfig\"}}},\"description\":\"Successful response\"}},\"security\":[],\"summa" + "ry\":\"Get public login configuration\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"pub" + "lic\",\"version\":1}}},\"/api/auth/login/totp\":{\"post\":{\"description\":\"Consumes an unexpired passwor" + "d-verified challenge bound to the issuing IP/user agent. Six-digit codes are single-use per acco" + "unt time step; account throttling applies. Success sets an HttpOnly session cookie, and optional" + "ly a trusted-device cookie. Never automatically retry a completion after an ambiguous response.\"" + ",\"operationId\":\"completeTotpLogin\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/TotpLoginRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpLoginResponse\"}}},\"description\":\"Val" + "idated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication da" + "ta must not be cached.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Authenticated s" + "ession; optional trusted device if enabled and requested.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (incl" + "uding replay).\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP a" + "ttempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure." + "\"}},\"security\":[],\"summary\":\"Complete an MFA login challenge\",\"tags\":[\"Identity\"],\"x-lightnvr-au" + "thorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/logout\":{\"post\":{\"description\":\"Public a" + "nd idempotent. Deletes the session from the supplied cookie and clears session/trusted-device co" + "okies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 requires" + " Accept containing application/json or X-Requested-With; otherwise302 redirects to login.\",\"oper" + "ationId\":\"logout\",\"parameters\":[{\"description\":\"Use application/json for the JSON success envelo" + "pe.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Presence also sel" + "ects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":{\"type\":\"strin" + "g\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Browser/form redir" + "ect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"de" + "scription\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[],\"summary\":\"" + "End the current browser session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\"" + ",\"version\":1}}},\"/api/auth/sessions\":{\"get\":{\"description\":\"Session, Basic or legacy key persona" + "l self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal he" + "re; a valid session cookie retains existing precedence. Ownership is always the authenticated ac" + "count, including administrators. Synthetic auth-disabled principals are rejected. Responses are " + "private, no-store; credential material is never returned. Returns at most 32 unexpired entries o" + "rdered by most recent activity, then descending ID. There is no pagination in this bounded contr" + "act. Pending MFA challenges are excluded from sessions.\",\"operationId\":\"listAuthSessions\",\"respo" + "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthSessions" + "Response\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Incl" + "udes private and no-store. The common JSON response helper also appends no-cache, no-store, must" + "-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including sc" + "oped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for s" + "ession/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"" + "Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper al" + "so appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Syntheti" + "c auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"de" + "scription\":\"Includes private and no-store. The common JSON response helper also appends no-cache" + ", no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failur" + "e.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON re" + "sponse helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}}," + "\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}]" + ",\"summary\":\"List own sessions\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scope" + "d_identity\",\"version\":1}}},\"/api/auth/sessions/{id}\":{\"delete\":{\"description\":\"Session, Basic or" + " legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot res" + "olve a principal here; a valid session cookie retains existing precedence. Ownership is always t" + "he authenticated account, including administrators. Synthetic auth-disabled principals are rejec" + "ted. Responses are private, no-store; credential material is never returned. The ID must be a ca" + "nonical positive decimal safe integer without signs, leading zeros, escapes or trailing path seg" + "ments. Revocation is independent of the 32-entry list limit, can remove expired records, and exc" + "ludes pending MFA sessions. Current-cookie clearing uses the exact removed credential. Missing o" + "r another owner\342\200\231s ID returns the same 404; database failure is 500. A successful revoke invali" + "dates future use, but does not cancel already-authorized in-flight requests.\",\"operationId\":\"rev" + "okeAuthSession\",\"parameters\":[{\"description\":\"Canonical positive decimal safe integer; no leadin" + "g zeros or encoded aliases.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199" + "254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/CredentialRevoked\"}}},\"description\":\"Successful response\",\"h" + "eaders\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response" + " helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cook" + "ie\":{\"description\":\"Clears session or trusted_device only when the exact current credential was " + "removed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential ID.\",\"headers\":{\"Cache-Contr" + "ol\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends " + "no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal " + "credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pendi" + "ng password change for session/Basic authentication. Legacy keys retain their existing independe" + "nt policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common" + " JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"strin" + "g\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"header" + "s\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response help" + "er also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No " + "matching owned credential.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-s" + "tore. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema" + "\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"desc" + "ription\":\"Includes private and no-store. The common JSON response helper also appends no-cache, " + "no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"ba" + "sicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke own session\",\"tags" + "\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/aut" + "h/trusted-devices\":{\"get\":{\"description\":\"Session, Basic or legacy key personal self-service onl" + "y. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid sessio" + "n cookie retains existing precedence. Ownership is always the authenticated account, including a" + "dministrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store;" + " credential material is never returned. Returns at most 32 unexpired entries ordered by most rec" + "ent activity, then descending ID. There is no pagination in this bounded contract. Pending MFA c" + "hallenges are excluded from sessions.\",\"operationId\":\"listTrustedDevices\",\"responses\":{\"200\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TrustedDevicesResponse\"}}},\"" + "description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private a" + "nd no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\"," + "\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-onl" + "y, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic a" + "uthentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\"" + ":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-" + "cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disable" + "d principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"In" + "cludes private and no-store. The common JSON response helper also appends no-cache, no-store, mu" + "st-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":" + "{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper " + "also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"" + "sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Li" + "st own trusted devices\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_ident" + "ity\",\"version\":1}}},\"/api/auth/trusted-devices/{id}\":{\"delete\":{\"description\":\"Session, Basic or" + " legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot res" + "olve a principal here; a valid session cookie retains existing precedence. Ownership is always t" + "he authenticated account, including administrators. Synthetic auth-disabled principals are rejec" + "ted. Responses are private, no-store; credential material is never returned. The ID must be a ca" + "nonical positive decimal safe integer without signs, leading zeros, escapes or trailing path seg" + "ments. Revocation is independent of the 32-entry list limit, can remove expired records, and exc" + "ludes pending MFA sessions. Current-cookie clearing uses the exact removed credential. Missing o" + "r another owner\342\200\231s ID returns the same 404; database failure is 500. A successful revoke invali" + "dates future use, but does not cancel already-authorized in-flight requests.\",\"operationId\":\"rev" + "okeTrustedDevice\",\"parameters\":[{\"description\":\"Canonical positive decimal safe integer; no lead" + "ing zeros or encoded aliases.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":90071" + "99254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/CredentialRevoked\"}}},\"description\":\"Successful response\"," + "\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON respon" + "se helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Co" + "okie\":{\"description\":\"Clears session or trusted_device only when the exact current credential wa" + "s removed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential ID.\",\"headers\":{\"Cache-Con" + "trol\":{\"description\":\"Includes private and no-store. The common JSON response helper also append" + "s no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable persona" + "l credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pen" + "ding password change for session/Basic authentication. Legacy keys retain their existing indepen" + "dent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The comm" + "on JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"str" + "ing\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"head" + "ers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response he" + "lper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"N" + "o matching owned credential.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no" + "-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"sche" + "ma\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"de" + "scription\":\"Includes private and no-store. The common JSON response helper also appends no-cache" + ", no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"" + "basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke own trusted devi" + "ce\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}}," + "\"/api/auth/users\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server " + "independently checks the concrete target and current effective authority. Basic credentials, ses" + "sion cookies and legacy keys retain their existing behavior. Mutations must not be automatically" + " retried.\",\"operationId\":\"listAdminUsers\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/AdminUsers\"}}},\"description\":\"Validated success.\"},\"400\":{\"c" "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" "Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" "nts/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{" @@ -2875,696 +3131,577 @@ static const unsigned char lightnvr_openapi_document[] = " role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" "/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may alr" "eady have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"bas" - "icAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create admin user\",\"tags\":" - "[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements" - "\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}\":{\"delete\":{" - "\"description\":\"Requires users.manage. Cannot delete yourself or the last legacy administrator; d" - "eletion and credential revocation are durable.\",\"operationId\":\"deleteAdminUser\",\"parameters\":[{\"" - "description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe i" - "nteger. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"na" - "me\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"respo" - "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserDel" - "eted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Au" - "thentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; sco" - "ped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exis" - "t.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"50" - "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Database or internal failure; a mutation may already have applied. Read current state befo" - "re retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTo" - "ken\":[]}],\"summary\":\"Delete admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authoriza" - "tion\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}" - ",\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently chec" - "ks the concrete target and current effective authority. Basic credentials, session cookies and l" - "egacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operat" - "ionId\":\"getAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Brow" - "ser adapters require a positive safe integer. Concrete target access is checked independently of" - " operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern" - "\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid pat" - "h, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective" - " authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"" - "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, dupl" - "icate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have a" - "pplied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}" - ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get admin user\",\"tags\":[\"Identity administra" - "tion\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"res" - "ource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"A non-scoped owner may edit its username/emai" - "l. Other fields and other users require effective users.manage. Role is rejected; use versioned " - "policy management. CIDR input is validated before saving. Password changes invalidate existing s" - "essions/challenges/trusted devices; locked passwords cannot change.\",\"operationId\":\"updateAdminU" - "ser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters requ" - "ire a positive safe integer. Concrete target access is checked independently of operation discov" - "ery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"typ" - "e\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/AdminUserUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" - "valid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Effective authority is insufficient; scoped credentials cannot perform non-scoped management ope" - "rations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable r" - "ole, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may alrea" - "dy have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basic" - "Auth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update admin user\",\"tags\":[\"Identit" - "y administration\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api" - "/auth/users/{id}/api-key\":{\"post\":{\"description\":\"Only a non-scoped owner or non-scoped users.ma" - "nage principal may rotate the legacy key. Scoped tokens are always forbidden, including tokens w" - "ith users.manage. The unrestricted key is returned once; lists never expose it. Discovery does n" - "ot grant access to another user.\",\"operationId\":\"createLegacyApiKey\",\"parameters\":[{\"description" - "\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Con" - "crete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"r" - "equired\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LegacyApiKeyCreated\"}}}" - ",\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentica" - "tion required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped cred" - "entials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"40" - "9\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Da" - "tabase or internal failure; a mutation may already have applied. Read current state before retry" - "ing.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBear" - "er\":[]}],\"summary\":\"Create legacy api key\",\"tags\":[\"Identity administration\"],\"x-lightnvr-author" - "ization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/users/{id}/login-lockout/clear\"" - ":{\"post\":{\"description\":\"Discovery is an operation-level capability. The server independently ch" - "ecks the concrete target and current effective authority. Basic credentials, session cookies and" - " legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"oper" - "ationId\":\"clearUserLoginLockout\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit us" - "er ID. Browser adapters require a positive safe integer. Concrete target access is checked indep" - "endently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":" - "19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/LoginLockoutCleared\"}}},\"description\":\"Validated success.\"}" - ",\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid." - "\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped ma" - "nagement operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout," - " immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutati" - "on may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\"" - ":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear user login lockou" - "t\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\"" - ":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}/password\":{\"p" - "ut\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other " - "accounts, and all scoped-token requests, require effective users.manage (owner permission inters" - "ected with token action/scope). Discovery describes the operation; it does not grant access to a" - "n arbitrary target ID. Requires old_password for self-service without users.manage and for a for" - "ced own password change. Success atomically changes the password and revokes sessions, pending c" - "hallenges and trusted devices. Changing your own password clears browser cookies. Basic/key comp" - "atibility remains supported.\",\"operationId\":\"changePassword\",\"parameters\":[{\"description\":\"Posit" - "ive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters " - "require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\"" - ":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string" - "\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Passwo" - "rdChangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/PasswordChangedResponse\"}}},\"description\":\"Validated success.\",\"h" - "eaders\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cach" - "ed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential," - " current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service" - " or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"500\":{\"" + "icAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List admin users\",\"tags\":[\"Identi" + "ty administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users" + ".manage\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires a non-scoped users.man" + "age principal. Initial legacy role is converted to built-in policy grants; the caller may only d" + "elegate actions it holds. Returns 200. New users are provisioned inactive until policy and CIDR " + "rules have been applied.\",\"operationId\":\"createAdminUser\",\"requestBody\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserCreate\"}}},\"required\":true},\"responses\":" + "{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"des" + "cription\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication " + "required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentia" + "ls cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"" "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" - ":[]},{\"bearerToken\":[]}],\"summary\":\"Change or reset a password\",\"tags\":[\"Identity\"],\"x-lightnvr-" - "authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/password-lock\"" - ":{\"put\":{\"description\":\"Discovery is an operation-level capability. The server independently che" - "cks the concrete target and current effective authority. Basic credentials, session cookies and " - "legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"opera" - "tionId\":\"setUserPasswordLock\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user " - "ID. Browser adapters require a positive safe integer. Concrete target access is checked independ" - "ently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19," - "\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/PasswordLockRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Val" - "idated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or cr" - "edential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perf" - "orm non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revisi" - "on, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal " - "failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":" - "[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set us" - "er password lock\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\"" - ",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id" - "}/totp/disable\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its" - " own account. Other accounts, and all scoped-token requests, require effective users.manage (own" - "er permission intersected with token action/scope). Discovery describes the operation; it does n" - "ot grant access to an arbitrary target ID. Disables MFA, erases the secret and revokes other ses" - "sions/challenges/trusted devices. The initiating authenticated session is retained.\",\"operationI" - "d\":\"disableTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading z" - "eroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete " - "self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\"" - ":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpChangedResponse\"}}},\"description\":\"Valida" - "ted success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data " - "must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal la" - "cks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not f" - "ound.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts a" - "re account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"secu" - "rity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"" - "Disable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"versi" - "on\":1}}},\"/api/auth/users/{id}/totp/setup\":{\"post\":{\"description\":\"A session, Basic credential o" - "r legacy key may manage its own account. Other accounts, and all scoped-token requests, require " - "effective users.manage (owner permission intersected with token action/scope). Discovery describ" - "es the operation; it does not grant access to an arbitrary target ID. Returns enrollment secret " - "only here; setup expires after ten minutes. Replacing pending enrollment invalidates the previou" - "s secret. An enabled account returns 409.\",\"operationId\":\"setupTotp\",\"parameters\":[{\"description" - "\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser a" - "dapters require safe positive integer IDs. Concrete self-service targets only the current accoun" - "t.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\"" - ":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/TotpSetupResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"des" - "cription\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"" - "}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge o" - "r code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; pa" - "ssword may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state c" - "hanged; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically ret" - "ry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{" - "\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start MFA enrollment\",\"tags\":[\"Identity\"],\"x-l" - "ightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/s" - "tatus\":{\"get\":{\"description\":\"A session, Basic credential or legacy key may manage its own accou" - "nt. Other accounts, and all scoped-token requests, require effective users.manage (owner permiss" - "ion intersected with token action/scope). Discovery describes the operation; it does not grant a" - "ccess to an arbitrary target ID. Returns enabled/configured booleans without secrets. Unavailabi" - "lity is not evidence that MFA is disabled.\",\"operationId\":\"getTotpStatus\",\"parameters\":[{\"descri" - "ption\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; brow" - "ser adapters require safe positive integer IDs. Concrete self-service targets only the current a" - "ccount.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"" - "type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/TotpStatusResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\"" - ":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"s" - "tring\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, chall" - "enge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.mana" - "ge; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"500\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failu" - "re.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}]" - ",\"summary\":\"Read MFA enrollment status\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"" - "identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/verify\":{\"post\":{\"description\":\"A" - " session, Basic credential or legacy key may manage its own account. Other accounts, and all sco" - "ped-token requests, require effective users.manage (owner permission intersected with token acti" - "on/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID" - ". Consumes a fresh enrollment code, enables MFA and revokes other sessions/challenges/trusted de" - "vices. The initiating authenticated session is retained.\",\"operationId\":\"verifyTotp\",\"parameters" - "\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the ha" - "ndler; browser adapters require safe positive integer IDs. Concrete self-service targets only th" - "e current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"" - "^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/TotpCodeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpChangedResponse\"}}},\"description\":\"Validate" - "d success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data mu" - "st not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" - "id credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lack" - "s self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not fou" - "nd.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are" - " account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"securi" - "ty\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Ve" - "rify and enable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management" - "\",\"version\":1}}},\"/api/auth/verify\":{\"get\":{\"description\":\"Session, Basic or legacy key only; sc" - "oped tokens do not resolve here. Required-password-change accounts may use verify. Auth-disabled" - " mode returns synthetic admin and anonymous demo returns synthetic viewer; neither permits perso" - "nalized discovery. Missing authentication clears stale session cookie.\",\"operationId\":\"verifyIde" - "ntity\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/AuthVerifyResponse\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"}},\"" - "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}]," - "\"summary\":\"Verify current account identity\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kin" - "d\":\"non_scoped_identity\",\"version\":1}}},\"/api/authorization/actions\":{\"get\":{\"description\":\"Disc" - "overy is an operation-level capability. The server independently checks the concrete target and " - "current effective authority. Basic credentials, session cookies and legacy keys retain their exi" - "sting behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuthorizationAc" - "tions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/AdminActionCatalog\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body" - ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is in" - "sufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Targe" - "t does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use " - "conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read cur" - "rent state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" - ":[]},{\"bearerToken\":[]}],\"summary\":\"List authorization actions\",\"tags\":[\"Identity administration" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resourc" - "e\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles\":{\"get\":{\"description\":\"Discovery is an oper" - "ation-level capability. The server independently checks the concrete target and current effectiv" - "e authority. Basic credentials, session cookies and legacy keys retain their existing behavior. " - "Mutations must not be automatically retried.\",\"operationId\":\"listAuthorizationRoles\",\"responses\"" - ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoles\"}}},\"d" - "escription\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticatio" - "n required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credent" - "ials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Datab" - "ase or internal failure; a mutation may already have applied. Read current state before retrying" - ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\"" - ":[]}],\"summary\":\"List authorization roles\",\"tags\":[\"Identity administration\"],\"x-lightnvr-author" - "ization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}]" - ",\"version\":1}},\"post\":{\"description\":\"Discovery is an operation-level capability. The server ind" - "ependently checks the concrete target and current effective authority. Basic credentials, sessio" - "n cookies and legacy keys retain their existing behavior. Mutations must not be automatically re" - "tried.\",\"operationId\":\"createAuthorizationRole\",\"requestBody\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}},\"required\":true},\"responses\":{\"201\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWritten\"}}},\"descrip" - "tion\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requ" - "ired or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials c" - "annot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Pol" - "icy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or" - " internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"" - "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}]," - "\"summary\":\"Create authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorizati" - "on\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"ver" - "sion\":1}}},\"/api/authorization/roles/{uuid}\":{\"delete\":{\"description\":\"Discovery is an operation" - "-level capability. The server independently checks the concrete target and current effective aut" - "hority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutat" - "ions must not be automatically retried.\",\"operationId\":\"deleteAuthorizationRole\",\"parameters\":[{" - "\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a" - "-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"req" - "uestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminVersionReq" - "uest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/AdminRoleDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid pa" - "th, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effectiv" - "e authority is insufficient; scoped credentials cannot perform non-scoped management operations." - "\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, dup" - "licate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have " - "applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" - "},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Delete authorization role\",\"tags\":[\"" - "Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":" - "[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Discovery is an" - " operation-level capability. The server independently checks the concrete target and current eff" - "ective authority. Basic credentials, session cookies and legacy keys retain their existing behav" - "ior. Mutations must not be automatically retried.\",\"operationId\":\"updateAuthorizationRole\",\"para" - "meters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"patter" - "n\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"strin" - "g\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Admin" - "RoleWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/AdminRoleWritten\"}}},\"description\":\"Validated success.\"},\"400\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" - "id path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Eff" - "ective authority is insufficient; scoped credentials cannot perform non-scoped management operat" - "ions.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role" - ", duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already " - "have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAut" - "h\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Update authorization role\",\"tag" - "s\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requireme" - "nts\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/simulate\":{" - "\"post\":{\"description\":\"Requires users.manage. Evaluates the target user, not a token, against a " - "concrete existing camera for camera-scoped actions or no camera for global actions. Does not mod" - "ify grants; an audit event is appended.\",\"operationId\":\"simulateAuthorization\",\"requestBody\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminSimulationRequest\"}}},\"" - "required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/AdminSimulation\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query o" - "r body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority" - " is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"" + "\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databas" + "e or internal failure; a mutation may already have applied. Read current state before retrying.\"" + "}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[" + "]}],\"summary\":\"Create admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":" + "{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version" + "\":1}}},\"/api/auth/users/{id}\":{\"delete\":{\"description\":\"Requires users.manage. Cannot delete you" + "rself or the last legacy administrator; deletion and credential revocation are durable.\",\"operat" + "ionId\":\"deleteAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. B" + "rowser adapters require a positive safe integer. Concrete target access is checked independently" + " of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"patt" + "ern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/AdminUserDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nvalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"" "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or i" - "n-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Re" - "ad current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyH" - "eader\":[]},{\"bearerToken\":[]}],\"summary\":\"Simulate authorization\",\"tags\":[\"Identity administrati" - "on\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resou" - "rce\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{id}\":{\"get\":{\"description\":\"Discovery is " - "an operation-level capability. The server independently checks the concrete target and current e" - "ffective authority. Basic credentials, session cookies and legacy keys retain their existing beh" - "avior. Mutations must not be automatically retried.\",\"operationId\":\"getUserAuthorizationPolicy\"," - "\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a" - " positive safe integer. Concrete target access is checked independently of operation discovery.\"" - ",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"s" - "tring\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/AdminUserPolicy\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body" - ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is in" - "sufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Targe" - "t does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use " - "conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read cur" - "rent state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyH" - "eader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Get user authorization policy\",\"tags\":[\"Identity admi" - "nistration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"u" - "sers.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires a non-scoped users." - "manage principal, current expected_policy_version, and mode policy. Replaces the grants atomical" - "ly. Delegated actions and camera scope cannot exceed caller authority. Self-lockout is rejected." - " Effective permissions and scoped-token intersections are re-evaluated on subsequent requests.\"," - "\"operationId\":\"setUserAuthorizationPolicy\",\"parameters\":[{\"description\":\"Positive decimal signed" - " 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is che" - "cked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"m" - "axLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminPolicyWrite\"}}},\"required\":true},\"responses\":{\"20" - "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserPolicy\"}}},\"d" - "escription\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticatio" - "n required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credent" - "ials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Datab" - "ase or internal failure; a mutation may already have applied. Read current state before retrying" - ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\"" - ":[]}],\"summary\":\"Set user authorization policy\",\"tags\":[\"Identity administration\"],\"x-lightnvr-a" - "uthorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"r" - "aw\"}],\"version\":1}}},\"/api/authorization/users/{user_id}/tokens\":{\"get\":{\"description\":\"Requires" - " a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credential" - "s are always403 even with users.manage. Invalid path may produce400 before authentication. Curre" - "nt target and policy checks still run for each request.\",\"operationId\":\"listUserTokens\",\"respons" - "es\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenList\"}" - "}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable cred" - "ential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"}" - ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth" - "\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List token metadata for an allow" - "ed owner\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1" - "}},\"parameters\":[{\"description\":\"Positive user ID. Non-scoped caller may manage their own tokens" - "; other owners require users.manage. Visibility never grants authority over arbitrary IDs.\",\"in\"" - ":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"post\":{\"desc" - "ription\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage." - " Scoped credentials are always403 even with users.manage. Invalid path may produce400 before aut" - "hentication. Current target and policy checks still run for each request. Requested actions and " - "scope must be contained by both owner and requester. Only201 returns the one-time secret. Denied" - "/invalid issuance leaves tokens and policy unchanged; audit entries may be written.\",\"operationI" - "d\":\"createUserToken\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiTokenCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenCreated\"}}},\"description\":\"Successful res" - "ponse\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or " - "requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"409\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Active token limit rea" - "ched.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" - "sicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create a scoped token\",\"t" - "ags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/au" - "thorization/users/{user_id}/tokens/{token_uuid}\":{\"delete\":{\"description\":\"Requires a non-scoped" - " principal: self is allowed; another owner requires users.manage. Scoped credentials are always4" - "03 even with users.manage. Invalid path may produce400 before authentication. Current target and" - " policy checks still run for each request. Revocation invalidates subsequent credential requests" - "; it does not rewrite owner policy.\",\"operationId\":\"revokeUserToken\",\"responses\":{\"200\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SuccessResponse\"}}},\"description\"" - ":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":" + "\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management op" + "erations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable " + "role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may alre" + "ady have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" + "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete admin user\",\"tags\":[\"Identi" + "ty administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users" + ".manage\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Discovery is an operation-level " + "capability. The server independently checks the concrete target and current effective authority." + " Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations mu" + "st not be automatically retried.\",\"operationId\":\"getAdminUser\",\"parameters\":[{\"description\":\"Pos" + "itive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete " + "target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"require" + "d\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"" + "Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or" + " credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot p" + "erform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy rev" + "ision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or intern" + "al failure; a mutation may already have applied. Read current state before retrying.\"}},\"securit" + "y\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get" + " admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"req" + "uirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"A no" + "n-scoped owner may edit its username/email. Other fields and other users require effective users" + ".manage. Role is rejected; use versioned policy management. CIDR input is validated before savin" + "g. Password changes invalidate existing sessions/challenges/trusted devices; locked passwords ca" + "nnot change.\",\"operationId\":\"updateAdminUser\",\"parameters\":[{\"description\":\"Positive decimal sig" + "ned 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is " + "checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":" + "{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserUpdate\"}}},\"required\":true},\"responses\":{\"" + "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"descr" + "iption\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication re" + "quired or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials" + " cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"P" + "olicy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database " + "or internal failure; a mutation may already have applied. Read current state before retrying.\"}}" + ",\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summ" + "ary\":\"Update admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"" + "identity_management\",\"version\":1}}},\"/api/auth/users/{id}/api-key\":{\"post\":{\"description\":\"Only " + "a non-scoped owner or non-scoped users.manage principal may rotate the legacy key. Scoped tokens" + " are always forbidden, including tokens with users.manage. The unrestricted key is returned once" + "; lists never expose it. Discovery does not grant access to another user.\",\"operationId\":\"create" + "LegacyApiKey\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adap" + "ters require a positive safe integer. Concrete target access is checked independently of operati" + "on discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9" + "]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/LegacyApiKeyCreated\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid p" + "ath, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effecti" + "ve authority is insufficient; scoped credentials cannot perform non-scoped management operations" + ".\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, du" + "plicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have" + " applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[" + "]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create legacy api key\",\"tags\":[\"Ide" + "ntity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"" + "/api/auth/users/{id}/login-lockout/clear\":{\"post\":{\"description\":\"Discovery is an operation-leve" + "l capability. The server independently checks the concrete target and current effective authorit" + "y. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations " + "must not be automatically retried.\",\"operationId\":\"clearUserLoginLockout\",\"parameters\":[{\"descri" + "ption\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer" + ". Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"i" + "d\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":" + "{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginLockoutCleare" + "d\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authe" + "ntication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped" + " credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"" + "},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":" "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"500\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Intern" - "al or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyAp" - "iKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke an allowed owner token\",\"tags\":[\"Tokens\"]" - ",\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"parameters\":[{\"descript" - "ion\":\"Positive user ID. Non-scoped caller may manage their own tokens; other owners require user" - "s.manage. Visibility never grants authority over arbitrary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"r" - "equired\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Token UUID belonging to t" - "his owner.\",\"in\":\"path\",\"name\":\"token_uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}" - "-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}]},\"/api/camera" - "-collections\":{\"get\":{\"description\":\"Collection visibility requires global camera.configure, own" - "ership or sharing; a shared collection with zero visible live.view matches is hidden from a non-" - "owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart s" - "electors are disclosed only to the owner or manager; other readers receive null plus selector_re" - "dacted. Read-like preview performs no mutation.\",\"operationId\":\"listCameraCollections\",\"response" - "s\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectio" - "nsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or sel" - "ector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"" - "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization fail" - "ure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}" - "],\"summary\":\"List visible camera collections\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind" - "\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation." - " Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-spec" - "ific visibility, ownership and reference checks still apply.\",\"operationId\":\"createCameraCollect" - "ion\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Camera" - "CollectionCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful operation.\"},\"400" + "\":\"Database or internal failure; a mutation may already have applied. Read current state before " + "retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken" + "\":[]}],\"summary\":\"Clear user login lockout\",\"tags\":[\"Identity administration\"],\"x-lightnvr-autho" + "rization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\"" + ":1}}},\"/api/auth/users/{id}/password\":{\"put\":{\"description\":\"A session, Basic credential or lega" + "cy key may manage its own account. Other accounts, and all scoped-token requests, require effect" + "ive users.manage (owner permission intersected with token action/scope). Discovery describes the" + " operation; it does not grant access to an arbitrary target ID. Requires old_password for self-s" + "ervice without users.manage and for a forced own password change. Success atomically changes the" + " password and revokes sessions, pending challenges and trusted devices. Changing your own passwo" + "rd clears browser cookies. Basic/key compatibility remains supported.\",\"operationId\":\"changePass" + "word\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are a" + "ccepted by the handler; browser adapters require safe positive integer IDs. Concrete self-servic" + "e targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLengt" + "h\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/PasswordChangeRequest\"}}},\"required\":true},\"responses\":{\"200\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordChangedResponse\"" + "}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-st" + "ore; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid pat" + "h or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay)" + ".\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"" + "404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Target user not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Change or reset a p" + "assword\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":" + "1}}},\"/api/auth/users/{id}/password-lock\":{\"put\":{\"description\":\"Discovery is an operation-level" + " capability. The server independently checks the concrete target and current effective authority" + ". Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations m" + "ust not be automatically retried.\",\"operationId\":\"setUserPasswordLock\",\"parameters\":[{\"descripti" + "on\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. C" + "oncrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\"," + "\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordLockRequest\"}}},\"r" + "equired\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body." + "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is ins" + "ufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target" + " does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use c" + "onflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read curr" + "ent state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":" + "[]},{\"bearerToken\":[]}],\"summary\":\"Set user password lock\",\"tags\":[\"Identity administration\"],\"x" + "-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"r" + "aw\"}],\"version\":1}}},\"/api/auth/users/{id}/totp/disable\":{\"post\":{\"description\":\"A session, Basi" + "c credential or legacy key may manage its own account. Other accounts, and all scoped-token requ" + "ests, require effective users.manage (owner permission intersected with token action/scope). Dis" + "covery describes the operation; it does not grant access to an arbitrary target ID. Disables MFA" + ", erases the secret and revokes other sessions/challenges/trusted devices. The initiating authen" + "ticated session is retained.\",\"operationId\":\"disableTotp\",\"parameters\":[{\"description\":\"Positive" + " decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters req" + "uire safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"p" + "ath\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}" + "],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Totp" + "ChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\"" + ":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400" "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not al" - "lowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, referen" - "ce or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" - "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateCameraCollection\",\"tags\":[\"Flee" - "t\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"re" - "source\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}\":{\"delete\":{\"description\":\"Author" - "ization is checked before mutation. Requests preserve existing Basic authentication and legacy A" - "PI-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\"" - ",\"operationId\":\"deleteCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true" - ",\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful " - "operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Op" - "eration is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCook" - "ie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteCameraCollecti" - "on\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"came" - "ra.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Collection visibility requi" - "res global camera.configure, ownership or sharing; a shared collection with zero visible live.vi" - "ew matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scope" - "d to authorized cameras. Smart selectors are disclosed only to the owner or manager; other reade" - "rs receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"" - "getCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern" - "\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string" - "\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/C" - "ameraCollection\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path o" - "r selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access den" - "ied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization" - " failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken" - "\":[]}],\"summary\":\"Read a visible camera collection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":" - "{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before muta" - "tion. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource" - "-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateCameraCo" - "llection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/component" - "s/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/CameraCollectionUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successf" - "ul operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":" + "on\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (i" + "ncluding replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password ma" + "y be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; r" + "efresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"50" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHe" + "ader\":[]},{\"bearerToken\":[]}],\"summary\":\"Disable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorizat" + "ion\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/setup\":{\"post\":{\"de" + "scription\":\"A session, Basic credential or legacy key may manage its own account. Other accounts" + ", and all scoped-token requests, require effective users.manage (owner permission intersected wi" + "th token action/scope). Discovery describes the operation; it does not grant access to an arbitr" + "ary target ID. Returns enrollment secret only here; setup expires after ten minutes. Replacing p" + "ending enrollment invalidates the previous secret. An enabled account returns 409.\",\"operationId" + "\":\"setupTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zero" + "es are accepted by the handler; browser adapters require safe positive integer IDs. Concrete sel" + "f-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"" + "maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpSetupResponse\"}}},\"description\":\"Validated s" + "uccess.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must " + "not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " + "credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks s" + "elf-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found." + "\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are ac" + "count-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\"" + ":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start" + " MFA enrollment\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"v" + "ersion\":1}}},\"/api/auth/users/{id}/totp/status\":{\"get\":{\"description\":\"A session, Basic credenti" + "al or legacy key may manage its own account. Other accounts, and all scoped-token requests, requ" + "ire effective users.manage (owner permission intersected with token action/scope). Discovery des" + "cribes the operation; it does not grant access to an arbitrary target ID. Returns enabled/config" + "ured booleans without secrets. Unavailability is not evidence that MFA is disabled.\",\"operationI" + "d\":\"getTotpStatus\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading" + " zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concret" + "e self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schem" + "a\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpStatusResponse\"}}},\"description\":\"Valid" + "ated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data" + " must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" + "valid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal l" + "acks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not " + "found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" + "},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read MFA enrollment status\",\"tags\":[\"Identi" + "ty\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{i" + "d}/totp/verify\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its" + " own account. Other accounts, and all scoped-token requests, require effective users.manage (own" + "er permission intersected with token action/scope). Discovery describes the operation; it does n" + "ot grant access to an arbitrary target ID. Consumes a fresh enrollment code, enables MFA and rev" + "okes other sessions/challenges/trusted devices. The initiating authenticated session is retained" + ".\",\"operationId\":\"verifyTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user " + "ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer I" + "Ds. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":" + "true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpCodeRequest\"}}},\"required\":true}," + "\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpCh" + "angedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"" + "Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":" "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionC" - "ookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateCameraColle" - "ction\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"c" - "amera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}/members\":{\"ge" - "t\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing;" - " a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager." - " Member/effective counts and samples are scoped to authorized cameras. Smart selectors are discl" - "osed only to the owner or manager; other readers receive null plus selector_redacted. Read-like " - "preview performs no mutation.\",\"operationId\":\"getCameraCollectionMembers\",\"parameters\":[{\"in\":\"p" - "ath\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-" - "F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersResponse\"}}},\"descrip" - "tion\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"conten" + "\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (inc" + "luding replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may " + "be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; ref" + "resh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHead" + "er\":[]},{\"bearerToken\":[]}],\"summary\":\"Verify and enable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-au" + "thorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/verify\":{\"get\":{\"descriptio" + "n\":\"Session, Basic or legacy key only; scoped tokens do not resolve here. Required-password-chan" + "ge accounts may use verify. Auth-disabled mode returns synthetic admin and anonymous demo return" + "s synthetic viewer; neither permits personalized discovery. Missing authentication clears stale " + "session cookie.\",\"operationId\":\"verifyIdentity\",\"responses\":{\"200\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthVerifyResponse\"}}},\"description\":\"Successful respo" + "nse\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"No usable credential.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"lega" + "cyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Verify current account identity\",\"tags\":[\"Id" + "entity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/authoriza" + "tion/actions\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server inde" + "pendently checks the concrete target and current effective authority. Basic credentials, session" + " cookies and legacy keys retain their existing behavior. Mutations must not be automatically ret" + "ried.\",\"operationId\":\"listAuthorizationActions\",\"responses\":{\"200\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminActionCatalog\"}}},\"description\":\"Validated succes" + "s.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential inva" + "lid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scope" + "d management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lock" + "out, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mu" + "tation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorization " + "actions\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"require" + "ments\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles\":{\"" + "get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks" + " the concrete target and current effective authority. Basic credentials, session cookies and leg" + "acy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operatio" + "nId\":\"listAuthorizationRoles\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/AdminRoles\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path" + ", query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective " + "authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"}" + ",\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, dupli" + "cate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have ap" + "plied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}," + "{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List authorization roles\",\"tags\":[\"Ide" + "ntity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"" + "action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Discovery is an o" + "peration-level capability. The server independently checks the concrete target and current effec" + "tive authority. Basic credentials, session cookies and legacy keys retain their existing behavio" + "r. Mutations must not be automatically retried.\",\"operationId\":\"createAuthorizationRole\",\"reques" + "tBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}}" + ",\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/AdminRoleWritten\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, quer" + "y or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective author" + "ity is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate o" + "r in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied." + " Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"lega" + "cyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create authorization role\",\"tags\":[\"Identity" + " administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"actio" + "n\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles/{uuid}\":{\"delete\":" + "{\"description\":\"Discovery is an operation-level capability. The server independently checks the " + "concrete target and current effective authority. Basic credentials, session cookies and legacy k" + "eys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":" + "\"deleteAuthorizationRole\",\"parameters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"r" + "equired\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" + "[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/AdminVersionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleDeleted\"}}},\"description\":" + "\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required o" + "r credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot " + "perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy re" + "vision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or inter" + "nal failure; a mutation may already have applied. Read current state before retrying.\"}},\"securi" + "ty\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summa" + "ry\":\"Delete authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"" + "kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":" + "1}},\"put\":{\"description\":\"Discovery is an operation-level capability. The server independently c" + "hecks the concrete target and current effective authority. Basic credentials, session cookies an" + "d legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"ope" + "rationId\":\"updateAuthorizationRole\",\"parameters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\"" + ":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a" + "-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWritten\"}}},\"descript" + "ion\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requi" + "red or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials ca" + "nnot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Poli" + "cy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or " + "internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"s" + "ecurity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"" + "summary\":\"Update authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorizatio" + "n\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"vers" + "ion\":1}}},\"/api/authorization/simulate\":{\"post\":{\"description\":\"Requires users.manage. Evaluates" + " the target user, not a token, against a concrete existing camera for camera-scoped actions or n" + "o camera for global actions. Does not modify grants; an audit event is appended.\",\"operationId\":" + "\"simulateAuthorization\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/AdminSimulationRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminSimulation\"}}},\"description\":\"Validated " + "success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credentia" + "l invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non" + "-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, sel" + "f-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure" + "; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sess" + "ionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Simulate auth" + "orization\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requi" + "rements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{" + "id}\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently" + " checks the concrete target and current effective authority. Basic credentials, session cookies " + "and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"o" + "perationId\":\"getUserAuthorizationPolicy\",\"parameters\":[{\"description\":\"Positive decimal signed 6" + "4-bit user ID. Browser adapters require a positive safe integer. Concrete target access is check" + "ed independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"max" + "Length\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserPolicy\"}}},\"description\":\"Validated succes" + "s.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential inva" + "lid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scope" + "d management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lock" + "out, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mu" + "tation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Get user aut" + "horization policy\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_sc" + "oped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"d" + "escription\":\"Requires a non-scoped users.manage principal, current expected_policy_version, and " + "mode policy. Replaces the grants atomically. Delegated actions and camera scope cannot exceed ca" + "ller authority. Self-lockout is rejected. Effective permissions and scoped-token intersections a" + "re re-evaluated on subsequent requests.\",\"operationId\":\"setUserAuthorizationPolicy\",\"parameters\"" + ":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive sa" + "fe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\"" + ",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"r" + "equestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminPolicyWr" + "ite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/AdminUserPolicy\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path" + ", query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective " + "authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"}" + ",\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, dupli" + "cate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have ap" + "plied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}," + "{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Set user authorization policy\",\"tags\":" + "[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements" + "\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{user_id" + "}/tokens\":{\"get\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner" + " requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path ma" + "y produce400 before authentication. Current target and policy checks still run for each request." + "\",\"operationId\":\"listUserTokens\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiTokenList\"}}},\"description\":\"Successful response\"},\"400\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" + "d request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidd" + "en.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"User or token not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"se" + "curity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"s" + "ummary\":\"List token metadata for an allowed owner\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":" + "{\"kind\":\"non_scoped_identity\",\"version\":1}},\"parameters\":[{\"description\":\"Positive user ID. Non-" + "scoped caller may manage their own tokens; other owners require users.manage. Visibility never g" + "rants authority over arbitrary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"min" + "imum\":1,\"type\":\"integer\"}}],\"post\":{\"description\":\"Requires a non-scoped principal: self is allo" + "wed; another owner requires users.manage. Scoped credentials are always403 even with users.manag" + "e. Invalid path may produce400 before authentication. Current target and policy checks still run" + " for each request. Requested actions and scope must be contained by both owner and requester. On" + "ly201 returns the one-time secret. Denied/invalid issuance leaves tokens and policy unchanged; a" + "udit entries may be written.\",\"operationId\":\"createUserToken\",\"requestBody\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenCreateRequest\"}}},\"required\":true},\"r" + "esponses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiToken" + "Created\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"conten" "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No us" - "able authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or i" - "nvisible object.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Only static collections have explicit members.\"},\"500\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, " - "authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"a" - "piKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized static collection members\",\"tags" - "\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description" - "\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and" - " legacy API-key compatibility. Resource-specific visibility, ownership and reference checks stil" - "l apply.\",\"operationId\":\"setCameraCollectionMembers\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"r" - "equired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersRequest\"}}},\"req" - "uired\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/CameraCollectionMembersResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" - "id path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for " - "this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or stat" - "e conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"" - "apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraCollectionMembers\",\"tags\":[\"Fleet\"],\"x" - "-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource" - "\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}/preview\":{\"post\":{\"description\":\"Collec" - "tion visibility requires global camera.configure, ownership or sharing; a shared collection with" - " zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts " - "and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or" - " manager; other readers receive null plus selector_redacted. Read-like preview performs no mutat" - "ion.\",\"operationId\":\"previewCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required" - "\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-f" - "A-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/CameraCollectionPreview\"}}},\"description\":\"Successful authorized read.\"}" - ",\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database" - ", authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{" - "\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview authorized collection matches\",\"tags\":" - "[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/camera-favorit" - "es\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing " - "Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership a" - "nd reference checks still apply.\",\"operationId\":\"listCameraFavorites\",\"responses\":{\"200\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavoritesResponse\"}}},\"des" - "cription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication r" - "equired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible" - " resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"se" - "curity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\"" - ":\"ListCameraFavorites\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"vers" - "ion\":1}}},\"/api/camera-favorites/{uuid}\":{\"delete\":{\"description\":\"Remove only the current user " - "favorite, including cameras no longer visible. Idempotent; scoped tokens and demo identities can" - "not mutate.\",\"operationId\":\"deleteCameraFavorite\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"requ" - "ired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavoriteRemoval\"}}},\"descriptio" - "n\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required" - ".\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resour" - "ce.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\"" - ":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delet" - "eCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"vers" - "ion\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve exist" - "ing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownersh" - "ip and reference checks still apply.\",\"operationId\":\"addCameraFavorite\",\"parameters\":[{\"in\":\"pat" - "h\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"response" - "s\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavorite\"" - "}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenti" - "cation required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or i" - "nvisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed" - ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"" - "summary\":\"AddCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_cam" - "era\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags\":" - "{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors" - " may be included. Global live.view can expose the full organization catalog. Camera-specific tag" - " access requires live.view on that raw camera identity; missing objects may return 404 before au" - "thentication.\",\"operationId\":\"listFleetTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/FleetTagsResponse\"}}},\"description\":\"Successful authorize" + "able credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not" + " found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Active token limit reached.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure." + "\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":" + "[]}],\"summary\":\"Create a scoped token\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non" + "_scoped_identity\",\"version\":1}}},\"/api/authorization/users/{user_id}/tokens/{token_uuid}\":{\"dele" + "te\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires use" + "rs.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 " + "before authentication. Current target and policy checks still run for each request. Revocation i" + "nvalidates subsequent credential requests; it does not rewrite owner policy.\",\"operationId\":\"rev" + "okeUserToken\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/SuccessResponse\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"" + "},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"U" + "ser or token not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessi" + "onCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke " + "an allowed owner token\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identit" + "y\",\"version\":1}},\"parameters\":[{\"description\":\"Positive user ID. Non-scoped caller may manage th" + "eir own tokens; other owners require users.manage. Visibility never grants authority over arbitr" + "ary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}," + "{\"description\":\"Token UUID belonging to this owner.\",\"in\":\"path\",\"name\":\"token_uuid\",\"required\":" + "true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-" + "F]{12}$\",\"type\":\"string\"}}]},\"/api/camera-collections\":{\"get\":{\"description\":\"Collection visibil" + "ity requires global camera.configure, ownership or sharing; a shared collection with zero visibl" + "e live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples " + "are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; ot" + "her readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"opera" + "tionId\":\"listCameraCollections\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/CameraCollectionsResponse\"}}},\"description\":\"Successful authorized rea" + "d.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"}," + "\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Data" + "base, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[" + "]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera collections\",\"tags\":[\"" + "Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"" + "Authorization is checked before mutation. Requests preserve existing Basic authentication and le" + "gacy API-key compatibility. Resource-specific visibility, ownership and reference checks still a" + "pply.\",\"operationId\":\"createCameraCollection\",\"requestBody\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/CameraCollectionCreate\"}}},\"required\":true},\"responses\":{\"201" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"d" + "escription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication" + " required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisib" + "le resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"" + "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summar" + "y\":\"CreateCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requir" + "ements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections" + "/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve e" + "xisting Basic authentication and legacy API-key compatibility. Resource-specific visibility, own" + "ership and reference checks still apply.\",\"operationId\":\"deleteCameraCollection\",\"parameters\":[{" + "\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}]," + "\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetM" + "utationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"M" + "issing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Opera" + "tion failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTo" + "ken\":[]}],\"summary\":\"DeleteCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\"" + ":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"" + "description\":\"Collection visibility requires global camera.configure, ownership or sharing; a sh" + "ared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Memb" + "er/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed " + "only to the owner or manager; other readers receive null plus selector_redacted. Read-like previ" + "ew performs no mutation.\",\"operationId\":\"getCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":" + "\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-" + "fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful authorize" "d read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" "}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"sc" "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principa" @@ -3573,30 +3710,221 @@ static const unsigned char lightnvr_openapi_document[] = "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"" "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" "\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" - "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera tags\",\"tags\":[\"Fl" - "eet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Au" - "thorization is checked before mutation. Requests preserve existing Basic authentication and lega" - "cy API-key compatibility. Resource-specific visibility, ownership and reference checks still app" - "ly.\",\"operationId\":\"createFleetTag\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/FleetTagCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful op" - "eration.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Oper" - "ation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Re" - "vision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie" - "\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetTag\",\"tags\"" - ":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configu" - "re\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags/{uuid}\":{\"delete\":{\"description\":\"Author" - "ization is checked before mutation. Requests preserve existing Basic authentication and legacy A" - "PI-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\"" - ",\"operationId\":\"deleteFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema" - "\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operatio" + "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera collection\",\"ta" + "gs\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"descripti" + "on\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication a" + "nd legacy API-key compatibility. Resource-specific visibility, ownership and reference checks st" + "ill apply.\",\"operationId\":\"updateCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"req" + "uired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionUpdate\"}}},\"required\":tru" + "e},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Cam" + "eraCollection\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"40" + "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Op" + "eration failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"beare" + "rToken\":[]}],\"summary\":\"UpdateCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"ki" + "nd\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/ap" + "i/camera-collections/{uuid}/members\":{\"get\":{\"description\":\"Collection visibility requires globa" + "l camera.configure, ownership or sharing; a shared collection with zero visible live.view matche" + "s is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to auth" + "orized cameras. Smart selectors are disclosed only to the owner or manager; other readers receiv" + "e null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"getCamera" + "CollectionMembers\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":" + "\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}" + "}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Cam" + "eraCollectionMembersResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid r" + "equest/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or acti" + "on access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"409\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Only static collections hav" + "e explicit members.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[" + "{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read au" + "thorized static collection members\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authent" + "icated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests p" + "reserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibi" + "lity, ownership and reference checks still apply.\",\"operationId\":\"setCameraCollectionMembers\",\"p" + "arameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/Ca" + "meraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/CameraCollectionMembersRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersResponse\"}}},\"description" + "\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required." + "\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resourc" + "e.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":" + "[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCam" + "eraCollectionMembers\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements" + "\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid" + "}/preview\":{\"post\":{\"description\":\"Collection visibility requires global camera.configure, owner" + "ship or sharing; a shared collection with zero visible live.view matches is hidden from a non-ow" + "ner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart sel" + "ectors are disclosed only to the owner or manager; other readers receive null plus selector_reda" + "cted. Read-like preview performs no mutation.\",\"operationId\":\"previewCameraCollection\",\"paramete" + "rs\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]" + "{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionPreview\"}}},\"de" + "scription\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing" + " or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\"" + ":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Previ" + "ew authorized collection matches\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authentic" + "ated\",\"version\":1}}},\"/api/camera-favorites\":{\"get\":{\"description\":\"Authorization is checked bef" + "ore mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. " + "Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listCa" + "meraFavorites\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/CameraFavoritesResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid pat" + "h or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this p" + "rincipal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conf" + "lict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKey" + "Header\":[]},{\"bearerToken\":[]}],\"summary\":\"ListCameraFavorites\",\"tags\":[\"Fleet\"],\"x-lightnvr-aut" + "horization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/camera-favorites/{uuid}\":{\"delete\":{\"de" + "scription\":\"Remove only the current user favorite, including cameras no longer visible. Idempote" + "nt; scoped tokens and demo identities cannot mutate.\",\"operationId\":\"deleteCameraFavorite\",\"para" + "meters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/Camer" + "aUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/CameraFavoriteRemoval\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or re" + "quest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principa" + "l.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"}" + ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" + ":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authoriza" + "tion\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked" + " before mutation. Requests preserve existing Basic authentication and legacy API-key compatibili" + "ty. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"ad" + "dCameraFavorite\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/CameraFavorite\"}}},\"description\":\"Successful operation.\"},\"400\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" + "alid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed fo" + "r this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or st" + "ate conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}," + "{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"AddCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightn" + "vr-authorization\":{\"kind\":\"non_scoped_camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"" + "raw\"}],\"version\":1}}},\"/api/camera-tags\":{\"get\":{\"description\":\"Visibility and counts follow exi" + "sting live.view scope; location ancestors may be included. Global live.view can expose the full " + "organization catalog. Camera-specific tag access requires live.view on that raw camera identity;" + " missing objects may return 404 before authentication.\",\"operationId\":\"listFleetTags\",\"responses" + "\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagsRespons" + "e\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"}," + "\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"" + "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summar" + "y\":\"List visible camera tags\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated" + "\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preser" + "ve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility," + " ownership and reference checks still apply.\",\"operationId\":\"createFleetTag\",\"requestBody\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagCreate\"}}},\"required\":" + "true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "FleetTag\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Mis" + "sing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operati" + "on failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToke" + "n\":[]}],\"summary\":\"CreateFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\"," + "\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags" + "/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve e" + "xisting Basic authentication and legacy API-key compatibility. Resource-specific visibility, own" + "ership and reference checks still apply.\",\"operationId\":\"deleteFleetTag\",\"parameters\":[{\"in\":\"pa" + "th\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"respons" + "es\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationA" + "ck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authe" + "ntication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing o" + "r invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation fai" + "led.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}" + "],\"summary\":\"DeleteFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requi" + "rements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Vi" + "sibility and counts follow existing live.view scope; location ancestors may be included. Global " + "live.view can expose the full organization catalog. Camera-specific tag access requires live.vie" + "w on that raw camera identity; missing objects may return 404 before authentication.\",\"operation" + "Id\":\"getFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"" + "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}" + "],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Flee" + "tTag\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector." + "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" + "on\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}" + "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" + "mary\":\"Read a visible camera tag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authentic" + "ated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests pre" + "serve existing Basic authentication and legacy API-key compatibility. Resource-specific visibili" + "ty, ownership and reference checks still apply.\",\"operationId\":\"updateFleetTag\",\"parameters\":[{\"" + "in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"" + "requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagUpda" + "te\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/FleetTag\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or r" + "equest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this princip" + "al.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"" + "},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" + "\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\"" + ":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}}" + ",\"/api/camera-tags/{uuid}/merge\":{\"post\":{\"description\":\"Authorization is checked before mutatio" + "n. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-sp" + "ecific visibility, ownership and reference checks still apply.\",\"operationId\":\"mergeFleetTag\",\"p" + "arameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/Ca" + "meraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/FleetTagMergeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/FleetTagMergeResult\"}}},\"description\":\"Successful operatio" "n.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" "description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" "\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"" @@ -3606,229 +3934,248 @@ static const unsigned char lightnvr_openapi_document[] = "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision" ", reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" "nents/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]}," - "{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetTag\",\"tags\":[\"Fle" - "et\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"r" - "esource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and counts follow existing live." - "view scope; location ancestors may be included. Global live.view can expose the full organizatio" - "n catalog. Camera-specific tag access requires live.view on that raw camera identity; missing ob" - "jects may return 404 before authentication.\",\"operationId\":\"getFleetTag\",\"parameters\":[{\"in\":\"pa" - "th\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F" - "]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful author" - "ized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated princ" - "ipal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\"" - ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" - "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera tag\",\"tags\":" - "[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":" - "\"Authorization is checked before mutation. Requests preserve existing Basic authentication and l" - "egacy API-key compatibility. Resource-specific visibility, ownership and reference checks still " - "apply.\",\"operationId\":\"updateFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true," - "\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagUpdate\"}}},\"required\":true},\"responses\":{\"200" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"descripti" - "on\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication require" - "d.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resou" - "rce.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security" - "\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Upda" - "teFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"actio" - "n\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags/{uuid}/merge\":{\"post\":" - "{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authe" - "ntication and legacy API-key compatibility. Resource-specific visibility, ownership and referenc" - "e checks still apply.\",\"operationId\":\"mergeFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"r" - "equired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagMergeRequest\"}}},\"required\":tru" - "e},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Fle" - "etTagMergeResult\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"}," - "\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" - "arerToken\":[]}],\"summary\":\"MergeFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"g" - "lobal\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/came" - "ras/{uuid}/location\":{\"put\":{\"description\":\"Authorization is checked before mutation. Requests p" - "reserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibi" - "lity, ownership and reference checks still apply.\",\"operationId\":\"setCameraLocation\",\"parameters" - "\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"" - "}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraL" - "ocationAssignmentRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/CameraLocationAssignment\"}}},\"description\":\"Successful o" - "peration.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Ope" - "ration is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"R" - "evision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCooki" - "e\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraLocation\",\"t" - "ags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.con" - "figure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/cameras/{uuid}/tags\":{\"get\":{\"description\":\"Visi" - "bility and counts follow existing live.view scope; location ancestors may be included. Global li" - "ve.view can expose the full organization catalog. Camera-specific tag access requires live.view " - "on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId" - "\":\"getCameraTagAssignments\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"" - "pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":" - "\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/CameraTagAssignmentsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera o" - "r action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorizati" - "on or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" - "\":[]},{\"bearerToken\":[]}],\"summary\":\"Read tags for an authorized camera\",\"tags\":[\"Fleet\"],\"x-lig" - "htnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"" - "version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve e" - "xisting Basic authentication and legacy API-key compatibility. Resource-specific visibility, own" - "ership and reference checks still apply.\",\"operationId\":\"setCameraTags\",\"parameters\":[{\"in\":\"pat" - "h\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestB" - "ody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignments" - "Request\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/CameraTagAssignmentsResponse\"}}},\"description\":\"Successful operation.\"},\"" - "400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" - "ption\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not" - " allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, refe" - "rence or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" - "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraTags\",\"tags\":[\"Fleet\"],\"x" - "-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource" - "\":\"raw\"},{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/client-config\":{\"get\":{\"d" - "escription\":\"Any usable principal, including scoped tokens, can read this intentionally limited " - "shape. Existing auth-disabled/demo behavior remains; this is not public in normal authenticated " - "mode.\",\"operationId\":\"getClientConfig\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ClientConfig\"}}},\"description\":\"Successful response\"},\"401\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"session" - "Cookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get safe browser" - " playback configuration\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\"," - "\"version\":1}}},\"/api/detection/models\":{\"get\":{\"description\":\"Requires global camera.configure o" - "r that permission on an existing camera. Built-in models remain present if the configured model " - "directory is absent. File models include paths and support/build metadata only for an authorized" - " configurator.\",\"operationId\":\"listEvidenceModels\",\"responses\":{\"200\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceModels\"}}},\"description\":\"Validated success" - ".\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current c" - "amera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or " - "not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request excee" - "ds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protec" - "ted storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" - "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence models\",\"tags\":[\"Eviden" - "ce\"],\"x-lightnvr-authorization\":{\"kind\":\"global_or_camera\",\"requirements\":[{\"action\":\"camera.con" - "figure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/detection/results/{name}\":{\"get\":{\"description\":" - "\"Current camera live.view authorizes the query. Captured detection UUIDs take precedence over re" - "used stream names; only legacy NULL/empty UUID rows fall back to name. Optional start/end are ep" - "och-second filters; absent bounds use a recent interval of at least 30 seconds.\",\"operationId\":\"" - "getEvidenceDetections\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized in" - "dependently.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}" - "},{\"in\":\"query\",\"name\":\"start\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"en" - "d\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/EvidenceDetections\"}}},\"description\":\"Validated success.\"" - "},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current cam" - "era policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or no" - "t visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds" - " operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protecte" - "d storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth" - "\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence detections\",\"tags\":[\"Evide" - "nce\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resourc" - "e\":\"raw\"}],\"version\":1}}},\"/api/event-destinations\":{\"get\":{\"description\":\"Global event administ" - "ration. Native validation additionally enforces referenced destinations, registered event/health" - " types, compatible predicates, available timezones and topic/TLS constraints. Preview never publ" - "ishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"listEventDestinations\",\"r" - "esponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDes" - "tinations\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"40" - "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object" - " not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHe" - "ader\":[]},{\"bearerToken\":[]}],\"summary\":\"List event destinations\",\"tags\":[\"Events\"],\"x-lightnvr-" - "authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}]," - "\"version\":1}},\"post\":{\"description\":\"Global event administration. Native validation additionally" - " enforces referenced destinations, registered event/health types, compatible predicates, availab" - "le timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not" - " imply delivery.\",\"operationId\":\"createEventDestination\",\"requestBody\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinationCreate\"}}},\"required\":true},\"respo" - "nses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestina" - "tion\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"" + "{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"MergeFleetTag\",\"tags\":[\"Flee" + "t\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"re" + "source\":\"raw\"}],\"version\":1}}},\"/api/cameras/{uuid}/location\":{\"put\":{\"description\":\"Authorizati" + "on is checked before mutation. Requests preserve existing Basic authentication and legacy API-ke" + "y compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"ope" + "rationId\":\"setCameraLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":" + "{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/CameraLocationAssignmentRequest\"}}},\"required\":true},\"response" + "s\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraLocationA" + "ssignment\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Mi" + "ssing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operat" + "ion failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTok" + "en\":[]}],\"summary\":\"SetCameraLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"came" + "ra\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/cameras" + "/{uuid}/tags\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; loca" + "tion ancestors may be included. Global live.view can expose the full organization catalog. Camer" + "a-specific tag access requires live.view on that raw camera identity; missing objects may return" + " 404 before authentication.\",\"operationId\":\"getCameraTagAssignments\",\"parameters\":[{\"in\":\"path\"," + "\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}" + "-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsResponse\"}}},\"description\":\"S" + "uccessful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable aut" + "henticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible" + " object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCo" + "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read tags for an a" + "uthorized camera\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{" + "\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Authorization is che" + "cked before mutation. Requests preserve existing Basic authentication and legacy API-key compati" + "bility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\"" + ":\"setCameraTags\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/CameraTagAssignmentsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsResponse\"}}}" + ",\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticat" + "ion required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invi" + "sible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}" + "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" + "mary\":\"SetCameraTags\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements" + "\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"},{\"action\":\"live.view\",\"resource\":\"raw\"}],\"vers" + "ion\":1}}},\"/api/client-config\":{\"get\":{\"description\":\"Any usable principal, including scoped tok" + "ens, can read this intentionally limited shape. Existing auth-disabled/demo behavior remains; th" + "is is not public in normal authenticated mode.\",\"operationId\":\"getClientConfig\",\"responses\":{\"20" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ClientConfig\"}}},\"desc" + "ription\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy eva" + "luation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bear" + "erToken\":[]}],\"summary\":\"Get safe browser playback configuration\",\"tags\":[\"Identity\"],\"x-lightnv" + "r-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/detection/models\":{\"get\":{\"descrip" + "tion\":\"Requires global camera.configure or that permission on an existing camera. Built-in model" + "s remain present if the configured model directory is absent. File models include paths and supp" + "ort/build metadata only for an authorized configurator.\",\"operationId\":\"listEvidenceModels\",\"res" + "ponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceMo" + "dels\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication" + " required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restr" + "iction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or tr" + "ansfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mut" + "ation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"" + "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summar" + "y\":\"List evidence models\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"global_or_came" + "ra\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/detecti" + "on/results/{name}\":{\"get\":{\"description\":\"Current camera live.view authorizes the query. Capture" + "d detection UUIDs take precedence over reused stream names; only legacy NULL/empty UUID rows fal" + "l back to name. Optional start/end are epoch-second filters; absent bounds use a recent interval" + " of at least 30 seconds.\",\"operationId\":\"getEvidenceDetections\",\"parameters\":[{\"description\":\"Co" + "ncrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"name\",\"required\":true" + ",\"schema\":{\"minLength\":1,\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"start\",\"schema\":{\"minimum\":0,\"t" + "ype\":\"integer\"}},{\"in\":\"query\",\"name\":\"end\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}}],\"responses" + "\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceDetectio" + "ns\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication r" + "equired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restric" + "tion.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or tran" + "sfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutat" + "ion may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"se" + "curity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\"" + ":\"Get evidence detections\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requ" + "irements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-destinations\":{\"g" + "et\":{\"description\":\"Global event administration. Native validation additionally enforces referen" + "ced destinations, registered event/health types, compatible predicates, available timezones and " + "topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\"" + ",\"operationId\":\"listEventDestinations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/EventDestinations\"}}},\"description\":\"Successful response.\"},\"40" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effe" + "ctive authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict" + ", limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessi" + "onCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List event des" + "tinations\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"actio" + "n\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Global event admin" + "istration. Native validation additionally enforces referenced destinations, registered event/hea" + "lth types, compatible predicates, available timezones and topic/TLS constraints. Preview never p" + "ublishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"createEventDestination" + "\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDest" + "inationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"" "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not " - "found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persi" - "stence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" - ":[]},{\"bearerToken\":[]}],\"summary\":\"Create event destination\",\"tags\":[\"Events\"],\"x-lightnvr-auth" - "orization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"ver" - "sion\":1}}},\"/api/event-destinations/{uuid}\":{\"delete\":{\"description\":\"Global event administratio" - "n. Native validation additionally enforces referenced destinations, registered event/health type" - "s, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes" - "; mutation acknowledgments do not imply delivery.\",\"operationId\":\"deleteEventDestination\",\"param" - "eters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},{" - "\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1," - "\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/EventMutationAck\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid reques" - "t or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority deni" - "ed.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or reference" - "d object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" - "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete event destination\",\"tags\":[" - "\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configur" - "e\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Global event administration. Native va" - "lidation additionally enforces referenced destinations, registered event/health types, compatibl" - "e predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation a" - "cknowledgments do not imply delivery.\",\"operationId\":\"getEventDestination\",\"parameters\":[{\"in\":\"" - "path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"responses\":{\"2" - "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}}," + "\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective" + " authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, lim" + "it or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create event destin" + "ation\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"" + "events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-destinations/{uuid}\":{\"delete\":{" + "\"description\":\"Global event administration. Native validation additionally enforces referenced d" + "estinations, registered event/health types, compatible predicates, available timezones and topic" + "/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"ope" + "rationId\":\"deleteEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"sch" + "ema\":{\"format\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\"" + ":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutationAck\"}}},\"description\":\"Succe" + "ssful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"" + "403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision co" + "nflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}" + "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" + "mary\":\"Delete event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"" + "requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description" + "\":\"Global event administration. Native validation additionally enforces referenced destinations," + " registered event/health types, compatible predicates, available timezones and topic/TLS constra" + "ints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"g" + "etEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":" + "\"uuid\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " + "request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authorit" + "y denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or ref" + "erenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]}," + "{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event destination\",\"tags" + "\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.confi" + "gure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Global event administration. Native" + " validation additionally enforces referenced destinations, registered event/health types, compat" + "ible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutatio" + "n acknowledgments do not imply delivery.\",\"operationId\":\"updateEventDestination\",\"parameters\":[{" + "\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestB" + "ody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinationUpda" + "te\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid re" + "quest or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority " + "denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or refer" + "enced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"" + "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update event destination\",\"tag" + "s\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.conf" + "igure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes\":{\"get\":{\"description\":\"Global event" + " administration. Native validation additionally enforces referenced destinations, registered eve" + "nt/health types, compatible predicates, available timezones and topic/TLS constraints. Preview n" + "ever publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"listEventRoutes\"" + ",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Event" + "Routes\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object no" + "t found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Per" + "sistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeade" + "r\":[]},{\"bearerToken\":[]}],\"summary\":\"List event routes\",\"tags\":[\"Events\"],\"x-lightnvr-authoriza" + "tion\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\"" + ":1}},\"post\":{\"description\":\"Global event administration. Native validation additionally enforces" + " referenced destinations, registered event/health types, compatible predicates, available timezo" + "nes and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply de" + "livery.\",\"operationId\":\"createEventRoute\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/EventRouteCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Suc" + "cessful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"}" + ",\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision " + "conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure." + "\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"s" + "ummary\":\"Create event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requ" + "irements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/pre" + "view\":{\"post\":{\"description\":\"Global event administration. Native validation additionally enforc" + "es referenced destinations, registered event/health types, compatible predicates, available time" + "zones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply " + "delivery.\",\"operationId\":\"previewEventRoute\",\"requestBody\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/EventRoutePreviewRequest\"}}},\"required\":true},\"responses\":{\"20" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutePreview\"}}}," "\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" "components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":" "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenti" @@ -3839,442 +4186,291 @@ static const unsigned char lightnvr_openapi_document[] = "iption\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"appl" "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or" " internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" - "arerToken\":[]}],\"summary\":\"Get event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{" - "\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"p" - "ut\":{\"description\":\"Global event administration. Native validation additionally enforces referen" - "ced destinations, registered event/health types, compatible predicates, available timezones and " - "topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\"" - ",\"operationId\":\"updateEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true" - ",\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/EventDestinationUpdate\"}}},\"required\":true},\"responses\":{\"200" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"d" - "escription\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentica" - "tion required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"4" - "09\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or i" - "nternal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bear" - "erToken\":[]}],\"summary\":\"Update event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":" - "{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}}," - "\"/api/event-routes\":{\"get\":{\"description\":\"Global event administration. Native validation additi" - "onally enforces referenced destinations, registered event/health types, compatible predicates, a" - "vailable timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments " - "do not imply delivery.\",\"operationId\":\"listEventRoutes\",\"responses\":{\"200\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutes\"}}},\"description\":\"Successful resp" - "onse.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Re" - "quired effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, na" - "me conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"securit" - "y\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Lis" - "t event routes\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"" - "action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Global event " - "administration. Native validation additionally enforces referenced destinations, registered even" - "t/health types, compatible predicates, available timezones and topic/TLS constraints. Preview ne" - "ver publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"createEventRoute\"" - ",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute" - "Create\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid requ" - "est or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority de" - "nied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referen" - "ced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" - "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create event route\",\"tags\":[\"Eve" - "nts\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"" - "resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/preview\":{\"post\":{\"description\":\"Global even" - "t administration. Native validation additionally enforces referenced destinations, registered ev" - "ent/health types, compatible predicates, available timezones and topic/TLS constraints. Preview " - "never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"previewEventRou" - "te\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRo" - "utePreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/EventRoutePreview\"}}},\"description\":\"Successful response.\"},\"40" - "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effe" - "ctive authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict" - ", limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessi" - "onCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview event " - "route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"" - "events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/{uuid}\":{\"delete\":{\"descr" - "iption\":\"Global event administration. Native validation additionally enforces referenced destina" - "tions, registered event/health types, compatible predicates, available timezones and topic/TLS c" - "onstraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operation" - "Id\":\"deleteEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"forma" - "t\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":" - "9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutationAck\"}}},\"description\":\"Successful respon" - "se.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requ" - "ired effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name" - " conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\"" - ":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delet" - "e event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"a" - "ction\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Global event ad" - "ministration. Native validation additionally enforces referenced destinations, registered event/" - "health types, compatible predicates, available timezones and topic/TLS constraints. Preview neve" - "r publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventRoute\",\"par" - "ameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}" - "],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Even" - "tRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object no" - "t found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Per" - "sistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeade" - "r\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorizati" - "on\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1" - "}},\"put\":{\"description\":\"Global event administration. Native validation additionally enforces re" - "ferenced destinations, registered event/health types, compatible predicates, available timezones" - " and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply deliv" - "ery.\",\"operationId\":\"updateEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true," - "\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/EventRouteUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"" - "Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required" - ".\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revisi" - "on conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failu" - "re.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}]" - ",\"summary\":\"Update event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"r" - "equirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/events/catalo" - "g\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces r" + "arerToken\":[]}],\"summary\":\"Preview event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"k" + "ind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/a" + "pi/event-routes/{uuid}\":{\"delete\":{\"description\":\"Global event administration. Native validation" + " additionally enforces referenced destinations, registered event/health types, compatible predic" + "ates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowled" + "gments do not imply delivery.\",\"operationId\":\"deleteEventRoute\",\"parameters\":[{\"in\":\"path\",\"name" + "\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revis" + "ion\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"respo" + "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutatio" + "nAck\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not " + "found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persi" + "stence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" + ":[]},{\"bearerToken\":[]}],\"summary\":\"Delete event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorizat" + "ion\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":" + "1}},\"get\":{\"description\":\"Global event administration. Native validation additionally enforces r" "eferenced destinations, registered event/health types, compatible predicates, available timezone" "s and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply deli" - "very.\",\"operationId\":\"getEventCatalog\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/EventCatalog\"}}},\"description\":\"Successful response.\"},\"400\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective" - " authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, lim" - "it or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCoo" - "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event catalog\"," + "very.\",\"operationId\":\"getEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"s" + "chema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effecti" + "ve authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, l" + "imit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionC" + "ookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event route\"," "\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events." - "configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/fleet/cameras/query\":{\"post\":{\"description\":\"" - "Read-only query over live.view-filtered, runtime-enriched inventory. Rows, totals and facets use" - " matching authorized cameras; operational queues use all authorized cameras before query filters" - ". can_configure is evaluated separately for each returned camera. Unknown collection is 404; out" - "-of-range pages may be empty with nonzero totals. No stream credentials are returned.\",\"operatio" - "nId\":\"queryFleetCameras\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/FleetQueryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQueryResponse\"}}},\"description\":\"Successful " - "authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated" - " principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"}" - ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]}," - "{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Query authorized fleet camer" - "as\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fle" - "et/selectors/preview\":{\"post\":{\"description\":\"Read-only query over live.view-filtered, runtime-e" - "nriched inventory. Rows, totals and facets use matching authorized cameras; operational queues u" - "se all authorized cameras before query filters. can_configure is evaluated separately for each r" - "eturned camera. Unknown collection is 404; out-of-range pages may be empty with nonzero totals. " - "No stream credentials are returned.\",\"operationId\":\"previewFleetSelector\",\"requestBody\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetPreviewRequest\"}}},\"required" - "\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/FleetPreviewResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request" - "/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action acc" - "ess denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serial" - "ization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bear" - "erToken\":[]}],\"summary\":\"Preview a selector over authorized fleet cameras\",\"tags\":[\"Fleet\"],\"x-l" - "ightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/views\":{\"get\":{\"descri" - "ption\":\"Owned and shared saved filter configurations; up to 256 list entries. Stored selector re" - "ferences do not grant camera access and must be reauthorized on query. Existing can_share reflec" - "ts the legacy admin role, not arbitrary action grants. Mutation contracts remain outside this re" - "ad slice.\",\"operationId\":\"listFleetViews\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/FleetSavedViewsResponse\"}}},\"description\":\"Successful author" - "ized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated princ" - "ipal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\"" + "configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Global event administration. N" + "ative validation additionally enforces referenced destinations, registered event/health types, c" + "ompatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mu" + "tation acknowledgments do not imply delivery.\",\"operationId\":\"updateEventRoute\",\"parameters\":[{\"" + "in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBo" + "dy\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRouteUpdate\"}}}," + "\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or refe" + "rence.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"40" + "4\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object." + "\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" + "},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update event route\",\"tags\":[\"Events\"],\"x-li" + "ghtnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"" + "raw\"}],\"version\":1}}},\"/api/events/catalog\":{\"get\":{\"description\":\"Global event administration. " + "Native validation additionally enforces referenced destinations, registered event/health types, " + "compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; m" + "utation acknowledgments do not imply delivery.\",\"operationId\":\"getEventCatalog\",\"responses\":{\"20" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventCatalog\"}}},\"desc" + "ription\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticatio" + "n required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\"" ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" - "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List owned and shared fleet views\"" - ",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"desc" - "ription\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain " - "the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion requir" - "e the current revision; shared visibility does not grant modification.\",\"operationId\":\"createFle" - "etView\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Fle" - "etViewCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful operation.\"},\"400\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" - "nvalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed " - "for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or " - "state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" - "},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetView\",\"tags\":[\"Fleet\"],\"x-lightn" - "vr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/views/{uuid}\":{\"delete\":{\"d" - "escription\":\"Personal views are owner-managed; only administrators may share. Scoped tokens reta" - "in the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion req" - "uire the current revision; shared visibility does not grant modification.\",\"operationId\":\"delete" - "FleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successfu" - "l operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"c" + "n\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or inte" + "rnal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerT" + "oken\":[]}],\"summary\":\"Get event catalog\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"g" + "lobal\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/flee" + "t/cameras/query\":{\"post\":{\"description\":\"Read-only query over live.view-filtered, runtime-enrich" + "ed inventory. Rows, totals and facets use matching authorized cameras; operational queues use al" + "l authorized cameras before query filters. can_configure is evaluated separately for each return" + "ed camera. Unknown collection is 404; out-of-range pages may be empty with nonzero totals. No st" + "ream credentials are returned.\",\"operationId\":\"queryFleetCameras\",\"requestBody\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQueryRequest\"}}},\"required\":true},\"r" + "esponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQue" + "ryResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or sel" + "ector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"" + "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization fail" + "ure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}" + "],\"summary\":\"Query authorized fleet cameras\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\"" + ":\"authenticated\",\"version\":1}}},\"/api/fleet/selectors/preview\":{\"post\":{\"description\":\"Read-only" + " query over live.view-filtered, runtime-enriched inventory. Rows, totals and facets use matching" + " authorized cameras; operational queues use all authorized cameras before query filters. can_con" + "figure is evaluated separately for each returned camera. Unknown collection is 404; out-of-range" + " pages may be empty with nonzero totals. No stream credentials are returned.\",\"operationId\":\"pre" + "viewFleetSelector\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/FleetPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetPreviewResponse\"}}},\"description\":\"Successful au" + "thorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated p" + "rincipal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"" + "500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"" + "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview a selector over author" + "ized fleet cameras\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version" + "\":1}}},\"/api/fleet/views\":{\"get\":{\"description\":\"Owned and shared saved filter configurations; u" + "p to 256 list entries. Stored selector references do not grant camera access and must be reautho" + "rized on query. Existing can_share reflects the legacy admin role, not arbitrary action grants. " + "Mutation contracts remain outside this read slice.\",\"operationId\":\"listFleetViews\",\"responses\":{" + "\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedViewsResp" + "onse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector." + "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" + "on\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}" + "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" + "mary\":\"List owned and shared fleet views\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"a" + "uthenticated\",\"version\":1}},\"post\":{\"description\":\"Personal views are owner-managed; only admini" + "strators may share. Scoped tokens retain the existing owner identity behavior. Demo identities c" + "annot mutate. Updates and deletion require the current revision; shared visibility does not gran" + "t modification.\",\"operationId\":\"createFleetView\",\"requestBody\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/FleetViewCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"descrip" + "tion\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requi" + "red.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible res" + "ource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"securi" + "ty\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Cr" + "eateFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}" + "},\"/api/fleet/views/{uuid}\":{\"delete\":{\"description\":\"Personal views are owner-managed; only adm" + "inistrators may share. Scoped tokens retain the existing owner identity behavior. Demo identitie" + "s cannot mutate. Updates and deletion require the current revision; shared visibility does not g" + "rant modification.\",\"operationId\":\"deleteFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"re" + "quired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true" + "},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Flee" + "tMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" + "on\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Ope" + "ration failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearer" + "Token\":[]}],\"summary\":\"DeleteFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"aut" + "henticated\",\"version\":1}},\"get\":{\"description\":\"Owned and shared saved filter configurations; up" + " to 256 list entries. Stored selector references do not grant camera access and must be reauthor" + "ized on query. Existing can_share reflects the legacy admin role, not arbitrary action grants. M" + "utation contracts remain outside this read slice.\",\"operationId\":\"getFleetView\",\"parameters\":[{\"" + "in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-" + "9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Succ" + "essful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authen" + "ticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible ob" + "ject.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCooki" + "e\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an owned or shar" + "ed fleet view\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}" + ",\"put\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped t" + "okens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and de" + "letion require the current revision; shared visibility does not grant modification.\",\"operationI" + "d\":\"updateFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"" + "#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/FleetViewUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Succes" + "sful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" + "on\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessio" + "nCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetView" + "\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/healt" + "h\":{\"get\":{\"operationId\":\"getStreamHealth\",\"parameters\":[{\"description\":\"Return only process liv" + "eness.\",\"in\":\"query\",\"name\":\"liveness\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Return autho" + "rized stream histories.\",\"in\":\"query\",\"name\":\"history_only\",\"schema\":{\"type\":\"boolean\"}},{\"descr" + "iption\":\"Include stream history arrays.\",\"in\":\"query\",\"name\":\"sparklines\",\"schema\":{\"type\":\"bool" + "ean\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/HealthResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a" + " request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authe" + "ntication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failu" + "re.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAu" + "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get stream health\",\"tags\":[\"System\"]," + "\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/health/hls\":{\"get\":{\"operationI" + "d\":\"getHlsHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/HlsHealth\"}}},\"description\":\"Successful response. Process-control responses acknowle" + "dge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"c" "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCo" - "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetView\",\"" - "tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"get\":{\"descrip" - "tion\":\"Owned and shared saved filter configurations; up to 256 list entries. Stored selector ref" - "erences do not grant camera access and must be reauthorized on query. Existing can_share reflect" - "s the legacy admin role, not arbitrary action grants. Mutation contracts remain outside this rea" - "d slice.\",\"operationId\":\"getFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true," - "\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12" - "}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/FleetSavedView\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or a" - "ction access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization " - "or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" - "]},{\"bearerToken\":[]}],\"summary\":\"Read an owned or shared fleet view\",\"tags\":[\"Fleet\"],\"x-lightn" - "vr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Personal views are" - " owner-managed; only administrators may share. Scoped tokens retain the existing owner identity " - "behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shar" - "ed visibility does not grant modification.\",\"operationId\":\"updateFleetView\",\"parameters\":[{\"in\":" - "\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requ" - "estBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetViewUpdate\"" - "}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/FleetSavedView\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path o" - "r request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this prin" - "cipal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflic" - "t.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHea" - "der\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorizat" - "ion\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/health\":{\"get\":{\"operationId\":\"getStreamHealth" - "\",\"parameters\":[{\"description\":\"Return only process liveness.\",\"in\":\"query\",\"name\":\"liveness\",\"s" - "chema\":{\"type\":\"boolean\"}},{\"description\":\"Return authorized stream histories.\",\"in\":\"query\",\"na" - "me\":\"history_only\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Include stream history arrays.\"," - "\"in\":\"query\",\"name\":\"sparklines\",\"schema\":{\"type\":\"boolean\"}}],\"responses\":{\"200\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HealthResponse\"}}},\"description\":\"Succe" - "ssful response. Process-control responses acknowledge a request; verify eventual completion inde" - "pendently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required a" - "uthority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavai" - "lable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken" - "\":[]}],\"summary\":\"Get stream health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"publi" - "c\",\"version\":1}}},\"/api/health/hls\":{\"get\":{\"operationId\":\"getHlsHealth\",\"responses\":{\"200\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HlsHealth\"}}},\"description\":\"" - "Successful response. Process-control responses acknowledge a request; verify eventual completion" - " independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requi" - "red authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily u" - "navailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearer" - "Token\":[]}],\"summary\":\"Get hls health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"pub" - "lic\",\"version\":1}}},\"/api/ice-servers\":{\"get\":{\"description\":\"Return configured ICE servers for " - "a principal permitted to view at least one camera. TURN credentials are intentionally provided t" - "o that authorized browser.\",\"operationId\":\"getIceServers\",\"responses\":{\"200\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/IceServersResponse\"}}},\"description\":\"Succes" - "sful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentica" - "tion credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera u" - "navailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databa" - "se, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service" - " temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" - "]},{\"bearerToken\":[]}],\"summary\":\"Return configured ICE servers for a principal permitted to vie" - "w at least one camera. TURN credentials are intentionally provided to that authorized browser.\"," - "\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.v" - "iew\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigation-bookmarks\":{\"get\":{\"description\":\"Own" - "ed by current user ID, including scoped-token owners; token replay scope must still permit every" - " saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. Lis" - "t filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-toke" - "n siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operati" - "onId\":\"listEvidenceBookmarks\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/EvidenceBookmarks\"}}},\"description\":\"Validated success.\"},\"400\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" - "id request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feat" - "ure configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"40" - "9\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limit" - "s.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or med" - "ia work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHe" - "ader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence bookmarks\",\"tags\":[\"Evidence\"],\"x-lightnv" - "r-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Owned by current u" - "ser ID, including scoped-token owners; token replay scope must still permit every saved camera. " - "Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invis" - "ible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings reta" - "in existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"createEv" - "idenceBookmark\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/EvidenceBookmarkCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success" - ".\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current c" - "amera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or " - "not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request excee" - "ds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protec" - "ted storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" - "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create evidence bookmark\",\"tags\":[\"Ev" - "idence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigation-b" - "ookmarks/{uuid}\":{\"delete\":{\"description\":\"Owned by current user ID, including scoped-token owne" - "rs; token replay scope must still permit every saved camera. Foreign ownership is concealed as40" - "4; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only" - " title/note with current revision. Scoped-token siblings retain existing same-owner compatibilit" - "y. Bookmarks do not hold recordings.\",\"operationId\":\"deleteEvidenceBookmark\",\"parameters\":[{\"des" - "cription\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"r" - "equired\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" - "[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/EvidenceBookmarkDelete\"}}},\"required\":true},\"responses\":{\"200\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkDeleted\"}}},\"des" - "cription\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"}," - "\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"40" - "4\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer confli" - "ct.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may hav" - "e applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"" - "sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete ev" - "idence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version" - "\":1}},\"get\":{\"description\":\"Owned by current user ID, including scoped-token owners; token repla" - "y scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccess" - "ible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note wit" - "h current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do" - " not hold recordings.\",\"operationId\":\"getEvidenceBookmark\",\"parameters\":[{\"description\":\"Concret" - "e resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"sch" - "ema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\"," - "\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"" - "},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configur" - "ation or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revisi" - "on, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temp" - "orarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"" - "bearerToken\":[]}],\"summary\":\"Get evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorizatio" - "n\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Owned by current user ID, includi" - "ng scoped-token owners; token replay scope must still permit every saved camera. Foreign ownersh" - "ip is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/" - "counts. Updates only title/note with current revision. Scoped-token siblings retain existing sam" - "e-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"updateEvidenceBookmark\"" - ",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"pa" - "th\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F" - "]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkUpdate\"}}},\"required\":true},\"respo" - "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBook" - "mark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication" - " required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restr" - "iction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or tr" - "ansfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mut" - "ation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"" - "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summar" - "y\":\"Update evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authentica" - "ted\",\"version\":1}}},\"/api/investigations/recordings/preview\":{\"post\":{\"description\":\"Discovery i" - "ndicates an operation; concrete cameras, captured identities, ownership and current permissions " - "are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain " - "supported.\",\"operationId\":\"previewEvidenceRecordings\",\"requestBody\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeRequest\"}}},\"required\":true},\"responses\"" - ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidencePreview\"}" + "Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence " + "failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"ba" + "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get hls health\",\"tags\":[\"System\"" + "],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/ice-servers\":{\"get\":{\"descrip" + "tion\":\"Return configured ICE servers for a principal permitted to view at least one camera. TURN" + " credentials are intentionally provided to that authorized browser.\",\"operationId\":\"getIceServer" + "s\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ice" + "ServersResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is" + " applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No" + " mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity confli" + "cts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream" + " media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCooki" + "e\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Return configured ICE" + " servers for a principal permitted to view at least one camera. TURN credentials are intentional" + "ly provided to that authorized browser.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"" + "camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/integratio" + "ns/capabilities\":{\"get\":{\"description\":\"Requires an authenticated principal and returns only its" + " scope: source identity, contract versions, operations (supported and authorized), limits, histo" + "ry, delivery and the cameras where the principal holds live.view, snapshot.create or observation" + "s.annotate (contract \302\2479). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":" + "\"getIntegrationCapabilities\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/IntegrationCapabilities\"}}},\"description\":\"Capability document for the ca" + "lling principal.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer body).\"},\"50" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"" + "description\":\"Internal failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable" + " (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\"" + ",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{" + "\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get integration capabilities\",\"tags\":[\"Integra" + "tions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigation-bo" + "okmarks\":{\"get\":{\"description\":\"Owned by current user ID, including scoped-token owners; token r" + "eplay scope must still permit every saved camera. Foreign ownership is concealed as404; own inac" + "cessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note" + " with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmark" + "s do not hold recordings.\",\"operationId\":\"listEvidenceBookmarks\",\"responses\":{\"200\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarks\"}}},\"description\":\"" + "Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"De" + "nied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resou" + "rce not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"" + "},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCook" + "ie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence bookma" + "rks\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\"" + ":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope mus" + "t still permit every saved camera. Foreign ownership is concealed as404; own inaccessible camera" + "s may return403. List filters invisible bookmarks/counts. Updates only title/note with current r" + "evision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold r" + "ecordings.\",\"operationId\":\"createEvidenceBookmark\",\"requestBody\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkCreate\"}}},\"required\":true},\"responses\":" + "{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}" "}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" "/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applicatio" "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requi" @@ -4288,352 +4484,498 @@ static const unsigned char lightnvr_openapi_document[] = "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation " "may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" "/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"securi" - "ty\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Pr" - "eview evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requ" - "irements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/" - "search\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured ide" - "ntities, ownership and current permissions are rechecked for every request. Basic, legacy keys a" - "nd scoped-token owner intersections remain supported.\",\"operationId\":\"searchEvidence\",\"requestBo" - "dy\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSearchRequest" - "\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/EvidenceSearch\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request." - "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configu" - "ration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revis" - "ion, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work tem" - "porarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{" - "\"bearerToken\":[]}],\"summary\":\"Search evidence\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"" - "kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/segment-at\":{\"post\":{\"description\":\"Di" - "scovery indicates an operation; concrete cameras, captured identities, ownership and current per" - "missions are rechecked for every request. Basic, legacy keys and scoped-token owner intersection" - "s remain supported.\",\"operationId\":\"resolveEvidenceSegment\",\"requestBody\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSegmentAtRequest\"}}},\"required\":true},\"" - "responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Evidenc" - "eSegmentAt\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenti" - "cation required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write" - " restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media" - " or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure;" - " a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable" - ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"" - "summary\":\"Resolve evidence segment\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"came" - "ra\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/invest" - "igations/thumbnail-samples\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete c" - "ameras, captured identities, ownership and current permissions are rechecked for every request. " - "Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"sample" - "EvidenceThumbnails\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/EvidenceThumbnailRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThumbnails\"}}},\"description\":\"Validated" - " success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by c" - "urrent camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not f" - "ound or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Reque" - "st exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{" - "\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Sample evidence thumbnails\",\"" - "tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"record" - "ings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/thumbnail/{recording_id}/{of" - "fset_ms}\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured id" - "entities, ownership and current permissions are rechecked for every request. Basic, legacy keys " - "and scoped-token owner intersections remain supported.\",\"operationId\":\"getEvidenceThumbnail\",\"pa" - "rameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\"," - "\"name\":\"recording_id\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}},{\"descrip" - "tion\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"offset_ms\",\"" - "required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{" - "\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media byt" - "es; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be d" - "isabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"" - "content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supporte" - "d byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"40" + "ty\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Cr" + "eate evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"" + "version\":1}}},\"/api/investigation-bookmarks/{uuid}\":{\"delete\":{\"description\":\"Owned by current u" + "ser ID, including scoped-token owners; token replay scope must still permit every saved camera. " + "Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invis" + "ible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings reta" + "in existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"deleteEv" + "idenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independe" + "ntly.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F" + "]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkDelete\"}}},\"require" + "d\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/EvidenceBookmarkDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"" + "401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configurati" + "on or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision," + " protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nternal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work tempora" + "rily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bea" + "rerToken\":[]}],\"summary\":\"Delete evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorizatio" + "n\":{\"kind\":\"authenticated\",\"version\":1}},\"get\":{\"description\":\"Owned by current user ID, includi" + "ng scoped-token owners; token replay scope must still permit every saved camera. Foreign ownersh" + "ip is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/" + "counts. Updates only title/note with current revision. Scoped-token siblings retain existing sam" + "e-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"getEvidenceBookmark\",\"p" + "arameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\"" + ",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4" + "}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated su" + "ccess.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by curr" + "ent camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not foun" + "d or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request " + "exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"P" + "rotected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" + "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence bookmark\",\"tags\":[\"" + "Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\"" + ":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit " + "every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403" + ". List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped" + "-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"op" + "erationId\":\"updateEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; " + "authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a" + "-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"req" + "uestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmar" + "kUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" + " request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, featur" + "e configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits." + "\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media" + " work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHead" + "er\":[]},{\"bearerToken\":[]}],\"summary\":\"Update evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr" + "-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/recordings/preview\":" + "{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities," + " ownership and current permissions are rechecked for every request. Basic, legacy keys and scope" + "d-token owner intersections remain supported.\",\"operationId\":\"previewEvidenceRecordings\",\"reques" + "tBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeReque" + "st\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/EvidencePreview\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid reque" + "st.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature conf" + "iguration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Re" + "vision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"50" "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera p" - "olicy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not vis" - "ible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds oper" - "ation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutati" - "on may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"sec" - "urity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":" - "\"Get evidence thumbnail\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requir" - "ements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/ti" - "meline\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured ide" - "ntities, ownership and current permissions are rechecked for every request. Basic, legacy keys a" - "nd scoped-token owner intersections remain supported.\",\"operationId\":\"queryEvidenceTimeline\",\"re" - "questBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeR" - "equest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/EvidenceTimeline\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " - "request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature" - " configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"" - "},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media " - "work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeade" - "r\":[]},{\"bearerToken\":[]}],\"summary\":\"Query evidence timeline\",\"tags\":[\"Evidence\"],\"x-lightnvr-a" - "uthorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}]," - "\"version\":1}}},\"/api/live/layouts\":{\"get\":{\"description\":\"Authorization is checked before mutati" - "on. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-s" - "pecific visibility, ownership and reference checks still apply.\",\"operationId\":\"listLiveLayouts\"" - ",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveL" - "ayoutsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"4" - "01\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"O" - "peration failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bear" - "erToken\":[]}],\"summary\":\"ListLiveLayouts\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"a" - "uthenticated\",\"version\":1}},\"post\":{\"description\":\"Personal layouts are owner-managed. Only admi" - "nistrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live" - ".view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/d" - "eletion require current revision.\",\"operationId\":\"createLiveLayout\",\"requestBody\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutCreate\"}}},\"required\":true},\"" - "responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLay" - "out\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Auth" - "entication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing " - "or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation fa" - "iled.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]" - "}],\"summary\":\"CreateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_" - "identity\",\"version\":1}}},\"/api/live/layouts/{uuid}\":{\"delete\":{\"description\":\"Personal layouts a" - "re owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Ca" - "mera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot" - " exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"deleteLiveLayout\",\"" - "parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/C" - "ameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation." - "\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is" - " not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, " - "reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"" - "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteLiveLayout\",\"tags\":[\"Fle" - "et\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"put\":{\"description\"" - ":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identiti" - "es cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed" - " 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":" - "\"updateLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/" - "components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/LiveLayoutUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayout\"}}},\"description\":\"Successful " - "operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Op" - "eration is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"c" + "ion\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work " + "temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]" + "},{\"bearerToken\":[]}],\"summary\":\"Preview evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-au" + "thorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"" + "version\":1}}},\"/api/investigations/search\":{\"post\":{\"description\":\"Discovery indicates an operat" + "ion; concrete cameras, captured identities, ownership and current permissions are rechecked for " + "every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"opera" + "tionId\":\"searchEvidence\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/EvidenceSearchRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSearch\"}}},\"description\":\"Validated s" + "uccess.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by cur" + "rent camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not fou" + "nd or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request" + " exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"c" "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCook" - "ie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateLiveLayout\",\"t" - "ags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/liv" - "e/plans\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve exis" - "ting Basic authentication and legacy API-key compatibility. Resource-specific visibility, owners" - "hip and reference checks still apply.\",\"operationId\":\"listFloorPlans\",\"responses\":{\"200\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlansResponse\"}}},\"descript" - "ion\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requir" - "ed.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible reso" - "urce.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"securit" - "y\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Lis" - "tFloorPlans\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"" - "post\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped ide" - "ntity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion requir" - "e current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"createFloorPlan" - "\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan" - "Create\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path" - " or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this pr" - "incipal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state confl" - "ict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyH" - "eader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authoriz" - "ation\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"" - "}],\"version\":1}}},\"/api/live/plans/{uuid}\":{\"delete\":{\"description\":\"Shared plan library. Requir" - "es global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera plac" - "ements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; " - "null clears it.\",\"operationId\":\"deleteFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"requi" - "red\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"" - "responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPl" - "anDeletion\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"M" - "issing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Opera" - "tion failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTo" - "ken\":[]}],\"summary\":\"DeleteFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_s" - "coped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get" - "\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic aut" - "hentication and legacy API-key compatibility. Resource-specific visibility, ownership and refere" - "nce checks still apply.\",\"operationId\":\"getFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"" - "required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Succ" - "essful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"4" - "09\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sess" - "ionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorPlan\"" - ",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"descr" - "iption\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo i" - "dentities cannot mutate. Camera placements must be visible. Updates/deletion require current rev" - "ision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"updateFloorPlan\",\"parameters" - "\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"" - "}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPl" - "anUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid pa" - "th or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this " - "principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state con" - "flict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" - "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-author" - "ization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"ra" - "w\"}],\"version\":1}}},\"/api/live/plans/{uuid}/background\":{\"delete\":{\"description\":\"Authorization " - "is checked before mutation. Requests preserve existing Basic authentication and legacy API-key c" - "ompatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operat" - "ionId\":\"deleteFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"sch" - "ema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"}" - ",\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is n" - "ot allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, re" - "ference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" - "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFloorPlanBackground\",\"tags" - "\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"ca" - "mera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorization is checked " - "before mutation. Requests preserve existing Basic authentication and legacy API-key compatibilit" - "y. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"get" - "FloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"" - "#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"form" - "at\":\"binary\",\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"desc" - "ription\":\"Exact uploaded PNG/JPEG bytes, accessible only when the plan is visible.\"},\"400\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" - "valid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed f" - "or this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or s" - "tate conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}" - ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-" - "lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Raw structur" - "ally validated PNG/JPEG, at most 786432 bytes. Atomic publication preserves previous bytes on in" - "valid upload. Returns the new plan revision; no request revision is used.\",\"operationId\":\"setFlo" - "orPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"bin" - "ary\",\"maxLength\":786432,\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format\":\"binary\",\"maxLength\":7" - "86432,\"type\":\"string\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allow" + "Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"b" + "asicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Search evidence\",\"tags\":[\"Evide" + "nce\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/seg" + "ment-at\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured id" + "entities, ownership and current permissions are rechecked for every request. Basic, legacy keys " + "and scoped-token owner intersections remain supported.\",\"operationId\":\"resolveEvidenceSegment\",\"" + "requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSegm" + "entAtRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/EvidenceSegmentAt\"}}},\"description\":\"Validated success.\"},\"400\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nvalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, " + "feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"}" + ",\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation l" + "imits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or" + " media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiK" + "eyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Resolve evidence segment\",\"tags\":[\"Evidence\"],\"x-li" + "ghtnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":" + "\"raw\"}],\"version\":1}}},\"/api/investigations/thumbnail-samples\":{\"post\":{\"description\":\"Discovery" + " indicates an operation; concrete cameras, captured identities, ownership and current permission" + "s are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remai" + "n supported.\",\"operationId\":\"sampleEvidenceThumbnails\",\"requestBody\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThumbnailRequest\"}}},\"required\":true},\"respo" + "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThum" + "bnails\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticati" + "on required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write res" + "triction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or " + "transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a m" + "utation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}}" + ",\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summ" + "ary\":\"Sample evidence thumbnails\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera" + "\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investig" + "ations/thumbnail/{recording_id}/{offset_ms}\":{\"get\":{\"description\":\"Discovery indicates an opera" + "tion; concrete cameras, captured identities, ownership and current permissions are rechecked for" + " every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"oper" + "ationId\":\"getEvidenceThumbnail\",\"parameters\":[{\"description\":\"Concrete resource identifier; auth" + "orized independently.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"pattern\":\"^[" + "0-9]+$\",\"type\":\"string\"}},{\"description\":\"Concrete resource identifier; authorized independently" + ".\",\"in\":\"path\",\"name\":\"offset_ms\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"" + "}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}}," + "\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to " + "the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rec" + "hecks captured identity.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"s" + "tring\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\"" + ":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"}," + "\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"40" + "4\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer confli" + "ct.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"}," + "\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media wo" + "rk temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" + ":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence thumbnail\",\"tags\":[\"Evidence\"],\"x-lightnvr-auth" + "orization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"ve" + "rsion\":1}}},\"/api/investigations/timeline\":{\"post\":{\"description\":\"Discovery indicates an operat" + "ion; concrete cameras, captured identities, ownership and current permissions are rechecked for " + "every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"opera" + "tionId\":\"queryEvidenceTimeline\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/EvidenceRangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceTimeline\"}}},\"description\":\"Val" + "idated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denie" + "d by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource" + " not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"" + "503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\"" + ":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Query evidence timeline" + "\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"rec" + "ordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/layouts\":{\"get\":{\"description\":\"Aut" + "horization is checked before mutation. Requests preserve existing Basic authentication and legac" + "y API-key compatibility. Resource-specific visibility, ownership and reference checks still appl" + "y.\",\"operationId\":\"listLiveLayouts\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/LiveLayoutsResponse\"}}},\"description\":\"Successful operation.\"},\"40" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not a" + "llowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, refere" + "nce or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicA" + "uth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListLiveLayouts\",\"tags\":[\"Fleet\"],\"x" + "-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Personal l" + "ayouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mu" + "tate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slot" + "s cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"createLiveL" + "ayout\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Live" + "LayoutCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/LiveLayout\"}}},\"description\":\"Successful operation.\"},\"400\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" + "id path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for " + "this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or stat" + "e conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"" + "apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-" + "authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/live/layouts/{uuid}\":{\"delete\"" + ":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens an" + "d demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows " + "must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\"" + ",\"operationId\":\"deleteLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"sche" + "ma\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"20" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"" + "description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticatio" + "n required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisi" + "ble resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}}," + "\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summa" + "ry\":\"DeleteLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\"" + ",\"version\":1}},\"put\":{\"description\":\"Personal layouts are owner-managed. Only administrators sha" + "re. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible;" + " columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion requir" + "e current revision.\",\"operationId\":\"updateLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"" + "required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutUpdate\"}}},\"required\":true}," + "\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLa" + "yout\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Aut" + "hentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing" + " or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation f" + "ailed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[" + "]}],\"summary\":\"UpdateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped" + "_identity\",\"version\":1}}},\"/api/live/plans\":{\"get\":{\"description\":\"Authorization is checked befo" + "re mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. R" + "esource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listFlo" + "orPlans\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/FloorPlansResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or reques" + "t.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"}" + ",\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"50" + "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]}" + ",{\"bearerToken\":[]}],\"summary\":\"ListFloorPlans\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"ki" + "nd\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Shared plan library. Requires global ca" + "mera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must " + "be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears " + "it.\",\"operationId\":\"createFloorPlan\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/FloorPlanCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful" + " operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"O" + "peration is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFloorPlan\",\"t" + "ags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":" + "\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/plans/{uuid}\":{\"delete\":{\"descri" + "ption\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo id" + "entities cannot mutate. Camera placements must be visible. Updates/deletion require current revi" + "sion. Omitted sketch preserves it; null clears it.\",\"operationId\":\"deleteFloorPlan\",\"parameters\"" + ":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}" + "}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRev" + "isionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/FloorPlanDeletion\"}}},\"description\":\"Successful operation.\"},\"400\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allow" "ed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" "hemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"applicatio" "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference " - "or state conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Image too large or too short.\"},\"415\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Unsupported image content" - " type.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" - "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr" - "-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resour" - "ce\":\"raw\"}],\"version\":1}}},\"/api/locations\":{\"get\":{\"description\":\"Visibility and counts follow " - "existing live.view scope; location ancestors may be included. Global live.view can expose the fu" - "ll organization catalog. Camera-specific tag access requires live.view on that raw camera identi" - "ty; missing objects may return 404 before authentication.\",\"operationId\":\"listFleetLocations\",\"r" - "esponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLoc" - "ationsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or" - " selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access deni" - "ed.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization " - "failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\"" - ":[]}],\"summary\":\"List visible camera locations\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"ki" - "nd\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutatio" - "n. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-sp" - "ecific visibility, ownership and reference checks still apply.\",\"operationId\":\"createFleetLocati" - "on\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLo" - "cationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"" + "or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\"" + ":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFloorPlan\",\"tags\":[\"Fleet\"],\"x-lig" + "htnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"r" + "esource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorization is checked before mutation. R" + "equests preserve existing Basic authentication and legacy API-key compatibility. Resource-specif" + "ic visibility, ownership and reference checks still apply.\",\"operationId\":\"getFloorPlan\",\"parame" + "ters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraU" + "uid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Oper" + "ation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerT" + "oken\":[]}],\"summary\":\"GetFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authent" + "icated\",\"version\":1}},\"put\":{\"description\":\"Shared plan library. Requires global camera.configur" + "e and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. U" + "pdates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operati" + "onId\":\"updateFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref" + "\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/FloorPlanUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successf" + "ul operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionC" + "ookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFloorPlan\"," + "\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action" + "\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/plans/{uuid}/background\":{\"del" + "ete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic " + "authentication and legacy API-key compatibility. Resource-specific visibility, ownership and ref" + "erence checks still apply.\",\"operationId\":\"deleteFloorPlanBackground\",\"parameters\":[{\"in\":\"path\"" + ",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\"" + ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"de" + "scription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication " + "required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisibl" + "e resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"s" + "ecurity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary" + "\":\"DeleteFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_gl" + "obal\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"desc" + "ription\":\"Authorization is checked before mutation. Requests preserve existing Basic authenticat" + "ion and legacy API-key compatibility. Resource-specific visibility, ownership and reference chec" + "ks still apply.\",\"operationId\":\"getFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\"" + ",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"cont" + "ent\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format" + "\":\"binary\",\"type\":\"string\"}}},\"description\":\"Exact uploaded PNG/JPEG bytes, accessible only when" + " the plan is visible.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"}," + "\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"" + "},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"" + "sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorP" + "lanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}" + ",\"put\":{\"description\":\"Raw structurally validated PNG/JPEG, at most 786432 bytes. Atomic publica" + "tion preserves previous bytes on invalid upload. Returns the new plan revision; no request revis" + "ion is used.\",\"operationId\":\"setFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"r" + "equired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"i" + "mage/jpeg\":{\"schema\":{\"format\":\"binary\",\"maxLength\":786432,\"type\":\"string\"}},\"image/png\":{\"schem" + "a\":{\"format\":\"binary\",\"maxLength\":786432,\"type\":\"string\"}}},\"required\":true},\"responses\":{\"200\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"descriptio" + "n\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required" + ".\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resour" + "ce.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Revision, reference or state conflict.\"},\"413\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Image too large or too short.\"}" + ",\"415\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Unsupported image content type.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionC" + "ookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetFloorPlanBackg" + "round\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{" + "\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/locations\":{\"get\":{\"descript" + "ion\":\"Visibility and counts follow existing live.view scope; location ancestors may be included." + " Global live.view can expose the full organization catalog. Camera-specific tag access requires " + "live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"o" + "perationId\":\"listFleetLocations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/FleetLocationsResponse\"}}},\"description\":\"Successful authorized read." + "\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"4" + "03\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databa" + "se, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}" + ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera locations\",\"tags\":[\"Flee" + "t\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Auth" + "orization is checked before mutation. Requests preserve existing Basic authentication and legacy" + " API-key compatibility. Resource-specific visibility, ownership and reference checks still apply" + ".\",\"operationId\":\"createFleetLocation\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/FleetLocationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"" + "Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"}," + "\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"" + "},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"" + "sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFle" + "etLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"actio" + "n\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/locations/{uuid}\":{\"delete\":{\"desc" + "ription\":\"Authorization is checked before mutation. Requests preserve existing Basic authenticat" + "ion and legacy API-key compatibility. Resource-specific visibility, ownership and reference chec" + "ks still apply.\",\"operationId\":\"deleteFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"r" + "equired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\"" + ":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"" + "},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource" + ".\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[" + "{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteF" + "leetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"act" + "ion\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and co" + "unts follow existing live.view scope; location ancestors may be included. Global live.view can e" + "xpose the full organization catalog. Camera-specific tag access requires live.view on that raw c" + "amera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getFleetLo" + "cation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-" + "F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"respons" + "es\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"" + "}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"4" + "01\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"se" + "curity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\"" + ":\"Read a visible camera location\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authentic" + "ated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests pre" + "serve existing Basic authentication and legacy API-key compatibility. Resource-specific visibili" + "ty, ownership and reference checks still apply.\",\"operationId\":\"updateFleetLocation\",\"parameters" + "\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"" + "}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLo" + "cationUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"" "$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful operation.\"},\"400\":{\"con" "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" "valid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" @@ -4644,244 +4986,258 @@ static const unsigned char lightnvr_openapi_document[] = "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or s" "tate conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" "iError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}" - ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lig" + ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lig" "htnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"r" - "aw\"}],\"version\":1}}},\"/api/locations/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked " - "before mutation. Requests preserve existing Basic authentication and legacy API-key compatibilit" - "y. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"del" - "eteFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed" - " for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or" - " state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[" - "]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetLocation\",\"tags\":[\"Fleet\"],\"x-l" - "ightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":" - "\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and counts follow existing live.view scop" - "e; location ancestors may be included. Global live.view can expose the full organization catalog" - ". Camera-specific tag access requires live.view on that raw camera identity; missing objects may" - " return 404 before authentication.\",\"operationId\":\"getFleetLocation\",\"parameters\":[{\"in\":\"path\"," - "\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}" - "-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful autho" - "rized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated prin" - "cipal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"bas" - "icAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera location\",\"" - "tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"descrip" - "tion\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication" - " and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks " - "still apply.\",\"operationId\":\"updateFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"requ" - "ired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationUpdate\"}}},\"required\":true},\"" - "responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLo" - "cation\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"A" - "uthentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missi" - "ng or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation" - " failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\"" - ":[]}],\"summary\":\"UpdateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"globa" - "l\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/expo" - "rt\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identit" - "ies, ownership and current permissions are rechecked for every request. Basic, legacy keys and s" - "coped-token owner intersections remain supported.\",\"operationId\":\"exportEvidenceLpr\",\"requestBod" - "y\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprExportReque" - "st\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/EvidenceLprReads\"}}},\"description\":\"Validated success.\",\"headers\":{\"Content-Di" - "sposition\":{\"schema\":{\"const\":\"attachment; filename=\\\"lightnvr-lpr-export.json\\\"\",\"type\":\"string" - "\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current" - " camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found o" - "r not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exc" - "eeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Prot" - "ected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basic" - "Auth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export evidence lpr\",\"tags\":[\"Evide" - "nce\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.export\",\"resour" - "ce\":\"raw\"},{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\":\"lpr.read\",\"resource\":\"raw\"}],\"ver" - "sion\":1}}},\"/api/lpr/reads/{read_uuid}\":{\"delete\":{\"description\":\"Discovery indicates an operati" - "on; concrete cameras, captured identities, ownership and current permissions are rechecked for e" - "very request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operat" - "ionId\":\"deleteEvidenceLpr\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorize" - "d independently.\",\"in\":\"path\",\"name\":\"read_uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-" - "F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"respons" - "es\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprDel" - "eted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication" - " required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restr" - "iction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or tr" - "ansfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mut" - "ation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"" - "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summar" - "y\":\"Delete evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requir" - "ements\":[{\"action\":\"lpr.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/search\":{\"post\":{\"de" - "scription\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership a" - "nd current permissions are rechecked for every request. Basic, legacy keys and scoped-token owne" - "r intersections remain supported.\",\"operationId\":\"searchEvidenceLpr\",\"requestBody\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprSearchRequest\"}}},\"required" - "\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/EvidenceLprReads\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or de" - "mo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protect" - "ed media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal " - "failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily una" - "vailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken" - "\":[]}],\"summary\":\"Search evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"c" - "amera\",\"requirements\":[{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\":\"lpr.read\",\"resource\":" - "\"raw\"}],\"version\":1}}},\"/api/metrics\":{\"get\":{\"operationId\":\"getMetrics\",\"responses\":{\"200\":{\"co" - "ntent\":{\"text/plain\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"Successful response. Process-c" - "ontrol responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"s" - "essionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get metric" - "s\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"syst" - "em.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/motion/trigger\":{\"post\":{\"description\":\"Requir" - "es camera.configure on the named stream. Start/stop/pulse report annotation, tag and recording e" - "ffects independently; tags require an active recording and detection-recording schedules may sup" - "press recording. Pulse is bounded to 600000 ms. Invalid requests may return 400 before principal" - " evaluation.\",\"operationId\":\"triggerMotion\",\"requestBody\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/MotionTrigger\"}}},\"required\":true},\"responses\":{\"202\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MotionTriggered\"}}},\"description\":" - "\"Accepted motion effects are enumerated separately.\"},\"400\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"" - "401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" - "ption\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Obje" - "ct not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKey" - "Header\":[]},{\"bearerToken\":[]}],\"summary\":\"Trigger motion\",\"tags\":[\"Events\"],\"x-lightnvr-authori" - "zation\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"versio" - "n\":1}}},\"/api/onvif/device/add\":{\"post\":{\"operationId\":\"addOnvifDevice\",\"requestBody\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAddRequest\"}}},\"required\":true" - "},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Onvi" - "fAddResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly opti" - "onal.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}," - "\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\"" + "aw\"}],\"version\":1}}},\"/api/lpr/export\":{\"post\":{\"description\":\"Discovery indicates an operation;" + " concrete cameras, captured identities, ownership and current permissions are rechecked for ever" + "y request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operation" + "Id\":\"exportEvidenceLpr\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/EvidenceLprExportRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprReads\"}}},\"description\":\"Validat" + "ed success.\",\"headers\":{\"Content-Disposition\":{\"schema\":{\"const\":\"attachment; filename=\\\"lightnv" + "r-lpr-export.json\\\"\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication req" + "uired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Denied by current camera policy, feature configuration or demo write restricti" + "on.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transf" + "er conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutatio" + "n may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"secu" + "rity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"" + "Export evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requiremen" + "ts\":[{\"action\":\"lpr.export\",\"resource\":\"raw\"},{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\"" + ":\"lpr.read\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/reads/{read_uuid}\":{\"delete\":{\"descripti" + "on\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and curr" + "ent permissions are rechecked for every request. Basic, legacy keys and scoped-token owner inter" + "sections remain supported.\",\"operationId\":\"deleteEvidenceLpr\",\"parameters\":[{\"description\":\"Conc" + "rete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"read_uuid\",\"required\":t" + "rue,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F" + "]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/EvidenceLprDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\"" ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" - " input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, " - "denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied a" - "ccess, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, u" - "navailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[" - "]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"addOnvifDevice\",\"tags\":[\"Device controls\"]" - ",\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resou" - "rce\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/claim\":{\"post\":{\"operationId\":\"claimOnvifDevice\"," - "\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifClaimR" - "equest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/DeviceSuccess\"}}},\"description\":\"Device result; optional device capabiliti" - "es remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fa" - "iled device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed dev" - "ice operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device oper" - "ation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}" - ",\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{" + " request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, featur" + "e configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits." + "\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media" + " work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHead" + "er\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-auth" + "orization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.delete\",\"resource\":\"raw\"}],\"version\":" + "1}}},\"/api/lpr/search\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete camera" + "s, captured identities, ownership and current permissions are rechecked for every request. Basic" + ", legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"searchEvide" + "nceLpr\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Evi" + "denceLprSearchRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/EvidenceLprReads\"}}},\"description\":\"Validated success.\"},\"4" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera " + "policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not vi" + "sible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds ope" + "ration limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected st" + "orage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" + "},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Search evidence lpr\",\"tags\":[\"Evidence\"],\"x" + "-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.search\",\"resource\":\"raw" + "\"},{\"action\":\"lpr.read\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/metrics\":{\"get\":{\"operationId\":\"" + "getMetrics\",\"responses\":{\"200\":{\"content\":{\"text/plain\":{\"schema\":{\"type\":\"string\"}}},\"descripti" + "on\":\"Successful response. Process-control responses acknowledge a request; verify eventual compl" + "etion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporar" + "ily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bear" + "erToken\":[]}],\"summary\":\"Get metrics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"glob" + "al\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/motion/trig" + "ger\":{\"post\":{\"description\":\"Requires camera.configure on the named stream. Start/stop/pulse rep" + "ort annotation, tag and recording effects independently; tags require an active recording and de" + "tection-recording schedules may suppress recording. Pulse is bounded to 600000 ms. Invalid reque" + "sts may return 400 before principal evaluation.\",\"operationId\":\"triggerMotion\",\"requestBody\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MotionTrigger\"}}},\"required\"" + ":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/MotionTriggered\"}}},\"description\":\"Accepted motion effects are enumerated separately.\"},\"400\":{" "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionC" - "ookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"claimOnvifDevice\"" - ",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action" - "\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/profiles\":{\"get\":{\"ope" - "rationId\":\"getOnvifProfiles\",\"parameters\":[{\"in\":\"header\",\"name\":\"X-Device-URL\",\"required\":true," - "\"schema\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Username\",\"re" - "quired\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-" - "Password\",\"required\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}}],\"responses\"" - ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifProfilesResp" - "onse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"" - "},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" - "id input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input" - ", denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied" - " access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access," - " unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavail" - "able device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"a" - "piKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifProfiles\",\"tags\":[\"Device controls\"],\"x-" - "lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\"" - ":\"raw\"}],\"version\":1}}},\"/api/onvif/device/test\":{\"post\":{\"operationId\":\"testOnvifDevice\",\"reque" - "stBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifTestRequest\"" - "}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilitie" - "s remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fai" - "led device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed devi" - "ce operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device opera" - "tion.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}," - "\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" - " input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCo" - "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"testOnvifDevice\",\"" - "tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":" - "\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/devices\":{\"get\":{\"operationId\":" - "\"getOnvifDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/OnvifDevicesResponse\"}}},\"description\":\"Device result; optional device capabilities " + ":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effectiv" + "e authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, li" + "mit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCo" + "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Trigger motion\",\"t" + "ags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.co" + "nfigure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/observations\":{\"get\":{\"description\":\"Cursor-pag" + "ed observation changes, ordered by persistence sequence and bounded by a watermark taken at quer" + "y start (contract \302\2477). Results are limited to capture cameras where the principal holds live.vi" + "ew; an explicitly requested camera without it returns 403. limit counts observations after in-pa" + "ge dedup. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"listObservations" + "\",\"parameters\":[{\"description\":\"Comma-separated capture camera UUIDs, at most 10 (contract D1). " + "Default: all cameras where the principal holds live.view.\",\"in\":\"query\",\"name\":\"camera_uuid\",\"re" + "quired\":false,\"schema\":{\"maxLength\":369,\"pattern\":\"^[0-9a-fA-F-]{36}(,[0-9a-fA-F-]{36}){0,9}$\",\"" + "type\":\"string\"}},{\"description\":\"Comma-separated original detector labels, at most 8 of 1..31 by" + "tes; exact ASCII case-insensitive match (contract D1).\",\"in\":\"query\",\"name\":\"label\",\"required\":f" + "alse,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},{\"description\":\"Inclusive lower b" + "ound on observed_at.\",\"in\":\"query\",\"name\":\"since\",\"required\":false,\"schema\":{\"$ref\":\"#/component" + "s/schemas/ObservationTime\"}},{\"description\":\"Exclusive upper bound on observed_at; must be after" + " since.\",\"in\":\"query\",\"name\":\"until\",\"required\":false,\"schema\":{\"$ref\":\"#/components/schemas/Obs" + "ervationTime\"}},{\"description\":\"Opaque cursor from next_cursor or oldest_cursor, or the literal " + "head (start live-only). Absent: start of retained history.\",\"in\":\"query\",\"name\":\"cursor\",\"requir" + "ed\":false,\"schema\":{\"maxLength\":256,\"minLength\":1,\"type\":\"string\"}},{\"description\":\"Observations" + " per page.\",\"in\":\"query\",\"name\":\"limit\",\"required\":false,\"schema\":{\"default\":100,\"maximum\":500,\"" + "minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ObservationPage\"}}},\"description\":\"History page. Items hold current sta" + "te; dedupe by (source, id, revision).\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ObservationError\"}}},\"description\":\"Invalid filter (invalid_request) or mal" + "formed cursor (invalid_cursor).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth lay" + "er body).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Observa" + "tionError\"}}},\"description\":\"A requested camera_uuid is not authorized for live.view (code forbi" + "dden).\"},\"410\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Observatio" + "nError\"}}},\"description\":\"Cursor older than retained history, from another DB epoch, or ahead of" + " the head (code cursor_expired); resume from oldest_cursor.\"},\"500\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure " + "(code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"" + "headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"" + "integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToke" + "n\":[]}],\"summary\":\"List observation history\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":" + "{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/o" + "bservations/{id}\":{\"get\":{\"description\":\"One observation with its classifications, authorized by" + " live.view on its immutable capture camera (contract \302\2478.1). Contract: docs/integrations/OBSERVA" + "TIONS_CONTRACT.md.\",\"operationId\":\"getObservation\",\"parameters\":[{\"description\":\"Observation ID." + "\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/ObservationUuid" + "\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/O" + "bservation\"}}},\"description\":\"Current observation state.\"},\"400\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Invalid observation " + "ID (code invalid_request).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer bo" + "dy).\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationE" + "rror\"}}},\"description\":\"Unknown observation, no live.view on its capture camera, or the capture " + "camera was deleted; indistinguishable (code observation_not_found).\"},\"500\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal " + "failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavaila" + "ble).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1," + "\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" + "arerToken\":[]}],\"summary\":\"Get observation\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{" + "\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/ob" + "servations/{id}/classifications/{namespace}/{name}\":{\"put\":{\"description\":\"Create or update one " + "classification annotation on an observation (contract \302\24710). Authorized with observations.annota" + "te on the observation's capture-time camera (contract \302\24712.1); authorization is evaluated before" + " the namespace and the body, so an observation the caller cannot see is an indistinguishable 404" + ". The key is two path segments, {namespace}/{name}; namespace must equal the server-derived writ" + "er namespace of the authenticated user (D3, advertised as capabilities principal.writer_namespac" + "e). Identical retries are no-ops (200, changed=false). A stored change bumps the observation rev" + "ision and publishes an io.lightnvr.detection.observation_changed.v1 event with change_cause clas" + "sification. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"putObservation" + "Classification\",\"parameters\":[{\"description\":\"Observation ID.\",\"in\":\"path\",\"name\":\"id\",\"required" + "\":true,\"schema\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}},{\"description\":\"Writer namespac" + "e.\",\"in\":\"path\",\"name\":\"namespace\",\"required\":true,\"schema\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,3" + "1}$\",\"type\":\"string\"}},{\"description\":\"Annotation name.\",\"in\":\"path\",\"name\":\"name\",\"required\":tr" + "ue,\"schema\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationClassificationWrite\"}}}," + "\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ObservationClassificationResult\"}}},\"description\":\"Annotation updated, or identical r" + "etry (changed=false).\"},\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ObservationClassificationResult\"}}},\"description\":\"Annotation created.\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"I" + "nvalid or unknown body field (code invalid_request).\"},\"401\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credenti" + "al (existing auth layer body).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ObservationError\"}}},\"description\":\"live.view without observations.annotate on the" + " capture camera (forbidden), or a key namespace or body writer field that is not the caller's de" + "rived namespace, or a key created by another user (namespace_forbidden).\"},\"404\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Unkn" + "own observation, neither live.view nor observations.annotate on its capture camera, or the captu" + "re camera was deleted; indistinguishable (code observation_not_found).\"},\"409\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"revisi" + "on_conflict or owner_decision_locked (current carries the stored annotation when the key exists)" + ", or annotation_limit (limit).\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ObservationError\"}}},\"description\":\"input_asset_id, input_revision or the evidence" + " state does not match the observation evidence (code evidence_mismatch, with observation_asset_i" + "d and evidence_state).\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ObservationError\"}}},\"description\":\"Internal failure (code internal_error).\"},\"503\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"descripti" + "on\":\"Temporarily unavailable (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description" + "\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCo" + "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Put observation cl" + "assification\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\"" + ":[{\"action\":\"observations.annotate\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/observations/{id}/sn" + "apshot\":{\"get\":{\"description\":\"The unannotated evidence JPEG that was the inference input or sub" + "mitted image (contract \302\2478.2). Requires snapshot.create on the capture camera; a caller with liv" + "e.view but not snapshot.create gets 403 (contract D4). Never substitutes a latest frame or anoth" + "er asset. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"getObservationSn" + "apshot\",\"parameters\":[{\"description\":\"Observation ID.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"" + "schema\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}},{\"in\":\"header\",\"name\":\"If-None-Match\",\"" + "required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schem" + "a\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Evidence image bytes; not JSON.\",\"header" + "s\":{\"Cache-Control\":{\"description\":\"private, max-age=300\",\"schema\":{\"type\":\"string\"}},\"ETag\":{\"d" + "escription\":\"Quoted asset ID.\",\"schema\":{\"type\":\"string\"}}}},\"202\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Evidence is still " + "pending (code evidence_pending). Honour Retry-After and give up polling after 120 s total.\",\"hea" + "ders\":{\"Retry-After\":{\"description\":\"Integer seconds, 1..30.\",\"schema\":{\"maximum\":30,\"minimum\":1" + ",\"type\":\"integer\"}}}},\"304\":{\"description\":\"If-None-Match matched the ETag.\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"I" + "nvalid observation ID (code invalid_request).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (exi" + "sting auth layer body).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ObservationError\"}}},\"description\":\"live.view is held but snapshot.create is not (code fo" + "rbidden).\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Observa" + "tionError\"}}},\"description\":\"Evidence unavailable with a reason (evidence_unavailable), or the o" + "bservation is unknown or not visible (observation_not_found).\"},\"410\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Evidence media " + "expired (code evidence_expired); metadata remains readable.\"},\"500\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure " + "(code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"" + "headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"" + "integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToke" + "n\":[]}],\"summary\":\"Get observation evidence image\",\"tags\":[\"Integrations\"],\"x-lightnvr-authoriza" + "tion\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"snapshot.create\",\"resource\":\"raw\"}],\"version\":" + "1}}},\"/api/onvif/device/add\":{\"post\":{\"operationId\":\"addOnvifDevice\",\"requestBody\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAddRequest\"}}},\"required\":true},\"" + "responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAd" + "dResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optiona" + "l.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"40" + "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" + "valid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid in" + "put, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, den" + "ied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acce" + "ss, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unav" + "ailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}," + "{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"addOnvifDevice\",\"tags\":[\"Device controls\"],\"x" + "-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource" + "\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/claim\":{\"post\":{\"operationId\":\"claimOnvifDevice\",\"re" + "questBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifClaimRequ" + "est\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/DeviceSuccess\"}}},\"description\":\"Device result; optional device capabilities " "remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" "ts/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or faile" "d device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" @@ -4896,1039 +5252,805 @@ static const unsigned char lightnvr_openapi_document[] = "nvalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{" "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid i" "nput, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCook" - "ie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDevices\",\"ta" - "gs\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"c" - "amera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/discovery/discover\":{\"post\":{\"ope" - "rationId\":\"discoverOnvifDevices\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/OnvifDiscoveryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDevicesResponse\"}}},\"description" - "\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid in" - "put, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, den" - "ied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acce" - "ss, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unav" - "ailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable " - "device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device o" - "r failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "ie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"claimOnvifDevice\",\"t" + "ags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"" + "camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/profiles\":{\"get\":{\"operat" + "ionId\":\"getOnvifProfiles\",\"parameters\":[{\"in\":\"header\",\"name\":\"X-Device-URL\",\"required\":true,\"sc" + "hema\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Username\",\"requi" + "red\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Pas" + "sword\",\"required\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}}],\"responses\":{\"" + "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifProfilesRespons" + "e\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"" + "400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " + "input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, d" + "enied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied ac" + "cess, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, un" + "available device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailabl" + "e device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiK" + "eyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifProfiles\",\"tags\":[\"Device controls\"],\"x-lig" + "htnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"r" + "aw\"}],\"version\":1}}},\"/api/onvif/device/test\":{\"post\":{\"operationId\":\"testOnvifDevice\",\"requestB" + "ody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifTestRequest\"}}}" + ",\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities r" + "emain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" "s/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed" - " device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" - "arerToken\":[]}],\"summary\":\"discoverOnvifDevices\",\"tags\":[\"Device controls\"],\"x-lightnvr-authoriz" - "ation\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version" - "\":1}}},\"/api/onvif/discovery/status\":{\"get\":{\"operationId\":\"getOnvifDiscoveryStatus\",\"responses\"" - ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDiscoverySta" - "tus\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"}" - ",\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input," - " denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied " - "access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, " - "unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavaila" - "ble device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"ap" - "iKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDiscoveryStatus\",\"tags\":[\"Device controls" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"res" - "ource\":\"raw\"}],\"version\":1}}},\"/api/openapi.json\":{\"get\":{\"description\":\"Uses actual session, Ba" - "sic, legacy API-key or scoped-token identity with existing credential precedence and restriction" - "s. Synthetic auth_disabled/anonymous demo principals are rejected with403. Real authenticated de" - "mo users retain their actual action permissions. No requested-user/role override. Every response" - " is private and non-cacheable; downloaded documentation never grants authority.\",\"operationId\":\"" - "getOpenApi\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/OpenApiDocument\"}}},\"description\":\"Permission-aware partial document. Never a fallback to " - "the canonical unfiltered source.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success " - "and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"co" - "nst\":\"private, no-store\",\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, includ" - "ing expired/revoked tokens, inactive users, CIDR or password-change restrictions.\",\"headers\":{\"C" - "ache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or p" - "ersistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"4" - "03\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Synthetic auth_disabled or anonymous demo identity is excluded from personalized discover" - "y.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared" - " cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type" - "\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update schedulin" - "g failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. N" - "o shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-stor" - "e\",\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"" - "bearerToken\":[]}],\"summary\":\"Discover the current principal\342\200\231s API contract\",\"tags\":[\"Discovery" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings\":{\"get\":{\"" - "description\":\"Replay-authorized rows and counts; empty scope without an explicit stream returns " - "an empty page. Every explicitly named stream must be authorized or the request returns generic40" - "3. Completed rows and sufficiently recent in-progress rows are included; stale incomplete rows a" - "ge out. Filters and collection membership intersect authorization. Current handlers preserve aut" - "h-disabled/demo deployment behavior.\",\"operationId\":\"listRecordings\",\"parameters\":[{\"description" - "\":\"Comma-separated current stream names; every requested name must be replay authorized.\",\"in\":\"" - "query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Optional col" - "lection UUID, resolved on the server and intersected with replay scope.\",\"in\":\"query\",\"name\":\"co" - "llection_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 start bo" - "und; invalid strings currently behave as no bound.\",\"in\":\"query\",\"name\":\"start\",\"required\":false" - ",\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 end bound; invalid strings currently be" - "have as no bound.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"desc" - "ription\":\"Default1; nonpositive/invalid numeric values normalize to1.\",\"in\":\"query\",\"name\":\"page" - "\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Default20, numeric maximum1000; " - "nonpositive/invalid values normalize to20. Case-insensitive all returns all matching authorized " - "rows, sets page1 and recounts.\",\"in\":\"query\",\"name\":\"limit\",\"required\":false,\"schema\":{\"anyOf\":[" - "{\"type\":\"integer\"},{\"const\":\"all\"}]}},{\"description\":\"Unsupported fields fall back to start_time" - ".\",\"in\":\"query\",\"name\":\"sort\",\"required\":false,\"schema\":{\"enum\":[\"id\",\"stream_name\",\"start_time\"" - ",\"end_time\",\"size_bytes\"]}},{\"description\":\"asc or desc; unsupported values fall back to desc.\"," - "\"in\":\"query\",\"name\":\"order\",\"required\":false,\"schema\":{\"enum\":[\"asc\",\"desc\"]}},{\"description\":\"0" - " all,1 with detection,-1 without; numeric values clamp to this interval.\",\"in\":\"query\",\"name\":\"h" - "as_detection\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Comma-separated labe" - "ls; implies detection-only filter.\",\"in\":\"query\",\"name\":\"detection_label\",\"required\":false,\"sche" - "ma\":{\"type\":\"string\"}},{\"description\":\"Leading0 means unprotected, leading1 protected; otherwise" - " all.\",\"in\":\"query\",\"name\":\"protected\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"descriptio" - "n\":\"Comma-separated recording tags.\",\"in\":\"query\",\"name\":\"tag\",\"required\":false,\"schema\":{\"type\"" - ":\"string\"}},{\"description\":\"Comma-separated capture methods, including continuous,scheduled,dete" - "ction,motion,manual.\",\"in\":\"query\",\"name\":\"capture_method\",\"required\":false,\"schema\":{\"type\":\"st" - "ring\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/RecordingsPage\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"40" - "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Re" - "cording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, polic" - "y, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, unavailable source or busy m" - "edia preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bea" - "rerToken\":[]}],\"summary\":\"List authorized recordings\",\"tags\":[\"Recording reads\"],\"x-lightnvr-aut" - "horization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-delete\":{\"post\":{\"desc" - "ription\":\"Fixed IDs preflight every existing member before dispatch; forbidden members reject th" - "e whole batch. Filter jobs capture authorized UUID scope before dispatch. Operational missing/pr" - "otected failures are counted separately and can coexist with successes. Poll the owned job; canc" - "ellation is unsupported.\",\"operationId\":\"deleteRecordings\",\"requestBody\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionRequest\"}}},\"required\":true},\"r" - "esponses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Recordin" - "gDeletionJobAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request" - ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; author" - "ization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture cam" - "era, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creatio" - "n or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" - "arerToken\":[]}],\"summary\":\"Create a recording deletion job\",\"tags\":[\"Recording actions\"],\"x-ligh" - "tnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"ra" - "w\"}],\"version\":1}}},\"/api/recordings/batch-delete/progress/{job_id}\":{\"get\":{\"description\":\"Requ" - "ires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An a" - "dministrator, sibling token or owner using another credential cannot borrow a scoped-token-owned" - " job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera" - " grants are not rechecked for job lookup. Terminal complete includes partial operational failure" - "s; inspect all counters. There is no cancel operation.\",\"operationId\":\"getRecordingDeletionProgr" - "ess\",\"parameters\":[{\"description\":\"Opaque deletion job identifier; possession alone grants no ac" - "cess.\",\"in\":\"path\",\"name\":\"job_id\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"20" - "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionProgr" - "ess\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No u" - "sable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not fou" - "nd.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"s" - "essionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned" - " recording deletion progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"a" - "uthenticated\",\"version\":1}}},\"/api/recordings/batch-download\":{\"post\":{\"description\":\"Preflights" - " every requested recording before creating the job. Missing404/in-progress409 may abort in input" - " order before a later forbidden member; these early errors create no job or archive. ZIP32 uses " - "original media, including recovery-download files with saved playback issues. Detected unavailab" - "le-source/member-count and ZIP32-limit failures reject the result. The current writer does not c" - "onsistently detect filesystem read/write/close failures; complete alone does not prove archive i" - "ntegrity. Cancellation is unsupported.\",\"operationId\":\"createRecordingExport\",\"requestBody\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportRequest\"}}},\"r" - "equired\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/RecordingExportAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed pat" - "h or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbi" - "dden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current" - " capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, una" - "vailable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or f" - "ile failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Busy job slots, unavailable source, or shutdown.\"}},\"security\":[{\"sessio" - "nCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create an autho" - "rized ZIP export job\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"" - "requirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/" - "batch-download/result/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID " - "and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or " - "owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently ow" - "ned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for j" - "ob lookup. Export ownership also matches username. Pending/running jobs return409 with error Job" - " is not_ready; failed jobs return409 with error Job is error. Only complete archives are downloa" - "dable. Results can be read repeatedly while retained; result access refreshes job retention. Thi" - "s is not a one-time token. No cancellation endpoint exists.\",\"operationId\":\"downloadRecordingExp" - "ort\",\"parameters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":" - "\"path\",\"name\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single " - "byte range using the common file-serving parser.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":" - "\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\"" - ":\"string\"}}},\"description\":\"Complete ZIP archive.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\"" - ":\"bytes\"}},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\"" - ":\"integer\"}}}},\"206\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}" - "}},\"description\":\"Single ZIP byte range.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}" - "},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer" - "\"}},\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job n" - "ot found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"416\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker crea" - "tion or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{" - "\"bearerToken\":[]}],\"summary\":\"Download an owned completed ZIP export\",\"tags\":[\"Recording actions" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-down" - "load/status/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact " - "scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner usin" - "g another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs r" - "eturn404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup." - " Export ownership also matches the stored username. Jobs can be reclaimed after their retention " - "interval; polling does not make a job permanent.\",\"operationId\":\"getRecordingExportStatus\",\"para" - "meters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"path\",\"na" - "me\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportStatus\"}}},\"description" - "\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"}," - "\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Recording, current capture camera, collection, or owned job not found.\"}},\"security\":[{" - "\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read own" - "ed recording export progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"a" - "uthenticated\",\"version\":1}}},\"/api/recordings/batch-protect\":{\"post\":{\"description\":\"An otherwis" - "e-valid forbidden existing member yields403 before any protection writes. Invalid/missing IDs an" - "d subsequent database write failures are counted, so operational failures may produce partial su" - "ccess.\",\"operationId\":\"protectRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/RecordingBatchProtectionRequest\"}}},\"required\":true},\"responses\":" - "{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchProt" - "ectionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization " - "denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, col" - "lection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or fil" - "e failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToke" - "n\":[]}],\"summary\":\"Set protection on recording IDs\",\"tags\":[\"Recording actions\"],\"x-lightnvr-aut" - "horization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"ve" - "rsion\":1}}},\"/api/recordings/batch-tags\":{\"post\":{\"description\":\"Preflights all IDs and rejects " - "missing/forbidden members before writes. Invalid IDs return400. Operational database failures do" - " not promise an all-or-nothing transaction.\",\"operationId\":\"updateRecordingTagsBatch\",\"requestBo" - "dy\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchTagsReq" - "uest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/RecordingBatchTagsResult\"}}},\"description\":\"Successful response\"},\"400\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"M" - "alformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch " - "member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Record" - "ing, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, al" - "location, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}," - "{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Add or remove tags on recording IDs\",\"tags\":[" - "\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evid" - "ence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/detection-labels\":{\"get\":{\"desc" - "ription\":\"Unique detection labels within replay-authorized camera scope. An empty scope returns " - "labels:[].\",\"operationId\":\"listRecordingDetectionLabels\",\"responses\":{\"200\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDetectionLabels\"}}},\"description\":\"S" - "uccessful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparati" - "on or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" - "\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized detection label facets\",\"tags\":[\"Recording " - "reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/downl" - "oad/{recording_id}\":{\"get\":{\"description\":\"Authorizes the stored capture camera UUID using curre" - "nt raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the " - "current stream name. Requires recordings.export before source probes. In-progress media returns4" - "09. Saved playback failures do not block original-byte recovery download. prepare=1 returns read" - "y/preparing JSON; ordinary success is an attachment. No compatibility transcoding is requested b" - "y this route. Byte ranges use the same permissive local parser as playback: prefix/suffix/open-e" - "nded accepted and clamped, malformed missing-dash/multiple ranges may parse as a single range. L" - "ocal416 omits Content-Range.\",\"operationId\":\"downloadRecording\",\"parameters\":[{\"description\":\"Ex" - "actly1 selects JSON readiness without media bytes.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":fal" - "se,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"UI hint for externally stored recordings. Actual" - " source selection follows stored metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archi" - "ve\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=s" - "tart-, or bytes=-suffix range. Actual permissive parser limitations are documented above.\",\"in\":" - "\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{" - "\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}," - "\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"" - "format\":\"binary\",\"type\":\"string\"}}},\"description\":\"prepare=1 JSON ready, or complete media body." - "\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"" - "Attachment with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"strin" - "g\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"description\":\"Requested media" - " is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"schema\":{\"type\":\"string\"" - "}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{" - "\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"t" - "ype\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":" - "\"A single satisfiable media byte range. Validate bytes/protocol separately from JSON.\",\"headers\"" - ":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Attachment " - "with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}},\"Conte" - "nt-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end" - "/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or que" - "ry.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"4" - "04\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Record" - "ing still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is n" - "ot satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\"" - ":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$" - "ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable" - " source or busy media preparation.\",\"headers\":{\"Retry-After\":{\"description\":\"Present when prepar" - "ation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":" - "[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Download or prepare orig" - "inal recording media\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"" - "requirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"de" - "scription\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing" - "; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is auth" - "oritative; a deleted capture camera yields404, without rebinding to a reused display name. Legac" - "y rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":t" - "rue,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/files\":{\"delete\":{\"description\":" - "\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current " - "permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersect" - "ions remain supported.\",\"operationId\":\"deleteEvidenceRecordingFile\",\"parameters\":[{\"description\"" - ":\"Exact registered recording file path. Unknown paths return404; this is not an arbitrary filesy" - "stem operation. Captured recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"require" - "d\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileDeleted\"}}},\"description\":\"Validated su" - "ccess.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFi" - "leDeleted\"}}},\"description\":\"Deletion scheduled; not proof of file removal.\"},\"400\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid r" - "equest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature " - "configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"}" - ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media w" - "ork temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" - "\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence recording file\",\"tags\":[\"Evidence\"],\"x-ligh" - "tnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"ra" - "w\"}],\"version\":1}}},\"/api/recordings/files/check\":{\"get\":{\"description\":\"Discovery indicates an " - "operation; concrete cameras, captured identities, ownership and current permissions are rechecke" - "d for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\"," - "\"operationId\":\"checkEvidenceRecordingFile\",\"parameters\":[{\"description\":\"Exact registered record" - "ing file path. Unknown paths return404; this is not an arbitrary filesystem operation. Captured " - "recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":true,\"schema\":{\"minLen" - "gth\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/EvidenceFileCheck\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid r" - "equest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature " - "configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"}" - ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media w" - "ork temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" - "\":[]},{\"bearerToken\":[]}],\"summary\":\"Check evidence recording file\",\"tags\":[\"Evidence\"],\"x-light" - "nvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"ra" - "w\"}],\"version\":1}}},\"/api/recordings/play/{recording_id}\":{\"get\":{\"description\":\"Requires record" - "ings.replay for capture identity before inspecting source availability. In-progress409; saved in" - "valid_media/no_video/decode_error422 with recovery explanation. prepare=1 returns JSON ready/pre" - "paring; ordinary success serves media bytes. Archive/HEVC preparation can return202 (prepare) or" - "503 (ordinary) with Retry-After2. Authorization uses the logical recording, never a caller-suppl" - "ied filesystem path. Local byte ranges accept prefix, suffix and open-ended forms; excessive end" - "/suffix clamp to EOF. Invalid unit, start\342\211\245size, reversed bounds and zero suffix yield416 JSON." - " Local416 currently omits Content-Range; archive416 supplies bytes */size. Existing parser is pe" - "rmissive: bytes=0 (missing dash) becomes full206 and multi-ranges are treated as their first num" - "eric range, not multipart. Do not infer strict RFC rejection of malformed ranges.\",\"operationId\"" - ":\"playRecording\",\"parameters\":[{\"description\":\"Exactly1 selects JSON readiness without media byt" - "es.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":" - "\"Exactly1 requests compatibility cache;0 requests original. Absent defaults to prepare=1 compati" - "bility behavior.\",\"in\":\"query\",\"name\":\"transcode\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}}," - "{\"description\":\"UI hint for externally stored recordings. Actual source selection follows stored" - " metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\",\"required\":false,\"schema\":{\"" - "type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=start-, or bytes=-suffix range. A" - "ctual permissive parser limitations are documented above.\",\"in\":\"header\",\"name\":\"Range\",\"schema\"" - ":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string" - "\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"f" - "ormat\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"" - "}}},\"description\":\"prepare=1 JSON ready, or complete media body.\",\"headers\":{\"Accept-Ranges\":{\"d" - "escription\":\"The local playback handler and file server both emit this header; clients can obser" - "ve combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"by" - "tes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"des" - "cription\":\"Requested media is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":" - "{\"schema\":{\"type\":\"string\"}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\"" - ":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"sch" - "ema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":" - "\"string\"}}},\"description\":\"A single satisfiable media byte range. Validate bytes/protocol separa" - "tely from JSON.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file " - "server both emit this header; clients can observe combined bytes, bytes. Archive responses emit " - "one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{" - "\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Loca" - "l416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"s" + " device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device " + "operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operatio" + "n.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"40" + "9\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" + "valid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid in" + "put, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCooki" + "e\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"testOnvifDevice\",\"tag" + "s\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"ca" + "mera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/devices\":{\"get\":{\"operationId\":\"ge" + "tOnvifDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/OnvifDevicesResponse\"}}},\"description\":\"Device result; optional device capabilities rem" + "ain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed d" + "evice operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device op" + "eration.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation." + "\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inva" + "lid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid inpu" + "t, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\"" + ":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDevices\",\"tags\"" + ":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"came" + "ra.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/discovery/discover\":{\"post\":{\"operat" + "ionId\":\"discoverOnvifDevices\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/OnvifDiscoveryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDevicesResponse\"}}},\"description\":\"" + "Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input" + ", denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied" + " access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access," + " unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavail" + "able device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable dev" + "ice or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or f" + "ailed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed de" + "vice operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"beare" + "rToken\":[]}],\"summary\":\"discoverOnvifDevices\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorizati" + "on\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1" + "}}},\"/api/onvif/discovery/status\":{\"get\":{\"operationId\":\"getOnvifDiscoveryStatus\",\"responses\":{\"" + "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDiscoveryStatus" + "\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"4" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nvalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid i" + "nput, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, de" + "nied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acc" + "ess, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, una" + "vailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable" + " device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" + "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDiscoveryStatus\",\"tags\":[\"Device controls\"]," + "\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resour" + "ce\":\"raw\"}],\"version\":1}}},\"/api/openapi.json\":{\"get\":{\"description\":\"Uses actual session, Basic" + ", legacy API-key or scoped-token identity with existing credential precedence and restrictions. " + "Synthetic auth_disabled/anonymous demo principals are rejected with403. Real authenticated demo " + "users retain their actual action permissions. No requested-user/role override. Every response is" + " private and non-cacheable; downloaded documentation never grants authority.\",\"operationId\":\"get" + "OpenApi\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/OpenApiDocument\"}}},\"description\":\"Permission-aware partial document. Never a fallback to the" + " canonical unfiltered source.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and" + " error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const" + "\":\"private, no-store\",\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including" + " expired/revoked tokens, inactive users, CIDR or password-change restrictions.\",\"headers\":{\"Cach" + "e-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or pers" + "istent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"403\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Synthetic auth_disabled or anonymous demo identity is excluded from personalized discovery.\"" + ",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared ca" + "che, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"" + "string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling f" + "ailure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No s" + "hared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\"," + "\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bea" + "rerToken\":[]}],\"summary\":\"Discover the current principal\342\200\231s API contract\",\"tags\":[\"Discovery\"]," + "\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings\":{\"get\":{\"des" + "cription\":\"Replay-authorized rows and counts; empty scope without an explicit stream returns an " + "empty page. Every explicitly named stream must be authorized or the request returns generic403. " + "Completed rows and sufficiently recent in-progress rows are included; stale incomplete rows age " + "out. Filters and collection membership intersect authorization. Current handlers preserve auth-d" + "isabled/demo deployment behavior.\",\"operationId\":\"listRecordings\",\"parameters\":[{\"description\":\"" + "Comma-separated current stream names; every requested name must be replay authorized.\",\"in\":\"que" + "ry\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Optional collec" + "tion UUID, resolved on the server and intersected with replay scope.\",\"in\":\"query\",\"name\":\"colle" + "ction_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 start bound" + "; invalid strings currently behave as no bound.\",\"in\":\"query\",\"name\":\"start\",\"required\":false,\"s" + "chema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 end bound; invalid strings currently behav" + "e as no bound.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"descrip" + "tion\":\"Default1; nonpositive/invalid numeric values normalize to1.\",\"in\":\"query\",\"name\":\"page\",\"" + "required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Default20, numeric maximum1000; non" + "positive/invalid values normalize to20. Case-insensitive all returns all matching authorized row" + "s, sets page1 and recounts.\",\"in\":\"query\",\"name\":\"limit\",\"required\":false,\"schema\":{\"anyOf\":[{\"t" + "ype\":\"integer\"},{\"const\":\"all\"}]}},{\"description\":\"Unsupported fields fall back to start_time.\"," + "\"in\":\"query\",\"name\":\"sort\",\"required\":false,\"schema\":{\"enum\":[\"id\",\"stream_name\",\"start_time\",\"e" + "nd_time\",\"size_bytes\"]}},{\"description\":\"asc or desc; unsupported values fall back to desc.\",\"in" + "\":\"query\",\"name\":\"order\",\"required\":false,\"schema\":{\"enum\":[\"asc\",\"desc\"]}},{\"description\":\"0 al" + "l,1 with detection,-1 without; numeric values clamp to this interval.\",\"in\":\"query\",\"name\":\"has_" + "detection\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Comma-separated labels;" + " implies detection-only filter.\",\"in\":\"query\",\"name\":\"detection_label\",\"required\":false,\"schema\"" + ":{\"type\":\"string\"}},{\"description\":\"Leading0 means unprotected, leading1 protected; otherwise al" + "l.\",\"in\":\"query\",\"name\":\"protected\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":" + "\"Comma-separated recording tags.\",\"in\":\"query\",\"name\":\"tag\",\"required\":false,\"schema\":{\"type\":\"s" + "tring\"}},{\"description\":\"Comma-separated capture methods, including continuous,scheduled,detecti" + "on,motion,manual.\",\"in\":\"query\",\"name\":\"capture_method\",\"required\":false,\"schema\":{\"type\":\"strin" + "g\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "RecordingsPage\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"s" "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":" "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" "\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" "schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"conten" "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recor" - "ding, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in" - " progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiab" - "le.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Conclusive saved interrupted-media failure; playback is unavailable. Download rec" - "overy is a separately authorized export operation.\"},\"500\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparatio" - "n or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"" - "#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"" - "description\":\"Shutdown, unavailable source or busy media preparation.\",\"headers\":{\"Retry-After\":" - "{\"description\":\"Present when preparation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"" - "}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}]," - "\"summary\":\"Prepare or play an authorized recording\",\"tags\":[\"Recording reads\"],\"x-lightnvr-autho" - "rization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"ver" - "sion\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use" - " permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization" - ". Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to " - "a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\"" - ":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/prot" - "ected\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured ident" - "ities, ownership and current permissions are rechecked for every request. Basic, legacy keys and" - " scoped-token owner intersections remain supported.\",\"operationId\":\"countEvidenceProtected\",\"par" - "ameters\":[{\"description\":\"Optional current camera name; otherwise count only recordings visible " - "by replay scope.\",\"in\":\"query\",\"name\":\"stream\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceProtectedCount\"}}}" - ",\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication require" - "d.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"" - "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer c" - "onflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation ma" - "y have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security" - "\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Coun" - "t evidence protected\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"ve" - "rsion\":1}}},\"/api/recordings/sync\":{\"post\":{\"description\":\"Discovery indicates an operation; con" - "crete cameras, captured identities, ownership and current permissions are rechecked for every re" - "quest. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":" - "\"syncEvidenceRecordings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/EvidenceSyncComplete\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" - " request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, featur" - "e configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\"" - ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits." - "\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media" - " work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHead" - "er\":[]},{\"bearerToken\":[]}],\"summary\":\"Sync evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr" - "-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}" - "],\"version\":1}}},\"/api/recordings/tags\":{\"get\":{\"description\":\"Unique recording tags within repl" - "ay-authorized camera scope. An empty scope returns tags:[]; no hidden-camera facet values are pu" - "blished.\",\"operationId\":\"listRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/RecordingTags\"}}},\"description\":\"Successful response\"},\"40" - "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization fail" - "ure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}" - "],\"summary\":\"List authorized recording tag facets\",\"tags\":[\"Recording reads\"],\"x-lightnvr-author" - "ization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/thumbnail/{recording_id}/{index" - "}\":{\"get\":{\"description\":\"JPEG cache/generation requires capture-camera recordings.replay. Globa" - "l disabled thumbnails403 and disabled hover frames403 are checked before auth. Otherwise authori" - "zation precedes cache/file access. Index0,1,2 selects start/middle/end samples; index>0 may be d" - "isabled by thumbnails_per_recording=1. Cache uses private max-age86400; source preparation/delet" - "ion can return503/409. Thumbnails do not assert original media is playable.\",\"operationId\":\"getR" - "ecordingThumbnail\",\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"typ" - "e\":\"string\"}}},\"description\":\"JPEG thumbnail bytes.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{" - "\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Single cached/generated JPEG byte range.\",\"h" - "eaders\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit th" - "is header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"" - "schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"i" - "nteger\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omit" - "s it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable creden" - "tial.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current captu" - "re camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion" - " pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Data" - "base, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/Reco" - "rdingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparati" - "on.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}]" - ",\"summary\":\"Read an authorized recording thumbnail\",\"tags\":[\"Recording reads\"],\"x-lightnvr-autho" - "rization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"ver" - "sion\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use" - " permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization" - ". Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to " - "a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\"" - ":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Thumbna" - "il sample index.\",\"in\":\"path\",\"name\":\"index\",\"required\":true,\"schema\":{\"maximum\":2,\"minimum\":0,\"" - "type\":\"integer\"}}]},\"/api/recordings/{recording_id}\":{\"delete\":{\"description\":\"Authorizes the st" - "ored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy nu" - "ll/empty UUID rows fall back to the current stream name. Protected/in-progress/active-transfer m" - "edia returns409. A202 response means deletion is queued, not that bytes have disappeared.\",\"oper" - "ationId\":\"deleteRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/RecordingDeletionResult\"}}},\"description\":\"Successful response\"},\"202\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionResult\"}}},\"" - "description\":\"Deletion accepted and pending; status is deletion_pending.\"},\"400\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed pa" - "th or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forb" - "idden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, curren" - "t capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, un" - "available source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or " - "file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerT" - "oken\":[]}],\"summary\":\"Delete a recording\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization" - "\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}}" - ",\"get\":{\"description\":\"Authorizes recordings.replay using current raw facts for stored capture i" - "dentity before emitting metadata. This response does not include tags; use the separate tags end" - "point.\",\"operationId\":\"getRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/RecordingDetail\"}}},\"description\":\"Successful response\"},\"400\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media op" - "tion forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Recording, current capture camera, collection or source file not foun" - "d.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown," - " unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":" - "[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read recording detail\",\"tags\":[\"Recording" - " reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.repla" - "y\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. " - "Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fa" - "il404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields" - "404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream" - " name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"" - "}}]},\"/api/recordings/{recording_id}/protect\":{\"parameters\":[{\"description\":\"Stable recording da" - "tabase ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unk" - "nown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture ca" - "mera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the cur" - "rent stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type" - "\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw" - " facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the curren" - "t stream name. Protected media cannot be deleted by ordinary recording deletion.\",\"operationId\":" - "\"setRecordingProtection\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/RecordingProtectionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingProtectionResult\"}}},\"descripti" - "on\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"" - "},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Capture identity or batch member forbidden; authorization denial occurs before mutati" - "on or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not f" - "ound.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{" - "\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set reco" - "rding protection\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requ" - "irements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{reco" - "rding_id}/retention\":{\"parameters\":[{\"description\":\"Stable recording database ID. Most existing " - "handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before a" - "uthorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without r" - "ebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"" - "path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"de" - "scription\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture" - " camera returns404; legacy null/empty UUID rows fall back to the current stream name. Negative i" - "nput clears the override, zero means indefinite. The returned integer reflects the converted inp" - "ut; clearing does not normalize it to -1.\",\"operationId\":\"setRecordingRetention\",\"requestBody\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingRetentionRequest\"" - "}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/RecordingRetentionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malfor" - "med path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch membe" - "r forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, " - "current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocat" - "ion, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"api" - "KeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set or clear a recording retention override\",\"tags" - "\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"e" - "vidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recording_id}/tags\":{\"get\":" - "{\"description\":\"Uses stored capture identity and recordings.replay before returning tags.\",\"oper" - "ationId\":\"getRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"},\"400\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option fo" - "rbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"5" - "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCoo" - "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized " - "recording\342\200\231s tags\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requ" - "irements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"descri" - "ption\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; ze" - "ro/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authorit" - "ative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy ro" - "ws with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true," - "\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture ca" - "mera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID r" - "ows fall back to the current stream name. Authorization precedes tag mutation.\",\"operationId\":\"s" - "etRecordingTags\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/RecordingTagsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful respon" - "se\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture ide" - "ntity or batch member forbidden; authorization denial occurs before mutation or job creation.\"}," - "\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Datab" - "ase, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"" - "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Replace recording tags\",\"tags\"" - ":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ev" - "idence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/settings\":{\"get\":{\"operationId\":\"getSyst" - "emSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/SystemSettings\"}}},\"description\":\"Successful response. Process-control responses acknowled" - "ge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"A" - "uthentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence f" - "ailure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicA" - "uth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system settings\",\"tags\":[\"System" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resourc" - "e\":\"raw\"}],\"version\":1}},\"post\":{\"operationId\":\"updateSystemSettings\",\"requestBody\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettingsUpdate\"}}},\"required\":t" - "rue},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/S" - "ystemSettingsSaved\"}}},\"description\":\"Successful response. Process-control responses acknowledge" - " a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Aut" - "hentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence fai" - "lure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAut" - "h\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update system settings\",\"tags\":[\"Syste" - "m\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resour" - "ce\":\"raw\"}],\"version\":1}}},\"/api/settings/go2rtc/validate\":{\"post\":{\"operationId\":\"validateGo2rt" - "cConfiguration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/Go2rtcConfigurationInput\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcConfigurationValidation\"}}},\"description\":\"S" - "uccessful response. Process-control responses acknowledge a request; verify eventual completion " - "independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requir" - "ed authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily un" - "available.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToke" - "n\":[]}],\"summary\":\"Validate go2rtc configuration\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{" - "\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api" - "/setup/status\":{\"get\":{\"operationId\":\"getSetupStatus\",\"responses\":{\"200\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupStatus\"}}},\"description\":\"Successful respon" - "se. Process-control responses acknowledge a request; verify eventual completion independently.\"}" - ",\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority den" - "ied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"s" - "ecurity\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summ" - "ary\":\"Get setup status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":" - "1}},\"post\":{\"description\":\"Before setup completion this bootstrap operation is public. Once comp" - "lete it requires system.admin; resetting requires that authority. Malformed/absent optional fiel" - "ds retain the native default of completing setup.\",\"operationId\":\"updateSetupStatus\",\"requestBod" - "y\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupUpdate\"}}},\"requi" - "red\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/SetupUpdated\"}}},\"description\":\"Successful response. Process-control responses acknowledge" - " a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Aut" - "hentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence fai" - "lure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basic" - "Auth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update setup status\",\"tags\":[\"Syste" - "m\"],\"x-lightnvr-authorization\":{\"kind\":\"bootstrap_setup\",\"version\":1}}},\"/api/snapshots/{camera}" - "/{filename}\":{\"get\":{\"description\":\"New MQTT snapshot URLs use the captured immutable camera UUI" - "D namespace. UUID URLs authorize that UUID and never fall back to a display-name directory. Exis" - "ting name-only snapshot URLs remain compatible and authorize the current named camera; old files" - " do not contain recoverable capture identity. Camera snapshot.create is required.\",\"operationId\"" - ":\"getEvidenceSnapshot\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized in" - "dependently.\",\"in\":\"path\",\"name\":\"camera\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string" - "\"}},{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":" - "\"filename\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"conte" - "nt\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized medi" - "a bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may" - " be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"20" - "6\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A sup" - "ported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}" - "},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current cam" - "era policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or no" - "t visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds" - " operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a m" - "utation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}}" - ",\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summ" - "ary\":\"Get evidence snapshot\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"re" - "quirements\":[{\"action\":\"snapshot.create\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-archive" - "\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resourc" - "e existence, revisions and reference/state checks still apply. Basic and legacy-key compatibilit" - "y is preserved.\",\"operationId\":\"getStorageArchive\",\"responses\":{\"200\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveResponse\"}}},\"description\":\"Successfu" - "l operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global s" - "torage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state co" - "nflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"C" - "atalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"ap" - "iKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr" - "-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}" - "],\"version\":1}}},\"/api/storage-archive/retry\":{\"post\":{\"description\":\"Requires global storage.co" - "nfigure, including for scoped tokens. Resource existence, revisions and reference/state checks s" - "till apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"retryStorageArchive" - "\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageAr" - "chiveRetryRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/StorageArchiveRetryResponse\"}}},\"description\":\"Successful opera" - "tion.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage." - "configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict." - "\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog " - "or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHea" - "der\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-auth" - "orization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"ve" - "rsion\":1}}},\"/api/storage-compliance\":{\"get\":{\"description\":\"Requires global storage.configure, " - "including for scoped tokens. Resource existence, revisions and reference/state checks still appl" - "y. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageCompliance\",\"respo" - "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCompl" - "ianceResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Au" - "thentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"" - "},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\"" - ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[" - "{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStor" - "ageCompliance\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"" - "action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations\":{\"get\":{" - "\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existenc" - "e, revisions and reference/state checks still apply. Basic and legacy-key compatibility is prese" - "rved.\",\"operationId\":\"listStorageMigrations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/StorageMigrationsResponse\"}}},\"description\":\"Successful o" - "peration.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global stor" - "age.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state confl" - "ict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Cata" - "log or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" - "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageMigrations\",\"tags\":[\"Storage\"],\"x-lightnv" - "r-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"" - "}],\"version\":1}},\"post\":{\"description\":\"Queues move (default) or copy of a complete recording. A" - " 202 job is not completion. Poll the job state; verify checksum/publication and cleanup independ" - "ently. Permission is global storage.configure, not creator ownership.\",\"operationId\":\"createStor" - "ageMigration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/StorageMigrationCreate\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operatio" - "n.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.con" - "figure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"}," - "\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or " - "storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" - "\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-auth" - "orization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"ve" - "rsion\":1}}},\"/api/storage-migrations/{uuid}\":{\"get\":{\"description\":\"Requires global storage.conf" - "igure, including for scoped tokens. Resource existence, revisions and reference/state checks sti" - "ll apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageMigration\"," - "\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/" - "CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"}" + "ding, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, " + "media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, unavailable source or busy medi" + "a preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearer" + "Token\":[]}],\"summary\":\"List authorized recordings\",\"tags\":[\"Recording reads\"],\"x-lightnvr-author" + "ization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-delete\":{\"post\":{\"descrip" + "tion\":\"Fixed IDs preflight every existing member before dispatch; forbidden members reject the w" + "hole batch. Filter jobs capture authorized UUID scope before dispatch. Operational missing/prote" + "cted failures are counted separately and can coexist with successes. Poll the owned job; cancell" + "ation is unsupported.\",\"operationId\":\"deleteRecordings\",\"requestBody\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionRequest\"}}},\"required\":true},\"resp" + "onses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDe" + "letionJobAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"}" ",\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resour" - "ce missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavaila" - "ble.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}}," - "\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summa" - "ry\":\"GetStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requir" - "ements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migration" - "s/{uuid}/cancel\":{\"post\":{\"description\":\"Global storage.configure permits acting on any job. Can" - "cel may request cancellation of an active job; retry requeues eligible failed/cancelled work. Po" - "ll state to observe completion and cleanup; the acknowledgement is not a completed transfer.\",\"o" - "perationId\":\"cancelStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"s" - "chema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful ope" - "ration.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storag" - "e.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflic" - "t.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalo" - "g or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyH" - "eader\":[]},{\"bearerToken\":[]}],\"summary\":\"CancelStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr" - "-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}" - "],\"version\":1}}},\"/api/storage-migrations/{uuid}/retry\":{\"post\":{\"description\":\"Global storage.c" - "onfigure permits acting on any job. Cancel may request cancellation of an active job; retry requ" - "eues eligible failed/cancelled work. Poll state to observe completion and cleanup; the acknowled" - "gement is not a completed transfer.\",\"operationId\":\"retryStorageMigration\",\"parameters\":[{\"in\":\"" - "path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"respo" - "nses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigra" - "tion\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticat" - "ion required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{" + "ription\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authoriza" + "tion denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera" + ", collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation o" + "r file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"beare" + "rToken\":[]}],\"summary\":\"Create a recording deletion job\",\"tags\":[\"Recording actions\"],\"x-lightnv" + "r-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}" + "],\"version\":1}}},\"/api/recordings/batch-delete/progress/{job_id}\":{\"get\":{\"description\":\"Require" + "s the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An admi" + "nistrator, sibling token or owner using another credential cannot borrow a scoped-token-owned jo" + "b. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera gr" + "ants are not rechecked for job lookup. Terminal complete includes partial operational failures; " + "inspect all counters. There is no cancel operation.\",\"operationId\":\"getRecordingDeletionProgress" + "\",\"parameters\":[{\"description\":\"Opaque deletion job identifier; possession alone grants no acces" + "s.\",\"in\":\"path\",\"name\":\"job_id\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionProgress" + "\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usab" + "le credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found." + "\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sess" + "ionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned re" + "cording deletion progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"auth" + "enticated\",\"version\":1}}},\"/api/recordings/batch-download\":{\"post\":{\"description\":\"Preflights ev" + "ery requested recording before creating the job. Missing404/in-progress409 may abort in input or" + "der before a later forbidden member; these early errors create no job or archive. ZIP32 uses ori" + "ginal media, including recovery-download files with saved playback issues. Detected unavailable-" + "source/member-count and ZIP32-limit failures reject the result. The current writer does not cons" + "istently detect filesystem read/write/close failures; complete alone does not prove archive inte" + "grity. Cancellation is unsupported.\",\"operationId\":\"createRecordingExport\",\"requestBody\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportRequest\"}}},\"requ" + "ired\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/RecordingExportAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path o" + "r request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidde" + "n; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current ca" + "pture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavai" + "lable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file" + " failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Busy job slots, unavailable source, or shutdown.\"}},\"security\":[{\"sessionCo" + "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create an authoriz" + "ed ZIP export job\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"req" + "uirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/bat" + "ch-download/result/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and" + " exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or own" + "er using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned" + " jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job " + "lookup. Export ownership also matches username. Pending/running jobs return409 with error Job is" + " not_ready; failed jobs return409 with error Job is error. Only complete archives are downloadab" + "le. Results can be read repeatedly while retained; result access refreshes job retention. This i" + "s not a one-time token. No cancellation endpoint exists.\",\"operationId\":\"downloadRecordingExport" + "\",\"parameters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"pa" + "th\",\"name\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single byt" + "e range using the common file-serving parser.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"st" + "ring\"}}],\"responses\":{\"200\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"s" + "tring\"}}},\"description\":\"Complete ZIP archive.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"b" + "ytes\"}},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"i" + "nteger\"}}}},\"206\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}}," + "\"description\":\"Single ZIP byte range.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"" + "Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}" + ",\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"N" + "o usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not " + "found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"416\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creatio" + "n or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" + "arerToken\":[]}],\"summary\":\"Download an owned completed ZIP export\",\"tags\":[\"Recording actions\"]," + "\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-downloa" + "d/status/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact sco" + "ped-token UUID, when created by a scoped token. An administrator, sibling token or owner using a" + "nother credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs retu" + "rn404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Ex" + "port ownership also matches the stored username. Jobs can be reclaimed after their retention int" + "erval; polling does not make a job permanent.\",\"operationId\":\"getRecordingExportStatus\",\"paramet" + "ers\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"path\",\"name\"" + ":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportStatus\"}}},\"description\":\"" + "Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"40" + "4\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Recording, current capture camera, collection, or owned job not found.\"}},\"security\":[{\"se" + "ssionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned " + "recording export progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"auth" + "enticated\",\"version\":1}}},\"/api/recordings/batch-protect\":{\"post\":{\"description\":\"An otherwise-v" + "alid forbidden existing member yields403 before any protection writes. Invalid/missing IDs and s" + "ubsequent database write failures are counted, so operational failures may produce partial succe" + "ss.\",\"operationId\":\"protectRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/RecordingBatchProtectionRequest\"}}},\"required\":true},\"responses\":{\"2" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchProtect" + "ionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{" "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Stora" - "ge operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"session" - "Cookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageMigr" - "ation\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":" - "\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies\":{\"get\":{\"descripti" - "on\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisio" - "ns and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"op" - "erationId\":\"listStoragePolicies\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/StoragePoliciesResponse\"}}},\"description\":\"Successful operation.\"},\"4" + ":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization den" + "ial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collec" + "tion, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file f" + "ailure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":" + "[]}],\"summary\":\"Set protection on recording IDs\",\"tags\":[\"Recording actions\"],\"x-lightnvr-author" + "ization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"versi" + "on\":1}}},\"/api/recordings/batch-tags\":{\"post\":{\"description\":\"Preflights all IDs and rejects mis" + "sing/forbidden members before writes. Invalid IDs return400. Operational database failures do no" + "t promise an all-or-nothing transaction.\",\"operationId\":\"updateRecordingTagsBatch\",\"requestBody\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchTagsReques" + "t\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/RecordingBatchTagsResult\"}}},\"description\":\"Successful response\"},\"400\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malf" + "ormed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch mem" + "ber forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording" + ", current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, alloc" + "ation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"a" + "piKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Add or remove tags on recording IDs\",\"tags\":[\"Re" + "cording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidenc" + "e.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/detection-labels\":{\"get\":{\"descrip" + "tion\":\"Unique detection labels within replay-authorized camera scope. An empty scope returns lab" + "els:[].\",\"operationId\":\"listRecordingDetectionLabels\",\"responses\":{\"200\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDetectionLabels\"}}},\"description\":\"Succ" + "essful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation " + "or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" + "]},{\"bearerToken\":[]}],\"summary\":\"List authorized detection label facets\",\"tags\":[\"Recording rea" + "ds\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/download" + "/{recording_id}\":{\"get\":{\"description\":\"Authorizes the stored capture camera UUID using current " + "raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the cur" + "rent stream name. Requires recordings.export before source probes. In-progress media returns409." + " Saved playback failures do not block original-byte recovery download. prepare=1 returns ready/p" + "reparing JSON; ordinary success is an attachment. No compatibility transcoding is requested by t" + "his route. Byte ranges use the same permissive local parser as playback: prefix/suffix/open-ende" + "d accepted and clamped, malformed missing-dash/multiple ranges may parse as a single range. Loca" + "l416 omits Content-Range.\",\"operationId\":\"downloadRecording\",\"parameters\":[{\"description\":\"Exact" + "ly1 selects JSON readiness without media bytes.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false," + "\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"UI hint for externally stored recordings. Actual so" + "urce selection follows stored metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\"" + ",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=star" + "t-, or bytes=-suffix range. Actual permissive parser limitations are documented above.\",\"in\":\"he" + "ader\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"fo" + "rmat\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"vi" + "deo/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"for" + "mat\":\"binary\",\"type\":\"string\"}}},\"description\":\"prepare=1 JSON ready, or complete media body.\",\"" + "headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Att" + "achment with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}" + "},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"description\":\"Requested media is" + " preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"schema\":{\"type\":\"string\"}}}" + "},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"sc" + "hema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type" + "\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A " + "single satisfiable media byte range. Validate bytes/protocol separately from JSON.\",\"headers\":{\"" + "Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Attachment wit" + "h sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}},\"Content-" + "Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/to" + "tal. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query." + "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording" + " still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not " + "satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"" + "content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref" + "\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable so" + "urce or busy media preparation.\",\"headers\":{\"Retry-After\":{\"description\":\"Present when preparati" + "on is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]}" + ",{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Download or prepare origina" + "l recording media\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"req" + "uirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"descr" + "iption\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; z" + "ero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authori" + "tative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy r" + "ows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true" + ",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/files\":{\"delete\":{\"description\":\"Di" + "scovery indicates an operation; concrete cameras, captured identities, ownership and current per" + "missions are rechecked for every request. Basic, legacy keys and scoped-token owner intersection" + "s remain supported.\",\"operationId\":\"deleteEvidenceRecordingFile\",\"parameters\":[{\"description\":\"E" + "xact registered recording file path. Unknown paths return404; this is not an arbitrary filesyste" + "m operation. Captured recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":" + "true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileDeleted\"}}},\"description\":\"Validated succe" + "ss.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileD" + "eleted\"}}},\"description\":\"Deletion scheduled; not proof of file removal.\"},\"400\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid requ" + "est.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature con" + "figuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"R" + "evision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"5" "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure " - "denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage" - " service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{" - "\"bearerToken\":[]}],\"summary\":\"ListStoragePolicies\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\"" - ":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}" - ",\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource" - " existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility" - " is preserved.\",\"operationId\":\"createStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyCreate\"}}},\"required\":true},\"responses\":{\"" - "201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"d" - "escription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication require" - "d.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision," - " reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operatio" - "n failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]}" - ",{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePolicy\",\"tags\"" - ":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.conf" - "igure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/preview\":{\"post\":{\"description\":" - "\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions a" - "nd reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operat" - "ionId\":\"previewStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/StoragePolicyPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyPreview\"}}},\"descript" - "ion\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"4" - "03\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, refere" - "nce or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation faile" - "d.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" - "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"PreviewStoragePolicy\",\"tags\":[\"Sto" - "rage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\"" - ",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/{uuid}\":{\"delete\":{\"description\":\"Requ" - "ires global storage.configure, including for scoped tokens. Resource existence, revisions and re" - "ference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId" - "\":\"deleteStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref" - "\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{" - "\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Success" - "ful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global" - " storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state " - "conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"" - "apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePolicy\",\"tags\":[\"Storage\"],\"x-ligh" - "tnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"r" - "aw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scope" - "d tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy" - "-key compatibility is preserved.\",\"operationId\":\"getStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"n" - "ame\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"" - "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"d" - "escription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication require" - "d.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision," - " reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operatio" - "n failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]}" - ",{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePolicy\",\"tags\":[\"" - "Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configu" - "re\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, in" - "cluding for scoped tokens. Resource existence, revisions and reference/state checks still apply." - " Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePolicy\",\"paramete" - "rs\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUui" - "d\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Stora" - "gePolicyUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied." - "\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Stora" - "ge target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage servic" - "e unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearer" - "Token\":[]}],\"summary\":\"UpdateStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind" - "\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api" - "/storage-policies/{uuid}/apply\":{\"post\":{\"description\":\"Preview counts records whose captured po" - "licy revision differs. Apply updates those revision references transactionally and requires unfi" - "nished transfers to finish first. It does not itself delete files or complete asynchronous archi" - "ve/migration work.\",\"operationId\":\"applyStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\"," - "\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyApplyRequest\"}}},\"requir" - "ed\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/StoragePolicyApplyResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid reque" - "st.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"co" + "tion\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work" + " temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" + "]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence recording file\",\"tags\":[\"Evidence\"],\"x-lightnv" + "r-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}" + "],\"version\":1}}},\"/api/recordings/files/check\":{\"get\":{\"description\":\"Discovery indicates an ope" + "ration; concrete cameras, captured identities, ownership and current permissions are rechecked f" + "or every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"op" + "erationId\":\"checkEvidenceRecordingFile\",\"parameters\":[{\"description\":\"Exact registered recording" + " file path. Unknown paths return404; this is not an arbitrary filesystem operation. Captured rec" + "ording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":true,\"schema\":{\"minLength" + "\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/EvidenceFileCheck\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid requ" + "est.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature con" + "figuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"co" "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"R" - "esource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target una" - "vailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable" + "evision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"5" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work" + " temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" + "]},{\"bearerToken\":[]}],\"summary\":\"Check evidence recording file\",\"tags\":[\"Evidence\"],\"x-lightnvr" + "-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}" + "],\"version\":1}}},\"/api/recordings/play/{recording_id}\":{\"get\":{\"description\":\"Requires recording" + "s.replay for capture identity before inspecting source availability. In-progress409; saved inval" + "id_media/no_video/decode_error422 with recovery explanation. prepare=1 returns JSON ready/prepar" + "ing; ordinary success serves media bytes. Archive/HEVC preparation can return202 (prepare) or503" + " (ordinary) with Retry-After2. Authorization uses the logical recording, never a caller-supplied" + " filesystem path. Local byte ranges accept prefix, suffix and open-ended forms; excessive end/su" + "ffix clamp to EOF. Invalid unit, start\342\211\245size, reversed bounds and zero suffix yield416 JSON. Lo" + "cal416 currently omits Content-Range; archive416 supplies bytes */size. Existing parser is permi" + "ssive: bytes=0 (missing dash) becomes full206 and multi-ranges are treated as their first numeri" + "c range, not multipart. Do not infer strict RFC rejection of malformed ranges.\",\"operationId\":\"p" + "layRecording\",\"parameters\":[{\"description\":\"Exactly1 selects JSON readiness without media bytes." + "\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"Ex" + "actly1 requests compatibility cache;0 requests original. Absent defaults to prepare=1 compatibil" + "ity behavior.\",\"in\":\"query\",\"name\":\"transcode\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"d" + "escription\":\"UI hint for externally stored recordings. Actual source selection follows stored me" + "tadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\",\"required\":false,\"schema\":{\"typ" + "e\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=start-, or bytes=-suffix range. Actu" + "al permissive parser limitations are documented above.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"" + "type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}" + ",\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"form" + "at\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}}" + ",\"description\":\"prepare=1 JSON ready, or complete media body.\",\"headers\":{\"Accept-Ranges\":{\"desc" + "ription\":\"The local playback handler and file server both emit this header; clients can observe " + "combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes" + ", bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"descri" + "ption\":\"Requested media is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"s" + "chema\":{\"type\":\"string\"}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"s" + "tring\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema" + "\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"st" + "ring\"}}},\"description\":\"A single satisfiable media byte range. Validate bytes/protocol separatel" + "y from JSON.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file ser" + "ver both emit this header; clients can observe combined bytes, bytes. Archive responses emit one" + " bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"sc" + "hema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local41" + "6 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recordin" + "g, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in pr" + "ogress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable." + "\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Conclusive saved interrupted-media failure; playback is unavailable. Download recove" + "ry is a separately authorized export operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation o" + "r serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"des" + "cription\":\"Shutdown, unavailable source or busy media preparation.\",\"headers\":{\"Retry-After\":{\"d" + "escription\":\"Present when preparation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}" + "}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"su" + "mmary\":\"Prepare or play an authorized recording\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authoriz" + "ation\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"versio" + "n\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use pe" + "rmissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. S" + "tored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a r" + "eused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"r" + "ecording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/protect" + "ed\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identiti" + "es, ownership and current permissions are rechecked for every request. Basic, legacy keys and sc" + "oped-token owner intersections remain supported.\",\"operationId\":\"countEvidenceProtected\",\"parame" + "ters\":[{\"description\":\"Optional current camera name; otherwise count only recordings visible by " + "replay scope.\",\"in\":\"query\",\"name\":\"stream\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceProtectedCount\"}}},\"d" + "escription\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"" + "},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"" + "404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conf" + "lict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may h" + "ave applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[" + "{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Count e" + "vidence protected\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"versi" + "on\":1}}},\"/api/recordings/sync\":{\"post\":{\"description\":\"Discovery indicates an operation; concre" + "te cameras, captured identities, ownership and current permissions are rechecked for every reque" + "st. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"sy" + "ncEvidenceRecordings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/EvidenceSyncComplete\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid re" + "quest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature c" + "onfiguration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"}," + "\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media wo" + "rk temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" + ":[]},{\"bearerToken\":[]}],\"summary\":\"Sync evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-au" + "thorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"" + "version\":1}}},\"/api/recordings/tags\":{\"get\":{\"description\":\"Unique recording tags within replay-" + "authorized camera scope. An empty scope returns tags:[]; no hidden-camera facet values are publi" + "shed.\",\"operationId\":\"listRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/RecordingTags\"}}},\"description\":\"Successful response\"},\"401\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure" ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"" - "summary\":\"ApplyStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"re" - "quirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools" - "\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resourc" - "e existence, revisions and reference/state checks still apply. Basic and legacy-key compatibilit" - "y is preserved.\",\"operationId\":\"listStoragePools\",\"responses\":{\"200\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolsResponse\"}}},\"description\":\"Successful o" + "summary\":\"List authorized recording tag facets\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authoriza" + "tion\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/thumbnail/{recording_id}/{index}\":" + "{\"get\":{\"description\":\"JPEG cache/generation requires capture-camera recordings.replay. Global d" + "isabled thumbnails403 and disabled hover frames403 are checked before auth. Otherwise authorizat" + "ion precedes cache/file access. Index0,1,2 selects start/middle/end samples; index>0 may be disa" + "bled by thumbnails_per_recording=1. Cache uses private max-age86400; source preparation/deletion" + " can return503/409. Thumbnails do not assert original media is playable.\",\"operationId\":\"getReco" + "rdingThumbnail\",\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":" + "\"string\"}}},\"description\":\"JPEG thumbnail bytes.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"fo" + "rmat\":\"binary\",\"type\":\"string\"}}},\"description\":\"Single cached/generated JPEG byte range.\",\"head" + "ers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this " + "header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"sch" + "ema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"inte" + "ger\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits i" + "t.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credentia" + "l.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture " + "camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pe" + "nding or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databas" + "e, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"" + "schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/Recordi" + "ngPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation." + "\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"s" + "ummary\":\"Read an authorized recording thumbnail\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authoriz" + "ation\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"versio" + "n\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use pe" + "rmissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. S" + "tored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a r" + "eused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"r" + "ecording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Thumbnail " + "sample index.\",\"in\":\"path\",\"name\":\"index\",\"required\":true,\"schema\":{\"maximum\":2,\"minimum\":0,\"typ" + "e\":\"integer\"}}]},\"/api/recordings/{recording_id}\":{\"delete\":{\"description\":\"Authorizes the store" + "d capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/" + "empty UUID rows fall back to the current stream name. Protected/in-progress/active-transfer medi" + "a returns409. A202 response means deletion is queued, not that bytes have disappeared.\",\"operati" + "onId\":\"deleteRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/RecordingDeletionResult\"}}},\"description\":\"Successful response\"},\"202\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionResult\"}}},\"des" + "cription\":\"Deletion accepted and pending; status is deletion_pending.\"},\"400\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path " + "or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidd" + "en; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current c" + "apture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unava" + "ilable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or fil" + "e failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToke" + "n\":[]}],\"summary\":\"Delete a recording\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{" + "\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}},\"g" + "et\":{\"description\":\"Authorizes recordings.replay using current raw facts for stored capture iden" + "tity before emitting metadata. This response does not include tags; use the separate tags endpoi" + "nt.\",\"operationId\":\"getRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/RecordingDetail\"}}},\"description\":\"Successful response\"},\"400\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" + "valid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media optio" + "n forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"" + "},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, un" + "available source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}" + ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read recording detail\",\"tags\":[\"Recording re" + "ads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\"," + "\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Mos" + "t existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail4" + "04 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404" + ", without rebinding to a reused display name. Legacy rows with no UUID use the current stream na" + "me.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]" + "},\"/api/recordings/{recording_id}/protect\":{\"parameters\":[{\"description\":\"Stable recording datab" + "ase ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknow" + "n IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camer" + "a yields404, without rebinding to a reused display name. Legacy rows with no UUID use the curren" + "t stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"" + "integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw fa" + "cts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current s" + "tream name. Protected media cannot be deleted by ordinary recording deletion.\",\"operationId\":\"se" + "tRecordingProtection\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/RecordingProtectionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingProtectionResult\"}}},\"description\"" + ":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"" + "403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation " + "or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not foun" + "d.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"se" + "ssionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set recordi" + "ng protection\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"require" + "ments\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recordi" + "ng_id}/retention\":{\"parameters\":[{\"description\":\"Stable recording database ID. Most existing han" + "dlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before auth" + "orization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebi" + "nding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"pat" + "h\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"descr" + "iption\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture ca" + "mera returns404; legacy null/empty UUID rows fall back to the current stream name. Negative inpu" + "t clears the override, zero means indefinite. The returned integer reflects the converted input;" + " clearing does not normalize it to -1.\",\"operationId\":\"setRecordingRetention\",\"requestBody\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingRetentionRequest\"}}}" + ",\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/RecordingRetentionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed" + " path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member f" + "orbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, cur" + "rent capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation" + ", worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKey" + "Header\":[]},{\"bearerToken\":[]}],\"summary\":\"Set or clear a recording retention override\",\"tags\":[" + "\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evid" + "ence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recording_id}/tags\":{\"get\":{\"d" + "escription\":\"Uses stored capture identity and recordings.replay before returning tags.\",\"operati" + "onId\":\"getRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid p" + "ath or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbi" + "dden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie" + "\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized rec" + "ording\342\200\231s tags\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"require" + "ments\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"descripti" + "on\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/" + "invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritati" + "ve; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows " + "with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"sc" + "hema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camer" + "a UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows" + " fall back to the current stream name. Authorization precedes tag mutation.\",\"operationId\":\"setR" + "ecordingTags\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/RecordingTagsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"" + "},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identi" + "ty or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"40" + "4\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database" + ", policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"bas" + "icAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Replace recording tags\",\"tags\":[\"" + "Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evide" + "nce.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/settings\":{\"get\":{\"operationId\":\"getSystemS" + "ettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/SystemSettings\"}}},\"description\":\"Successful response. Process-control responses acknowledge " + "a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Auth" + "entication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence fail" + "ure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth" + "\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system settings\",\"tags\":[\"System\"]," + "\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":" + "\"raw\"}],\"version\":1}},\"post\":{\"operationId\":\"updateSystemSettings\",\"requestBody\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettingsUpdate\"}}},\"required\":true" + "},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Syst" + "emSettingsSaved\"}}},\"description\":\"Successful response. Process-control responses acknowledge a " + "request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authen" + "tication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failur" + "e.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":" + "[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update system settings\",\"tags\":[\"System\"]" + ",\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\"" + ":\"raw\"}],\"version\":1}}},\"/api/settings/go2rtc/validate\":{\"post\":{\"operationId\":\"validateGo2rtcCo" + "nfiguration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/Go2rtcConfigurationInput\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcConfigurationValidation\"}}},\"description\":\"Succ" + "essful response. Process-control responses acknowledge a request; verify eventual completion ind" + "ependently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required " + "authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unava" + "ilable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":" + "[]}],\"summary\":\"Validate go2rtc configuration\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"ki" + "nd\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/se" + "tup/status\":{\"get\":{\"operationId\":\"getSetupStatus\",\"responses\":{\"200\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupStatus\"}}},\"description\":\"Successful response." + " Process-control responses acknowledge a request; verify eventual completion independently.\"},\"4" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied" + ".\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"secu" + "rity\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary" + "\":\"Get setup status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}" + ",\"post\":{\"description\":\"Before setup completion this bootstrap operation is public. Once complet" + "e it requires system.admin; resetting requires that authority. Malformed/absent optional fields " + "retain the native default of completing setup.\",\"operationId\":\"updateSetupStatus\",\"requestBody\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupUpdate\"}}},\"required" + "\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/SetupUpdated\"}}},\"description\":\"Successful response. Process-control responses acknowledge a " + "request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authen" + "tication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failur" + "e.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAut" + "h\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update setup status\",\"tags\":[\"System\"]" + ",\"x-lightnvr-authorization\":{\"kind\":\"bootstrap_setup\",\"version\":1}}},\"/api/snapshots/{camera}/{f" + "ilename}\":{\"get\":{\"description\":\"New MQTT snapshot URLs use the captured immutable camera UUID n" + "amespace. UUID URLs authorize that UUID and never fall back to a display-name directory. Existin" + "g name-only snapshot URLs remain compatible and authorize the current named camera; old files do" + " not contain recoverable capture identity. Camera snapshot.create is required.\",\"operationId\":\"g" + "etEvidenceSnapshot\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized indep" + "endently.\",\"in\":\"path\",\"name\":\"camera\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}" + ",{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"fi" + "lename\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\"" + ":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media b" + "ytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be" + " disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":" + "{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A suppor" + "ted byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"" + "400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera" + " policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not v" + "isible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds op" + "eration limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a muta" + "tion may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"s" + "ecurity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary" + "\":\"Get evidence snapshot\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requi" + "rements\":[{\"action\":\"snapshot.create\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-archive\":{" + "\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource e" + "xistence, revisions and reference/state checks still apply. Basic and legacy-key compatibility i" + "s preserved.\",\"operationId\":\"getStorageArchive\",\"responses\":{\"200\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveResponse\"}}},\"description\":\"Successful o" "peration.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" "r\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" "#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"a" @@ -5941,230 +6063,349 @@ static const unsigned char lightnvr_openapi_document[] = "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"conte" "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Cata" "log or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" - "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStoragePools\",\"tags\":[\"Storage\"],\"x-lightnvr-aut" - "horization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"v" - "ersion\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped token" - "s. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key co" - "mpatibility is preserved.\",\"operationId\":\"createStoragePool\",\"requestBody\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolCreate\"}}},\"required\":true},\"respon" - "ses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}" - "}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication re" - "quired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revi" - "sion, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage ope" - "ration failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie" - "\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePool\",\"ta" - "gs\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.c" - "onfigure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools/{uuid}\":{\"delete\":{\"description\"" + "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-au" + "thorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"" + "version\":1}}},\"/api/storage-archive/retry\":{\"post\":{\"description\":\"Requires global storage.confi" + "gure, including for scoped tokens. Resource existence, revisions and reference/state checks stil" + "l apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"retryStorageArchive\",\"" + "requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchi" + "veRetryRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/StorageArchiveRetryResponse\"}}},\"description\":\"Successful operatio" + "n.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.con" + "figure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"}," + "\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or " + "storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" + "\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-authori" + "zation\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"versi" + "on\":1}}},\"/api/storage-compliance\":{\"get\":{\"description\":\"Requires global storage.configure, inc" + "luding for scoped tokens. Resource existence, revisions and reference/state checks still apply. " + "Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageCompliance\",\"response" + "s\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageComplian" + "ceResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authe" + "ntication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"" + "409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"s" + "essionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorage" + "Compliance\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"act" + "ion\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations\":{\"get\":{\"de" + "scription\":\"Requires global storage.configure, including for scoped tokens. Resource existence, " + "revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserve" + "d.\",\"operationId\":\"listStorageMigrations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/StorageMigrationsResponse\"}}},\"description\":\"Successful oper" + "ation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage" + ".configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict" + ".\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog" + " or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHe" + "ader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageMigrations\",\"tags\":[\"Storage\"],\"x-lightnvr-a" + "uthorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}]," + "\"version\":1}},\"post\":{\"description\":\"Queues move (default) or copy of a complete recording. A 20" + "2 job is not completion. Poll the job state; verify checksum/publication and cleanup independent" + "ly. Permission is global storage.configure, not creator ownership.\",\"operationId\":\"createStorage" + "Migration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "StorageMigrationCreate\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"" + "},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.config" + "ure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"42" + "2\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or sto" + "rage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" + "]},{\"bearerToken\":[]}],\"summary\":\"CreateStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authori" + "zation\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"versi" + "on\":1}}},\"/api/storage-migrations/{uuid}\":{\"get\":{\"description\":\"Requires global storage.configu" + "re, including for scoped tokens. Resource existence, revisions and reference/state checks still " + "apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageMigration\",\"pa" + "rameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/Cam" + "eraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"4" + "01\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource " + "missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable" + ".\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"se" + "curity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\"" + ":\"GetStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requireme" + "nts\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{" + "uuid}/cancel\":{\"post\":{\"description\":\"Global storage.configure permits acting on any job. Cancel" + " may request cancellation of an active job; retry requeues eligible failed/cancelled work. Poll " + "state to observe completion and cleanup; the acknowledgement is not a completed transfer.\",\"oper" + "ationId\":\"cancelStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"sche" + "ma\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operat" + "ion.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.c" + "onfigure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"" + "},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog o" + "r storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHead" + "er\":[]},{\"bearerToken\":[]}],\"summary\":\"CancelStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-au" + "thorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"" + "version\":1}}},\"/api/storage-migrations/{uuid}/retry\":{\"post\":{\"description\":\"Global storage.conf" + "igure permits acting on any job. Cancel may request cancellation of an active job; retry requeue" + "s eligible failed/cancelled work. Poll state to observe completion and cleanup; the acknowledgem" + "ent is not a completed transfer.\",\"operationId\":\"retryStorageMigration\",\"parameters\":[{\"in\":\"pat" + "h\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"response" + "s\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigratio" + "n\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication" + " required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"R" + "evision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage " + "operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageMigrati" + "on\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"st" + "orage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies\":{\"get\":{\"description\"" ":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions " "and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"opera" - "tionId\":\"deleteStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"" - "$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schem" - "a\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Suc" - "cessful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Gl" - "obal storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/jso" - "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or st" - "ate conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" - "},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePool\",\"tags\":[\"Storage\"],\"x-li" - "ghtnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":" - "\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for sco" - "ped tokens. Resource existence, revisions and reference/state checks still apply. Basic and lega" - "cy-key compatibility is preserved.\",\"operationId\":\"getStoragePool\",\"parameters\":[{\"in\":\"path\",\"n" - "ame\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"" - "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"des" - "cription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required." - "\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, r" - "eference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation " - "failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{" - "\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePool\",\"tags\":[\"Stor" - "age\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\"," - "\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, includ" - "ing for scoped tokens. Resource existence, revisions and reference/state checks still apply. Bas" - "ic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePool\",\"parameters\":[{" - "\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}]," - "\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool" - "Update\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid re" - "quest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target " - "unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavaila" - "ble.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}" - "],\"summary\":\"UpdateStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"" - "requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-tar" - "gets\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Res" - "ource existence, revisions and reference/state checks still apply. Basic and legacy-key compatib" - "ility is preserved.\",\"operationId\":\"listStorageTargets\",\"responses\":{\"200\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetsResponse\"}}},\"description\":\"Succ" - "essful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Glo" - "bal storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or sta" - "te conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\"" + "tionId\":\"listStoragePolicies\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/StoragePoliciesResponse\"}}},\"description\":\"Successful operation.\"},\"400\"" ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]}" - ",{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageTargets\",\"tags\":[\"Storage\"],\"x-li" - "ghtnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":" - "\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for sc" - "oped tokens. Resource existence, revisions and reference/state checks still apply. Basic and leg" - "acy-key compatibility is preserved.\",\"operationId\":\"createStorageTarget\",\"requestBody\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetCreate\"}}},\"required\"" - ":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"A" - "uthentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing." - "\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":" - "[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create" - "StorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"" - "action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}\":{\"de" - "lete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource e" - "xistence, revisions and reference/state checks still apply. Basic and legacy-key compatibility i" - "s preserved.\",\"operationId\":\"deleteStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"requ" - "ired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\"," - "\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\"" - ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"" - "}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication r" - "equired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Rev" - "ision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage op" - "eration failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCooki" - "e\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStorageTarget\"," - "\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storag" - "e.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.conf" - "igure, including for scoped tokens. Resource existence, revisions and reference/state checks sti" - "ll apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageTarget\",\"pa" - "rameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/Cam" - "eraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\"" + "n\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure den" + "ied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"S" + "torage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage se" + "rvice unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" + "arerToken\":[]}],\"summary\":\"ListStoragePolicies\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"" + "kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"p" + "ost\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource ex" + "istence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is" + " preserved.\",\"operationId\":\"createStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/StoragePolicyCreate\"}}},\"required\":true},\"responses\":{\"201" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"desc" + "ription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"" + "},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, re" + "ference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation f" + "ailed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"" + "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePolicy\",\"tags\":[\"" + "Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configu" + "re\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/preview\":{\"post\":{\"description\":\"Re" + "quires global storage.configure, including for scoped tokens. Resource existence, revisions and " + "reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operation" + "Id\":\"previewStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/StoragePolicyPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyPreview\"}}},\"description" + "\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\"" ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource mis" - "sing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"}" - ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"secur" - "ity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"G" - "etStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[" - "{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires gl" - "obal storage.configure, including for scoped tokens. Resource existence, revisions and reference" - "/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"upda" - "teStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/StorageTargetUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Success" - "ful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global" - " storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state " - "conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema" - "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"" - "apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStorageTarget\",\"tags\":[\"Storage\"],\"x-ligh" - "tnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"r" - "aw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}/probe\":{\"post\":{\"description\":\"Requires global" - " storage.configure, including for scoped tokens. Resource existence, revisions and reference/sta" - "te checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"probeSto" - "rageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid r" - "equest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target" - " unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavail" - "able.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]" - "}],\"summary\":\"ProbeStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\"" - ",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage/c" - "leanup\":{\"post\":{\"description\":\"Signals the asynchronous cleanup controller. Success acknowledge" - "s scheduling, not deletion or reclaimed bytes. Invalid bodies are rejected before scheduling. Pr" - "otected media and retention constraints still apply.\",\"operationId\":\"triggerStorageCleanup\",\"req" - "uestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCleanupR" - "equest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/StorageCleanupResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{" + "n\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference" + " or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"" + "},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" + "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"PreviewStoragePolicy\",\"tags\":[\"Storag" + "e\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"r" + "esource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/{uuid}\":{\"delete\":{\"description\":\"Require" + "s global storage.configure, including for scoped tokens. Resource existence, revisions and refer" + "ence/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"" + "deleteStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"" + "#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"ma" + "ximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful" + " operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global st" + "orage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state con" + "flict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Ca" + "talog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"api" + "KeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnv" + "r-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"" + "}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped t" + "okens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-ke" + "y compatibility is preserved.\",\"operationId\":\"getStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name" + "\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"desc" + "ription\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"" + "},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, re" + "ference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation f" + "ailed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"" + "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePolicy\",\"tags\":[\"Sto" + "rage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\"" + ",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, inclu" + "ding for scoped tokens. Resource existence, revisions and reference/state checks still apply. Ba" + "sic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePolicy\",\"parameters\"" + ":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}" + "}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageP" + "olicyUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" + "alid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"}," + "\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage " + "target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service u" + "navailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTok" + "en\":[]}],\"summary\":\"UpdateStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"" + "global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/st" + "orage-policies/{uuid}/apply\":{\"post\":{\"description\":\"Preview counts records whose captured polic" + "y revision differs. Apply updates those revision references transactionally and requires unfinis" + "hed transfers to finish first. It does not itself delete files or complete asynchronous archive/" + "migration work.\",\"operationId\":\"applyStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"re" + "quired\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyApplyRequest\"}}},\"required\"" + ":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/StoragePolicyApplyResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request." + "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Reso" + "urce missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavai" + "lable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}" + "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" + "mary\":\"ApplyStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requi" + "rements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools\":{" + "\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource e" + "xistence, revisions and reference/state checks still apply. Basic and legacy-key compatibility i" + "s preserved.\",\"operationId\":\"listStoragePools\",\"responses\":{\"200\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolsResponse\"}}},\"description\":\"Successful oper" + "ation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage" + ".configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" + "s/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict" + ".\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog" + " or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHe" + "ader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStoragePools\",\"tags\":[\"Storage\"],\"x-lightnvr-author" + "ization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"vers" + "ion\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. " + "Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compa" + "tibility is preserved.\",\"operationId\":\"createStoragePool\",\"requestBody\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolCreate\"}}},\"required\":true},\"responses" + "\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}}," + "\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requi" + "red.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revisio" + "n, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operat" + "ion failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[" + "]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePool\",\"tags\"" + ":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.conf" + "igure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools/{uuid}\":{\"delete\":{\"description\":\"R" + "equires global storage.configure, including for scoped tokens. Resource existence, revisions and" + " reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operatio" + "nId\":\"deleteStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$re" + "f\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":" + "{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Succes" + "sful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Globa" + "l storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":" + "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state" + " conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{" "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denie" - "d.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Sto" - "rage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage serv" - "ice unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bear" - "erToken\":[]}],\"summary\":\"TriggerStorageCleanup\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"" - "kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"" - "/api/storage/health\":{\"get\":{\"description\":\"Requires global storage.configure, including for sco" - "ped tokens. Resource existence, revisions and reference/state checks still apply. Basic and lega" - "cy-key compatibility is preserved.\",\"operationId\":\"getStorageHealth\",\"responses\":{\"200\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageHealthResponse\"}}},\"descri" + ":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{" + "\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePool\",\"tags\":[\"Storage\"],\"x-light" + "nvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"ra" + "w\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped" + " tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-" + "key compatibility is preserved.\",\"operationId\":\"getStoragePool\",\"parameters\":[{\"in\":\"path\",\"name" + "\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"descri" "ption\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" "ents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\"" ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"}," @@ -6177,49 +6418,402 @@ static const unsigned char lightnvr_openapi_document[] = "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation fai" "led.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" ",\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" - "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageHealth\",\"tags\":[\"Stora" - "ge\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"" - "resource\":\"raw\"}],\"version\":1}}},\"/api/streams\":{\"get\":{\"description\":\"Authenticated list discov" - "ery remains available even with no matching camera grants. Actual rows and totals are filtered p" - "er camera. Permission checks use runtime plus bounded go2rtc health observations; this can diffe" - "r from full-read raw-health authorization. This pilot describes the existing auth-disabled/demo " - "behavior only as a deployment caveat; personalized discovery itself requires a real identity.\",\"" - "operationId\":\"listStreams\",\"parameters\":[{\"description\":\"Exactly true or 1 selects a paginated s" - "ummary envelope. All other values, including absence, select the compatibility array. Other list" - " query options only apply to summary mode.\",\"in\":\"query\",\"name\":\"summary\",\"required\":false,\"sche" - "ma\":{\"default\":\"false\",\"type\":\"string\"}},{\"description\":\"Summary row shape: admin/default or liv" - "e filters live.view; lpr filters the conjunction lpr.search and lpr.read and emits only identity" - ".\",\"in\":\"query\",\"name\":\"surface\",\"required\":false,\"schema\":{\"default\":\"admin\",\"enum\":[\"admin\",\"l" - "ive\",\"lpr\"],\"type\":\"string\"}},{\"description\":\"Summary page number.\",\"in\":\"query\",\"name\":\"page\",\"" - "required\":false,\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"descrip" - "tion\":\"Summary page size.\",\"in\":\"query\",\"name\":\"page_size\",\"required\":false,\"schema\":{\"default\":" - "50,\"maximum\":100,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Case-insensitive substring over " - "authorized name, UUID, tags, location path and safe address. Handler buffer retains up to 255 by" - "tes.\",\"in\":\"query\",\"name\":\"search\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"" - "Summary sort key; equal values break ties by name.\",\"in\":\"query\",\"name\":\"sort_by\",\"required\":fal" - "se,\"schema\":{\"default\":\"name\",\"enum\":[\"name\",\"status\",\"resolution\",\"fps\",\"recording\"],\"type\":\"st" - "ring\"}},{\"description\":\"Summary sort direction.\",\"in\":\"query\",\"name\":\"sort_order\",\"required\":fal" - "se,\"schema\":{\"default\":\"asc\",\"enum\":[\"asc\",\"desc\"],\"type\":\"string\"}},{\"description\":\"Summary ava" - "ilability filter, applied before total.\",\"in\":\"query\",\"name\":\"availability\",\"required\":false,\"sc" - "hema\":{\"default\":\"all\",\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"stri" - "ng\"}},{\"description\":\"Exactly true or 1 requests admin_url for the admin summary surface. Other " - "values omit it; live/LPR surfaces never include it. Values are redacted unless that same camera " - "is configurable.\",\"in\":\"query\",\"name\":\"include_admin_url\",\"required\":false,\"schema\":{\"default\":\"" - "false\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/StreamListResponse\"}}},\"description\":\"Compatibility array or requested summa" - "ry page; empty authorized results are valid.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update valu" - "es.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive us" - "ers, CIDR or password-change restrictions.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, ser" - "ialization or update scheduling failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"" - "apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized streams\",\"tags\":[\"Streams\"],\"x-" - "lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Create a ca" - "mera using global camera.configure. Creation intent is distinct from editing an existing camera." - "\",\"operationId\":\"createStream\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/StreamCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamCreated\"}}},\"description\":\"Successf" + "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePool\",\"tags\":[\"Storage" + "\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"re" + "source\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including" + " for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic " + "and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePool\",\"parameters\":[{\"in" + "\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"re" + "questBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolUpd" + "ate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid reque" + "st.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"R" + "esource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target una" + "vailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable" + ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"" + "summary\":\"UpdateStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"req" + "uirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-target" + "s\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resour" + "ce existence, revisions and reference/state checks still apply. Basic and legacy-key compatibili" + "ty is preserved.\",\"operationId\":\"listStorageTargets\",\"responses\":{\"200\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetsResponse\"}}},\"description\":\"Success" + "ful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global" + " storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state " + "conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"" + "apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageTargets\",\"tags\":[\"Storage\"],\"x-light" + "nvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"ra" + "w\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scope" + "d tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy" + "-key compatibility is preserved.\",\"operationId\":\"createStorageTarget\",\"requestBody\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetCreate\"}}},\"required\":tr" + "ue},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/St" + "orageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Auth" + "entication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"}," + "\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"" + "sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateSto" + "rageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"act" + "ion\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}\":{\"delet" + "e\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource exis" + "tence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is p" + "reserved.\",\"operationId\":\"deleteStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"require" + "d\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"re" + "quired\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"" + "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}}" + ",\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requ" + "ired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revisi" + "on, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage opera" + "tion failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":" + "[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStorageTarget\",\"ta" + "gs\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.c" + "onfigure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configu" + "re, including for scoped tokens. Resource existence, revisions and reference/state checks still " + "apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageTarget\",\"param" + "eters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/Camera" + "Uuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missin" + "g.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"5" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security" + "\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetS" + "torageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"a" + "ction\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires globa" + "l storage.configure, including for scoped tokens. Resource existence, revisions and reference/st" + "ate checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateS" + "torageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/StorageTargetUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful" + " operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global st" + "orage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state con" + "flict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{" + "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Ca" + "talog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"api" + "KeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnv" + "r-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"" + "}],\"version\":1}}},\"/api/storage-targets/{uuid}/probe\":{\"post\":{\"description\":\"Requires global st" + "orage.configure, including for scoped tokens. Resource existence, revisions and reference/state " + "checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"probeStorag" + "eTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components" + "/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid requ" + "est.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target un" + "available.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailabl" + "e.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}]," + "\"summary\":\"ProbeStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"r" + "equirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage/clea" + "nup\":{\"post\":{\"description\":\"Signals the asynchronous cleanup controller. Success acknowledges s" + "cheduling, not deletion or reclaimed bytes. Invalid bodies are rejected before scheduling. Prote" + "cted media and retention constraints still apply.\",\"operationId\":\"triggerStorageCleanup\",\"reques" + "tBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCleanupRequ" + "est\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/StorageCleanupResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nvalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"" + "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storag" + "e target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service" + " unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerT" + "oken\":[]}],\"summary\":\"TriggerStorageCleanup\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kin" + "d\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/ap" + "i/storage/health\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped" + " tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-" + "key compatibility is preserved.\",\"operationId\":\"getStorageHealth\",\"responses\":{\"200\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageHealthResponse\"}}},\"descripti" + "on\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"40" + "3\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, referen" + "ce or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed" + ".\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basic" + "Auth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageHealth\",\"tags\":[\"Storage\"" + "],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"res" + "ource\":\"raw\"}],\"version\":1}}},\"/api/streams\":{\"get\":{\"description\":\"Authenticated list discovery" + " remains available even with no matching camera grants. Actual rows and totals are filtered per " + "camera. Permission checks use runtime plus bounded go2rtc health observations; this can differ f" + "rom full-read raw-health authorization. This pilot describes the existing auth-disabled/demo beh" + "avior only as a deployment caveat; personalized discovery itself requires a real identity.\",\"ope" + "rationId\":\"listStreams\",\"parameters\":[{\"description\":\"Exactly true or 1 selects a paginated summ" + "ary envelope. All other values, including absence, select the compatibility array. Other list qu" + "ery options only apply to summary mode.\",\"in\":\"query\",\"name\":\"summary\",\"required\":false,\"schema\"" + ":{\"default\":\"false\",\"type\":\"string\"}},{\"description\":\"Summary row shape: admin/default or live f" + "ilters live.view; lpr filters the conjunction lpr.search and lpr.read and emits only identity.\"," + "\"in\":\"query\",\"name\":\"surface\",\"required\":false,\"schema\":{\"default\":\"admin\",\"enum\":[\"admin\",\"live" + "\",\"lpr\"],\"type\":\"string\"}},{\"description\":\"Summary page number.\",\"in\":\"query\",\"name\":\"page\",\"req" + "uired\":false,\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"descriptio" + "n\":\"Summary page size.\",\"in\":\"query\",\"name\":\"page_size\",\"required\":false,\"schema\":{\"default\":50," + "\"maximum\":100,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Case-insensitive substring over aut" + "horized name, UUID, tags, location path and safe address. Handler buffer retains up to 255 bytes" + ".\",\"in\":\"query\",\"name\":\"search\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Sum" + "mary sort key; equal values break ties by name.\",\"in\":\"query\",\"name\":\"sort_by\",\"required\":false," + "\"schema\":{\"default\":\"name\",\"enum\":[\"name\",\"status\",\"resolution\",\"fps\",\"recording\"],\"type\":\"strin" + "g\"}},{\"description\":\"Summary sort direction.\",\"in\":\"query\",\"name\":\"sort_order\",\"required\":false," + "\"schema\":{\"default\":\"asc\",\"enum\":[\"asc\",\"desc\"],\"type\":\"string\"}},{\"description\":\"Summary availa" + "bility filter, applied before total.\",\"in\":\"query\",\"name\":\"availability\",\"required\":false,\"schem" + "a\":{\"default\":\"all\",\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"" + "}},{\"description\":\"Exactly true or 1 requests admin_url for the admin summary surface. Other val" + "ues omit it; live/LPR surfaces never include it. Values are redacted unless that same camera is " + "configurable.\",\"in\":\"query\",\"name\":\"include_admin_url\",\"required\":false,\"schema\":{\"default\":\"fal" + "se\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/StreamListResponse\"}}},\"description\":\"Compatibility array or requested summary " + "page; empty authorized results are valid.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values." + "\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"No usable authenticated credential, including expired/revoked tokens, inactive users" + ", CIDR or password-change restrictions.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serial" + "ization or update scheduling failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"api" + "KeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized streams\",\"tags\":[\"Streams\"],\"x-lig" + "htnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Create a camer" + "a using global camera.configure. Creation intent is distinct from editing an existing camera.\",\"" + "operationId\":\"createStream\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/StreamCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamCreated\"}}},\"description\":\"Successful " + "response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication " + "credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavai" + "lable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, s" + "cheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temp" + "orarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"" + "bearerToken\":[]}],\"summary\":\"Create a camera using global camera.configure. Creation intent is d" + "istinct from editing an existing camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":" + "\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/st" + "reams/test\":{\"post\":{\"description\":\"Probe an administrator-supplied media URL. A failed media pr" + "obe returns 200 with success=false; it does not create a camera.\",\"operationId\":\"probeStream\",\"r" + "equestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamProbeRe" + "quest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/StreamProbeResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inva" + "lid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Ac" + "tion or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current" + " state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal fai" + "lure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"" + "security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summar" + "y\":\"Probe an administrator-supplied media URL. A failed media probe returns 200 with success=fal" + "se; it does not create a camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"global\"" + ",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{n" + "ame}\":{\"delete\":{\"description\":\"Queue camera deletion using captured identity. Acknowledgment do" + "es not prove completion. Default disables the camera; permanent=true removes its configuration.\"" + ",\"operationId\":\"deleteStream\",\"parameters\":[{\"description\":\"Exactly true requests permanent dele" + "tion; absence or another value disables.\",\"in\":\"query\",\"name\":\"permanent\",\"schema\":{\"type\":\"stri" + "ng\"}}],\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/StreamDeleteAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutat" + "ion is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera deni" + "ed. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity " + "conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" + "ents/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Up" + "stream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessio" + "nCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Queue camera de" + "letion using captured identity. Acknowledgment does not prove completion. Default disables the c" + "amera; permanent=true removes its configuration.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":" + "{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"" + "get\":{\"operationId\":\"getStream\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/StreamSingleResponse\"}}},\"description\":\"Successful response.\"},\"400\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"C" + "urrent state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or intern" + "al failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"s" + "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable" + ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"" + "summary\":\"getStream\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirement" + "s\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current" + " stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embe" + "dded slash is unsupported by the full-read suffix parser; normal creation restricts names to let" + "ters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is " + "not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLeng" + "th\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"description\":\"Requires camera.configure on the " + "named camera using raw DB facts; no read permission is implied. Boolean set_privacy_mode selects" + " a privacy-only request and rejects accompanying configuration fields. Ordinary updates are part" + "ial; enable_disabled=true with enabled=false is rejected. Unknown ordinary fields are ignored, a" + "nd 202 is not a durable-success guarantee. A 502 privacy response can follow a saved flag when g" + "o2rtc removal fails; retry is required and the stream may remain viewable in the meantime.\",\"ope" + "rationId\":\"updateStream\",\"requestBody\":{\"content\":{\"application/json\":{\"examples\":{\"privacy\":{\"v" + "alue\":{\"set_privacy_mode\":true}},\"zeroRetention\":{\"value\":{\"detection_threshold\":0,\"retention_da" + "ys\":0}}},\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateRequest\"}}},\"required\":true},\"respon" + "ses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamPrivacy" + "Updated\"}}},\"description\":\"Privacy-only update completed.\"},\"202\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateAccepted\"}}},\"description\":\"Ordinary update" + " accepted for asynchronous application.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"}" + ",\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, " + "CIDR or password-change restrictions.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"The authenticated principal cannot perform the " + "action on this camera.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"No runtime stream with this name exists. Existing stream handl" + "ers may resolve this before checking credentials.\"},\"413\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"An explicitly bounded override exce" + "eds its supported byte size.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or " + "update scheduling failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Privacy flag was saved, but removing the stream from go2rt" + "c failed. The camera may still be viewable; retry the privacy-only request. This error is not a " + "rollback of the saved privacy flag.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKe" + "yHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update stream configuration or privacy\",\"tags\":[\"Str" + "eams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\"," + "\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/daynight\":{\"get\":{\"operationId\":\"getDayni" + "ght\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/D" + "aynightResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly" + " optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operatio" + "n.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"40" + "3\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"In" + "valid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid in" + "put, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, den" + "ied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acce" + "ss, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" + "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getDaynight\",\"tags\":[\"Device controls" + "\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":" + "\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLen" + "gth\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setDaynight\",\"requestBody\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightRequest\"}}},\"required\"" + ":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/DaynightUpdated\"}}},\"description\":\"Device result; optional device capabilities remain explicitl" + "y optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operati" + "on.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"4" + "03\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nvalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid i" + "nput, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, de" + "nied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acc" + "ess, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicA" + "uth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setDaynight\",\"tags\":[\"Device control" + "s\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"re" + "source\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/detection-engines\":{\"get\":{\"operationId\":\"ge" + "tStreamDetectionEngines\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successful response.\"},\"400\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" + "alid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"A" + "ction or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Curren" + "t state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal fa" + "ilure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}}," + "\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summa" + "ry\":\"getStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"" + "requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"des" + "cription\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255" + " UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation rest" + "ricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require " + "encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true," + "\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setStreamDetect" + "ionEngines\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/StreamDetectionEnginesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successf" "ul response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" "rror\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/" "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticati" @@ -6233,649 +6827,595 @@ static const unsigned char lightnvr_openapi_document[] = "mponents/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{" "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service t" "emporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]}" - ",{\"bearerToken\":[]}],\"summary\":\"Create a camera using global camera.configure. Creation intent i" - "s distinct from editing an existing camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kin" - "d\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api" - "/streams/test\":{\"post\":{\"description\":\"Probe an administrator-supplied media URL. A failed media" - " probe returns 200 with success=false; it does not create a camera.\",\"operationId\":\"probeStream\"" - ",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamProb" - "eRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/StreamProbeResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" - "nvalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Curr" - "ent state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal " - "failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}" - "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" - "mary\":\"Probe an administrator-supplied media URL. A failed media probe returns 200 with success=" - "false; it does not create a camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"glob" - "al\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams" - "/{name}\":{\"delete\":{\"description\":\"Queue camera deletion using captured identity. Acknowledgment" - " does not prove completion. Default disables the camera; permanent=true removes its configuratio" - "n.\",\"operationId\":\"deleteStream\",\"parameters\":[{\"description\":\"Exactly true requests permanent d" - "eletion; absence or another value disables.\",\"in\":\"query\",\"name\":\"permanent\",\"schema\":{\"type\":\"s" - "tring\"}}],\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/StreamDeleteAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mu" - "tation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera d" - "enied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capaci" - "ty conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" - "ponents/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"ses" - "sionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Queue camera" - " deletion using captured identity. Acknowledgment does not prove completion. Default disables th" - "e camera; permanent=true removes its configuration.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorizatio" - "n\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}" - "},\"get\":{\"operationId\":\"getStream\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/StreamSingleResponse\"}}},\"description\":\"Successful response.\"},\"400" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"4" - "03\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or int" - "ernal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":" - "{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavaila" - "ble.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}" - "],\"summary\":\"getStream\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirem" - "ents\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Curr" - "ent stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. E" - "mbedded slash is unsupported by the full-read suffix parser; normal creation restricts names to " - "letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID " - "is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxL" - "ength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"description\":\"Requires camera.configure on t" - "he named camera using raw DB facts; no read permission is implied. Boolean set_privacy_mode sele" - "cts a privacy-only request and rejects accompanying configuration fields. Ordinary updates are p" - "artial; enable_disabled=true with enabled=false is rejected. Unknown ordinary fields are ignored" - ", and 202 is not a durable-success guarantee. A 502 privacy response can follow a saved flag whe" - "n go2rtc removal fails; retry is required and the stream may remain viewable in the meantime.\",\"" - "operationId\":\"updateStream\",\"requestBody\":{\"content\":{\"application/json\":{\"examples\":{\"privacy\":" - "{\"value\":{\"set_privacy_mode\":true}},\"zeroRetention\":{\"value\":{\"detection_threshold\":0,\"retention" - "_days\":0}}},\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateRequest\"}}},\"required\":true},\"res" - "ponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamPriv" - "acyUpdated\"}}},\"description\":\"Privacy-only update completed.\"},\"202\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateAccepted\"}}},\"description\":\"Ordinary upd" - "ate accepted for asynchronous application.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values" - ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"No usable authenticated credential, including expired/revoked tokens, inactive user" - "s, CIDR or password-change restrictions.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"The authenticated principal cannot perform t" - "he action on this camera.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"No runtime stream with this name exists. Existing stream ha" - "ndlers may resolve this before checking credentials.\"},\"413\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"An explicitly bounded override e" - "xceeds its supported byte size.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization " - "or update scheduling failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Privacy flag was saved, but removing the stream from go" - "2rtc failed. The camera may still be viewable; retry the privacy-only request. This error is not" - " a rollback of the saved privacy flag.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"ap" - "iKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update stream configuration or privacy\",\"tags\":[\"" - "Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configur" - "e\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/daynight\":{\"get\":{\"operationId\":\"getDa" - "ynight\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/DaynightResponse\"}}},\"description\":\"Device result; optional device capabilities remain explici" - "tly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device opera" - "tion.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}," - "\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" - " input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, " - "denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied a" - "ccess, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basi" - "cAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getDaynight\",\"tags\":[\"Device contr" - "ols\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resourc" - "e\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"max" - "Length\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setDaynight\",\"requestBody\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightRequest\"}}},\"requir" - "ed\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/DaynightUpdated\"}}},\"description\":\"Device result; optional device capabilities remain explic" + ",{\"bearerToken\":[]}],\"summary\":\"setStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-author" + "ization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"versi" + "on\":1}}},\"/api/streams/{name}/full\":{\"get\":{\"description\":\"Requires live.view on this camera usi" + "ng raw DB facts. camera.configure on the same camera using runtime facts determines sensitive fi" + "elds. A configure-only grant does not imply read access.\",\"operationId\":\"getStreamFull\",\"respons" + "es\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamFullResp" + "onse\"}}},\"description\":\"Wrapped details with per-camera conditional redaction.\"},\"400\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" + "d path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including e" + "xpired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"403\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"The auth" + "enticated principal cannot perform the action on this camera.\"},\"404\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No runtime stream with " + "this name exists. Existing stream handlers may resolve this before checking credentials.\"},\"500\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" + "n\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"}},\"secur" + "ity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"R" + "ead full stream configuration\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"r" + "equirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description" + "\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 b" + "ytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts na" + "mes to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding" + ". UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\"" + ":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/imaging/options\":{\"get\"" + ":{\"operationId\":\"getImagingOptions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ImagingOptionsResponse\"}}},\"description\":\"Device result; optional " + "device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\"" + ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavail" + "able device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable dev" + "ice or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or f" + "ailed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed de" + "vice operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device ope" + "ration.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" + "}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"" + "},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"secu" + "rity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"" + "getImagingOptions\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requi" + "rements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"nam" + "e\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/stre" + "ams/{name}/imaging/settings\":{\"get\":{\"operationId\":\"getImagingSettings\",\"responses\":{\"200\":{\"con" + "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsResponse\"}}},\"d" + "escription\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " + "input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, d" + "enied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/js" + "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied ac" + "cess, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, un" + "available device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"" + "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailabl" + "e device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device" + " or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" + "\":[]},{\"bearerToken\":[]}],\"summary\":\"getImagingSettings\",\"tags\":[\"Device controls\"],\"x-lightnvr-" + "authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"versio" + "n\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLe" + "ngth\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setImagingSettings\",\"requestBody\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsRequest\"}}},\"required\":" + "true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" + "DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explic" "itly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" "iError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device oper" "ation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" "}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}" - ",\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{" + ",\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{" + "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" + ":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" + "d input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input," + " denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied " + "access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"bas" + "icAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setImagingSettings\",\"tags\":[\"Devi" + "ce controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.conf" + "igure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/absolute\":{\"parameters\":[{\"in\"" + ":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]" + ",\"post\":{\"operationId\":\"absolutePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" + "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"D" + "evice result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input," + " denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/" + "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied " + "access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, " + "unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavaila" + "ble device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable devi" + "ce or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fa" + "iled device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed dev" + "ice operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearer" + "Token\":[]}],\"summary\":\"absolutePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind" + "\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/strea" + "ms/{name}/ptz/capabilities\":{\"get\":{\"operationId\":\"getPtzCapabilities\",\"responses\":{\"200\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzCapabilities\"}}},\"descriptio" + "n\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid i" + "nput, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, de" + "nied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acc" + "ess, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, una" + "vailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable" + " device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device " + "or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or faile" + "d device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"b" + "earerToken\":[]}],\"summary\":\"getPtzCapabilities\",\"tags\":[\"Device controls\"],\"x-lightnvr-authoriza" + "tion\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"p" + "arameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"" + "type\":\"string\"}}]},\"/api/streams/{name}/ptz/goto-preset\":{\"parameters\":[{\"in\":\"path\",\"name\":\"nam" + "e\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operation" + "Id\":\"gotoPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/PtzGotoPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device resul" + "t; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acc" + "ess, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, una" + "vailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable" + " device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device " + "or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or faile" + "d device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device" + " operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operati" + "on.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}]" + ",\"summary\":\"gotoPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera" + "\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}" + "/ptz/home\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"" + "minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"homePtz\",\"requestBody\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"r" + "esponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCo" + "mmandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly opt" + "ional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}" + ",\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{" "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content" + ":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content" "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"appl" + "d input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"appl" "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input," - " denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/" + " denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/" "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied " - "access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"bas" - "icAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setDaynight\",\"tags\":[\"Device cont" - "rols\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\"," - "\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/detection-engines\":{\"get\":{\"operationId\":" - "\"getStreamDetectionEngines\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successful response.\"},\"400\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{" + "access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, " + "unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":" + "[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"homePtz\",\"tags\":[\"Device controls\"],\"x-li" + "ghtnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}" + "],\"version\":1}}},\"/api/streams/{name}/ptz/move\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"requi" + "red\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"move" + "Ptz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxe" + "sRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device ca" + "pabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable devi" + "ce or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fa" + "iled device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed dev" + "ice operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" + "iError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device oper" + "ation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}" + ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{" "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"sche" - "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Cur" - "rent state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal" - " failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"" - "}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"su" - "mmary\":\"getStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera" - "\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"" - "description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to " - "255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation r" - "estricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may requi" - "re encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":tr" - "ue,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setStreamDet" - "ectionEngines\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/StreamDetectionEnginesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Succe" - "ssful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentic" - "ation credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera " - "unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Datab" - "ase, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Servic" - "e temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":" - "[]},{\"bearerToken\":[]}],\"summary\":\"setStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-aut" - "horization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"ve" - "rsion\":1}}},\"/api/streams/{name}/full\":{\"get\":{\"description\":\"Requires live.view on this camera " - "using raw DB facts. camera.configure on the same camera using runtime facts determines sensitive" - " fields. A configure-only grant does not imply read access.\",\"operationId\":\"getStreamFull\",\"resp" - "onses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamFullR" - "esponse\"}}},\"description\":\"Wrapped details with per-camera conditional redaction.\"},\"400\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, includin" - "g expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"403\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"The a" - "uthenticated principal cannot perform the action on this camera.\"},\"404\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No runtime stream wi" - "th this name exists. Existing stream handlers may resolve this before checking credentials.\"},\"5" - "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"}},\"se" - "curity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\"" - ":\"Read full stream configuration\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\"" - ",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"descript" - "ion\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-" - "8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts" - " names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encod" - "ing. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"sche" - "ma\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/imaging/options\":{\"g" - "et\":{\"operationId\":\"getImagingOptions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ImagingOptionsResponse\"}}},\"description\":\"Device result; option" - "al device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"sche" + ":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"" + "sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"movePtz\"," + "\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\"" + ":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/preset\":{\"parameters\":" + "[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"stri" + "ng\"}}],\"put\":{\"operationId\":\"setPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/PtzPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresetResult\"}}},\"description" + "\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid in" + "put, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, den" + "ied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acce" + "ss, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"sche" "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unav" - "ailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$r" "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable " - "device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" "omponents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device o" - "r failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "r failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" "s/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed" - " device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device " - "operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" - "or\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operatio" - "n.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"s" - "ecurity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary" - "\":\"getImagingOptions\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"re" - "quirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"" - "name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/s" - "treams/{name}/imaging/settings\":{\"get\":{\"operationId\":\"getImagingSettings\",\"responses\":{\"200\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsResponse\"}}}" - ",\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inval" - "id input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input" - ", denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application" - "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied" - " access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access," - " unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavail" - "able device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable dev" - "ice or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHea" - "der\":[]},{\"bearerToken\":[]}],\"summary\":\"getImagingSettings\",\"tags\":[\"Device controls\"],\"x-lightn" - "vr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"ver" - "sion\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"mi" - "nLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setImagingSettings\",\"requestBody\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsRequest\"}}},\"require" - "d\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain exp" - "licitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" - "/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device o" - "peration.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation" - ".\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid inp" - "ut, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"applicati" - "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, deni" - "ed access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"" - "basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setImagingSettings\",\"tags\":[\"D" - "evice controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.c" - "onfigure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/absolute\":{\"parameters\":[{\"" - "in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"" - "}}],\"post\":{\"operationId\":\"absolutePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{" - "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\"" - ":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid inp" - "ut, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"applicati" + " device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"be" + "arerToken\":[]}],\"summary\":\"setPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{" + "\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/" + "streams/{name}/ptz/presets\":{\"get\":{\"operationId\":\"getPtzPresets\",\"responses\":{\"200\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresets\"}}},\"description\":\"Device" + " result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"applicati" "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, deni" - "ed access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\"" + "ed access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\"" ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acces" - "s, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schem" + "s, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schem" "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unava" - "ilable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "ilable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$re" "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable d" - "evice or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "evice or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" "mponents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or" - " failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + " failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" "/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed " - "device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bea" - "rerToken\":[]}],\"summary\":\"absolutePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"k" - "ind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/st" - "reams/{name}/ptz/capabilities\":{\"get\":{\"operationId\":\"getPtzCapabilities\",\"responses\":{\"200\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzCapabilities\"}}},\"descrip" - "tion\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" - "d input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input," - " denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied " - "access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, " - "unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavaila" - "ble device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable devi" - "ce or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fa" - "iled device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]}," - "{\"bearerToken\":[]}],\"summary\":\"getPtzCapabilities\",\"tags\":[\"Device controls\"],\"x-lightnvr-author" - "ization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}" - ",\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":" - "1,\"type\":\"string\"}}]},\"/api/streams/{name}/ptz/goto-preset\":{\"parameters\":[{\"in\":\"path\",\"name\":\"" - "name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operat" - "ionId\":\"gotoPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/PtzGotoPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"applicatio" - "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device re" - "sult; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied " - "access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, " - "unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" - "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavaila" - "ble device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable devi" - "ce or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fa" - "iled device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed dev" - "ice operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device oper" - "ation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[" - "]}],\"summary\":\"gotoPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"cam" - "era\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{na" - "me}/ptz/home\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":25" - "5,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"homePtz\",\"requestBody\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false}" - ",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Devic" - "eCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly " - "optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device o" + "peration.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken" + "\":[]}],\"summary\":\"getPtzPresets\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"" + "camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"i" + "n\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}" + "}]},\"/api/streams/{name}/ptz/relative\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true" + ",\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"relativePtz\"," + "\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequ" + "est\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabil" + "ities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or" + " failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed " + "device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device o" + "peration.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" "r\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation" - ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403" + ".\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500" "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"cont" + "on\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" + "alid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessi" + "onCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"relativePtz\",\"" + "tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":" + "\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/set-home\":{\"parameters\"" + ":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"str" + "ing\"}}],\"post\":{\"operationId\":\"setPtzHome\",\"requestBody\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"desc" + "ription\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"cont" "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"a" + "alid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"a" "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid inp" - "ut, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"applicati" + "ut, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"applicati" "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, deni" - "ed access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\"" + "ed access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\"" ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied acces" - "s, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAut" - "h\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"homePtz\",\"tags\":[\"Device controls\"],\"x" - "-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"ra" - "w\"}],\"version\":1}}},\"/api/streams/{name}/ptz/move\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"re" - "quired\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"m" - "ovePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ptz" - "AxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device" - " capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "s, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unava" + "ilable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$re" "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable d" - "evice or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "evice or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" "mponents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or" - " failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + " failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[" + "]},{\"bearerToken\":[]}],\"summary\":\"setPtzHome\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorizati" + "on\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"" + "/api/streams/{name}/ptz/stop\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\"" + ":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"stopPtz\",\"requestBody\"" + ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"" + "required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities re" + "main explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" "/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed " - "device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" "/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device o" - "peration.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "peration.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" "r\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation" - ".\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502" + ".\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409" "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":" - "[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"movePt" - "z\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"acti" - "on\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/preset\":{\"parameter" - "s\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"s" - "tring\"}}],\"put\":{\"operationId\":\"setPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/PtzPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresetResult\"}}},\"descript" - "ion\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" - " input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, " - "denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied a" - "ccess, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, u" - "navailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailab" - "le device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable devic" - "e or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fai" - "led device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{" - "\"bearerToken\":[]}],\"summary\":\"setPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization" - "\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/a" - "pi/streams/{name}/ptz/presets\":{\"get\":{\"operationId\":\"getPtzPresets\",\"responses\":{\"200\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresets\"}}},\"description\":\"Dev" - "ice result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, d" - "enied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied ac" - "cess, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, un" - "available device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailabl" - "e device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device" - " or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fail" - "ed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed devic" - "e operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTo" - "ken\":[]}],\"summary\":\"getPtzPresets\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind" - "\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[" - "{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"strin" - "g\"}}]},\"/api/streams/{name}/ptz/relative\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":t" - "rue,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"relativePt" - "z\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesR" - "equest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capa" - "bilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device" - " or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fail" - "ed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed devic" - "e operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operat" - "ion.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"" - "500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" - "ption\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"se" - "ssionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"relativePtz" - "\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"actio" - "n\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/set-home\":{\"paramete" - "rs\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"" - "string\"}}],\"post\":{\"operationId\":\"setPtzHome\",\"requestBody\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"d" - "escription\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " - "input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, d" - "enied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied ac" - "cess, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, un" - "available device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailabl" - "e device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device" - " or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader" - "\":[]},{\"bearerToken\":[]}],\"summary\":\"setPtzHome\",\"tags\":[\"Device controls\"],\"x-lightnvr-authoriz" - "ation\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}" - "},\"/api/streams/{name}/ptz/stop\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"sche" - "ma\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"stopPtz\",\"requestBo" - "dy\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}" - "},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities" - " remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or fail" - "ed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed devic" - "e operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operat" - "ion.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"" - "409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" - "ption\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"c" - "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" - "Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " - "input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCoo" - "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"stopPtz\",\"tags\":[\"D" - "evice controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.cont" - "rol\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/recording\":{\"get\":{\"operationId\":\"ge" - "tStreamRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/StreamRecordingState\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid inpu" - "t. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or " - "camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state o" - "r capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"}," - "\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security" - "\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getS" - "treamRecording\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{" - "\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stre" - "am name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded " - "slash is unsupported by the full-read suffix parser; normal creation restricts names to letters," - " digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not a" - "ccepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":2" - "55,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Start manual recording (202 starting)" - " or stop it (200 idle). Continuous scheduled recording cannot be manually stopped. Follow the re" - "cording state to observe completion.\",\"operationId\":\"controlStreamRecording\",\"requestBody\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingRequest\"}}},\"re" - "quired\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/StreamRecordingChanged\"}}},\"description\":\"Successful response.\"},\"202\":{\"content\":{\"appl" - "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingChanged\"}}},\"description\":\"" - "Recording start accepted; not a durable recording guarantee.\"},\"400\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutati" - "on is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denie" - "d. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity c" - "onflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Ups" - "tream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"session" - "Cookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start manual rec" - "ording (202 starting) or stop it (200 idle). Continuous scheduled recording cannot be manually s" - "topped. Follow the recording state to observe completion.\",\"tags\":[\"Streams\"],\"x-lightnvr-author" + "on\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"cont" + "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" + "alid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid inp" + "ut, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie" + "\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"stopPtz\",\"tags\":[\"Devi" + "ce controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control" + "\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/recording\":{\"get\":{\"operationId\":\"getSt" + "reamRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/StreamRecordingState\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. " + "No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or cam" + "era denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or c" + "apacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"50" + "2\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[" + "{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStre" + "amRecording\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"ac" + "tion\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream " + "name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded sla" + "sh is unsupported by the full-read suffix parser; normal creation restricts names to letters, di" + "gits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not acce" + "pted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255," + "\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Start manual recording (202 starting) or" + " stop it (200 idle). Continuous scheduled recording cannot be manually stopped. Follow the recor" + "ding state to observe completion.\",\"operationId\":\"controlStreamRecording\",\"requestBody\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingRequest\"}}},\"requi" + "red\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/StreamRecordingChanged\"}}},\"description\":\"Successful response.\"},\"202\":{\"content\":{\"applica" + "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingChanged\"}}},\"description\":\"Rec" + "ording start accepted; not a durable recording guarantee.\"},\"400\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation " + "is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. " + "No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conf" + "licts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstre" + "am media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" + "schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCoo" + "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start manual record" + "ing (202 starting) or stop it (200 idle). Continuous scheduled recording cannot be manually stop" + "ped. Follow the recording state to observe completion.\",\"tags\":[\"Streams\"],\"x-lightnvr-authoriza" + "tion\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\"" + ":1}}},\"/api/streams/{name}/refresh\":{\"parameters\":[{\"description\":\"Current stream name, percent-" + "encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsuppor" + "ted by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyp" + "hen and underscore. Legacy configured names may require encoding. UUID is not accepted in place " + "of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1," + "\"type\":\"string\"}}],\"post\":{\"description\":\"Queue refresh of the captured camera identity. A respo" + "nse may coalesce with an existing refresh; it does not prove a successful camera connection.\",\"o" + "perationId\":\"refreshStream\",\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/StreamRefreshAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"I" + "nvalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"" + "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" + "\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Curr" + "ent state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal " + "failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}" + "},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"sum" + "mary\":\"Queue refresh of the captured camera identity. A response may coalesce with an existing r" + "efresh; it does not prove a successful camera connection.\",\"tags\":[\"Streams\"],\"x-lightnvr-author" "ization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"versi" - "on\":1}}},\"/api/streams/{name}/refresh\":{\"parameters\":[{\"description\":\"Current stream name, perce" - "nt-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsup" - "ported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, " - "hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in pla" - "ce of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\"" - ":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Queue refresh of the captured camera identity. A re" - "sponse may coalesce with an existing refresh; it does not prove a successful camera connection.\"" - ",\"operationId\":\"refreshStream\",\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/StreamRefreshAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{" + "on\":1}}},\"/api/streams/{name}/retention\":{\"get\":{\"operationId\":\"getStreamRetention\",\"responses\":" + "{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"}}" + "},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is appli" + "ed.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the opera" + "tion.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json" + "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service fai" + "led.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth" + "\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamRetention\",\"tags\":[\"Streams\"]," + "\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resour" + "ce\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as " + "one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the " + "full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and und" + "erscore. Legacy configured names may require encoding. UUID is not accepted in place of the name" + ".\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"str" + "ing\"}}],\"put\":{\"operationId\":\"setStreamRetention\",\"requestBody\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/StreamRetentionRequest\"}}},\"required\":true},\"responses\":{" + "\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"}}}" + ",\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{" "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\"" + ":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applie" + "d.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" + "description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" + "mponents/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operat" + "ion.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" + ",\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service fail" + "ed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\"" + ":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setStreamRetention\",\"tags\":[\"Streams\"],\"" + "x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resourc" + "e\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/zones\":{\"delete\":{\"operationId\":\"deleteStreamZone" + "s\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Str" + "eamZonesDeleted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is" + " applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" + "r\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No" + " mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"sc" + "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity confli" + "cts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" + "chemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream" + " media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" + "hemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCooki" + "e\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"deleteStreamZones\",\"t" + "ags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.c" + "onfigure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"operationId\":\"getStreamZones\",\"responses\":{\"2" + "00\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZones\"}}},\"desc" + "ription\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No u" + "sable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/com" + "ponents/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"" + "404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"}" + ",\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"sch" + "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"}," + "\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{" + "\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr" + "-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"versi" + "on\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. T" + "he runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix p" + "arser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy co" + "nfigured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"na" + "me\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"o" + "perationId\":\"saveStreamZones\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/StreamZonesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesSaved\"}}},\"description\":\"Success" + "ful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticat" + "ion credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera un" + "available.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databas" + "e, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":" + "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service " + "temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]" + "},{\"bearerToken\":[]}],\"summary\":\"saveStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":" + "{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}}," + "\"/api/system\":{\"get\":{\"operationId\":\"getSystemInformationLegacy\",\"responses\":{\"200\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"" + "Successful response. Process-control responses acknowledge a request; verify eventual completion" + " independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" + "piError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$" + "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requi" + "red authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily u" + "navailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTok" + "en\":[]}],\"summary\":\"Get system information legacy\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":" + "{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/ap" + "i/system/backup\":{\"post\":{\"operationId\":\"createSystemBackup\",\"responses\":{\"200\":{\"content\":{\"app" + "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemBackupCreated\"}}},\"description\":\"Su" + "ccessful response. Process-control responses acknowledge a request; verify eventual completion i" + "ndependently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" + "Error\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Require" + "d authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/j" + "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily una" + "vailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken" + "\":[]}],\"summary\":\"Create system backup\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"gl" + "obal\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/go" + "2rtc/effective-config\":{\"get\":{\"operationId\":\"getGo2rtcEffectiveConfiguration\",\"responses\":{\"200" + "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcEffectiveConfigur" + "ation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; v" + "erify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applic" + "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication r" + "equired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" + "\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\"" ":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descriptio" - "n\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"C" - "urrent state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or intern" - "al failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable" - ".\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"" - "summary\":\"Queue refresh of the captured camera identity. A response may coalesce with an existin" - "g refresh; it does not prove a successful camera connection.\",\"tags\":[\"Streams\"],\"x-lightnvr-aut" - "horization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"ve" - "rsion\":1}}},\"/api/streams/{name}/retention\":{\"get\":{\"operationId\":\"getStreamRetention\",\"response" - "s\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention" - "\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"40" - "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is ap" - "plied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the op" - "eration.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/j" - "son\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service " - "failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicA" - "uth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamRetention\",\"tags\":[\"Streams" - "\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"res" - "ource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded " - "as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by t" - "he full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and " - "underscore. Legacy configured names may require encoding. UUID is not accepted in place of the n" - "ame.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"" - "string\"}}],\"put\":{\"operationId\":\"setStreamRetention\",\"requestBody\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetentionRequest\"}}},\"required\":true},\"responses" - "\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"" - "}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401" - "\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descripti" - "on\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is app" - "lied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" - "},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" - "/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the ope" - "ration.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"" - "}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service f" - "ailed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" - "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setStreamRetention\",\"tags\":[\"Streams\"" - "],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"reso" - "urce\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/zones\":{\"delete\":{\"operationId\":\"deleteStreamZ" - "ones\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "StreamZonesDeleted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation" - " is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" - "rror\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied." - " No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{" - "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity con" - "flicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" - "s/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstr" - "eam media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCo" - "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"deleteStreamZones\"" - ",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camer" - "a.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"operationId\":\"getStreamZones\",\"responses\":" - "{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZones\"}}},\"d" - "escription\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"N" - "o usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" - "components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"" - "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation" - ".\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"" - "schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed." - "\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]" - "},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamZones\",\"tags\":[\"Streams\"],\"x-light" - "nvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"ve" - "rsion\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment" - ". The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffi" - "x parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy" - " configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\"," - "\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":" - "{\"operationId\":\"saveStreamZones\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/StreamZonesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesSaved\"}}},\"description\":\"Succ" - "essful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" - "ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenti" - "cation credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" - "s/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera" - " unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Data" - "base, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"conten" - "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Servi" - "ce temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\"" - ":[]},{\"bearerToken\":[]}],\"summary\":\"saveStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorizatio" - "n\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}" - "}},\"/api/system\":{\"get\":{\"operationId\":\"getSystemInformationLegacy\",\"responses\":{\"200\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description" + "n\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiK" + "eyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc effective configuration\",\"tags\":[\"System" + "\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resourc" + "e\":\"raw\"}],\"version\":1}}},\"/api/system/go2rtc/override-status\":{\"get\":{\"operationId\":\"getGo2rtcO" + "verrideDiagnostics\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/Go2rtcOverrideDiagnostics\"}}},\"description\":\"Successful response. Process-control " + "responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"" + "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid re" + "quest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"conte" + "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Serv" + "er or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/component" + "s/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionC" + "ookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc overri" + "de diagnostics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"" + "action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/health\":{\"get\":{\"operation" + "Id\":\"getHostHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/HostHealth\"}}},\"description\":\"Successful response. Process-control responses ackno" + "wledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persisten" + "ce failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" + "ror\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" + "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get host health\",\"tags\":[\"System" + "\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resourc" + "e\":\"raw\"}],\"version\":1}}},\"/api/system/health/incidents\":{\"get\":{\"operationId\":\"getHostHealthInc" + "idents\",\"parameters\":[{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"maximum\":100,\"minimum\":1,\"type\":\"i" + "nteger\"}},{\"in\":\"query\",\"name\":\"include_closed\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Opa" + "que versioned cursor from the preceding page; malformed cursors return 400.\",\"in\":\"query\",\"name\"" + ":\"cursor\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/HostHealthIncidents\"}}},\"description\":\"Successful response. Pro" + "cess-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":" + "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" + "\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" + "as/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"}," + "\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" + "iption\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" + ":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security" + "\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get " + "host health incidents\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requiremen" + "ts\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/info\":{\"get\":{\"oper" + "ationId\":\"getSystemInformation\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"Successful response. Process-cont" + "rol responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invali" + "d request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":" + "{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"c" + "ontent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"" + "Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" + "nents/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sess" + "ionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system in" + "formation\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"actio" + "n\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/logs\":{\"get\":{\"operationId\":\"ge" + "tSystemLogs\",\"parameters\":[{\"in\":\"query\",\"name\":\"level\",\"schema\":{\"type\":\"string\"}},{\"in\":\"query" + "\",\"name\":\"count\",\"schema\":{\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"last_ts\",\"schema\":{\"type\":\"s" + "tring\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" + "mas/SystemLogs\"}}},\"description\":\"Successful response. Process-control responses acknowledge a r" + "equest; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schem" + "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authent" + "ication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas" + "/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{" + "\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure" + ".\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[" + "]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system logs\",\"tags\":[\"System\"],\"x-ligh" + "tnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}]" + ",\"version\":1}}},\"/api/system/logs/clear\":{\"post\":{\"operationId\":\"clearSystemLogs\",\"responses\":{\"" + "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted" + "\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify" + " eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/" + "components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application" + "/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication requir" + "ed.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"co" + "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"S" + "ervice temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHea" + "der\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear system logs\",\"tags\":[\"System\"],\"x-lightnvr-authori" + "zation\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1" + "}}},\"/api/system/restart\":{\"post\":{\"operationId\":\"restartSystem\",\"responses\":{\"200\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description" "\":\"Successful response. Process-control responses acknowledge a request; verify eventual complet" "ion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schema" "s/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":" @@ -6885,10 +7425,142 @@ static const unsigned char lightnvr_openapi_document[] = "schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"applicat" "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporaril" "y unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearer" - "Token\":[]}],\"summary\":\"Get system information legacy\",\"tags\":[\"System\"],\"x-lightnvr-authorizatio" - "n\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"" - "/api/system/backup\":{\"post\":{\"operationId\":\"createSystemBackup\",\"responses\":{\"200\":{\"content\":{\"" - "application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemBackupCreated\"}}},\"description\":" + "Token\":[]}],\"summary\":\"Restart system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"glo" + "bal\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/shu" + "tdown\":{\"post\":{\"operationId\":\"shutdownSystem\",\"responses\":{\"200\":{\"content\":{\"application/json\"" + ":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful resp" + "onse. Process-control responses acknowledge a request; verify eventual completion independently." + "\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" + "scription\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" + "nts/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applicatio" + "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority d" + "enied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" + "}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}}," + "\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summa" + "ry\":\"Shutdown system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirement" + "s\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/status\":{\"get\":{\"ope" + "rationId\":\"getSystemStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" + "\"#/components/schemas/SystemStatus\"}}},\"description\":\"Successful response. Process-control respo" + "nses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request" + ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" + "escription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or" + " persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" + "emas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie" + "\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system status\",\"ta" + "gs\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.adm" + "in\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/telemetry/player\":{\"post\":{\"description\":\"A named st" + "ream requires live.view on that stream. Empty/malformed unnamed telemetry is ignored with 204; a" + "n acknowledgment does not imply storage of an event.\",\"operationId\":\"recordPlayerTelemetry\",\"req" + "uestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PlayerTelemetry" + "\"}}},\"required\":false},\"responses\":{\"204\":{\"description\":\"Successful response. Process-control r" + "esponses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"a" + "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid req" + "uest.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$re" + "f\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"conten" + "t\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Serve" + "r or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCo" + "okie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Record player tele" + "metry\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"" + "live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/manifest\":{\"get\":{\"description\":\"Dis" + "covery indicates an operation; concrete cameras, captured identities, ownership and current perm" + "issions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections" + " remain supported.\",\"operationId\":\"getEvidenceManifest\",\"parameters\":[{\"in\":\"query\",\"name\":\"stre" + "am\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601 or date-only; defaults t" + "o recent24hours.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO860" + "1 or date-only; defaults to recent24hours.\",\"in\":\"query\",\"name\":\"end\",\"schema\":{\"type\":\"string\"}" + "}],\"responses\":{\"200\":{\"content\":{\"application/vnd.apple.mpegurl\":{\"schema\":{\"format\":\"binary\",\"" + "type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are millise" + "conds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs " + "whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"application/vnd.apple.mpegurl\":{" + "\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authoriz" + "ed media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"applicat" + "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"}" + ",\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" + "ription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configura" + "tion or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\"" + ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revisio" + "n, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" + "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"" + "description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" + "omponents/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"" + "503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\"" + ":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence manifest\"," + "\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recor" + "dings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/play\":{\"get\":{\"description\":\"Disc" + "overy indicates an operation; concrete cameras, captured identities, ownership and current permi" + "ssions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections " + "remain supported.\",\"operationId\":\"redirectEvidencePlayback\",\"parameters\":[{\"in\":\"query\",\"name\":\"" + "stream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Positive epoch seconds or IS" + "O8601; defaults to now.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"" + "302\":{\"description\":\"Empty redirect to the chosen recording playback endpoint; playback independ" + "ently reauthorizes the recording.\",\"headers\":{\"Location\":{\"schema\":{\"pattern\":\"^/api/recordings/" + "play/[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" + "onents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/jso" + "n\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"" + "},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" + "cription\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"" + "404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descri" + "ption\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conf" + "lict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}" + "},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"sche" + "ma\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may h" + "ave applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiE" + "rror\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[" + "{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Redirec" + "t evidence playback\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requiremen" + "ts\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments\":{\"" + "get\":{\"description\":\"Requires stream or camera_uuid. Explicit camera_uuid is authoritative even " + "with a stale stream name. Raw capture-camera replay authorization precedes timeline lookup; dele" + "ted cameras return404. Timeline presence does not imply playable media.\",\"operationId\":\"getTimel" + "ineSegments\",\"parameters\":[{\"description\":\"Current stream name, required unless camera_uuid is p" + "resent.\",\"in\":\"query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description" + "\":\"Capture camera UUID; takes precedence over stream, invalid UUID400.\",\"in\":\"query\",\"name\":\"cam" + "era_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defau" + "lts24hours ago. Invalid nonempty time parses as epoch rather than400.\",\"in\":\"query\",\"name\":\"star" + "t\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defaultsnow." + " Date-only end expands to end of day.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"typ" + "e\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" + "/schemas/RecordingTimeline\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"applicati" + "on/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or que" + "ry.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#" + "/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"4" + "04\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" + "tion\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content" + "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Databa" + "se, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"ba" + "sicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized camera timeli" + "ne\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"act" + "ion\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments-by-ids\":{\"get" + "\":{\"description\":\"Filters each recording through stored capture identity and raw replay permissi" + "on. Hidden/missing/orphaned entries do not contribute counts or overall time bounds. Does not re" + "veal which requested IDs were denied.\",\"operationId\":\"getTimelineSegmentsByIds\",\"parameters\":[{\"" + "description\":\"Required comma-separated positive decimal uint64 recording IDs. Surrounding whites" + "pace and leading zeros are accepted; empty comma-separated fields are skipped. Signed, zero, ove" + "rflowing, or partially numeric tokens reject the entire request with HTTP 400. Order and duplica" + "tes are preserved; missing or unauthorized IDs are omitted. The HTTP query string is limited to " + "1023 bytes; an additional handler bound rejects more than 8640 IDs.\",\"in\":\"query\",\"name\":\"ids\",\"" + "required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"" + "schema\":{\"$ref\":\"#/components/schemas/RecordingTimelineByIds\"}}},\"description\":\"Successful respo" + "nse\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," + "\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" + "#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, poli" + "cy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\"" + ":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized timeline segments by sta" + "ble IDs\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\"" + ":1}}},\"/api/ui/workspaces\":{\"get\":{\"operationId\":\"getWorkspaces\",\"responses\":{\"200\":{\"content\":{" + "\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesResponse\"}}},\"description\":" "\"Successful response. Process-control responses acknowledge a request; verify eventual completio" "n independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/" "ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"" @@ -6898,424 +7570,175 @@ static const unsigned char lightnvr_openapi_document[] = "hemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"applicatio" "n/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily " "unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerTo" - "ken\":[]}],\"summary\":\"Create system backup\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":" - "\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system" - "/go2rtc/effective-config\":{\"get\":{\"operationId\":\"getGo2rtcEffectiveConfiguration\",\"responses\":{\"" - "200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcEffectiveConfi" - "guration\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request" - "; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"app" - "lication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticatio" - "n required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiEr" - "ror\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"5" - "03\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descrip" - "tion\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"a" - "piKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc effective configuration\",\"tags\":[\"Sys" - "tem\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"reso" - "urce\":\"raw\"}],\"version\":1}}},\"/api/system/go2rtc/override-status\":{\"get\":{\"operationId\":\"getGo2r" - "tcOverrideDiagnostics\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/Go2rtcOverrideDiagnostics\"}}},\"description\":\"Successful response. Process-contr" - "ol responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\"" - ":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid" - " request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"S" - "erver or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compon" - "ents/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessi" - "onCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc ove" - "rride diagnostics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":" - "[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/health\":{\"get\":{\"operat" - "ionId\":\"getHostHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/HostHealth\"}}},\"description\":\"Successful response. Process-control responses ac" - "knowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"40" - "1\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persis" - "tence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Ap" - "iError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{" - "\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get host health\",\"tags\":[\"Sys" - "tem\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"reso" - "urce\":\"raw\"}],\"version\":1}}},\"/api/system/health/incidents\":{\"get\":{\"operationId\":\"getHostHealth" - "Incidents\",\"parameters\":[{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"maximum\":100,\"minimum\":1,\"type\"" - ":\"integer\"}},{\"in\":\"query\",\"name\":\"include_closed\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"" - "Opaque versioned cursor from the preceding page; malformed cursors return 400.\",\"in\":\"query\",\"na" - "me\":\"cursor\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/HostHealthIncidents\"}}},\"description\":\"Successful response. " - "Process-control responses acknowledge a request; verify eventual completion independently.\"},\"40" - "0\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" - "ion\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sc" - "hemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied." - "\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"de" - "scription\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$r" - "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"secur" - "ity\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"G" - "et host health incidents\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"require" - "ments\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/info\":{\"get\":{\"o" - "perationId\":\"getSystemInformation\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"Successful response. Process-c" - "ontrol responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Inv" - "alid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":" - "{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description" - "\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/co" - "mponents/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"s" - "essionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system" - " information\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"ac" - "tion\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/logs\":{\"get\":{\"operationId\":" - "\"getSystemLogs\",\"parameters\":[{\"in\":\"query\",\"name\":\"level\",\"schema\":{\"type\":\"string\"}},{\"in\":\"qu" - "ery\",\"name\":\"count\",\"schema\":{\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"last_ts\",\"schema\":{\"type\"" - ":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/s" - "chemas/SystemLogs\"}}},\"description\":\"Successful response. Process-control responses acknowledge " - "a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"sc" - "hema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Auth" - "entication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sche" - "mas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json" - "\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence fail" - "ure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth" - "\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system logs\",\"tags\":[\"System\"],\"x-l" - "ightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw" - "\"}],\"version\":1}}},\"/api/system/logs/clear\":{\"post\":{\"operationId\":\"clearSystemLogs\",\"responses\"" - ":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccep" - "ted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; ver" - "ify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":" - "\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"applicat" - "ion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication req" - "uired.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{" - "\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\"" - ":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKey" - "Header\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear system logs\",\"tags\":[\"System\"],\"x-lightnvr-auth" - "orization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version" - "\":1}}},\"/api/system/restart\":{\"post\":{\"operationId\":\"restartSystem\",\"responses\":{\"200\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"descript" - "ion\":\"Successful response. Process-control responses acknowledge a request; verify eventual comp" - "letion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/sch" - "emas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schem" - "a\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"" - "content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":" - "\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" - "ts/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service tempora" - "rily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bea" - "rerToken\":[]}],\"summary\":\"Restart system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"" - "global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/" - "shutdown\":{\"post\":{\"operationId\":\"shutdownSystem\",\"responses\":{\"200\":{\"content\":{\"application/js" - "on\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful r" - "esponse. Process-control responses acknowledge a request; verify eventual completion independent" - "ly.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}," - "\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/comp" - "onents/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authorit" - "y denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErro" - "r\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"" - "}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"su" - "mmary\":\"Shutdown system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirem" - "ents\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/status\":{\"get\":{\"" - "operationId\":\"getSystemStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$re" - "f\":\"#/components/schemas/SystemStatus\"}}},\"description\":\"Successful response. Process-control re" - "sponses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid requ" - "est.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}}" - ",\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server" - " or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/" - "schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCoo" - "kie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system status\"," - "\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system." - "admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/telemetry/player\":{\"post\":{\"description\":\"A named" - " stream requires live.view on that stream. Empty/malformed unnamed telemetry is ignored with 204" - "; an acknowledgment does not imply storage of an event.\",\"operationId\":\"recordPlayerTelemetry\",\"" - "requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PlayerTeleme" - "try\"}}},\"required\":false},\"responses\":{\"204\":{\"description\":\"Successful response. Process-contro" - "l responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":" - "{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid " - "request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"" - "$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"con" - "tent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Se" - "rver or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessio" - "nCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Record player t" - "elemetry\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action" - "\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/manifest\":{\"get\":{\"description\":\"" - "Discovery indicates an operation; concrete cameras, captured identities, ownership and current p" - "ermissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersecti" - "ons remain supported.\",\"operationId\":\"getEvidenceManifest\",\"parameters\":[{\"in\":\"query\",\"name\":\"s" - "tream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601 or date-only; default" - "s to recent24hours.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO" - "8601 or date-only; defaults to recent24hours.\",\"in\":\"query\",\"name\":\"end\",\"schema\":{\"type\":\"strin" - "g\"}}],\"responses\":{\"200\":{\"content\":{\"application/vnd.apple.mpegurl\":{\"schema\":{\"format\":\"binary" - "\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are mill" - "iseconds and snap to the cache grid. Generation may be disabled. Manifest references recording I" - "Ds whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"application/vnd.apple.mpegurl" - "\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of autho" - "rized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"appli" - "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request" - ".\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"d" - "escription\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature config" - "uration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"conte" - "nt\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revi" - "sion, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$" - "ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\"" - ":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"" - "#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"" - "},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCook" - "ie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence manifes" - "t\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"re" - "cordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/play\":{\"get\":{\"description\":\"D" - "iscovery indicates an operation; concrete cameras, captured identities, ownership and current pe" - "rmissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersectio" - "ns remain supported.\",\"operationId\":\"redirectEvidencePlayback\",\"parameters\":[{\"in\":\"query\",\"name" - "\":\"stream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Positive epoch seconds or" - " ISO8601; defaults to now.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}}],\"responses\"" - ":{\"302\":{\"description\":\"Empty redirect to the chosen recording playback endpoint; playback indep" - "endently reauthorizes the recording.\",\"headers\":{\"Location\":{\"schema\":{\"pattern\":\"^/api/recordin" - "gs/play/[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/c" - "omponents/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/" - "json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication require" - "d.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"" - "description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"" - "},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"des" - "cription\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer c" - "onflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError" - "\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"s" - "chema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation ma" - "y have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/A" - "piError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security" - "\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Redi" - "rect evidence playback\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"require" - "ments\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments\"" - ":{\"get\":{\"description\":\"Requires stream or camera_uuid. Explicit camera_uuid is authoritative ev" - "en with a stale stream name. Raw capture-camera replay authorization precedes timeline lookup; d" - "eleted cameras return404. Timeline presence does not imply playable media.\",\"operationId\":\"getTi" - "melineSegments\",\"parameters\":[{\"description\":\"Current stream name, required unless camera_uuid i" - "s present.\",\"in\":\"query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"descript" - "ion\":\"Capture camera UUID; takes precedence over stream, invalid UUID400.\",\"in\":\"query\",\"name\":\"" - "camera_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; de" - "faults24hours ago. Invalid nonempty time parses as epoch rather than400.\",\"in\":\"query\",\"name\":\"s" - "tart\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defaultsn" - "ow. Date-only end expands to end of day.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"" - "type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compone" - "nts/schemas/RecordingTimeline\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"applic" - "ation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or " - "query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\"" - ":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"}" - ",\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"desc" - "ription\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"cont" - "ent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Dat" - "abase, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{" - "\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized camera tim" - "eline\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"" - "action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments-by-ids\":{\"" - "get\":{\"description\":\"Filters each recording through stored capture identity and raw replay permi" - "ssion. Hidden/missing/orphaned entries do not contribute counts or overall time bounds. Does not" - " reveal which requested IDs were denied.\",\"operationId\":\"getTimelineSegmentsByIds\",\"parameters\":" - "[{\"description\":\"Required comma-separated positive decimal uint64 recording IDs. Surrounding whi" - "tespace and leading zeros are accepted; empty comma-separated fields are skipped. Signed, zero, " - "overflowing, or partially numeric tokens reject the entire request with HTTP 400. Order and dupl" - "icates are preserved; missing or unauthorized IDs are omitted. The HTTP query string is limited " - "to 1023 bytes; an additional handler bound rejects more than 8640 IDs.\",\"in\":\"query\",\"name\":\"ids" - "\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\"" - ":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTimelineByIds\"}}},\"description\":\"Successful re" - "sponse\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}" - "}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref" - "\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, p" - "olicy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAu" - "th\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized timeline segments by " - "stable IDs\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"versi" - "on\":1}}},\"/api/ui/workspaces\":{\"get\":{\"operationId\":\"getWorkspaces\",\"responses\":{\"200\":{\"content" - "\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesResponse\"}}},\"descriptio" - "n\":\"Successful response. Process-control responses acknowledge a request; verify eventual comple" - "tion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schem" - "as/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\"" - ":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"co" - "ntent\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"R" - "equired authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components" - "/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"applica" - "tion/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporari" - "ly unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"beare" - "rToken\":[]}],\"summary\":\"Get workspaces\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"au" - "thenticated\",\"version\":1}},\"put\":{\"operationId\":\"updateWorkspaces\",\"requestBody\":{\"content\":{\"ap" - "plication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesUpdate\"}}},\"required\":true},\"r" - "esponses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Workspac" - "esResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a reque" - "st; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{" - "\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"a" - "pplication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authenticat" - "ion required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Api" - "Error\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"sch" - "ema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"}," - "\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descr" - "iption\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{" - "\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update workspaces\",\"tags\":[\"System\"],\"x-lightn" - "vr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/logout\":{\"get\":{\"description\":\"" - "Public and idempotent. Deletes the session from the supplied cookie and clears session/trusted-d" - "evice cookies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 " - "requires Accept containing application/json or X-Requested-With; otherwise302 redirects to login" - ".\",\"operationId\":\"legacyLogout\",\"parameters\":[{\"description\":\"Use application/json for the JSON " - "success envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Pre" - "sence also selects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":" - "{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/compo" - "nents/schemas/LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Brow" - "ser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"" - "Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":" - "[],\"summary\":\"End a browser session using the legacy route\",\"tags\":[\"Identity\"],\"x-lightnvr-auth" - "orization\":{\"kind\":\"public\",\"version\":1}}}},\"servers\":[{\"description\":\"Application root relative" - " to the actual served /api/openapi.json URL, including an existing proxy path prefix.\",\"url\":\".." - "/\"}],\"tags\":[{\"description\":\"Camera inventory and configuration.\",\"name\":\"Streams\"},{\"descriptio" - "n\":\"Current-principal contract visibility.\",\"name\":\"Discovery\"},{\"description\":\"Existing login, " - "session verification and safe browser bootstrap.\",\"name\":\"Identity\"},{\"description\":\"Self-servic" - "e or users.manage token lifecycle; scoped credentials cannot manage tokens.\",\"name\":\"Tokens\"},{\"" - "description\":\"Replay-authorized recording inventory, metadata, timeline and media protocols.\",\"n" - "ame\":\"Recording reads\"},{\"description\":\"Capture-identity protection, tags, deletion and export j" - "obs. Job access remains creator/token-bound; cancellation is unsupported.\",\"name\":\"Recording act" - "ions\"},{\"description\":\"User, policy and audit administration.\",\"name\":\"Identity administration\"}" - ",{\"description\":\"User, policy and audit administration.\",\"name\":\"Audit\"},{\"description\":\"System " - "administration, health, settings and bootstrap.\",\"name\":\"System\"},{\"description\":\"Event routing," - " destinations and external motion.\",\"name\":\"Events\"},{\"description\":\"Storage configuration, loca" - "l lifecycle and archive operations.\",\"name\":\"Storage\"},{\"description\":\"Investigations, detection" - "s, bookmarks, LPR and recording media protocols.\",\"name\":\"Evidence\"},{\"description\":\"PTZ, imagin" - "g and ONVIF inventory and device commands.\",\"name\":\"Device controls\"}],\"x-lightnvr-coverage\":{\"c" - "anonical_revision\":\"eng-api-four-pr-v1\",\"covered_operations\":[\"listStreams\",\"getStreamFull\",\"upd" - "ateStream\",\"getOpenApi\",\"getLoginConfig\",\"login\",\"logout\",\"verifyIdentity\",\"getClientConfig\",\"li" - "stUserTokens\",\"createUserToken\",\"revokeUserToken\",\"listRecordings\",\"getRecording\",\"listRecording" - "Tags\",\"listRecordingDetectionLabels\",\"getRecordingTags\",\"getTimelineSegments\",\"getTimelineSegmen" - "tsByIds\",\"playRecording\",\"getRecordingThumbnail\",\"setRecordingProtection\",\"setRecordingRetention" - "\",\"protectRecordings\",\"setRecordingTags\",\"updateRecordingTagsBatch\",\"deleteRecording\",\"deleteRec" - "ordings\",\"getRecordingDeletionProgress\",\"createRecordingExport\",\"getRecordingExportStatus\",\"down" - "loadRecording\",\"downloadRecordingExport\",\"listAuthSessions\",\"revokeAuthSession\",\"listTrustedDevi" - "ces\",\"revokeTrustedDevice\",\"completeTotpLogin\",\"legacyLogout\",\"changePassword\",\"disableTotp\",\"ge" - "tTotpStatus\",\"setupTotp\",\"verifyTotp\",\"queryFleetCameras\",\"previewFleetSelector\",\"listFleetViews" - "\",\"getFleetView\",\"listCameraCollections\",\"getCameraCollection\",\"getCameraCollectionMembers\",\"pre" - "viewCameraCollection\",\"listFleetLocations\",\"getFleetLocation\",\"listFleetTags\",\"getFleetTag\",\"get" - "CameraTagAssignments\",\"getSystemSettings\",\"updateSystemSettings\",\"validateGo2rtcConfiguration\",\"" - "getSystemInformationLegacy\",\"getSystemInformation\",\"getSystemStatus\",\"getHostHealth\",\"getHostHea" - "lthIncidents\",\"getSystemLogs\",\"clearSystemLogs\",\"createSystemBackup\",\"restartSystem\",\"shutdownSy" - "stem\",\"getGo2rtcEffectiveConfiguration\",\"getGo2rtcOverrideDiagnostics\",\"getWorkspaces\",\"updateWo" - "rkspaces\",\"getSetupStatus\",\"updateSetupStatus\",\"getStreamHealth\",\"getHlsHealth\",\"getMetrics\",\"re" - "cordPlayerTelemetry\",\"getEventCatalog\",\"listEventDestinations\",\"createEventDestination\",\"getEven" - "tDestination\",\"updateEventDestination\",\"deleteEventDestination\",\"listEventRoutes\",\"createEventRo" - "ute\",\"getEventRoute\",\"updateEventRoute\",\"deleteEventRoute\",\"previewEventRoute\",\"triggerMotion\",\"" - "createFleetLocation\",\"updateFleetLocation\",\"deleteFleetLocation\",\"createFleetTag\",\"updateFleetTa" - "g\",\"deleteFleetTag\",\"createCameraCollection\",\"updateCameraCollection\",\"deleteCameraCollection\",\"" - "setCameraLocation\",\"setCameraTags\",\"mergeFleetTag\",\"setCameraCollectionMembers\",\"createFleetView" - "\",\"updateFleetView\",\"deleteFleetView\",\"listLiveLayouts\",\"createLiveLayout\",\"updateLiveLayout\",\"d" - "eleteLiveLayout\",\"listCameraFavorites\",\"addCameraFavorite\",\"deleteCameraFavorite\",\"listFloorPlan" - "s\",\"getFloorPlan\",\"createFloorPlan\",\"updateFloorPlan\",\"deleteFloorPlan\",\"getFloorPlanBackground\"" - ",\"setFloorPlanBackground\",\"deleteFloorPlanBackground\",\"listAdminUsers\",\"createAdminUser\",\"getAdm" - "inUser\",\"updateAdminUser\",\"deleteAdminUser\",\"createLegacyApiKey\",\"setUserPasswordLock\",\"clearUse" - "rLoginLockout\",\"listAuthorizationActions\",\"simulateAuthorization\",\"listAuthorizationRoles\",\"crea" - "teAuthorizationRole\",\"updateAuthorizationRole\",\"deleteAuthorizationRole\",\"getUserAuthorizationPo" - "licy\",\"setUserAuthorizationPolicy\",\"listAuditEvents\",\"exportAuditEvents\",\"getAuditSettings\",\"upd" - "ateAuditSettings\",\"listStorageTargets\",\"createStorageTarget\",\"getStorageTarget\",\"updateStorageTa" - "rget\",\"deleteStorageTarget\",\"listStoragePools\",\"createStoragePool\",\"getStoragePool\",\"updateStora" - "gePool\",\"deleteStoragePool\",\"listStoragePolicies\",\"createStoragePolicy\",\"getStoragePolicy\",\"upda" - "teStoragePolicy\",\"deleteStoragePolicy\",\"probeStorageTarget\",\"previewStoragePolicy\",\"applyStorage" - "Policy\",\"listStorageMigrations\",\"createStorageMigration\",\"getStorageMigration\",\"cancelStorageMig" - "ration\",\"retryStorageMigration\",\"getStorageArchive\",\"retryStorageArchive\",\"getStorageCompliance\"" - ",\"getStorageHealth\",\"triggerStorageCleanup\",\"getEvidenceDetections\",\"listEvidenceModels\",\"search" - "EvidenceLpr\",\"exportEvidenceLpr\",\"deleteEvidenceLpr\",\"checkEvidenceRecordingFile\",\"deleteEvidenc" - "eRecordingFile\",\"countEvidenceProtected\",\"syncEvidenceRecordings\",\"queryEvidenceTimeline\",\"resol" - "veEvidenceSegment\",\"searchEvidence\",\"previewEvidenceRecordings\",\"sampleEvidenceThumbnails\",\"list" - "EvidenceBookmarks\",\"createEvidenceBookmark\",\"getEvidenceBookmark\",\"updateEvidenceBookmark\",\"dele" - "teEvidenceBookmark\",\"getEvidenceSnapshot\",\"getEvidenceThumbnail\",\"getEvidenceManifest\",\"redirect" - "EvidencePlayback\",\"getPtzCapabilities\",\"getPtzPresets\",\"movePtz\",\"absolutePtz\",\"relativePtz\",\"st" - "opPtz\",\"homePtz\",\"setPtzHome\",\"gotoPtzPreset\",\"setPtzPreset\",\"getImagingSettings\",\"setImagingSet" - "tings\",\"getImagingOptions\",\"getDaynight\",\"setDaynight\",\"getOnvifDiscoveryStatus\",\"getOnvifDevice" - "s\",\"discoverOnvifDevices\",\"getOnvifProfiles\",\"addOnvifDevice\",\"claimOnvifDevice\",\"testOnvifDevic" - "e\",\"createStream\",\"probeStream\",\"getStream\",\"deleteStream\",\"refreshStream\",\"getStreamRecording\"," - "\"controlStreamRecording\",\"getStreamZones\",\"saveStreamZones\",\"deleteStreamZones\",\"getStreamRetent" - "ion\",\"setStreamRetention\",\"getStreamDetectionEngines\",\"setStreamDetectionEngines\",\"getIceServers" - "\"],\"description\":\"232 reviewed OpenAPI operations; eight registered media-protocol surfaces and " - "one internal callback have explicit inventory dispositions outside this document. Discovery is f" - "iltered by current authority and never grants access.\",\"partial\":true},\"x-lightnvr-validators\":[" - "\"StreamSummaryPage\",\"LiveStreamSummaryPage\",\"LprStreamSummaryPage\",\"StreamCompatibilityList\",\"St" - "reamFullResponse\",\"StreamUpdateRequest\",\"StreamUpdateAccepted\",\"StreamPrivacyUpdated\",\"ApiError\"" - ",\"OpenApiDocument\",\"LoginRequest\",\"LoginResponse\",\"LoginConfig\",\"AuthVerifyResponse\",\"LogoutResp" - "onse\",\"ClientConfig\",\"ApiTokenCreateRequest\",\"ApiTokenMetadata\",\"ApiTokenList\",\"ApiTokenCreated\"" - ",\"SuccessResponse\",\"RecordingDetail\",\"RecordingsPage\",\"RecordingTags\",\"RecordingTagsById\",\"Recor" - "dingDetectionLabels\",\"RecordingTimeline\",\"RecordingTimelineByIds\",\"RecordingPlaybackReady\",\"Reco" - "rdingPlaybackPreparing\",\"RecordingProtectionRequest\",\"RecordingProtectionResult\",\"RecordingReten" - "tionRequest\",\"RecordingRetentionResult\",\"RecordingBatchProtectionRequest\",\"RecordingBatchProtect" - "ionResult\",\"RecordingTagsRequest\",\"RecordingBatchTagsRequest\",\"RecordingBatchTagsResult\",\"Record" - "ingDeletionResult\",\"RecordingDeletionFilter\",\"RecordingDeletionRequest\",\"RecordingDeletionJobAcc" - "epted\",\"RecordingDeletionProgress\",\"RecordingExportRequest\",\"RecordingExportAccepted\",\"Recording" - "ExportStatus\",\"AuthSessionsResponse\",\"TrustedDevicesResponse\",\"CredentialRevoked\",\"TotpLoginRequ" - "est\",\"TotpLoginResponse\",\"TotpCodeRequest\",\"TotpSetupResponse\",\"TotpStatusResponse\",\"TotpChanged" - "Response\",\"PasswordSelfChange\",\"PasswordAdminReset\",\"PasswordChangeRequest\",\"PasswordChangedResp" - "onse\",\"LoginMfaChallenge\",\"LoginSuccess\",\"FleetQueryRequest\",\"FleetPreviewRequest\",\"FleetQueryRe" - "sponse\",\"FleetPreviewResponse\",\"FleetSavedView\",\"FleetSavedViewsResponse\",\"FleetLocation\",\"Fleet" - "LocationsResponse\",\"FleetTag\",\"FleetTagsResponse\",\"CameraTagAssignmentsResponse\",\"CameraCollecti" - "on\",\"CameraCollectionsResponse\",\"CameraCollectionMembersResponse\",\"CameraCollectionPreview\",\"Sys" - "temSettings\",\"SystemSettingsUpdate\",\"SystemSettingsSaved\",\"SystemInformation\",\"SystemLogs\",\"Host" - "Health\",\"HostHealthIncidents\",\"Go2rtcConfigurationValidation\",\"Go2rtcConfigurationInput\",\"Go2rtc" - "EffectiveConfiguration\",\"Go2rtcOverrideDiagnostics\",\"SetupStatus\",\"SetupUpdate\",\"SetupUpdated\",\"" - "SystemStatus\",\"SystemActionAccepted\",\"SystemBackupCreated\",\"WorkspacesResponse\",\"WorkspacesUpdat" - "e\",\"StreamHealth\",\"ProcessLiveness\",\"StreamHealthHistory\",\"HlsHealth\",\"PlayerTelemetry\",\"EventDe" - "stination\",\"EventDestinations\",\"EventDestinationCreate\",\"EventDestinationUpdate\",\"EventPredicate" - "\",\"EventCameraScope\",\"EventSchedule\",\"EventRoute\",\"EventRoutes\",\"EventRouteCreate\",\"EventRouteUp" - "date\",\"EventRoutePreviewRequest\",\"EventCatalog\",\"EventRoutePreview\",\"EventMutationAck\",\"MotionTr" - "igger\",\"MotionTriggered\",\"FleetMutationAck\",\"FleetLocationCreate\",\"FleetLocationUpdate\",\"CameraL" - "ocationAssignmentRequest\",\"CameraLocationAssignment\",\"FleetTagCreate\",\"FleetTagUpdate\",\"FleetTag" - "MergeRequest\",\"FleetTagMergeResult\",\"CameraTagAssignmentsRequest\",\"CameraCollectionCreate\",\"Came" - "raCollectionUpdate\",\"CameraCollectionMembersRequest\",\"FleetViewCreate\",\"FleetViewUpdate\",\"FleetR" - "evisionRequest\",\"LiveCameraSlot\",\"LiveLayoutCreate\",\"LiveLayoutUpdate\",\"LiveLayout\",\"LiveLayouts" - "Response\",\"CameraFavorite\",\"CameraFavoritesResponse\",\"CameraFavoriteRemoval\",\"FloorSketchRect\",\"" - "FloorSketchWall\",\"FloorSketchArea\",\"FloorSketchLabel\",\"FloorSketch\",\"FloorPlanCamera\",\"FloorPlan" - "CameraRequest\",\"FloorPlanCreate\",\"FloorPlanUpdate\",\"FloorPlan\",\"FloorPlansResponse\",\"FloorPlanDe" - "letion\",\"CameraUuid\",\"AdminUser\",\"AdminUsers\",\"AdminUserCreate\",\"AdminUserUpdate\",\"AdminUserDele" - "ted\",\"LegacyApiKeyCreated\",\"PasswordLockRequest\",\"LoginLockoutCleared\",\"AdminActionCatalogEntry\"" - ",\"AdminActionCatalog\",\"AdminAuthorizationRole\",\"AdminRoles\",\"AdminRoleWrite\",\"AdminRoleWritten\"," - "\"AdminVersionRequest\",\"AdminRoleDeleted\",\"AdminPolicyGrant\",\"AdminUserPolicy\",\"AdminPolicyWrite\"" - ",\"AdminSimulationRequest\",\"AdminSimulation\",\"AuditEvent\",\"AuditEventsPage\",\"AuditDecisionMode\",\"" - "AuditSettings\",\"AuditSettingsWrite\",\"StorageTargetHealth\",\"StorageTarget\",\"StorageTargetsRespons" - "e\",\"StorageTargetCreate\",\"StorageTargetUpdate\",\"StoragePoolMember\",\"StoragePoolMemberRequest\",\"S" - "toragePool\",\"StoragePoolsResponse\",\"StoragePoolCreate\",\"StoragePoolUpdate\",\"StoragePolicy\",\"Stor" - "agePoliciesResponse\",\"StoragePolicyCreate\",\"StoragePolicyUpdate\",\"StoragePolicyPreviewRequest\",\"" - "StoragePolicyWinner\",\"StoragePolicyPreviewSample\",\"StoragePolicyConflict\",\"StoragePolicyPreview\"" - ",\"StoragePolicyApplyRequest\",\"StoragePolicyApplyResponse\",\"StorageMigration\",\"StorageMigrationsR" - "esponse\",\"StorageMigrationCreate\",\"StorageArchiveDeletion\",\"StorageArchiveResponse\",\"StorageArch" - "iveRetryRequest\",\"StorageArchiveRetryResponse\",\"StorageHealthResponse\",\"StorageCleanupRequest\",\"" - "StorageCleanupResponse\",\"StorageComplianceTarget\",\"StorageCompliancePolicy\",\"StorageComplianceVi" - "olation\",\"StorageComplianceResponse\",\"EvidenceDetection\",\"EvidenceDetections\",\"EvidenceModel\",\"E" - "videnceModels\",\"EvidenceFileCheck\",\"EvidenceFileDeleted\",\"EvidenceProtectedCount\",\"EvidenceSyncC" - "omplete\",\"EvidenceSegment\",\"EvidenceTimelineTrack\",\"EvidenceTimeline\",\"EvidenceSegmentAt\",\"Evide" - "nceRangeRequest\",\"EvidenceSegmentAtRequest\",\"EvidenceThumbnailRequest\",\"EvidencePreviewRecording" - "\",\"EvidencePreview\",\"EvidenceThumbnailSample\",\"EvidenceThumbnails\",\"EvidenceSearchResult\",\"Evide" - "nceSearch\",\"EvidenceSearchRequest\",\"EvidenceBookmarkFilters\",\"EvidenceBookmarkResult\",\"EvidenceB" - "ookmark\",\"EvidenceBookmarks\",\"EvidenceBookmarkCreate\",\"EvidenceBookmarkUpdate\",\"EvidenceBookmark" - "Delete\",\"EvidenceLprRead\",\"EvidenceLprReads\",\"EvidenceLprSearchRequest\",\"EvidenceLprExportReques" - "t\",\"EvidenceLprDeleted\",\"EvidenceBookmarkDeleted\",\"DeviceFloatRange\",\"DeviceCommandResult\",\"Devi" - "ceSuccess\",\"DeviceEmptyRequest\",\"PtzAxesRequest\",\"PtzPresetRequest\",\"PtzGotoPresetRequest\",\"PtzP" - "resetResult\",\"PtzPresets\",\"PtzCapabilities\",\"ImagingModeLevel\",\"ImagingModeLevelUpdate\",\"Imaging" - "Settings\",\"ImagingSettingsResponse\",\"ImagingModeOptions\",\"ImagingOptions\",\"ImagingOptionsRespons" - "e\",\"ImagingSettingsRequest\",\"DaynightRequest\",\"DaynightResponse\",\"DaynightUpdated\",\"OnvifDiscove" - "ryStatus\",\"OnvifDevice\",\"OnvifDevicesResponse\",\"OnvifDiscoveryRequest\",\"OnvifProfile\",\"OnvifProf" - "ilesResponse\",\"OnvifTestRequest\",\"OnvifAddRequest\",\"OnvifAddResult\",\"OnvifClaimRequest\",\"StreamS" - "ingleResponse\",\"StreamCreateRequest\",\"StreamCreated\",\"StreamProbeRequest\",\"StreamProbeResponse\"," - "\"StreamDeleteAccepted\",\"StreamRefreshAccepted\",\"StreamRecordingState\",\"StreamRecordingRequest\",\"" - "StreamRecordingChanged\",\"DetectionZoneWrite\",\"DetectionZone\",\"StreamZones\",\"StreamZonesRequest\"," - "\"StreamZonesSaved\",\"StreamZonesDeleted\",\"StreamRetentionRequest\",\"StreamRetention\",\"StreamDetect" - "ionEngineWrite\",\"StreamDetectionEngine\",\"StreamDetectionEnginesRequest\",\"StreamDetectionEngines\"" - ",\"IceServersResponse\"]}\012"; + "ken\":[]}],\"summary\":\"Get workspaces\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"authe" + "nticated\",\"version\":1}},\"put\":{\"operationId\":\"updateWorkspaces\",\"requestBody\":{\"content\":{\"appli" + "cation/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesUpdate\"}}},\"required\":true},\"resp" + "onses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesR" + "esponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request;" + " verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$r" + "ef\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"appl" + "ication/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication" + " required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiErr" + "or\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema" + "\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"50" + "3\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"descript" + "ion\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"ap" + "iKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update workspaces\",\"tags\":[\"System\"],\"x-lightnvr-" + "authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/logout\":{\"get\":{\"description\":\"Pub" + "lic and idempotent. Deletes the session from the supplied cookie and clears session/trusted-devi" + "ce cookies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 req" + "uires Accept containing application/json or X-Requested-With; otherwise302 redirects to login.\"," + "\"operationId\":\"legacyLogout\",\"parameters\":[{\"description\":\"Use application/json for the JSON suc" + "cess envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Presen" + "ce also selects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":{\"t" + "ype\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/componen" + "ts/schemas/LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Browser" + "/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Loc" + "ation\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[]," + "\"summary\":\"End a browser session using the legacy route\",\"tags\":[\"Identity\"],\"x-lightnvr-authori" + "zation\":{\"kind\":\"public\",\"version\":1}}}},\"servers\":[{\"description\":\"Application root relative to" + " the actual served /api/openapi.json URL, including an existing proxy path prefix.\",\"url\":\"../\"}" + "],\"tags\":[{\"description\":\"Camera inventory and configuration.\",\"name\":\"Streams\"},{\"description\":" + "\"Current-principal contract visibility.\",\"name\":\"Discovery\"},{\"description\":\"Existing login, ses" + "sion verification and safe browser bootstrap.\",\"name\":\"Identity\"},{\"description\":\"Self-service o" + "r users.manage token lifecycle; scoped credentials cannot manage tokens.\",\"name\":\"Tokens\"},{\"des" + "cription\":\"Replay-authorized recording inventory, metadata, timeline and media protocols.\",\"name" + "\":\"Recording reads\"},{\"description\":\"Capture-identity protection, tags, deletion and export jobs" + ". Job access remains creator/token-bound; cancellation is unsupported.\",\"name\":\"Recording action" + "s\"},{\"description\":\"User, policy and audit administration.\",\"name\":\"Identity administration\"},{\"" + "description\":\"User, policy and audit administration.\",\"name\":\"Audit\"},{\"description\":\"System adm" + "inistration, health, settings and bootstrap.\",\"name\":\"System\"},{\"description\":\"Event routing, de" + "stinations and external motion.\",\"name\":\"Events\"},{\"description\":\"Storage configuration, local l" + "ifecycle and archive operations.\",\"name\":\"Storage\"},{\"description\":\"Investigations, detections, " + "bookmarks, LPR and recording media protocols.\",\"name\":\"Evidence\"},{\"description\":\"PTZ, imaging a" + "nd ONVIF inventory and device commands.\",\"name\":\"Device controls\"},{\"description\":\"Durable detec" + "tion observations, evidence images, history cursors and integration capabilities (INT_01).\",\"nam" + "e\":\"Integrations\"}],\"x-lightnvr-coverage\":{\"canonical_revision\":\"eng-api-four-pr-v1\",\"covered_op" + "erations\":[\"listStreams\",\"getStreamFull\",\"updateStream\",\"getOpenApi\",\"getLoginConfig\",\"login\",\"l" + "ogout\",\"verifyIdentity\",\"getClientConfig\",\"listUserTokens\",\"createUserToken\",\"revokeUserToken\",\"" + "listRecordings\",\"getRecording\",\"listRecordingTags\",\"listRecordingDetectionLabels\",\"getRecordingT" + "ags\",\"getTimelineSegments\",\"getTimelineSegmentsByIds\",\"playRecording\",\"getRecordingThumbnail\",\"s" + "etRecordingProtection\",\"setRecordingRetention\",\"protectRecordings\",\"setRecordingTags\",\"updateRec" + "ordingTagsBatch\",\"deleteRecording\",\"deleteRecordings\",\"getRecordingDeletionProgress\",\"createReco" + "rdingExport\",\"getRecordingExportStatus\",\"downloadRecording\",\"downloadRecordingExport\",\"listAuthS" + "essions\",\"revokeAuthSession\",\"listTrustedDevices\",\"revokeTrustedDevice\",\"completeTotpLogin\",\"leg" + "acyLogout\",\"changePassword\",\"disableTotp\",\"getTotpStatus\",\"setupTotp\",\"verifyTotp\",\"queryFleetCa" + "meras\",\"previewFleetSelector\",\"listFleetViews\",\"getFleetView\",\"listCameraCollections\",\"getCamera" + "Collection\",\"getCameraCollectionMembers\",\"previewCameraCollection\",\"listFleetLocations\",\"getFlee" + "tLocation\",\"listFleetTags\",\"getFleetTag\",\"getCameraTagAssignments\",\"getSystemSettings\",\"updateSy" + "stemSettings\",\"validateGo2rtcConfiguration\",\"getSystemInformationLegacy\",\"getSystemInformation\"," + "\"getSystemStatus\",\"getHostHealth\",\"getHostHealthIncidents\",\"getSystemLogs\",\"clearSystemLogs\",\"cr" + "eateSystemBackup\",\"restartSystem\",\"shutdownSystem\",\"getGo2rtcEffectiveConfiguration\",\"getGo2rtcO" + "verrideDiagnostics\",\"getWorkspaces\",\"updateWorkspaces\",\"getSetupStatus\",\"updateSetupStatus\",\"get" + "StreamHealth\",\"getHlsHealth\",\"getMetrics\",\"recordPlayerTelemetry\",\"getEventCatalog\",\"listEventDe" + "stinations\",\"createEventDestination\",\"getEventDestination\",\"updateEventDestination\",\"deleteEvent" + "Destination\",\"listEventRoutes\",\"createEventRoute\",\"getEventRoute\",\"updateEventRoute\",\"deleteEven" + "tRoute\",\"previewEventRoute\",\"triggerMotion\",\"createFleetLocation\",\"updateFleetLocation\",\"deleteF" + "leetLocation\",\"createFleetTag\",\"updateFleetTag\",\"deleteFleetTag\",\"createCameraCollection\",\"updat" + "eCameraCollection\",\"deleteCameraCollection\",\"setCameraLocation\",\"setCameraTags\",\"mergeFleetTag\"," + "\"setCameraCollectionMembers\",\"createFleetView\",\"updateFleetView\",\"deleteFleetView\",\"listLiveLayo" + "uts\",\"createLiveLayout\",\"updateLiveLayout\",\"deleteLiveLayout\",\"listCameraFavorites\",\"addCameraFa" + "vorite\",\"deleteCameraFavorite\",\"listFloorPlans\",\"getFloorPlan\",\"createFloorPlan\",\"updateFloorPla" + "n\",\"deleteFloorPlan\",\"getFloorPlanBackground\",\"setFloorPlanBackground\",\"deleteFloorPlanBackgroun" + "d\",\"listAdminUsers\",\"createAdminUser\",\"getAdminUser\",\"updateAdminUser\",\"deleteAdminUser\",\"create" + "LegacyApiKey\",\"setUserPasswordLock\",\"clearUserLoginLockout\",\"listAuthorizationActions\",\"simulate" + "Authorization\",\"listAuthorizationRoles\",\"createAuthorizationRole\",\"updateAuthorizationRole\",\"del" + "eteAuthorizationRole\",\"getUserAuthorizationPolicy\",\"setUserAuthorizationPolicy\",\"listAuditEvents" + "\",\"exportAuditEvents\",\"getAuditSettings\",\"updateAuditSettings\",\"listStorageTargets\",\"createStora" + "geTarget\",\"getStorageTarget\",\"updateStorageTarget\",\"deleteStorageTarget\",\"listStoragePools\",\"cre" + "ateStoragePool\",\"getStoragePool\",\"updateStoragePool\",\"deleteStoragePool\",\"listStoragePolicies\",\"" + "createStoragePolicy\",\"getStoragePolicy\",\"updateStoragePolicy\",\"deleteStoragePolicy\",\"probeStorag" + "eTarget\",\"previewStoragePolicy\",\"applyStoragePolicy\",\"listStorageMigrations\",\"createStorageMigra" + "tion\",\"getStorageMigration\",\"cancelStorageMigration\",\"retryStorageMigration\",\"getStorageArchive\"" + ",\"retryStorageArchive\",\"getStorageCompliance\",\"getStorageHealth\",\"triggerStorageCleanup\",\"getEvi" + "denceDetections\",\"listEvidenceModels\",\"searchEvidenceLpr\",\"exportEvidenceLpr\",\"deleteEvidenceLpr" + "\",\"checkEvidenceRecordingFile\",\"deleteEvidenceRecordingFile\",\"countEvidenceProtected\",\"syncEvide" + "nceRecordings\",\"queryEvidenceTimeline\",\"resolveEvidenceSegment\",\"searchEvidence\",\"previewEvidenc" + "eRecordings\",\"sampleEvidenceThumbnails\",\"listEvidenceBookmarks\",\"createEvidenceBookmark\",\"getEvi" + "denceBookmark\",\"updateEvidenceBookmark\",\"deleteEvidenceBookmark\",\"getEvidenceSnapshot\",\"getEvide" + "nceThumbnail\",\"getEvidenceManifest\",\"redirectEvidencePlayback\",\"getPtzCapabilities\",\"getPtzPrese" + "ts\",\"movePtz\",\"absolutePtz\",\"relativePtz\",\"stopPtz\",\"homePtz\",\"setPtzHome\",\"gotoPtzPreset\",\"setP" + "tzPreset\",\"getImagingSettings\",\"setImagingSettings\",\"getImagingOptions\",\"getDaynight\",\"setDaynig" + "ht\",\"getOnvifDiscoveryStatus\",\"getOnvifDevices\",\"discoverOnvifDevices\",\"getOnvifProfiles\",\"addOn" + "vifDevice\",\"claimOnvifDevice\",\"testOnvifDevice\",\"createStream\",\"probeStream\",\"getStream\",\"delete" + "Stream\",\"refreshStream\",\"getStreamRecording\",\"controlStreamRecording\",\"getStreamZones\",\"saveStre" + "amZones\",\"deleteStreamZones\",\"getStreamRetention\",\"setStreamRetention\",\"getStreamDetectionEngine" + "s\",\"setStreamDetectionEngines\",\"getIceServers\",\"getIntegrationCapabilities\",\"listObservations\",\"" + "getObservation\",\"getObservationSnapshot\",\"putObservationClassification\"],\"description\":\"237 revi" + "ewed OpenAPI operations; eight registered media-protocol surfaces and one internal callback have" + " explicit inventory dispositions outside this document. Discovery is filtered by current authori" + "ty and never grants access.\",\"partial\":true},\"x-lightnvr-validators\":[\"StreamSummaryPage\",\"LiveS" + "treamSummaryPage\",\"LprStreamSummaryPage\",\"StreamCompatibilityList\",\"StreamFullResponse\",\"StreamU" + "pdateRequest\",\"StreamUpdateAccepted\",\"StreamPrivacyUpdated\",\"ApiError\",\"OpenApiDocument\",\"LoginR" + "equest\",\"LoginResponse\",\"LoginConfig\",\"AuthVerifyResponse\",\"LogoutResponse\",\"ClientConfig\",\"ApiT" + "okenCreateRequest\",\"ApiTokenMetadata\",\"ApiTokenList\",\"ApiTokenCreated\",\"SuccessResponse\",\"Record" + "ingDetail\",\"RecordingsPage\",\"RecordingTags\",\"RecordingTagsById\",\"RecordingDetectionLabels\",\"Reco" + "rdingTimeline\",\"RecordingTimelineByIds\",\"RecordingPlaybackReady\",\"RecordingPlaybackPreparing\",\"R" + "ecordingProtectionRequest\",\"RecordingProtectionResult\",\"RecordingRetentionRequest\",\"RecordingRet" + "entionResult\",\"RecordingBatchProtectionRequest\",\"RecordingBatchProtectionResult\",\"RecordingTagsR" + "equest\",\"RecordingBatchTagsRequest\",\"RecordingBatchTagsResult\",\"RecordingDeletionResult\",\"Record" + "ingDeletionFilter\",\"RecordingDeletionRequest\",\"RecordingDeletionJobAccepted\",\"RecordingDeletionP" + "rogress\",\"RecordingExportRequest\",\"RecordingExportAccepted\",\"RecordingExportStatus\",\"AuthSession" + "sResponse\",\"TrustedDevicesResponse\",\"CredentialRevoked\",\"TotpLoginRequest\",\"TotpLoginResponse\",\"" + "TotpCodeRequest\",\"TotpSetupResponse\",\"TotpStatusResponse\",\"TotpChangedResponse\",\"PasswordSelfCha" + "nge\",\"PasswordAdminReset\",\"PasswordChangeRequest\",\"PasswordChangedResponse\",\"LoginMfaChallenge\"," + "\"LoginSuccess\",\"FleetQueryRequest\",\"FleetPreviewRequest\",\"FleetQueryResponse\",\"FleetPreviewRespo" + "nse\",\"FleetSavedView\",\"FleetSavedViewsResponse\",\"FleetLocation\",\"FleetLocationsResponse\",\"FleetT" + "ag\",\"FleetTagsResponse\",\"CameraTagAssignmentsResponse\",\"CameraCollection\",\"CameraCollectionsResp" + "onse\",\"CameraCollectionMembersResponse\",\"CameraCollectionPreview\",\"SystemSettings\",\"SystemSettin" + "gsUpdate\",\"SystemSettingsSaved\",\"SystemInformation\",\"SystemLogs\",\"HostHealth\",\"HostHealthInciden" + "ts\",\"Go2rtcConfigurationValidation\",\"Go2rtcConfigurationInput\",\"Go2rtcEffectiveConfiguration\",\"G" + "o2rtcOverrideDiagnostics\",\"SetupStatus\",\"SetupUpdate\",\"SetupUpdated\",\"SystemStatus\",\"SystemActio" + "nAccepted\",\"SystemBackupCreated\",\"WorkspacesResponse\",\"WorkspacesUpdate\",\"StreamHealth\",\"Process" + "Liveness\",\"StreamHealthHistory\",\"HlsHealth\",\"PlayerTelemetry\",\"EventDestination\",\"EventDestinati" + "ons\",\"EventDestinationCreate\",\"EventDestinationUpdate\",\"EventPredicate\",\"EventCameraScope\",\"Even" + "tSchedule\",\"EventRoute\",\"EventRoutes\",\"EventRouteCreate\",\"EventRouteUpdate\",\"EventRoutePreviewRe" + "quest\",\"EventCatalog\",\"EventRoutePreview\",\"EventMutationAck\",\"MotionTrigger\",\"MotionTriggered\",\"" + "FleetMutationAck\",\"FleetLocationCreate\",\"FleetLocationUpdate\",\"CameraLocationAssignmentRequest\"," + "\"CameraLocationAssignment\",\"FleetTagCreate\",\"FleetTagUpdate\",\"FleetTagMergeRequest\",\"FleetTagMer" + "geResult\",\"CameraTagAssignmentsRequest\",\"CameraCollectionCreate\",\"CameraCollectionUpdate\",\"Camer" + "aCollectionMembersRequest\",\"FleetViewCreate\",\"FleetViewUpdate\",\"FleetRevisionRequest\",\"LiveCamer" + "aSlot\",\"LiveLayoutCreate\",\"LiveLayoutUpdate\",\"LiveLayout\",\"LiveLayoutsResponse\",\"CameraFavorite\"" + ",\"CameraFavoritesResponse\",\"CameraFavoriteRemoval\",\"FloorSketchRect\",\"FloorSketchWall\",\"FloorSke" + "tchArea\",\"FloorSketchLabel\",\"FloorSketch\",\"FloorPlanCamera\",\"FloorPlanCameraRequest\",\"FloorPlanC" + "reate\",\"FloorPlanUpdate\",\"FloorPlan\",\"FloorPlansResponse\",\"FloorPlanDeletion\",\"CameraUuid\",\"Admi" + "nUser\",\"AdminUsers\",\"AdminUserCreate\",\"AdminUserUpdate\",\"AdminUserDeleted\",\"LegacyApiKeyCreated\"" + ",\"PasswordLockRequest\",\"LoginLockoutCleared\",\"AdminActionCatalogEntry\",\"AdminActionCatalog\",\"Adm" + "inAuthorizationRole\",\"AdminRoles\",\"AdminRoleWrite\",\"AdminRoleWritten\",\"AdminVersionRequest\",\"Adm" + "inRoleDeleted\",\"AdminPolicyGrant\",\"AdminUserPolicy\",\"AdminPolicyWrite\",\"AdminSimulationRequest\"," + "\"AdminSimulation\",\"AuditEvent\",\"AuditEventsPage\",\"AuditDecisionMode\",\"AuditSettings\",\"AuditSetti" + "ngsWrite\",\"StorageTargetHealth\",\"StorageTarget\",\"StorageTargetsResponse\",\"StorageTargetCreate\",\"" + "StorageTargetUpdate\",\"StoragePoolMember\",\"StoragePoolMemberRequest\",\"StoragePool\",\"StoragePoolsR" + "esponse\",\"StoragePoolCreate\",\"StoragePoolUpdate\",\"StoragePolicy\",\"StoragePoliciesResponse\",\"Stor" + "agePolicyCreate\",\"StoragePolicyUpdate\",\"StoragePolicyPreviewRequest\",\"StoragePolicyWinner\",\"Stor" + "agePolicyPreviewSample\",\"StoragePolicyConflict\",\"StoragePolicyPreview\",\"StoragePolicyApplyReques" + "t\",\"StoragePolicyApplyResponse\",\"StorageMigration\",\"StorageMigrationsResponse\",\"StorageMigration" + "Create\",\"StorageArchiveDeletion\",\"StorageArchiveResponse\",\"StorageArchiveRetryRequest\",\"StorageA" + "rchiveRetryResponse\",\"StorageHealthResponse\",\"StorageCleanupRequest\",\"StorageCleanupResponse\",\"S" + "torageComplianceTarget\",\"StorageCompliancePolicy\",\"StorageComplianceViolation\",\"StorageComplianc" + "eResponse\",\"EvidenceDetection\",\"EvidenceDetections\",\"EvidenceModel\",\"EvidenceModels\",\"EvidenceFi" + "leCheck\",\"EvidenceFileDeleted\",\"EvidenceProtectedCount\",\"EvidenceSyncComplete\",\"EvidenceSegment\"" + ",\"EvidenceTimelineTrack\",\"EvidenceTimeline\",\"EvidenceSegmentAt\",\"EvidenceRangeRequest\",\"Evidence" + "SegmentAtRequest\",\"EvidenceThumbnailRequest\",\"EvidencePreviewRecording\",\"EvidencePreview\",\"Evide" + "nceThumbnailSample\",\"EvidenceThumbnails\",\"EvidenceSearchResult\",\"EvidenceSearch\",\"EvidenceSearch" + "Request\",\"EvidenceBookmarkFilters\",\"EvidenceBookmarkResult\",\"EvidenceBookmark\",\"EvidenceBookmark" + "s\",\"EvidenceBookmarkCreate\",\"EvidenceBookmarkUpdate\",\"EvidenceBookmarkDelete\",\"EvidenceLprRead\"," + "\"EvidenceLprReads\",\"EvidenceLprSearchRequest\",\"EvidenceLprExportRequest\",\"EvidenceLprDeleted\",\"E" + "videnceBookmarkDeleted\",\"DeviceFloatRange\",\"DeviceCommandResult\",\"DeviceSuccess\",\"DeviceEmptyReq" + "uest\",\"PtzAxesRequest\",\"PtzPresetRequest\",\"PtzGotoPresetRequest\",\"PtzPresetResult\",\"PtzPresets\"," + "\"PtzCapabilities\",\"ImagingModeLevel\",\"ImagingModeLevelUpdate\",\"ImagingSettings\",\"ImagingSettings" + "Response\",\"ImagingModeOptions\",\"ImagingOptions\",\"ImagingOptionsResponse\",\"ImagingSettingsRequest" + "\",\"DaynightRequest\",\"DaynightResponse\",\"DaynightUpdated\",\"OnvifDiscoveryStatus\",\"OnvifDevice\",\"O" + "nvifDevicesResponse\",\"OnvifDiscoveryRequest\",\"OnvifProfile\",\"OnvifProfilesResponse\",\"OnvifTestRe" + "quest\",\"OnvifAddRequest\",\"OnvifAddResult\",\"OnvifClaimRequest\",\"StreamSingleResponse\",\"StreamCrea" + "teRequest\",\"StreamCreated\",\"StreamProbeRequest\",\"StreamProbeResponse\",\"StreamDeleteAccepted\",\"St" + "reamRefreshAccepted\",\"StreamRecordingState\",\"StreamRecordingRequest\",\"StreamRecordingChanged\",\"D" + "etectionZoneWrite\",\"DetectionZone\",\"StreamZones\",\"StreamZonesRequest\",\"StreamZonesSaved\",\"Stream" + "ZonesDeleted\",\"StreamRetentionRequest\",\"StreamRetention\",\"StreamDetectionEngineWrite\",\"StreamDet" + "ectionEngine\",\"StreamDetectionEnginesRequest\",\"StreamDetectionEngines\",\"IceServersResponse\",\"Obs" + "ervation\",\"ObservationPage\",\"ObservationEvidence\",\"ObservationTransform\",\"ObservationChangeEvent" + "\",\"ObservationClassification\",\"ObservationClassificationWrite\",\"ObservationClassificationResult\"" + ",\"IntegrationCapabilities\",\"ObservationError\",\"ObservationPipelineDiagnostics\"]}\012"; static const size_t lightnvr_openapi_document_length = sizeof(lightnvr_openapi_document) - 1; -static const char lightnvr_openapi_document_revision[] = "8fe32ddf7330eb66c2bac8cbdbcb531b16d7a3b18b1666e25f4e9419e7a2e16d"; +static const char lightnvr_openapi_document_revision[] = "a064e9bcff92e5a21938b8394d711cc3ef103230f1b2863e12c06de62f7609d2"; #endif diff --git a/include/web/stream_observation_settings.h b/include/web/stream_observation_settings.h new file mode 100644 index 000000000..e56e5705f --- /dev/null +++ b/include/web/stream_observation_settings.h @@ -0,0 +1,48 @@ +#ifndef LIGHTNVR_STREAM_OBSERVATION_SETTINGS_H +#define LIGHTNVR_STREAM_OBSERVATION_SETTINGS_H + +/* + * INT_01 L9: per-camera observation evidence settings on the stream API + * (docs/integrations/OBSERVATIONS_CONTRACT.md section 13). + * + * JSON fields (stream GET/POST/PUT): + * observation_evidence_enabled bool, default false + * observation_labels CSV, <= 16 labels of 1..31 bytes; "" = all + * observation_max_age_days integer 1..90, default 7 + * observation_max_bytes integer 64 MiB..1 TiB, default 2 GiB + * + * Migration 0095 only enforces > 0, so these bounds are enforced here. + */ + +#include +#include +#include + +#include +#include "core/config.h" + +#define OBSERVATION_LABELS_MAX_COUNT 16 +#define OBSERVATION_LABEL_MAX_BYTES 31 +#define OBSERVATION_MAX_AGE_DAYS_MIN 1 +#define OBSERVATION_MAX_AGE_DAYS_MAX 90 +#define OBSERVATION_MAX_BYTES_MIN (64LL * 1024 * 1024) +#define OBSERVATION_MAX_BYTES_MAX (1024LL * 1024 * 1024 * 1024) + +/* Add the four fields to a stream JSON object (budgets <= 0 are reported as + * the defaults the database stores). */ +void stream_observation_settings_to_json(cJSON *object, const stream_config_t *config); + +/* + * Validate and apply whichever of the four fields body carries. Nothing in + * config changes unless every present field is valid. Returns 0 on success + * (changed set when a value differs) or -1 with a client message in error. + */ +int stream_observation_settings_apply_json(const cJSON *body, stream_config_t *config, + bool *changed, char *error, size_t error_size); + +/* Normalize a label CSV (trim each entry) into out. Returns 0, or -1 with a + * message in error when the list violates the bounds. */ +int stream_observation_labels_normalize(const char *input, char *out, size_t out_size, + char *error, size_t error_size); + +#endif diff --git a/int01-event-enrichment-yawamf-plan.md b/int01-event-enrichment-yawamf-plan.md new file mode 100644 index 000000000..2969e6d5d --- /dev/null +++ b/int01-event-enrichment-yawamf-plan.md @@ -0,0 +1,4741 @@ +# Plan: INT_01 — Event Evidence & YA-WAMF Integration (P0 + P1 + P2) + +**Generated**: 2026-11-08 +**Source PRD**: `docs/prd/INT_01_Event_Enrichment_YAWAMF.md` +**Baselines**: lightNVR `0ff0d2e3` (this repo); YA-WAMF `53540f48` (v2.23.1), +cloned at `/home/matteius/opensensor/YetAnother-WhosAtMyFeeder`. +**Out of scope**: P3 (recorded-media retrieval / recorded-frame refinement). + +## Overview + +Two codebases, three phases: + +- **P0 — Contract** (both repos consume it): freeze observation / evidence / + change-event / classification schemas, API errors, permission mappings, + budgets and capability names. Put them in the canonical OpenAPI document and + in shared JSON fixtures, and capture a Frigate regression baseline in YA-WAMF. +- **P1 — Snapshot integration**: + - lightNVR adds durable observations, evidence paired with the inference + frame, a transactional publication log feeding the existing + router/outbox, cursor-based history, and snapshot APIs. + - YA-WAMF extracts a source-provider boundary, migrates source identity, and + adds a lightNVR provider with cursor recovery. +- **P2 — Classification feedback**: + - lightNVR adds a scoped annotation API with a new authz action and a + minimal UI display. + - YA-WAMF adds durable write-back with owner-correction precedence. + +### Key design decisions (adopted unless the reviewer or owner objects) + +1. **Table naming.** `camera_observations` already exists for camera + first/last-video tracking (`src/database/db_camera_observations.c`), and + OPS_02 already uses the "evidence" vocabulary (`evidence.protect`, + `getEvidence*` operation IDs). New names: + - Tables: `object_observations`, `observation_assets`, + `observation_asset_refs`, `observation_changes`, + `observation_publications`, `observation_classifications`. + - OpenAPI operation IDs: `*Observation*`, never `*Evidence*`. +2. **Transactional publication.** `event_bus_publish` is in memory, so a crash + after the DB write loses the event. We stage it instead: + - Each observation revision writes its `observation_changes` row and an + `observation_publications` row in **the same SQLite transaction**. + - A relay thread turns pending publications into envelopes and calls the + router → outbox path. `durably_enqueue_event()` is `static` in + `event_producers.c:91`, so L5 exports a wrapper + `event_producer_enqueue_durable(const event_envelope_t*)` that returns + `enqueued | duplicate | no_route | mqtt_disabled | failed`. + - The relay marks a publication relayed only after the outbox accepts it. + `no_route` and `mqtt_disabled` are recorded as `relayed_no_route`, and the + cursor API is the recovery path for those. After the outbox, call + `event_bus_publish`, as the system-health producer does. + - Delivery is **at least once**. The outbox prunes terminal rows + (`db_event_outbox_prune_terminal`), so a late retry may redeliver. + Consumers dedupe on `source + message_id`. + - At startup the relay drains anything still pending. + - **Locking.** `begin_transaction()` holds the global non-recursive DB + mutex until commit. No existing `db_*` helper that locks the mutex + (for example `db_set_system_setting`) may be called inside these + transactions. + - Follow the `db_auth.c` pattern: lock the mutex, `BEGIN IMMEDIATE`, run + raw `sqlite3` statements, commit or roll back, unlock. + - Keep transactions short. The mutex is shared with recording-path + writes, and X2 measures its hold time. + - **Startup order** (`main.c`): run L4 reconciliation first, then start the + L3 evidence worker, then start the L5 relay drain, then start the + detection threads. Shut down in reverse. +3. **Message ID.** The message ID is a deterministic UUIDv5 of + `(source, observation_id, revision)`. Retries and relay restarts reuse it; + the outbox dedups on source + event ID + destination. +4. **Cursor.** The cursor is the monotonic `observation_changes.seq` + (`INTEGER PRIMARY KEY AUTOINCREMENT`), ordered by persistence, never by + camera time. + - It is encoded opaquely (base64url of `{v, seq, epoch}`). + - `epoch` is a **DB-generation UUID** stored in a new + `observation_meta` table. It is created with the table and regenerated + on restore or import. It is not `event_installation_uuid`, which + survives a DB restore while `seq` rewinds. + - A page reads up to a `watermark = MAX(seq)` snapshot taken at query + start, so concurrent writes cannot cause skips or endless repeats. + - `limit` counts **observations** after deduplicating to the latest + revision within the page. + - `has_more` is true when more changes with `seq > page_last_seq` exist at + or below the watermark. `next_cursor` is always the last *change* seq + scanned, not the last observation returned. + - These cursors return `410` + + `{code:"cursor_expired", oldest_cursor, oldest_observed_at}`: + - a cursor older than the prune boundary (`observation_meta.oldest_seq`); + - an epoch mismatch; + - `seq > MAX(seq)`. +5. **Evidence format.** Evidence is a JPEG encoded off-thread (decision from + 2026-11-08): + - Local inference copies the frame into a bounded, ref-counted queue + (an owned RGB24 buffer; no retained `AVFrame`). + - A worker encodes it with the existing + `jpeg_encoder_cache_encode_to_memory` (`src/video/ffmpeg_utils.c:702`, + an RGB24 input path). There is no new mjpeg helper. It writes to + `/observations///.jpg.tmp`. + - The worker then fsyncs, renames to the final name, and fsyncs the + directory. + - Only after that does it commit `pending→ready` in the same transaction as + the change and publication rows. +6. **Box coordinate space.** Boxes are normalized `xywh` relative to the + **stored evidence image**. The `transform` records `source_width/height`, + `inference_width/height`, `scale_x/y`, `pad_x/y` and `mode` + (`none|resize|letterbox`) so boxes stay auditable. + - SOD pre-resize (`src/video/sod_detection.c:328-365`) and API JPEG + submission each record their own transform. +7. **Permissions** (P0 confirms these): + - `GET /api/observations*` metadata uses `live.view`, the same as + `/api/detection/results`. + - `…/snapshot` uses `snapshot.create`. + - `GET /api/integrations/capabilities` requires an authenticated principal + and returns only that principal's scope. + - P2 adds `observations.annotate` (bit_index 19, camera-scoped, + non-destructive). 0078 uses bits 15–18. Built-in Administrator gets it; + nothing else gets it by default. + - The action is mirrored in C: catalog order defines the bit + (`authorization.c:22`), and the OpenAPI action enums list it too. +8. **YA-WAMF identity.** + - `detections.frigate_event` is `UNIQUE NOT NULL`; the PK is `id`. + Foreign keys reference that unique column, and so does + `processing_job_state.event_id`. + - Keep `frigate_event` as the internal event key, which avoids the + wholesale rename the PRD forbids. lightNVR rows get + `lnvr::`. It cannot collide with Frigate IDs, + which are `.-`. + - New **nullable or server-defaulted** columns: `source_kind`, + `source_instance`, `external_observation_id`, `source_camera_id`. + - `source_camera_id` holds the lightNVR camera UUID. `camera_name` stays + for display, and identity uses the UUID (A7 rename and name reuse). + - Add a partial `UNIQUE(source_kind, source_instance, + external_observation_id) WHERE source_kind IS NOT NULL`. + - Legacy rows are backfilled to `('frigate', , + frigate_event)` in the migration. The new binary also lazily backfills + NULL-source rows at startup, because old binaries can still insert. + YA-WAMF's `_verify_schema` deliberately lets older builds run against a + newer DB and only warns, so "older binary refuses" cannot be enforced + for already-shipped binaries. + - **Multi-bird mapping**: one lightNVR observation maps to one YA-WAMF + detection row, with one `bird_observations` row at `bird_index` 0. + - Several objects in one frame become sibling detections sharing an + evidence asset ID, and visit grouping places them in the same visit. + - Frigate keeps its current multi-bird-per-event model. + - W4 and W9 must check visit counts, leaderboard counts and multi-bird + display for siblings. + - **Notification policy for sources without lifecycle**: the existing + `notify_mode="final"` fires only on `event_type=="end"`, which lightNVR + never sends. + - For sources without `track_lifecycle`, "final" behaves as notify on + first admitted ready classification, bounded by the existing visit + cooldown. + - W7 reuses or reconciles `mark_notified` rather than adding a parallel + flag. + - **Durable jobs**: reuse `processing_job_state(pipeline, event_id)` for + write-back and retryable recovery work. Do not add new job tables. +9. **Migration numbering.** + - lightNVR P1 uses `0095`; P2 uses `0096`. Main is at `0094`; re-check + before each merge. + - YA-WAMF P1 is one Alembic revision (source identity + `source_cursors`, + plus any notification-intent columns W7 needs) to keep a single head. P2 + write-back reuses `processing_job_state`, so it should need no revision. + - Tasks touching the same migration stream are serialized. + +### Constraints carried from project memory + +- Small PRs branched from `main`. Every task below is a PR-sized unit. +- `rust/` is generated from C by CI. Never hand-edit it. New C files only need + CMake registration. +- Local `/usr/local` FFmpeg headers may be stale, which can cause false + crashes. Run the mjpeg/evidence tests in the project Docker/CI toolchain if + local results look wrong. +- Audio is untouched. If any shared settings or UI work touches audio paths, + test with `audio_disabled` on. + +## Prerequisites + +- lightNVR toolchain: CMake build, `tests/unit` (Unity-style C tests), + Playwright contract suite (`tests/contracts`, `node:sqlite` seeding), + `scripts/contracts/generate.mjs`, `scripts/generate_embedded_migrations.py`. +- YA-WAMF toolchain: Python 3, `pytest` (`backend/tests`, pure lane + `backend/tests/pure`), Alembic 1.20.1 (`render_as_batch=True` on SQLite), + FastAPI 0.143.2, aiomqtt 2.5.1, httpx 0.28.1, Svelte 5 + Vite 8 UI + (`npm run check`, `npm test`). +- A YA-WAMF fork branch (`feature/lightnvr-provider`) and a named maintenance + owner before production release (PRD §7). +- An E2E harness host with Docker (mosquitto, lightNVR, YA-WAMF) and a recorded + bird fixture video (two-bird and moving-bird clips). +- Context7 was unavailable. The APIs above are pinned from the repos' + requirement files. Re-verify these against the pinned versions while + implementing: + - Alembic batch-mode FK behavior. + - aiomqtt 2.x subscription and reconnect semantics. + - The RGB24 input behaviour of `jpeg_encoder_cache_encode_to_memory` at + detection-stream resolutions. + +## Dependency Graph + +``` +P0 C1 (contract ADR) ─┬─ C2 (OpenAPI + codegen) ─────────────────────────┐ + C4 (perf baseline) ├─ C3 (shared JSON fixtures) ─────────────┐ │ + W0 (Frigate regression fixtures) │ │ + │ │ +P1 lightNVR: C1 ── L1 (mig 0095) ── L2 (db layer) ─┬─ L3 (evidence queue/worker) ─┬─ L6 (local detector) + │ ├─ L4 (reconcile/retention) ├─ L7 (HTTP detector + ONVIF unavailable) + │ ├─ L5 (publication relay + event type) + │ └─ L8 (observation API) ◄── C2 + L2+C2 ── L9 (per-camera opt-in/budgets API+UI) + L3,L4,L5,L8 ── L10 (diagnostics) + L6 ── L7 (serialized: shared signatures) C3 ── L5, L7 + L5..L10 ── L11 (contract+reliability tests) ── L12 (docs+recipe) + + YA-WAMF: W0 ─┬─ W1 (provider boundary + Frigate provider) ── W3 (route all Frigate calls via provider) + └─ W2 (Alembic identity/cursor migration) + W1+W2+W3+C3 ── W4 (common ingestion pipeline) + W1+C3 ── W5 (lightNVR client/provider) + W2+W4+W5 ── W6 (cursor recovery) W2+W4 ── W7 (durable notification intent) + W3+W5 ── W8 (settings/onboarding/health UI) + W3,W6,W7,W8 ── W9 (YA-WAMF acceptance tests) + + X: L11+W9 ── X1 (E2E P1 suite + A14 pilot recipe) + C4+L6+L7+L10 ── X2 (A11 saturation + perf gate) + +P2 L2 ── L13 (mig 0096 + db classifications + authz action) + L13+L8+L5 ── L14 (PUT annotation API) ── L15 (minimal UI display) + W4+W5+W7+C3 ── W10 (YA-WAMF write-back + correction precedence) + X1+L14+L15+W10 ── X3 (P2 E2E: A12, A13, A7-annotate) +``` + +## Tasks + +### P0 — Contract and fixtures + +#### C1: Freeze the integration contract (ADR) +- **depends_on**: [] +- **location**: `docs/integrations/OBSERVATIONS_CONTRACT.md` (new), `docs/EVENT_CONTRACT.md`, `docs/prd/INT_01_Event_Enrichment_YAWAMF.md` (status/links only) +- **description**: Write the normative contract that every later task implements. + - Observation object (R1), field by field: + - Types, nullability and bounds: `label` ≤31 chars matching + `MAX_LABEL_LENGTH`; `confidence` 0..1; up to 20 objects per batch, + matching `MAX_DETECTIONS`. + - `box` format and coordinate space; the `transform` object. + - `detector` provenance: `engine_key`, `engine_type`, `model`, + `model_version`. + - `track`: `{id, namespace, session}`, where `session` is a per-process + boot UUID so a reused track ID never joins unrelated tracks. + - `recording_reference`. + - `revision` semantics: +1 per evidence-state or classification change. + - Evidence asset: + - States `pending|ready|unavailable|expired`. + - Bounded reason enum: `queue_full`, `budget_exhausted`, `encode_failed`, + `write_failed`, `interrupted`, `no_matching_media`, `disabled`, + `retention_expired`, `deleted`. + - Dimensions, `captured_at`, and the media URL template. + - Change event `io.lightnvr.detection.observation_changed.v1`: + - Payload: `observation_id`, `revision`, `camera_uuid`, `label`, + `evidence_state`, and `change_cause` (`created|evidence|classification`). + Consumers must skip `classification`-only changes for re-classification. + - Subject: `camera/`. Topic suffix under the existing prefix. Severity + `info`. + - History query parameters: `camera_uuid[]`, `label[]`, `since`/`until` on + `observed_at`, `cursor`, and `limit` (default 100, max 500). + - Cursor encoding, watermark rule, and the `410 cursor_expired` body. + - Error catalog: + - Snapshot: `202` + `Retry-After` (1–30 s) for pending; `410 + evidence_expired`; `404 evidence_unavailable` with reason; `404` for + unknown or unauthorized observations (indistinguishable). + - Annotation: `409 revision_conflict`, `409 owner_decision_locked`, + `422 evidence_mismatch`. + - Capability document: `source` identity, `contract_versions`, and + operations (`observations.history`, `observations.snapshot`, + `observations.annotate`, plus `track_lifecycle`, `recorded_frame`, + `event_clip` and `range_clip` = false), with limits. + - Classification annotation schema (R5): `key` = `/`; + `classifier`, `model_version`, `label`, `taxonomy_id?`, `confidence`, + `classified_at`, `input_asset_id`, `input_revision`, + `kind: automated|owner_confirmed`, and `expected_revision`. + - Writer namespace is derived from the API token's identity, never from the + request body. + - Permission mapping (design decision 7) and the audit fields to record. + - Provisional budgets: evidence off by default; per-camera queue 8 frames / + 32 MiB global; worker concurrency 1; 7 days / 2 GiB per camera; history + retention 30 days. Record these as provisional until X2 confirms them. + - Supported-version matrix stub. +- **validation**: A reviewer can implement every L* and W* task without + inventing a field or status. Every PRD R1–R6 and T1–T4 bullet maps to a + contract section (include a traceability table). The ADR is merged before L1 + and C2 start. +- **status**: Completed +- **log**: + - Wrote the normative contract (commit `3ffc7408`). Decisions 1–9 are + adopted; deviations D1–D11 are in contract §1. Facts were checked against + the code: envelope validator key rules, `event_envelope_create_with_severity_and_id`, + authz catalog order and bits 15–18 in 0078 (19 and sort_order 200 are + free), `live.view` / `snapshot.create` on the existing detection + endpoints, `MAX_LABEL_LENGTH` 32 / `MAX_DETECTIONS` 20, the topic format, + and YA-WAMF `notify_mode` / `mark_notified` / `processing_job_state`. + - `reason_not_testable`: documentation only. Static verification instead: + a script confirmed that all 24 PRD IDs (R1–R6, T1–T4, A1–A14) appear in + the traceability table, that 38 relative links and anchors resolve, and + that there are no dangling § references. + - **Gotchas for later tasks**: + - **D1**: list filters are comma-separated, because the query getter + returns only the first occurrence and `query_string` is 1,024 bytes. + - **D2**: error bodies are `{error, code, …}`, so they stay valid `ApiError`. + - **D3**: the writer namespace is the normalized username, and authority is + pinned to the user id. It is advertised in capabilities, so W10 must not + hard-code `yawamf`. + - **D4**: snapshot returns `403` when `live.view` is held but + `snapshot.create` is not. + - **D5**: every observation has an asset row. + - **D6 (L1)**: `observation_changes` also needs `evidence_state` and + `evidence_reason` columns. + - **D7 (L1/L5)**: the publication states are `pending`, `relayed`, + `relayed_no_route`, `expired` and `failed`. + - **D8 (L5)**: cap this type at 2,000 pending outbox rows. + - **D9 (L4/L5/L11)**: write a `.cursor_head.json` sidecar for restore + detection, and regenerate the epoch at both + `restore_database_from_backup` call sites in `db_core.c`. + - **L5** needs a UUIDv5 helper (mbedTLS SHA-1); the frozen namespace and + a test vector are in §5.4. **L2/L8** need a base64url helper. + - **L8** must use `httpd_evaluate_camera_identity_action` rather than the + `httpd_authorize_*` helpers, which emit `403` and would leak existence. + - §19 (Performance) is a placeholder for C4 to fill. +- **files edited/created**: + - `docs/integrations/OBSERVATIONS_CONTRACT.md` (new) + - `docs/EVENT_CONTRACT.md` (reserved-type pointer section) + - `docs/prd/INT_01_Event_Enrichment_YAWAMF.md` (status line only) + +#### C2: Add the contract to OpenAPI and regenerate projections +- **depends_on**: [C1] +- **location**: `docs/api/openapi.json`, `include/web/openapi_document_generated.h` (generated), `web/generated/*` (generated), `redocly.yaml` if lint rules need it +- **description**: Add these operations, with `x-lightnvr-authorization` + blocks that copy the existing shape (`{version:1, kind:'camera', + requirements:[{action, resource:'raw'}]}`): + - `getIntegrationCapabilities` + - `listObservations` + - `getObservation` + - `getObservationSnapshot` + - `putObservationClassification` (marked P2 and enforced only once L14 + lands) + + **Coverage gate**: `tests/contracts/tooling/coverage-inventory.test.mjs` + requires every `operationId` to map to a registration in + `src/web/libuv_api_handlers.c`, via `docs/api/contracts/coverage.json` + (sha256 + line numbers) and `x-lightnvr-coverage.covered_operations`. + - C2 therefore also registers all five routes as `501 not_implemented` + stubs in a new `src/web/api_handlers_observations.c` and regenerates + `coverage.json`. + - L8 and L14 replace the stubs and regenerate `coverage.json` again. + + Add schemas `Observation`, `ObservationEvidence`, `ObservationTransform`, + `ObservationPage`, `ObservationChangeEvent`, `ObservationClassification`, + `IntegrationCapabilities` and `ObservationError`. Run + `node scripts/contracts/generate.mjs` and `scripts/contracts/lint.mjs`. +- **validation**: `node scripts/contracts/lint.mjs`, `generate.mjs --check` + and `coverage-inventory.test.mjs` pass. The stub routes return `501`. + `tests/contracts/tooling/*` passes. The generated header compiles. The existing operation-ID lists in + `tests/contracts/*.spec.ts` stay unchanged; new IDs are not `*Evidence*`. +- **status**: Completed +- **log**: + - Commit `a847d2b6`. Added five operations under a new `Integrations` tag + and 20 schemas. They are the 8 required ones plus `ObservationUuid`, + `ObservationTime`, `ObservationEvidenceState`, `ObservationEvidenceReason`, + `ObservationBox`, `ObservationDetector`, `ObservationTrack`, + `ObservationRecordingReference`, `ObservationClassifications`, + `ObservationClassificationWrite` (the closed PUT body), + `ObservationClassificationResult` and `IntegrationCapabilityOperation`. + Ten of them are exported as web validators. + - **PUT path**: two segments, + `/api/observations/{id}/classifications/{namespace}/{name}`; native route + `/api/observations/#/classifications/#/#`. + - **Authorization**: + - capabilities: `{version:1, kind:"authenticated"}`; + - list and get: camera `live.view`; + - snapshot: camera `snapshot.create`; + - PUT: **`authenticated` for now**. `generate_native.py` rejects any + action missing from `authorization.c`, and `observations.annotate` + arrives in L13. + - **Stubs**: every route calls `httpd_check_action_access`. Without a + credential it returns `401`; otherwise it returns + `501 {"error","code":"not_implemented"}`. Every operation documents `501`. + - **Coverage regeneration**: no script existed (the earlier inventory was + hand-built), so I added **`scripts/contracts/coverage.mjs`**: + - `node scripts/contracts/coverage.mjs` refreshes line numbers, handler + names and `source_sha256`, and keeps every reviewed field; + - `--add-coverage ` also adds new registrations, each matched to + exactly one OpenAPI operation (`{param}` becomes `#`); + - `--check` fails on drift. + + It errors on a stale entry (route removed) and on a registration with no + entry. On the HEAD inputs it reproduced `coverage.json` byte for byte. New + entries use coverage group `observations`. The `notes` counts were updated + by hand to 246 registrations and 237 operations. + - **RED→GREEN**: `tests/contracts/tooling/observations-contract.test.mjs` + failed 3/3 before the change (no schemas, ops, stub file or validators). It + passes 3/3 after. `tests/unit/test_api_handlers_observations.c` first + failed to compile (missing header) and now passes 2 tests (501 with the + code, and 401 unauthenticated). + - **Validation**: + - `lint.mjs`: valid, with the same 5 pre-existing warnings as HEAD. + - `generate.mjs --check`: OK. + - `node --test tests/contracts/tooling/*.test.mjs`: 49/49. + - `tsc -p tests/contracts/tsconfig.json`: clean. + - Full `build-tls` build of `lightnvr`, plus `test_api_handlers_openapi` + (8/8) and `test_openapi_projection` (21/21): pass. + - Live fixture server: all 5 routes return `501` with admin credentials + and `401` anonymously, and the admin `/api/openapi.json` projection + lists all 5. + - **Gotchas for later tasks**: + - **Every** route edit in `libuv_api_handlers.c` shifts line numbers. Run + `node scripts/contracts/coverage.mjs` afterwards, or + `coverage-inventory.test.mjs` fails on the sha256 and line checks. + - **L8**: keep handler names, or the script picks up a rename + automatically. Remove the `501` response from the GET ops once they are + implemented. + - **L14**: change the PUT `x-lightnvr-authorization` to camera + `observations.annotate` after L13 adds it to `authorization.c`, then + drop the P2 note and the `501`. Update the expectation in + `observations-contract.test.mjs` too. + - `ObservationEvidence` and `ObservationChangeEvent` enforce the + reason/state and `snapshot_url` consistency with `if`/`then`. The + strict Ajv needs `properties` declared in every `required` subschema, + or redocly warns. + - Response schemas use `additionalProperties: true` (readers ignore + unknown fields), and the PUT body is closed. + - `ObservationError.code` is an enum of the §11 codes. Adding a code + means editing OpenAPI. + - `build/` has `BUILD_TESTS=OFF`. Use `build-tls` for unit tests. +- **files edited/created**: + - `docs/api/openapi.json`, `docs/api/contracts/coverage.json` + - generated: `include/web/openapi_document_generated.h`, `web/generated/api/{schema.d.ts,validators.js,validators.d.ts}` + - `src/web/api_handlers_observations.c` (new), `include/web/api_handlers_observations.h` (new), `src/web/libuv_api_handlers.c` + - `scripts/contracts/coverage.mjs` (new) + - `tests/contracts/tooling/observations-contract.test.mjs` (new), `tests/unit/test_api_handlers_observations.c` (new), `tests/unit/CMakeLists.txt` + +#### C3: Shared contract fixtures for both implementations +- **depends_on**: [C1] +- **location**: `tests/fixtures/integrations/observations/` (new). Vendor the same files into YA-WAMF at `backend/tests/fixtures/lightnvr/` with a `SOURCE.md` naming the lightNVR commit. +- **description**: Write canonical JSON fixtures: + - A single-bird observation in each of the pending, ready, unavailable and + expired states. + - Two birds sharing one asset. + - A letterboxed HTTP transform. + - Change events for `created`, `evidence` and `classification`. + - Paginated pages with cursors. + - The `cursor_expired`, `evidence_expired` and pending-202 bodies. + - A capabilities document. + - Classification PUT request/response pairs, including conflict cases. + + Include a small JPEG with known box coordinates. Add a validator test that + checks every fixture against the C2 OpenAPI schemas. +- **validation**: A new `tests/contracts/tooling/observation-fixtures.test.mjs` + validates every fixture against `docs/api/openapi.json`. A YA-WAMF pure test + loads the vendored copies (added in W5). +- **status**: Completed +- **log**: + - lightNVR commit `d968b4d5`; YA-WAMF commit `466d5a28` (vendored copy, + `SOURCE.md` names `d968b4d5` and lists SHA-256 sums). + - **Inventory** (46 files, all listed in `manifest.json` with kind, + schema, request/status/headers or MQTT topic): + - One scenario, one change log (`manifest.changes`, seq 1001–1013). + - `observations/` (10): single bird A at revisions 1–5 + (pending, ready (= contract §3.6), ready + automated annotation, + owner-confirmed, expired); B `unavailable(queue_full)`; two birds + C0/C1 sharing asset `9999…`; D, an HTTP letterbox (1280x720 → 640x640, + scale 0.5, pad_y 140, with the inference-space box in the manifest); + E, ONVIF `no_matching_media` with a null box. + - `events/` (8): complete envelopes in `event_envelope.c` key order for + `created`, `evidence` (ready and expired) and `classification`. IDs are + UUIDv5 per §5.4. A r2 is the contract test vector + `43ebdf02-…`. + - `message-ids.json`: the namespace derivation and every vector. + - `pages/` (4): three chained `limit=2` pages at watermark 1010 + (A reappears at r3; `next_cursor` is the last *scanned* change), plus + `cursor=head`. + - `errors/` (9): `cursor_expired`, `invalid_cursor`, `evidence_pending` + (202 + `Retry-After`), `evidence_unavailable`, `evidence_expired`, + `observation_not_found`, `forbidden` (D4), `not_implemented`, + `service_unavailable`. + - `capabilities/` p1 and p2. + - `classifications/` (8 exchanges, each with `observation_file` = the + precondition): 201 create, 200 no-op retry, 409 `revision_conflict`, + 200 owner confirm, 409 `owner_decision_locked`, 422 + `evidence_mismatch` (wrong asset; pending), 403 + `namespace_forbidden`. + - `media/two-birds-160x80.jpg` + box manifest (pixel coords, colors, + sha256). + - **Generator**: `scripts/contracts/observation-fixtures.mjs` + (`--check`, `--jpeg`, `--vendor `, `--check-vendor `). + Its helpers `uuidv5`, `messageId` and `encodeCursor` are exported. + - **RED→GREEN**: `observation-fixtures.test.mjs` failed 9/10 with 1 skip + before the fixtures existed. After adding them it passes 9/9 with the + vendor check skipped, and 10/10 once vendored. Negative cases: reason on + ready, `snapshot_url` on pending, ready without `snapshot_url`, expired + without reason, a 32-byte label, `object_index` 20, a closed PUT body + and an event reason on ready are all rejected. Tampering with an event + revision fails the message-ID and generator checks. + `node --test tests/contracts/tooling/*.test.mjs`: 59 tests, 58 pass, + 1 skip (the vendor check, before vendoring). + - **Re-sync**: after committing fixture changes in lightNVR, run + `node scripts/contracts/observation-fixtures.mjs --vendor ../YetAnother-WhosAtMyFeeder`. + It refuses if the fixtures are dirty, unless `--allow-dirty`. Then + commit in YA-WAMF. The lightNVR tooling test compares the copy byte for + byte when the sibling checkout exists or `LIGHTNVR_YAWAMF_DIR` is set, + and skips otherwise. + - **Gotchas for later tasks**: + - **JPEG bytes**: they are canonical and are re-encoded only with + `--jpeg`, because sharp/libjpeg output can drift. The JSON `bytes` and + `sha256` derive from the file on disk. + - **Envelope**: OpenAPI has no envelope schema. The test checks envelope + shape locally, and validates `data` against `ObservationChangeEvent`. + - **Two fixture-level conventions** not stated in the contract: + - pending evidence keeps `origin`, `media_type`, `captured_at` and + `transform`, and leaves `width`, `height`, `bytes` and `expires_at` + null; + - evidence that never had media has every media field null. + + L2 and L8 should match them or amend the contract. + - **Fixture vs. contract wording**: the expired fixture keeps + `expires_at` at its estimate, and the `evidence_expired` error carries + the actual `expired_at`. + - **W10**: the PUT fixtures use user `yawamf`. Read the namespace from + `capabilities.principal.writer_namespace`; the test asserts that each + exchange's namespace is advertised. + - **W5**: the vendored `README.md` links to + `../../../../docs/...`, which resolves only in lightNVR. +- **files edited/created**: + - `tests/fixtures/integrations/observations/**` (new: 46 files plus `README.md`) + - `scripts/contracts/observation-fixtures.mjs` (new) + - `tests/contracts/tooling/observation-fixtures.test.mjs` (new) + - YA-WAMF: `backend/tests/fixtures/lightnvr/**` (new, with `SOURCE.md`) + +#### C4: Declare the performance fixture and capture a disabled baseline +- **depends_on**: [] +- **location**: `tests/performance/observations/` (new), `docs/integrations/OBSERVATIONS_CONTRACT.md` §Performance (append after C1 merges, or hand it to the C1 author) +- **description**: + - Record the hardware, stream count, resolutions, fps, detector cadence and + retention budgets. + - Build a repeatable workload: N looped RTSP fixture streams through go2rtc, + with a local SOD detector at a fixed interval. + - Capture a baseline with the feature absent: recording-gap count, p95 + recording-path latency (use the existing segment/packet timing metrics), + RSS, CPU and disk write rate. + - Script it so X2 can rerun it with the feature enabled. +- **validation**: The script runs twice on the same host with p95 variance + under 2%. Results are committed as JSON with the host description. +- **status**: Completed (partial validation: the DB-probe series failed the + 2% repeatability check, see the log) +- **log**: + - **Harness.** `run-workload.mjs` sets up the workload: + - a standalone go2rtc with one always-on ffmpeg publisher per stream, + using a deterministic `testsrc2` clip; + - an isolated lightNVR from `fixture-server.mjs` on ext4/NVMe, never + tmpfs; + - continuous recording with 10 s segments; + - the SOD detector `tiny20.sod` every 1 s on each stream. + It then samples a 300 s window after 30 s of warm-up and writes the + results JSON (host, build, workload, latency, gaps, resources, raw + samples). + - **Recording-path latency proxies.** No packet-write latency metric + exists, so the harness uses two black-box proxies: + - `segment_interval_ms`, from segment-file creation intervals seen by + inotify; + - `db_probe_ms`, timing `GET /api/recordings?limit=1` with a session + cookie, which takes the global DB mutex. + - **Gaps** come from three sources: the `lightnvr_recording_gaps_total` + delta, the catalog-row gaps, and interval gaps. + - **Pure helpers** are in `metrics.mjs`, covered by 10 node:test cases + (all passing). `compare.mjs repeat|gate` implements the C4 and X2 + checks. + - **Baseline**, run twice (feature disabled, 4×720p at 15 fps): + - 0 gaps in every source. + - 120/120 segments. + - Detector at 1.003 inferences per stream per second. + - CPU about 145% of one core. + - RSS high-water about 875 MB. + - Disk writes 2.33 MB/s. + - **Repeatability:** + - `segment_interval_ms` p95 was 10035.3 / 10037.9 ms (0.03%, pass). + - `db_probe_ms` p95 was 11.95 / 12.32 ms (**3.02%, fail**). The host was + shared with parallel agents, and the load average reached 9.25 during + run 2. +- **gotchas / handoff to X2**: + - Synthetic content produces no VOC detections, so no observations would + be created. X2 must use a detection-producing clip (`--source`), and must + rerun disabled mode on that same clip and the same binary before running + `compare.mjs gate`. + - Enable the feature only through `--stream-extra` and `--config-extra` + (the new `fixture-server` `extraConfig` option). Scrape the L10 + queue/drop/enqueue histograms into `results.feature_metrics`. + - Re-check DB-probe variance on a quiet host with a 600 s or longer window. + If it stays above 2%, gate only on segment interval plus gaps and record + that in C1. + - The go2rtc `exec:` lazy sources raced the first recorder/detector + connect, which is why the harness uses always-on publishers. + - Basic auth runs PBKDF2 per request, which is why the harness uses a + session cookie. + - The local build needs `PKG_CONFIG_LIBDIR` to point at the system FFmpeg + because of the stale `/usr/local` headers. + - The binary was built from `c46bff1c`. The `build.revision` field reads + `3c61e235` because docs-only commits landed between the build and the + run. + - C1 should link `tests/performance/observations/README.md` from + §Performance. C4 did not edit `OBSERVATIONS_CONTRACT.md`. +- **files edited/created**: `tests/performance/observations/{run-workload.mjs,metrics.mjs,compare.mjs,workload.json,README.md}`, + `tests/performance/observations/results/2026-11-08-disabled-{run1,run2,repeatability}.json`, + `tests/contracts/tooling/observation-perf-metrics.test.mjs`, + `tests/contracts/fixture-server.mjs` (optional `extraConfig`) + +#### W0: Capture the YA-WAMF Frigate regression baseline +- **depends_on**: [] +- **location**: YA-WAMF `backend/tests/fixtures/frigate_regression/`, `backend/tests/test_frigate_regression_baseline.py` (new) +- **description**: Before any refactor, add recorded Frigate MQTT payloads and + mocked `FrigateClient` responses. Add tests that pin current behavior: + - initial classification on a `new` message; + - update recovery; + - `end` refinement (final-frame / HQ snapshot); + - false-positive handling (`_handle_false_positive`); + - corrections; + - favourites; + - backfill (`backfill_service`); + - clip and full-visit generation calls. + + Assert the DB rows, media-cache keys, notification-dispatch calls and the + exact Frigate HTTP paths requested. +- **validation**: `pytest backend/tests/test_frigate_regression_baseline.py` + is green on unmodified `53540f48`. This suite is the A10 gate and must stay + green through every W* task. +- **status**: Completed (YA-WAMF commit `c5e7fecd` on `feature/lightnvr-provider`, not pushed) +- **log**: + - Characterization tests: they pin shipped behaviour, so there is no RED + phase. 46 tests pass on unmodified `53540f48`. To show they bite, six + mutations were applied to a scratch copy of `backend/app`, and five failed + the suite on the first run: + - live snapshot fetched with `crop=False`: 7 failures; + - `mark_notified` dropped after dispatch: 5; + - snapshot cache key renamed: 4; + - false positive overriding an owner tag: 1; + - full-visit trigger `source` changed: 1. + - `final` mode firing on `update` survived at first, because a thumbnail + fallback rescued the `new`. A recovery test was added, and it now + fails 1. + - Running it: `cd backend && python3.12 -m venv .venv && .venv/bin/pip install -r requirements-base.txt -r requirements-provider-cpu.txt -r requirements-dev.txt`, + then `.venv/bin/pytest tests/test_frigate_regression_baseline.py`. The + `-full` provider file pulls CUDA wheels; the CPU one is enough. + - Full suite: 7455 passed. Twelve migration tests fail only when the venv is + not on `PATH`, because they shell out to a bare `alembic`. With + `PATH=.venv/bin:$PATH` they pass. + - Harness, which W1–W4 should reuse rather than copy: + - DB: `monkeypatch.setenv("DB_PATH", migrated_history_db)` and then the + real `init_db()`/`close_db()`, so every module's `get_db` hits a private + migrated copy without per-module patching. + - Frigate: `FakeFrigate` (an `httpx.MockTransport`) is installed on + `frigate_client._client`. The full-visit service builds its own + `httpx.AsyncClient`, so its module-level `httpx` is swapped for a + namespace whose `AsyncClient` uses the same transport. Assertions are + on `METHOD path?sorted-query` strings. + - Media cache: `SNAPSHOTS_DIR`, `CLIPS_DIR`, etc. are patched to + `tmp_path`, and `_available` is set to True. + - Notifications: the outbound boundary is + `notification_service.notify_detection`, driven by a real + `NotificationDispatcher`. + - HQ and full-visit in-memory schedulers are stubbed, but the durable + `processing_job_state` row is asserted. + - Gotchas for W1–W4: + - Live `new`/`update` requests `snapshot.jpg?crop=1&quality=95`, while a + recovered `end` and backfill request `crop=0`. + - The live miss chain is: cropped snapshot twice, then uncropped, then + `thumbnail.jpg`, then the recording window + `/api/{cam}/start/{t-2}/end/{t+8}/clip.mp4`. + - A notification without a cached photo fetches + `snapshot.jpg?crop=1&quality=85`. + - The cached snapshot's `source` is `frigate_snapshot_cropped`. The HQ job + pipeline name is `high_quality_snapshot`. + - `detection_event_bounds` stores datetime strings. + - In `standard` mode a recovered `end` does **not** notify; only `final` + does. This matters for the W7 policy for sources without lifecycle. + - Corrections and favourites make zero Frigate calls. + - Full visit uses the timestamp in the event ID for its window and makes 2 + bounded attempts. + - `taxonomy_service` is a shared singleton, so patch it + input-conditionally. + - YA-WAMF `CLAUDE.md` §10 forbids AI attribution trailers in commits, so + the YA-WAMF commit has none. Later W* commits should follow that rule. +- **files edited/created**: + - YA-WAMF `backend/tests/test_frigate_regression_baseline.py` (new) + - YA-WAMF `backend/tests/fixtures/frigate_regression/mqtt_events.json` (new) + - YA-WAMF `backend/tests/fixtures/frigate_regression/frigate_api.json` (new) + +### P1 — lightNVR + +#### L1: Migration 0095 — observation, asset, change and publication tables +- **depends_on**: [C1] +- **location**: `db/migrations/0095_add_object_observations.sql`, `include/database/db_embedded_migrations.h` (regenerate via `scripts/generate_embedded_migrations.py`), `src/database/db_schema.c` / `db_schema_cache.c` if they enumerate tables +- **description**: Create these tables, with `migrate:down`: + - **`object_observations`**: `uuid` PK, `camera_uuid`, `stream_name_at_capture`, + `batch_id`, `object_index`, `observed_at_ms`, `created_at_ms`, + `engine_key`, `engine_type`, `model`, `model_version`, `label`, + `confidence` with a CHECK, the box as four nullable REALs, track + `id/namespace/session`, `recording_id` (FK ON DELETE SET NULL), + `asset_uuid` (nullable), `revision`, `UNIQUE(batch_id, object_index)`. + Indexes on `(camera_uuid, observed_at_ms)`, `(label, observed_at_ms)` and + `created_at_ms`. + - **`observation_assets`**: `uuid` PK, `camera_uuid`, `state` (CHECK in the + four states), `reason`, `path`, `bytes`, `width`, `height`, + `captured_at_ms`, `transform_json`, `protected`, `expires_at_ms`, + `created_at_ms`, `updated_at_ms`. + - **`observation_changes`**: `seq` INTEGER PK AUTOINCREMENT, + `observation_uuid`, `revision`, `cause`, `created_at_ms`, + `UNIQUE(observation_uuid, revision)`. + - **`observation_publications`**: `change_seq` PK/FK, `message_id`, + `state` (`pending|relayed|failed`), `attempts`, `last_error`, + `updated_at_ms`. + - **Per-camera settings**: add `observation_evidence_enabled` (default 0), + `observation_labels` (CSV, default `''`, meaning all object labels), and + `observation_max_age_days` / `observation_max_bytes` columns on `streams`. + - **Global settings**: insert `system_settings` keys + `observation_history_retention_days` and + `observation_global_queue_bytes`. + - **`observation_meta`**: a single row with `epoch_uuid` and `oldest_seq` + (the prune boundary). It lives outside `system_settings` so it can be + written inside observation transactions (design decision 2). + + Asset refcount is derived from `object_observations.asset_uuid`, so no + separate refs table is needed. If C1 needs per-reference protection, add + `observation_asset_refs`. +- **validation**: A migration up/down/up test in + `tests/unit/test_db_migrations*.c`, or the existing migration test pattern, + passes. The embedded header is regenerated and checked in. A fresh DB and an + upgraded `0094` DB both reach the same schema. +- **status**: Completed +- **log**: + - RED: `tests/unit/test_observation_migration.c` failed 4/4 before the + migration existed ("0095 is not embedded", "no such table"). GREEN 4/4 + after it, in `build-tls` and `build-tls-asan`. The `migration|db_|schema|embedded` + ctest subset (36 tests) and the generator pytest pass. The only failures in + the full suite are the FFmpeg/SOD tests, which fail locally for unrelated + reasons (stale headers, tests that need CLI args). + - Run: `cd build-tls && make test_observation_migration && ./bin/test_observation_migration`. + - Followed the contract where it says more than the plan: + - `observation_changes` has `evidence_state` and `evidence_reason` (D6). + - The publication state CHECK allows exactly the five D7 values. + - Added `observation_assets.origin` and `media_type` (§4.1). + - Exact names for L2: + - `object_observations`: `uuid`, `camera_uuid`, `stream_name_at_capture`, + `batch_id`, `object_index`, `observed_at_ms`, `created_at_ms`, + `engine_key`, `engine_type`, `model`, `model_version`, `label`, + `confidence`, `box_x`, `box_y`, `box_width`, `box_height`, `track_id`, + `track_namespace`, `track_session`, `recording_id`, `asset_uuid`, + `revision`. + - `observation_assets`: `uuid`, `camera_uuid`, `state`, `reason`, + `origin`, `media_type`, `path`, `bytes`, `width`, `height`, + `captured_at_ms`, `transform_json`, `protected`, `expires_at_ms`, + `created_at_ms`, `updated_at_ms`. + - `observation_changes`: `seq`, `observation_uuid`, `revision`, `cause`, + `evidence_state`, `evidence_reason`, `created_at_ms`. + - `observation_publications`: `change_seq`, `message_id` (UNIQUE), + `state`, `attempts`, `last_error`, `updated_at_ms`. + - `observation_meta`: `id` (always 1), `epoch_uuid`, `oldest_seq` + (initially 1). + - `streams` columns: `observation_evidence_enabled`, `observation_labels`, + `observation_max_age_days`, `observation_max_bytes`. + - `system_settings` keys: `observation_history_retention_days` = 30 and + `observation_global_queue_bytes` = 33554432. + - Gotchas for L2/L4/L5: + - Revision 1 must have cause `created`, and only revision 1 may have it + (CHECK). + - `reason` / `evidence_reason` must be NULL exactly when the state is + `pending` or `ready`. Reason values are open text (1..32 chars), so + callers validate the §4.3 enum. + - The box is all four columns or none, and so is the track (all three). + - `object_index` must be 0..19. `engine_type` excludes `motion`. + - With `foreign_keys = ON` (set by `db_core.c`): + - `asset_uuid` references `observation_assets`, so insert the asset + before its observations and delete observations before the asset. + - Deleting a change cascades to its publication. Deleting an + observation cascades to its changes. + - Deleting a recording sets `recording_id` to NULL (indexed). + - The migration enforces no stream budget bounds beyond `> 0`. The + provisional §13 bounds belong in the L9 API. + - `migrate:down` drops all observation data and leaves evidence files on + disk. + - No `db_schema.c` / `db_schema_cache.c` changes were needed, because they + enumerate no tables. +- **files edited/created**: `db/migrations/0095_add_object_observations.sql`, `include/database/db_embedded_migrations.h`, `tests/unit/test_observation_migration.c`, `tests/unit/CMakeLists.txt` (commit `7c2245a5`) + +#### L2: Observation DB layer (idempotent writes, transitions, cursor queries) +- **depends_on**: [L1] +- **location**: `src/database/db_observations.c`, `include/database/db_observations.h` (new), `tests/unit/test_db_observations.c`, `tests/unit/CMakeLists.txt`, `CMakeLists.txt`. Also `include/core/config.h` (`stream_config_t` evidence fields) and `src/database/db_streams.c` (load and save of the 0095 stream columns), so L3, L4, L6 and L7 can read per-camera settings without waiting for L9. +- **description**: Implement the following. Every mutating call is one + transaction using the `db_auth.c` pattern: mutex, `BEGIN IMMEDIATE`, raw + statements, commit. It must call no mutex-taking helper (design + decision 2). Each call writes the observation row, `observation_changes` + and `observation_publications` together. + - **`db_observation_insert_batch(batch_id, camera_uuid, objects[], asset_uuid|NULL, initial_asset_state)`**: + - Inserts N observations plus the asset row. + - Uses `INSERT OR IGNORE` on `(batch_id, object_index)`, so a retry is a + no-op that returns the existing IDs. + - Writes revision 1 with cause `created`. + - **`db_observation_asset_transition(asset_uuid, from, to, reason, path, bytes, w, h)`**: + - A compare-and-set on `state`. + - Bumps the revision of every observation referencing the asset, with + cause `evidence`, and adds their change and publication rows. + - **`db_observations_list(query, cursor, limit, page_out)`**: + - Joins `observation_changes` (`seq > cursor AND seq <= watermark`) and + dedups to the latest revision per observation within the page. + - Applies authorized `camera_uuid` set, label and time filters, and + bounded `limit`. + - Returns `next_cursor` and `has_more`. + - **`db_observation_get(uuid, out)`**. + - **`db_observation_pending_publications(limit)`** and + **`db_observation_publication_mark(...)`**. + - **`db_observations_prune(before_ms, batch)`**: prunes changes and + observations, and records the prune boundary in + `observation_meta.oldest_seq` inside the same transaction. + + Use the capture-time `camera_uuid` from the stream row at insert. Never join + on stream name for authorization. +- **validation**: Unit tests cover: + - a duplicate batch retry; + - two objects sharing one asset; + - CAS transition races (two transitions, exactly one wins); + - a pending→ready transition appearing after the cursor already passed the + `created` change (A5); + - a watermark with concurrent inserts (no skip, no infinite repeat); + - an expired cursor (below the oldest seq); + - a filter that excludes another camera; + - rollback on an injected failure between the change and publication + inserts. +- **status**: Completed +- **log**: + - RED: `test_observation_codec` and `test_db_observations` failed to link + (every new symbol undefined). `test_db_streams` failed 2/42 on the new + evidence-setting tests ("Expected 7 Was 0"). GREEN: codec 6/6, + observations 15/15 (stable over repeated runs), streams 42/42, `test_uuid` + 3/3, in `build-tls` and `build-tls-asan` (ASan clean). The + `db_|stream|config|migration|uuid|observation|schema|embedded|api_handlers|event_|detection` + ctest subset passes 94/94. The full suite fails only the known + FFmpeg/SOD/audio tests (stale local headers). + - Run: `cd build-tls && make test_db_observations test_observation_codec && ./bin/test_db_observations && ./bin/test_observation_codec`. + - API for L3/L4/L5/L8/L13 (`include/database/db_observations.h`). Results + are `db_obs_result_t`: `OK`, `DUPLICATE`, `CAS_LOST`, `ERROR`, `INVALID`, + `NOT_FOUND`, `INVALID_CURSOR`, `CURSOR_EXPIRED` and `CONFLICT`. + - `db_observation_insert_batch(const observation_batch_input_t*, const observation_asset_input_t*, observation_insert_result_t*)`. + A retry of the same `batch_id` returns `DUPLICATE` with the existing + IDs and writes nothing. A reused `batch_id` on another camera returns + `CONFLICT`. An unknown `camera_uuid` returns `NOT_FOUND`. The stream + name defaults to the stream row's name. + - `db_observation_asset_transition(asset_uuid, from, to, reason, const observation_asset_media_t*, observation_transition_result_t*)`: + a compare-and-set that returns `CAS_LOST`. The allowed edges are + pending→ready, pending→unavailable(`encode_failed|write_failed|interrupted|disabled`), + ready→unavailable(`write_failed`) and ready→expired(`retention_expired|deleted`). + Insert-only reasons (`queue_full`, `budget_exhausted`, + `no_matching_media`) are rejected as transitions. + - `db_observation_bump_revision_locked(sqlite3*, source, observation_uuid, cause, now_ms, &seq, &revision)` + is for L13. The caller holds `get_db_mutex()` inside `BEGIN IMMEDIATE`. + It snapshots the current asset state into the change row. + - `db_observation_get`, `db_observation_asset_get`, and + `db_observations_list(const observation_list_query_t*, cursor, observation_page_t*)` + (free it with `db_observation_page_free`). The query takes an already + authorized camera set (`camera_uuids`/`camera_count`, or `all_cameras`), + up to 8 labels, `since`/`until` and `limit` (0 means 100; 1..500). The + cursor is NULL, `"head"` or opaque. On `CURSOR_EXPIRED`, the page + carries `oldest_cursor` and `oldest_observed_at_ms`. + - `db_observation_meta_get(observation_meta_t*)` returns the epoch, + `oldest_seq` and `head_seq`. + `db_observation_meta_regenerate_epoch()` is for D9 (L4/L11). It is not + yet wired into the `db_core.c` restore paths. + - `db_observation_pending_publications(observation_publication_t*, max)` + returns a count. Each entry carries everything the L5 envelope needs: + message_id, cause, the state and reason at that revision, + camera/label/observed_at, asset and the change `created_at_ms`. + `db_observation_publication_mark(seq, state, last_error)` moves a row + out of pending. Passing `PENDING` records a retry. Every call + increments `attempts`, and a row that is no longer pending returns + `CAS_LOST`. + - `db_observations_prune(before_ms, batch, observation_prune_result_t*)`. + - Helpers: + - `lightnvr_uuid_generate_v5()` (`utils/uuid.h`, mbedTLS `md` SHA-1). + `observation_message_id()` matches the §5.4 vector. + - `base64url_encode()` and the strict `base64url_decode()` + (`utils/base64.h`). + - `observation_cursor_encode()` / `observation_cursor_decode()` match the + §7.4 example. + - `observation_reason_is_valid()` implements the §4.3 enum. + - `stream_config_t` gains `observation_evidence_enabled`, + `observation_labels[512]`, `observation_max_age_days` and `int64_t + observation_max_bytes`. All three save paths and both load paths in + `db_streams.c` handle them. Budgets of 0 or less are saved and loaded as + 7 days / 2 GiB, so a zeroed config cannot violate the 0095 CHECKs. + `config.c` defaults are set too. + - Gotchas: + - `event_identity_init()` must run before any mutation, because message + IDs need the source. The source is read before the DB mutex is taken; + `event_identity_init` holds its own mutex while calling + `db_get_system_setting`. + - Reads (list, get, pending publications, meta) open a private read-only + connection in one read transaction (one WAL snapshot) and never take + the global mutex. The watermark and scan are therefore consistent. + - Observations whose capture camera has no `streams` row are excluded + from list and get. `streams.camera_uuid` is immutable (trigger), so a + soft-deleted (disabled) camera stays visible. + - Prune deletes asset rows only when they are unreferenced and + `unavailable`/`expired`. An unreferenced `ready`/`pending` asset keeps + its row, so L4 must delete the file, expire the asset, and then prune + or delete it. + - Expiry keeps `path`, `bytes`, `width` and `height` on the asset row. + `path` is internal and must never be serialized (L8). + - Boxes are rounded to 6 decimals at insert. Clamping is the producer's + job (L6/L7); out-of-range boxes are rejected with `INVALID`. + - The rollback test injects its failure with a TEMP trigger on + `observation_publications`, so production code has no test hook. +- **files edited/created**: `include/database/db_observations.h`, `src/database/db_observations.c`, `include/utils/uuid.h`, `src/utils/uuid.c`, `include/utils/base64.h`, `src/utils/base64.c`, `include/core/config.h`, `src/core/config.c`, `src/database/db_streams.c`, `tests/unit/test_db_observations.c`, `tests/unit/test_observation_codec.c`, `tests/unit/test_db_streams.c`, `tests/unit/CMakeLists.txt` (commit `49bda76e`) + +#### L3: Evidence frame queue and encoder worker +- **depends_on**: [L2] +- **location**: `src/core/observation_evidence.c`, `include/core/observation_evidence.h` (new), `tests/unit/test_observation_evidence.c`. Reuse `jpeg_encoder_cache_encode_to_memory` from `src/video/ffmpeg_utils.c`; add no new encoder. +- **description**: + - **Producer API**: + `observation_evidence_submit(camera_uuid, batch, owned_rgb_buffer, w, h, transform)`. + - It is non-blocking. It takes ownership of an RGB24 buffer the caller + already made (`frame_to_rgb24`), with no copy, or makes one bounded + copy. It does not retain an `AVFrame`, which avoids pinning decoder or + hwaccel surfaces. + - The submitted item carries the observation batch, and the worker + performs `db_observation_insert_batch`. The capture and detection + threads never touch the DB mutex for this feature. + - It returns `ACCEPTED`, `DROPPED_QUEUE_FULL`, `DROPPED_BUDGET` or + `DISABLED` immediately. It never waits on I/O or locks held by the + worker; use a try-lock or atomics for admission. + - There is also a variant that takes already-encoded JPEG bytes (for L7), + which skips encoding. + - **Queue**: bounded by count and bytes, globally and per camera. + - **Worker**: + - Encodes the JPEG via the shared encoder (skipped for pre-encoded + bytes) and writes `.tmp`. + - Calls `fsync`, renames, and fsyncs the parent directory. + - Then calls `db_observation_asset_transition(pending→ready)`. + - On any failure it unlinks the temp file and transitions to + `unavailable` with a reason. + - **Producer-side drops**: when the queue is full or over budget, the + producer pushes a small fixed-size drop record (batch metadata, no + image) onto a separate bounded ring. + - The worker inserts that batch directly as + `unavailable(queue_full|budget_exhausted)`, so admitted observations + stay visible in diagnostics. + - If the drop ring is also full, increment a counter (`dropped_untracked`) + and lose the observation. That is acceptable, because it is reported in + L10. + - **Startup order**: start only after L4 reconciliation completes (design + decision 2). + - **Lifecycle**: lazy start. No queue allocation or thread while every + camera has evidence disabled (PRD R6). Integrate with + `shutdown_coordinator.c` and drain or abandon on shutdown. Abandoned items + become `interrupted` at reconciliation (L4). + - **Counters**: accepted, dropped by reason, queue high-water, bytes + written. +- **validation**: + - Unit tests: queue saturation returns a drop without blocking (time-bound + each submit under 1 ms); the encoded JPEG decodes back to the same + dimensions; the atomic rename never leaves a final-named partial file + (inject a failure between write and rename); disabled mode allocates + nothing (assert via counters or a test hook); shutdown with a full queue + does not leak (ASan build `build-asan`). +- **status**: Completed +- **log**: + - RED: `test_observation_evidence` failed to link (every + `observation_evidence_*` symbol undefined). GREEN: 14/14, stable over 8 + runs, in `build-int01-L3` and `build-int01-L3-asan` (ASan/UBSan/LSan + clean). `test_db_observations`, `test_observation_retention`, + `test_observation_relay` and `test_shutdown_coordinator` also pass under + ASan. + - Run: `cd build-int01-L3 && make test_observation_evidence && ./bin/test_observation_evidence`. + - The local build needs the apt FFmpeg headers. With the stale + `/usr/local/include` headers, `jpeg_encoder_cache_create` fails ("Failed to + open MJPEG codec for cache: Invalid argument"), every asset correctly ends + `unavailable(encode_failed)`, and the ready/JPEG tests fail. The L3 build + dirs put a symlink dir of `/usr/include/x86_64-linux-gnu/libav*` first via + `CMAKE_C_FLAGS=-I`. CI is unaffected. + - API (`include/core/observation_evidence.h`): + - `observation_evidence_init(const observation_evidence_config_t*)` + records the storage root and bounds only. Defaults: 64 items, 32 MiB + global (`observation_global_queue_bytes`, read in `main.c`), 8 per + camera, per-camera bytes = global. + - L6: `observation_evidence_submit(const observation_batch_input_t*, uint8_t *rgb24, int w, int h, const observation_evidence_transform_t*, char asset_uuid_out[37])`. + The buffer must carry `RGB24_SWS_TAIL_PADDING` slack, as + `ffmpeg_frame_to_rgb24_padded()` returns it. Ownership is taken on + **every** return. + - L7: `observation_evidence_submit_jpeg(batch, uint8_t *jpeg, size_t size, int w, int h, transform, asset_uuid_out)` + stores the exact bytes with origin `submitted_image`. + `observation_evidence_submit_unavailable(batch, "no_matching_media", asset_uuid_out)` + records ONVIF/external batches through the drop ring, so the caller + never takes the DB mutex. + - Results are `ACCEPTED`, `DROPPED_QUEUE_FULL`, `DROPPED_BUDGET`, + `DISABLED` (not initialized or stopping; nothing recorded) and + `INVALID`. The producer builds the batch (`batch_id`, `camera_uuid`, + objects) and the worker inserts it. Do not call + `db_observation_insert_batch` yourself. + - L4: `observation_evidence_set_budget_exhausted(camera_uuid, bool)` and + `observation_evidence_is_budget_exhausted()`. This is static memory, so + it works before init and allocates nothing. Its signature matches L4's + weak declaration. + - L10: `observation_evidence_get_stats(observation_evidence_stats_t*)` + covers accepted, dropped + (`queue_full`/`budget`/`disabled`/`untracked`), ready, failures + (encode/write/db), `disabled_at_encode`, interrupted, `bytes_written`, + backlog items/bytes, drop-ring depth, high-water items/bytes and + `budget_exhausted_cameras`. `allocated` and `worker_running` back the + "no queue when disabled" check. Counters reset on init. + - Helpers: `observation_evidence_transform_json()` (the §4.4 JSON) and + `observation_evidence_relative_path()`. + `observation_evidence_set_enabled_probe()` and the stock + `observation_evidence_stream_enabled_probe` (installed by `main.c`) + read `streams.observation_evidence_enabled` before encoding and record + `unavailable(disabled)` when it is off. + - Path layout: `observations///.jpg`, + relative to `config.storage_path` and stored in `observation_assets.path`. + The date is the **UTC** date of `observed_at_ms`. The temp file is + `.tmp`, files are 0640 and directories 0750. This matches L4's + reconciler. + - Design: + - Admission uses atomic reservations, a Treiber stack (one consumer takes + the whole list with a single exchange, so there is no ABA) and a + preallocated 256-cell Vyukov ring for drop records. Wakeups use + `sem_post`. + - A producer can touch only two mutexes: the slot-registration lock (the + first submit for a camera) and a trylock on lazy start. The worker holds + neither. + - The worker owns a private `jpeg_encoder_cache_create` encoder, because + the global `jpeg_encoder_get_cached` table can evict an encoder another + thread is using. + - An item that is being processed still counts against the bounds. + - Gotchas: + - `main.c` order: L4 reconcile → L3 init → L5 relay start → detection. + Shutdown: detection → L5 relay stop → L3 shutdown (2 s drain) → event + bus/identity. Publications the worker writes during the drain are + relayed at the next start. + - Abandoned or never-encoded items are inserted directly as + `unavailable(interrupted)` (or `disabled`), not pending→X. L2 allows + these as initial states. Their origin, media type and transform are + NULL (no media). + - If the `pending→ready` commit fails after the rename, the final file is + unlinked, so a ready file never sits behind a non-ready row. + - `expires_at_ms` is not set on ready. L4/L8 derive it from the + per-camera age budget. + - Camera slots are never recycled (`MAX_STREAMS` of them). Past that, a + camera is bounded by the global limits only, and its budget flag cannot + be stored (logged). +- **files edited/created**: `include/core/observation_evidence.h`, `src/core/observation_evidence.c`, `tests/unit/test_observation_evidence.c`, `tests/unit/CMakeLists.txt`, `src/core/main.c` (commit `5247c8ab`) + +#### L4: Startup reconciliation, retention and eviction +- **depends_on**: [L2] +- **note**: L4 must not run its startup sweep while the L3 worker runs. Use + `main.c` ordering, then let periodic eviction skip assets in `pending` + state. +- **location**: `src/core/observation_retention.c`, `include/core/observation_retention.h` (new), hook into the existing storage maintenance loop (`src/storage/*` / `db_maintenance.c`), `tests/unit/test_observation_retention.c` +- **description**: + - **Startup**: + - Delete `*.jpg.tmp` under `/observations`. + - Transition every `pending` asset without a finalized file to + `unavailable(interrupted)`. + - Transition every `ready` asset whose file is missing to + `unavailable(write_failed)`. + - Delete orphan final files that have no asset row. + - **Periodic** (bounded batch per tick): + - Per-camera age and byte budgets. + - Delete an asset file only when no retained observation references it and + it is not `protected`. + - Transition the asset to `expired(retention_expired)` and keep the + metadata. + - Prune observation history past `observation_history_retention_days` and + advance the oldest cursor. + - **Budget exhausted with only protected assets left**: set a flag that makes + L3 admission return `DROPPED_BUDGET`, and surface it via L10. + - Never touch recordings, and never inherit the legacy 100-per-camera MQTT + snapshot policy. Recording deletion does not delete evidence; only + explicit evidence policy does. +- **validation**: Unit tests use a temp storage directory and seeded rows: + - an interrupted write is reconciled; + - a shared asset survives until its last reference expires (A3); + - a protected asset blocks eviction and flips the budget flag; + - the prune advances `observation_meta.oldest_seq`; + - recording deletion leaves assets intact. +- **status**: Completed +- **log**: + - RED: `test_observation_retention` failed to compile without + `core/observation_retention.h`, then failed to link with every new + symbol undefined once the header existed. GREEN 8/8 in `build-int01-L4` + and `build-int01-L4-asan` (ASan/UBSan clean, 3 repeated runs); + `test_db_observations` still passes 15/15. In the + `storage|db_|migration|observation|retention|backup|schema|stream|uuid|event_` + ctest subset, three tests fail, none from L4: + - `test_storage_reconcile_incomplete` segfaults inside FFmpeg mpeg4. The + older `build-tls` binary segfaults the same way (stale local headers). + - `test_api_handlers_event_routes` expects 13 event types and finds 14. + That count comes from L5's uncommitted event type. + - `test_api_handlers_observations` and `test_observation_evidence` are + L8's and L3's in-progress tests. + - Run: `cd build-int01-L4 && make test_observation_retention && ./bin/test_observation_retention`. + - API (`include/core/observation_retention.h`): + - `observation_retention_startup(storage_path, &result)`: the startup + sweep. Returns counts for temp files deleted, stale files deleted, + pending assets interrupted, ready assets with missing files, and + whether the epoch was regenerated. + - `observation_retention_startup_done()`. + - `observation_retention_run(storage_path, now_ms, &result)`: one + bounded pass. Returns 1 if startup has not run yet. + - `observation_retention_maintenance_tick()`: the storage-manager hook. + - `observation_retention_budget_exhausted(camera_uuid)`: non-blocking. + - `observation_retention_budget_exhausted_count()`. + - `observation_retention_camera_status(out, max)`: for L10. Returns + retained bytes, max bytes, max age and the flag per camera, as of the + last pass. + - `observation_cursor_head_write/read/reconcile()`: the D9 sidecar. + - Hooks: + - `main.c` calls `observation_retention_startup()` right after "Storage + manager initialized", under the + `/* INT_01 startup order: reconcile (L4) -> evidence worker (L3) -> relay (L5) */` + marker. That is after `event_identity_init()` and before L3, L5 and + detection start. + - `storage_manager.c` calls `observation_retention_maintenance_tick()` + on every heartbeat (60 s). The tick runs a pass at most every 300 s, + or on the next heartbeat when the previous pass hit a bound. The + storage thread starts inside `init_storage_manager()`, before + reconciliation, so the tick is a no-op until startup has completed. + - `db_core.c` regenerates `observation_meta.epoch_uuid` after either + `restore_database_from_backup()` call succeeds, once migrations have + run. This closes the gap L2 noted. + - Semantics chosen where the contract is loose: + - An asset's age is the newest `observed_at_ms` among the observations + that reference it, falling back to `captured_at` and then + `created_at`. So a reference expires at its own media age, not at the + 30-day history prune, and a shared asset survives until its last + reference expires (A3). + - Byte pressure counts only `ready` bytes, protected ones included. It + evicts the oldest unprotected assets first. + - The flag is set only when a camera is still over budget and has no + unprotected `ready` media left. If a pass bound stopped eviction, the + flag is not set and `more_work` is set instead. + - Bounds per pass: 256 evictions and 5,000 pruned change rows, pruned + 500 per transaction. + - Expiry runs the CAS `ready→expired(retention_expired)` first, then + unlinks the file. A lost CAS (for example a racing L8 delete) leaves + the file to the winner. A crash between the two steps leaves a stale + file, which the next startup sweep deletes. + - Assets whose observations were all pruned while `ready` are reclaimed: + expire, unlink, delete the row. L2's prune keeps those rows. Protected + unreferenced assets are kept. + - Startup also handles a pending asset whose final file exists (crash + after rename, before the `ready` commit). It becomes `interrupted` and + the file is deleted, because the transform and dimensions are only in + worker memory. + - A `ready` asset whose path resolves outside the evidence tree is + treated as missing (`write_failed`). + - Never touches recordings: + - Paths resolve only to `/observations/...*.jpg`. Absolute + paths, `observations/...` and paths relative to the evidence tree are + accepted. Any `.`, `..` or empty segment is refused. + - The sweep uses `lstat`, skips symlinks, and only deletes `*.jpg.tmp` + and `.jpg`. + - Tested with a stored path that escapes the tree into `recordings/`. + - Budget flag: + - L3's header declares `observation_evidence_set_budget_exhausted()`, + but its `.c` is not committed yet. L4 calls it through a weak + reference on every pass, sending set and clear changes. + - That call is a no-op until L3's object is linked, and needs no code + change once it is. In a static archive, the weak reference does not + pull the object in itself; L3's own `main.c` wiring does. + - Overlap with L5 (D9 sidecar): + - L5's uncommitted `observation_relay.c` also implements the sidecar + check and writer, with the same path and the same + `{"epoch","head_seq"}` format, so the two interoperate. + - The check must happen in L4, because the reconciliation transitions + advance the DB head and would hide a rewind from a later check. L4 + checks first and rewrites the sidecar after the sweep, so L5's + startup check is then a harmless no-op. + - Whichever task lands second could remove the duplicate. + - Gotchas: + - `db_core.c` regenerates the epoch with raw SQL on the init connection + instead of calling `db_observation_meta_regenerate_epoch()`. + `rebuild_recordings` links a hand-picked source list without + `db_observations.c` and its event-identity dependencies. + - The restore path has no unit test. L11's restore test covers it. + - Startup returns -1 and leaves periodic eviction off when a DB read or + transition fails, for example when `event_identity_init()` failed. +- **files edited/created**: `include/core/observation_retention.h`, `src/core/observation_retention.c`, `tests/unit/test_observation_retention.c`, `tests/unit/CMakeLists.txt`, `src/core/main.c`, `src/storage/storage_manager.c`, `src/database/db_core.c` (commit `7373d598`) + +#### L5: Publication relay and the new event type +- **depends_on**: [L2, C3] +- **location**: `src/core/event_envelope.c` (registry entry + validator for `io.lightnvr.detection.observation_changed.v1`), `src/core/event_producers.c` (export `event_producer_enqueue_durable`, wrapping the static `durably_enqueue_event`, with a result that distinguishes `no_route` and `mqtt_disabled`), `src/core/observation_relay.c` + header (new), `src/core/main.c` (start/stop), `tests/unit/test_observation_relay.c`, `tests/unit/test_event_envelope.c` +- **description**: + - The relay thread wakes on a condvar signaled after commit, plus a 1 s + poll. + - It reads pending publications in seq order (bounded batch) and builds an + envelope with the deterministic message ID (design decision 3). + - It sends each envelope through `event_producer_enqueue_durable` (router + → outbox), then marks the publication `relayed` or `relayed_no_route`. + Then it calls `event_bus_publish` for in-process subscribers. + - If the outbox is `FULL` or errors, it backs off and leaves the row pending. + Count the failure for L10. + - Rows with `cause=classification` still publish, so consumers can display + them. + - It drains on startup before steady state. + - Existing detection topics and `event_producer_publish_detection` stay + unchanged. +- **validation**: + - Unit tests: a crash simulation (insert a publication, do not relay, + re-init, relay drains it); a duplicate relay of the same seq is an outbox + `DUPLICATE` that is still marked relayed; the envelope validator accepts + the C3 fixtures and rejects missing fields; route suppression (Fleet 03) + applies and does not delete history. + - `test_event_router.c` and `test_event_outbox.c` still pass. +- **status**: Completed +- **log**: + - Commit `442ab71b`. Built in `build-int01-L5` (MQTT+SOD, mirrors + `build-tls`) and `build-int01-L5-asan` (Debug ASan/UBSan, mirrors + `build-tls-asan`). + - RED: `test_observation_relay` failed to link (every relay symbol and + `event_producer_enqueue_durable` undefined). `test_event_envelope` failed + 3/13: registry count 13≠14, registry entry missing, and fixtures + rejected with "event type is not registered". + - GREEN in both builds, ASan clean: `test_observation_relay` 12/12 (stable + over 5 runs), `test_event_envelope` 13/13, `test_event_outbox`, + `test_event_router`, `test_event_bus`, `test_event_detection_adapter`, + `test_mqtt_delivery_worker`, `test_event_route_suppression`, + `test_db_event_routes`, `test_db_observations` 15/15 and + `test_api_handlers_event_routes` 5/5. Its catalog count was bumped to + 14. ctest subset + `db_|stream|config|migration|uuid|observation|schema|embedded|api_handlers|event_|detection|mqtt`: + 98/98, excluding the in-progress L3/L8 tests. Node contract tooling: + 59/59. + - Run: `cd build-int01-L5 && make test_observation_relay test_event_envelope && ./bin/test_observation_relay && ./bin/test_event_envelope`. + - **API** (`include/core/observation_relay.h`): + - `observation_relay_start(const observation_relay_options_t*)` (NULL + means defaults). It runs the D9 check, does a bounded synchronous + drain (≤5 s), then starts the thread. It is idempotent. + `observation_relay_stop()` leaves rows pending. + - **Notify hook**: `observation_relay_notify()`. Start registers it + through the new `db_observations_set_publication_hook()`, so + `db_observation_insert_batch` and `db_observation_asset_transition` + wake the relay automatically after commit (L3 and L4 need nothing). + **L13** uses `db_observation_bump_revision_locked` inside its own + transaction, so it must call `observation_relay_notify()` after its + COMMIT. Otherwise the change waits for the 1 s poll. + - **Stats for L10**: `observation_relay_get_stats(observation_relay_stats_t*)`. + Fields: `running`, `passes`, `relayed`, `duplicates`, + `relayed_no_route`, `expired`, `failed`, `enqueue_full`, + `enqueue_errors`, `mark_errors`, `read_errors`, `cap_pauses`, + `backoffs`, `sidecar_writes`/`_errors`, `epoch_regenerations`, + `last_relayed_seq`, `oldest_pending_created_at_ms` (lag = + now − this; 0 = none), `last_pass_pending`, `outbox_type_pending`, + `consecutive_failures` and `backoff_until_ms`. The counts of pending, + failed and expired publication rows are not in the stats; L10 should + count them in `observation_publications`. + - Also exported: `observation_relay_build_envelope(pub, source, &event, ...)`, + `observation_relay_run_once(opts, &pass)` (one pass, no thread), + `observation_relay_check_cursor_head()` and + `observation_relay_write_cursor_head()`. `OBSERVATION_CHANGED_EVENT_TYPE` + and `OBSERVATION_RELAY_OUTBOX_CAP` (2000) are defined there too. + - **Wrapper** (`include/core/event_producers.h`): + `event_producer_enqueue_durable(const event_envelope_t*, err, size)` + returns `event_producer_enqueue_result_t`: `FAILED`(-1), `ENQUEUED`, + `DUPLICATE`, `NO_ROUTE`, `MQTT_DISABLED` or `FULL`. + `event_producer_enqueue_result_name()` gives the name. The old static + `durably_enqueue_event` now wraps it, keeping its 0/-1 mapping + (no_route and mqtt_disabled → 0), so the other producers behave + identically. With routes, all-duplicate → `DUPLICATE`; only the + disabled default broker matched → `MQTT_DISABLED`; any non-FULL + failure → `FAILED`. + - `db_event_outbox_count_undelivered_type(type, &count)` counts + `pending` + `delivering` rows for D8. + - **Relay behavior**: + - It reads pending rows in seq order, up to 64 per pass. + - FULL, an error, a mark error or the cap stops the batch at that row, + so later rows never overtake it. FULL and error mark the row + `PENDING` with `last_error`, which counts an attempt. A cap pause + does not count one. + - Backoff is 1 s doubling to 30 s, and notifies are ignored while + backing off. A full batch loops immediately. + - A row past the 3600 s delivery expiry (measured from the change's + `created_at_ms`) → `expired`. An invalid envelope → `failed`, and the + batch continues. + - `event_bus_publish` runs only after a successful mark. A + `CAS_LOST`/`NOT_FOUND` mark is skipped silently. + - Envelope `time` = `change_created_at_ms / 1000`. `observed_at` is + formatted `YYYY-MM-DDTHH:MM:SS.mmmZ`. Generated envelopes equal all + 8 C3 fixtures byte for byte (`cJSON_PrintUnformatted`). + - **D9 (owned here)**: + - The sidecar `/observations/.cursor_head.json` is + `{"epoch","head_seq"}`, written via tmp + fsync + rename + dir fsync. + It is written at most once per second, after passes that saw + pending rows. + - At start, if the sidecar epoch equals the DB epoch and + `head_seq > DB head`, the relay calls + `db_observation_meta_regenerate_epoch()`. A missing or other-epoch + sidecar is rewritten without regenerating. + - The in-process `restore_database_from_backup()` call sites in + `db_core.c` are still not wired; they remain for L11 or a follow-up. + - **Gotchas**: + - **L4**: orphan-file deletion must skip `.cursor_head.json` and + `.cursor_head.json.tmp` in `/observations/`, which is not a + camera directory. + - **Bus publish re-runs routes**: the MQTT compatibility subscriber + re-evaluates routes and hits outbox `DUPLICATE`, the same as for + system-health transitions. An outbox row pruned in between can + therefore redeliver, which at-least-once allows. + - **Detection predicates**: route predicates with + `detection.labels_any` and similar only match + `detection.object.v1`. A route that lists both types with a label + predicate never matches observation events, which are then recorded + as `relayed_no_route`. + - **No `event_type` index**: `event_outbox` has none, so the D8 count + scans at most about 10k rows. It runs only in passes that have + pending work. + - **Validator layering**: the validator uses + `observation_reason_is_valid`/`observation_asset_state_parse` from + `db_observations.c`, so `event_envelope.c` now includes + `database/db_observations.h`. Both are in `lightnvr_lib`. + - **Start placement in `main.c`**: right after the L4 reconcile block. + L3's worker start must go between them. Relay stop comes before + `event_bus_shutdown` on both shutdown paths. +- **files edited/created**: `include/core/observation_relay.h` (new), `src/core/observation_relay.c` (new), `tests/unit/test_observation_relay.c` (new), `src/core/event_envelope.c`, `include/core/event_producers.h`, `src/core/event_producers.c`, `include/database/db_event_outbox.h`, `src/database/db_event_outbox.c`, `include/database/db_observations.h`, `src/database/db_observations.c` (post-commit hook only), `src/core/main.c`, `tests/unit/test_event_envelope.c`, `tests/unit/test_api_handlers_event_routes.c`, `tests/unit/CMakeLists.txt`, `docs/EVENT_CONTRACT.md` + +#### L6: Local detector integration (SOD/TFLite/multi-engine) +- **depends_on**: [L2, L3] +- **location**: `src/video/unified_detection_thread.c`: + - `detect_on_decoded_frame` around line 3221, after zone, object and + threshold filtering; + - `detect_with_runtime_engines` / `append_engine_result` around line 3033, + where the multi-engine path already builds an `rgb` buffer; + - the detection-stream background inference thread around line 1100, and + its result hand-off to the packet thread around line 2396. + + Also `src/video/sod_detection.c` (expose the pre-resize transform) and a + sidecar `include/video/detection_evidence.h` (engine key, transform and an + owned RGB handle). Do not change the `detection_result_t` layout, which the + generated Rust and tests use. +- **description**: + - **Where to capture**: capture evidence **inside + `detect_on_decoded_frame`**, while the decoded frame or RGB buffer is + alive. + - `run_detection_on_frame` frees the frame (around lines 2993–2995) + before `report_detections`. + - The detection-stream path infers on a background thread whose result is + reported later on the packet thread, when no frame exists. + - **Eligibility**: when the camera has evidence enabled and any surviving + detection's label is eligible (`observation_labels` empty, or a match): + - generate a `batch_id`; + - hand the owned RGB buffer, the surviving objects and the transform to + `observation_evidence_submit`. The L3 worker inserts the batch. + - **Detection-stream path**: the transform is recorded against the + **detection stream's** resolution, not the main stream's. The evidence + handle travels with the result slot, or the submit happens on the + inference thread. + - Keep the existing `store_detections_in_db_for_camera` and + `event_producer_publish_detection` calls untouched. + - Track IDs: emit `track.session` = boot UUID and `namespace` = engine key; + omit the track when `track_id < 0`. + - Motion-only engines never create object observations. + - Box coordinates are already normalized to the full decoded frame. The + evidence is that full frame, so `transform.mode = none`, or `resize` when + SOD pre-resizes; document it. +- **validation**: + - A unit or integration test with a synthetic decoded frame plus a fake + engine result produces N observations sharing one asset whose JPEG + matches the frame. With evidence disabled there are no inserts and no + queue allocation. + - The A2 moving-bird fixture: the saved image equals the inference frame, + not a later frame. Compare a hash of the decoded inference frame against + the decoded evidence (PSNR above a threshold). + - Existing `tests/test_stream_detection.c` and `test_detection_*` pass. +- **status**: Completed +- **log**: + - RED: `test_detection_evidence` failed to link (every + `detection_evidence_*` symbol undefined). `test_detection_observation_evidence` + failed 3/5 ("Expected 2 Was 0", "Expected 4 Was 0": no observations from + the UDT). GREEN: 8/8 and 5/5 in `build-int01-L6` (LiteRT on) and + `build-int01-L6-asan` (ASan/UBSan clean). `test_detection_*`, + `test_udt_recording`, `test_api_detection`, `test_db_detections`, + `test_event_detection_adapter`, `test_observation_evidence`, + `test_db_observations`, `test_db_streams` and `tests/test_stream_detection` + pass in both. Failing for other reasons: `test_sod_unified` and + `test_sod_voc` (pre-existing), plus `test_observation_relay` and + `test_observation_diagnostics`, which come from another task's + uncommitted edits. + - Run: `cd build-int01-L6 && make test_detection_evidence test_detection_observation_evidence && ./bin/test_detection_evidence && ./bin/test_detection_observation_evidence`. + The build dirs reuse L3's apt-FFmpeg header symlink dir via `CMAKE_C_FLAGS=-I…`. + The ASan dir needs `-DENABLE_LITERT=OFF`, because xnnpack does not build + under Debug/ASan. + - Integration points (`src/video/unified_detection_thread.c`): + - `udt_evidence_wanted` / `udt_submit_evidence` (around line 3063) are + shared by both paths. + - Single engine: `detect_on_decoded_frame` keeps the embedded-model RGB + buffer (`evidence_rgb`, around line 3352) only when evidence is enabled. + It submits after the threshold compaction (around line 3390). + - Multi-engine: `detect_with_runtime_engines` records each engine's slice + of `combined` and submits one batch per object engine, because a batch + has a single detector provenance. The last engine gets the shared + buffer; earlier ones get a padded copy. Motion slices are never + submitted. + - Detection stream: the background thread already calls + `detect_on_decoded_frame`, so capture happens on the inference thread + at the detection stream's resolution. The result slot still carries + only `detection_result_t`. + - The API branch is untouched (L7). + - Settings: the ctx caches `observation_evidence_enabled` (atomic) and + `observation_labels` (under `ctx->mutex`) at thread start. + `unified_detection_set_observation_settings(stream_name, enabled, labels)` + refreshes them and is called from `put_stream_worker_locked` + (`api_handlers_streams_modify.c`). Nothing in this feature reads the DB + on the detection/packet threads. `recording_id` comes from + `detection_link_recording_id` (in-memory). + - Sidecar API (`include/video/detection_evidence.h`): + - `detection_evidence_build_batch`: clamped boxes (degenerate → null box), + confidence clamped to [0,1], `object_index` = position among eligible + objects, model basename, track only for `track_id >= 0` with + namespace = engine key and session = `detection_evidence_boot_session()` + (UUIDv4 per process). + - `detection_evidence_label_eligible`: case-insensitive, trims blanks; + empty means all labels. + - `detection_evidence_transform_compute` and + `detection_evidence_model_geometry`: + - SOD is `resize` to the network size, via new `sod_model_input_size()`. + - LiteRT is `letterbox`, mirroring `letterbox_rgb24()`. + - RealNet or unknown is `none`. + - The stored image is always the full decoded frame, and boxes are + already normalized to it. + - `detection_evidence_observed_at_ms`: wall-clock ms when still inside + the frame's second, else `frame_ts * 1000`. + - `detection_evidence_capture`: takes ownership of the RGB buffer on every + return. + - For L7: reuse `detection_evidence_build_batch` (pass + `engine_type = "api"`). Do not call `detection_evidence_capture`, which + accepts only `object`. Submit with `observation_evidence_submit_jpeg`, and + for ONVIF use `observation_evidence_submit_unavailable`. Reuse + `detection_evidence_boot_session()` for tracks. The single-engine API + branch of `detect_on_decoded_frame` and the "API engines isolated" + branch of `detect_with_runtime_engines` are where L7's signature changes + land. + - Gotchas: + - Unity has no double support, so tests use a local `ASSERT_NEAR`. + - `test_udt_recording` wraps `lightnvr_uuid_generate_v4`. Evidence is off + there, so batch IDs are never generated. +- **files edited/created**: `include/video/detection_evidence.h`, `src/video/detection_evidence.c`, `include/video/sod_detection.h`, `src/video/sod_detection.c`, `include/video/unified_detection_thread.h`, `src/video/unified_detection_thread.c`, `src/web/api_handlers_streams_modify.c`, `tests/unit/test_detection_evidence.c`, `tests/unit/test_detection_observation_evidence.c`, `tests/unit/CMakeLists.txt` (commit `7c7082b6`) + +#### L7: HTTP API detector integration and ONVIF unavailable evidence +- **depends_on**: [L2, L3, L6, C3] +- **note**: Serialized after L6, because both change call signatures in + `unified_detection_thread.c`. +- **location**: `src/video/api_detection.c` (`perform_detection_request` around line 681, `finalize_detections` around line 798, the go2rtc-snapshot path `api_detection_should_use_go2rtc_snapshot` around line 124), `src/video/onvif_detection.c`, `src/video/detection_integration.c`, `tests/unit/test_api_detection.c` +- **description**: + - **HTTP**: today `jpeg_data` is freed before `finalize_detections` + (`api_detection.c:996` and `:1093`). `finalize_detections` receives only + `stream_name`, calling `store_detections_in_db(stream_name, …)` with no + `camera_uuid`. + - Change the signatures so the submitted JPEG bytes (ownership + transferred) and `camera_uuid` reach `finalize_detections`. Update its + callers in `unified_detection_thread.c`. + - Retain those exact bytes, plus the transform from source frame size to + submitted JPEG size and any server-side letterbox the response + declares. + - Insert the batch, then call the encoded-bytes variant of L3 (no + re-encode). + - When the API path fetched a go2rtc snapshot *as the inference input*, + that snapshot **is** the evidence and is acceptable. + - Never fetch a second image after inference. + - **ONVIF and external events**: create observations only for object-class + events (not motion), with asset `unavailable(no_matching_media)`. +- **validation**: + - Unit tests: `test_api_detection.c` asserts that the asset bytes are + byte-identical to the request body image; a letterboxed transform fixture + maps boxes correctly (C3 fixture); ONVIF produces `unavailable` with the + reason. + - The A2 HTTP variant passes. +- **status**: Completed +- **log**: + - RED: against the pre-L7 sources, `test_api_detection`, + `test_api_detection_observation_evidence` and + `test_detection_current_frame` failed to compile (every new type and + function undeclared). Mutation check: with the evidence hand-off and + geometry mapping disabled, the new integration test failed 3/7 ("no + ready asset", "Expected 1 Was 0", "Expected 0.4 Was 256"). GREEN: + `test_api_detection` 27/27 and `test_api_detection_observation_evidence` + 7/7 (stable over 3 runs) in `build-int01-L7` (LiteRT on) and + `build-int01-L7-asan` (ASan/UBSan clean). `test_detection_current_frame`, + `test_detection_observation_evidence`, `test_detection_evidence`, + `test_udt_recording`, `test_detection_system_onvif`, + `test_onvif_poll_recovery`, `test_observation_evidence`, + `test_db_detections`, `test_event_detection_adapter`, + `test_detection_io_recovery` and `tests/test_stream_detection` pass in both. + - Run: `cd build-int01-L7 && make test_api_detection_observation_evidence && ./bin/test_api_detection_observation_evidence`. + The test runs a real loopback HTTP detector (multipart and DOODS2), a + real migrated DB and the real L3 worker. Build the targets one at a + time: a single multi-target `make -jN` races in this generator. + - Signatures (`include/video/api_detection.h`): + - `detect_objects_api_with_evidence(…, options, const api_detection_evidence_t *)` + and `detect_objects_api_snapshot_with_evidence(…)`. The old + `*_with_options` functions wrap them with NULL, so + `detection.c`/`api_handlers` callers are unchanged. + `api_detection_evidence_t` = `{camera_uuid, engine_key, + evidence_enabled, labels_csv, observed_at_ms}`, built by the caller + from the ctx cache. `observed_at_ms` 0 = stamped at call start, before + the request, never after. + - `finalize_detections` (static) now takes threshold, options, evidence, + response meta and an owned `submitted_image_t`. With a `camera_uuid` + it uses `store_detections_in_db_for_camera` and + `event_producer_publish_detection`; without one it keeps the legacy + stream-name calls. + - `api_detection_parse_response_meta()` (the old + `api_detection_parse_response` wraps it), + `api_detection_apply_response_geometry()` and + `api_detection_jpeg_dimensions()` (SOFn parse for go2rtc snapshots). + - `include/video/detection_evidence.h`: + `detection_evidence_capture_jpeg()` (engine_type `api`; takes the + JPEG on every return), `detection_evidence_record_no_media()` + (`onvif`/`external`; skips the `motion` label; `keep_boxes=false` + means box null) and `detection_evidence_is_motion_label()`. + - Letterbox extension (contract §4.4, "unless the response declares a + server-side letterbox"). No existing server sends it; it is opt-in and + documented in the header. The optional top-level `"letterbox":{width, + height, scale | scale_x/scale_y, pad_x, pad_y, box_units:"normalized"|"pixels"}` + sits next to `"model"` and `"model_version"`. Missing scale means the + uniform min ratio; missing pads mean centred. Boxes are mapped onto the + submitted image **before** zone filtering, so the detections table and + MQTT also get stored-image boxes. Without the declaration nothing + changes (mode `none`, inference = JPEG size). The C3 fixture D test + reads `manifest.json`'s `inference_box` and + `observations/http-letterbox.json`. + - Evidence objects are the zone/object-filtered survivors with + `confidence >= threshold`, the same set the detection thread keeps. + Model: response `model`, else the DOODS2 `detector_name` unless it is + `"default"`, else null (the URL is never used as a model). The + transform source is the decoded frame for the frame path; for a go2rtc + snapshot the JPEG is its own source. + - ONVIF: `udt_record_onvif_observations()` is called from + `onvif_detection_thread_func` after a successful poll. `observed_at` is + the wall time before `PullMessages`, matching `detect_motion_onvif`'s + event stamp. The engine key is the first `onvif` engine, else + `legacy-primary`. Settings come from the ctx cache and the record goes + through the drop ring, so the ONVIF thread never touches the DB mutex. + `onvif_detection.c` already classifies object events (person, vehicle, + bicycle, face, animal, else `motion`), so no change was needed there. + - Gaps: + - Multi-engine API engines are still isolated in + `detect_with_runtime_engines`: they never run, so they produce no + evidence. A comment there records this. + - No external-event producer exists. `store_detection_result` has no + callers, so `detection_evidence_record_no_media` with `external` is + ready but not wired. + - The ONVIF tests call `udt_record_onvif_observations` directly rather + than through the polling thread. + - For L11/X1/X2: + - The A2 HTTP check is in place: the asset bytes equal the request body + image, and the server sees exactly one request. + - A server-declared letterbox changes the boxes on legacy detection rows + too. + - The fake server sniffs `Content-Type: application/json`, not just + `application/json`, because lightNVR sends `accept: application/json`. + - The API path holds the JPEG for the whole request, up to the 10 s + timeout. That adds one JPEG of memory per in-flight API call. It is + not counted against the L3 bounds until it is submitted. +- **files edited/created**: `include/video/api_detection.h`, `src/video/api_detection.c`, `include/video/detection_evidence.h`, `src/video/detection_evidence.c`, `src/video/unified_detection_thread.c`, `tests/unit/test_api_detection.c`, `tests/unit/test_api_detection_observation_evidence.c` (new), `tests/unit/test_detection_current_frame.c`, `tests/unit/CMakeLists.txt` (commit `9830adb6`) + +#### L8: Observation and capability API handlers +- **depends_on**: [L2, C2] +- **location**: `src/web/api_handlers_observations.c` (new), `include/web/api_handlers.h`, `src/web/libuv_api_handlers.c` (C2 already registers the routes; replace the GET stubs and keep the PUT stub at 501), `docs/api/contracts/coverage.json` (regenerate), `src/web/openapi_projection.c` if routes must be listed, `tests/unit/test_api_handlers_observations.c` +- **description**: + - **Authorization**: resolve each observation's capture-time `camera_uuid`, + then authorize via the existing Fleet 02 camera-scoped helpers + (`src/core/authorization.c`). Use `live.view` for metadata and + `snapshot.create` for images. + - List queries intersect the requested `camera_uuid[]` with the + principal's authorized set. Unauthorized explicit cameras return `403`, + matching the investigation precedent. An unknown or unauthorized + single ID returns `404`. + - **Snapshot**: `ready` streams the file with `Content-Type: image/jpeg`, + `Cache-Control: private` and an ETag of the asset UUID. `pending` returns + `202` + `Retry-After`. `expired` returns `410`. `unavailable` returns + `404` with a reason. It never substitutes a latest frame. + - **Capabilities**: returns the installation source (`event_identity`), + contract versions, operations and limits. `annotate` is false until L14. + - **Audit**: snapshot fetches go through the existing audit path where + sensitive-access auditing exists. Never log bytes or tokens. +- **validation**: + - Unit tests per status code. + - Contract coverage in L11. + - OpenAPI projection lint shows each new route has exactly the declared + authorization requirement. +- **status**: Completed +- **log**: + - Commit `2dfe559f`. The four GET stubs are replaced; the PUT stays `501` + (L14). Handler names and routes are unchanged, so `coverage.json` did not + move (`coverage.mjs --check` OK). The `501` response is removed from the + four GET operations in OpenAPI, the projections are regenerated, and + `observations-contract.test.mjs` now expects `501` only on the PUT. + - **Behaviour**: + - **Authorization** uses `httpd_evaluate_camera_identity_action` on the + capture-time `camera_uuid` for get and snapshot, so unknown, + deleted-camera and unauthorized IDs are byte-identical + `404 observation_not_found`. List and capabilities use + `db_fleet_camera_load_readonly` and `authorization_filter_cameras`. + - **List**: D1 comma lists (≤10 cameras, lowercased; ≤8 labels of + 1..31 bytes), RFC 3339 `since`/`until` (`Z` or an offset), `limit` + 1..500 and `cursor`/`head`. An empty or undecodable parameter is + `400 invalid_request` with `field`. An explicit camera without + `live.view`, unknown UUIDs included, is `403 forbidden` (audited as a + denial). Otherwise the scope is the principal's `live.view` set, and an + empty set gives an empty page. Errors map to `400 invalid_cursor` and + `410 cursor_expired` with `oldest_cursor`/`oldest_observed_at`. + - **Snapshot**: + - `ready` streams the file through `http_serve_file` with + `image/jpeg`, `Cache-Control: private, max-age=300` and + `ETag: ""`; `If-None-Match` gives `304`. + - `pending` is `202` with `Retry-After`; `unavailable` is + `404 evidence_unavailable` with `reason`; `expired` is + `410 evidence_expired` with `reason` and `expired_at` (the asset's + `updated_at_ms`). + - D4: a denied `snapshot.create` re-checks `live.view` and returns + `403 forbidden` if it is held, otherwise `404`. + - A `ready` row whose file is gone re-reads the asset: `410` if it has + expired meanwhile, else `404 evidence_unavailable(write_failed)`. + Nothing is substituted. + - The stored path must be `observations/…/*.jpg` relative to + `storage_path` (L3's format), or absolute under it, with no `.`/`..` + segments. Anything else is `500` and is logged. + - **Capabilities**: + - `source` comes from `event_identity`, and `product_version` is + `LIGHTNVR_VERSION_STRING`. + - history and snapshot are `authorized` when the action is held on any + camera, else `reason: not_authorized`. `annotate` is + `supported: false, not_available_in_this_version`. + - limits come from the `db_observations.h` constants. + `history.retention_days` is read from `system_settings` (default + 30). `delivery.topic` is `/v1/events//{camera_uuid}`. + - `cameras` holds at most 1,024 entries, each with `evidence_enabled` + from the stream config and the actions held. + - `principal.writer_namespace` is the D3 normalization, or null for an + invalid or reserved name. + - **Audit**: + - Snapshot: every fetch is recorded as `snapshot.create` with + `target_type "observation"` and `target_uuid` = the observation ID. + The details are camera_uuid, asset_id, evidence_state and the + decision source, never a path. Allowed reads follow the per-action + decision mode: `off` records nothing, `summarize` goes through + `audit_log_authorization` on the camera (a summary row cannot carry + an observation ID), and `record` writes the full row. + - Get: goes through `audit_log_authorization(live.view)` as today. + - List: audits only explicit-camera denials. + - **Exported for L14** (`include/web/api_handlers_observations.h`): + `observation_record_to_json()`, which follows the C3 conventions (pending + nulls width/height/bytes/expires_at; no `origin` means every media field + is null; no `path`), plus `observation_format_time()`, + `observation_parse_time()` and `observation_writer_namespace()` + (returns `-1` invalid / `-2` reserved). + - **RED→GREEN**: + - RED: the extended `test_api_handlers_observations` failed to link (18 + undefined references to the new helpers and serializer). + - GREEN: 18/18 in `build-int01-L8` and in `build-int01-L8-asan`, ASan and + LSan clean. + - The tests run against a migrated DB seeded through the L2 API. They wrap + `httpd_check_action_access`, `httpd_evaluate_camera_identity_action`, + `authorization_filter_cameras`, `http_serve_file` and + `audit_log_append`. + - Coverage: every status code; the authorization intersection; D4; + malformed parameters (17 cases plus 11 cameras); paging, `head` and + expired-cursor resume; ETag/304; path containment; and the missing file. + - Exact `cJSON_Compare` equality with 8 observation fixtures and with the + `invalid-cursor`, `observation-not-found`, `snapshot-forbidden` and + `evidence-unavailable` bodies. Key and type shape matches the page, + capabilities, `cursor-expired`, `evidence-pending` and + `evidence-expired` fixtures. + - With `OBS_RESPONSE_DUMP_DIR` set, all 60 JSON bodies the test produces + validate against the generated web validators (Observation, + ObservationPage, ObservationError, IntegrationCapabilities). + - **Validation**: `node --test tests/contracts/tooling/*.test.mjs` 59/59; + `coverage.mjs --check` and `generate.mjs --check` OK; `lint.mjs` valid + with the same 5 pre-existing warnings. `test_api_handlers_openapi` 8/8, + `test_openapi_projection` 21/21, and the full `lightnvr` target builds. + - **Gotchas for later tasks**: + - **L9**: `openapi.json` now has no GET `501`s. Regenerate after your + schema edits; `coverage.json` only moves if `libuv_api_handlers.c` + does. + - **L10**: the setup probe `GET /api/observations?camera_uuid=X&limit=1` + works as is, and an unauthorized X is `403`, not an empty page. + `Retry-After` is provisional: 1 s while the asset is under 5 s old, + else 2 s. Once L3 exposes queue depth, wire `min(30, 2 × ahead)` into + `retry_after_seconds()`. + - **L11**: the unit tests wrap authz, so the Playwright suite must prove + real scoped-token isolation. The DB-restore case surfaces as `410` + with `oldest_cursor` in the current epoch. Capabilities + `history.oldest_observed_at` is installation-wide (the same value the + `410` body carries), not camera-scoped. + - **L14**: reuse `observation_record_to_json()` and fill the two + `classifications` lists inside it. Use `observation_writer_namespace()` + for the D3 check, and flip `observations.annotate` in + `handle_get_integration_capabilities()`; also add the + `observations.annotate` action to each `cameras[].actions`. The PUT + handler currently only authenticates and returns `501`. + - In unit tests, `--wrap=audit_log_append` does not intercept appends made + inside `audit_log.c` (such as via `audit_log_authorization`). Those + write to the real audit table. + - Concurrency: the L3 agent's plan commit swept in this task's staged + files. It was split back out by a soft reset of that local, unpushed + HEAD, so the L3 plan commit is now `0bf32a53`. Other agents should + stage with explicit paths or `-p` and commit right after staging. +- **files edited/created**: + - `src/web/api_handlers_observations.c`, `include/web/api_handlers_observations.h` + - `tests/unit/test_api_handlers_observations.c`, `tests/unit/CMakeLists.txt` (wraps, `OBS_FIXTURE_DIR`, timeout) + - `docs/api/openapi.json` (GET `501`s removed), generated `include/web/openapi_document_generated.h`, `web/generated/api/schema.d.ts` + - `tests/contracts/tooling/observations-contract.test.mjs` + +#### L9: Per-camera opt-in and budget configuration (API + UI) +- **depends_on**: [L2, C2] +- **location**: L2 already adds the `stream_config_t` and `db_streams` fields. `src/web/api_handlers_streams_get.c`, `src/web/api_handlers_streams_modify.c`, `docs/api/openapi.json` (stream schema fields; coordinate with C2's author or follow on), `web/js/components/preact/StreamConfigModal.tsx`, `web/js/types/*`, i18n strings +- **description**: + - Expose evidence enable, eligible labels, max age and max bytes per stream, + gated by `camera.configure`. + - The UI section is "Observation evidence for integrations". It notes that a + bird-capable object detector is required and that it is off by default. + - Validate bounds server-side. + - The detection thread reads these via the stream config reload path; a + changed setting takes effect without restart. +- **validation**: + - `test_db_streams` or the stream API unit tests round-trip the fields. + - `web` jest/Playwright: the toggle persists and the default is off. + - `tests/contracts/streams.api.spec.ts` still passes, with schema additions + accepted. +- **status**: Completed +- **log**: + - RED: `test_put_stream_launch_failure` failed 5/24 on the new tests + (fields missing from GET; PUT/POST accepted out-of-range values with + 202/201). The web spec failed on the missing util, then on the + StreamsView/modal wiring and the en.json keys. GREEN: the C test passes + 24/24; `test_db_streams` 42/42; `test_delete_stream_launch_failure` 7/7; + `test_stream_manager` 15/15. Web: jest 85 suites / 1018 tests, + `npm run typecheck` and eslint on the changed files are clean. + `streams.api.spec.ts` passes 9/9 against `build-int01-L9/bin/lightnvr`. + `generate.mjs --check`, `lint.mjs`, `coverage.mjs --check` and the + `tests/contracts/tooling` tests (59/59) pass. + - Run: `cd build-int01-L9 && make test_put_stream_launch_failure && ./bin/test_put_stream_launch_failure` + and `cd web && npx jest tests/observationEvidenceSettings.spec.js`. + - API fields (stream GET list, detail and `/full`; POST and PUT bodies): + - `observation_evidence_enabled`: boolean, default false. + - `observation_labels`: a CSV string. Entries are trimmed; at most 16 + entries of 1..31 printable ASCII bytes; empty entries are rejected. An + empty string means every label. Case is preserved, so matching must be + ASCII case-insensitive. + - `observation_max_age_days`: an integer, 1..90 (default 7). + - `observation_max_bytes`: an integer, 67108864..1099511627776 (default + 2147483648). + - Server behaviour: + - Null, a wrong type or a fractional number returns 400 with an + `observation_*` message, before any task is queued. + - The validator is shared by POST and PUT + (`stream_observation_settings_apply_json`), and the GET paths use + `stream_observation_settings_to_json`. + - Writes are gated by the existing `camera.configure` check. Reads are + visible to `live.view`, like the other detection settings. + - OpenAPI: the fields are optional on `StreamDetailsBase`, because the + captured baseline payloads predate them. They are typed and bounded on + `StreamConfigurationUpdate` and `StreamCreateRequest`. + - UI: the "Observation evidence for integrations" accordion + (`expandedSections.observation`) has a toggle (off), eligible labels, max + age in days and a size limit in MiB. It notes that a bird-capable + detector is required. `web/js/utils/observation-evidence.ts` maps the + form to and from the wire and validates before save. Strings are in + `en.json` only; other locales fall back to English. + - Reload path: + - A PUT persists through `update_stream_config()` and restarts nothing. + - `get_stream_config*()` reads SQLite, so the L3 encoder's + `observation_evidence_stream_enabled_probe` sees the change at once. + - L6 (uncommitted at the time of L9) caches the flag and labels in the + detection context at thread start and exposes + `unified_detection_set_observation_settings(name, enabled, labels)`. + No caller exists yet. Add this line to `put_stream_worker_locked()` + after `update_stream_config()` succeeds: + `unified_detection_set_observation_settings(task->stream_id, task->config.observation_evidence_enabled, task->config.observation_labels);` + Its -1 return (no running thread) can be ignored. + - L9 did not add the call, because the symbol was not yet in the tree. + - Gotcha: when this was written, L6's in-progress `detection_evidence.c` + in the shared working tree broke the full `lightnvr_lib` build. Build + the L9 targets before L6 edits land, or after they are committed. +- **files edited/created**: `include/web/stream_observation_settings.h`, `src/web/stream_observation_settings.c`, `src/web/api_handlers_streams_get.c`, `src/web/api_handlers_streams_modify.c`, `tests/unit/test_put_stream_launch_failure.c`, `docs/api/openapi.json`, `include/web/openapi_document_generated.h`, `web/generated/api/schema.d.ts`, `web/generated/api/validators.js`, `web/js/utils/observation-evidence.ts`, `web/js/types/streams.ts`, `web/js/components/preact/StreamsView.tsx`, `web/js/components/preact/StreamConfigModal.tsx`, `web/public/locales/en.json`, `web/tests/observationEvidenceSettings.spec.js` (commit `e365b6b3`) + +#### L10: Diagnostics and metrics +- **depends_on**: [L3, L4, L5, L8] +- **location**: `src/web/api_handlers_metrics.c`, `src/web/api_handlers_system_health.c` / `fleet_health.c`, `web/js/components/preact/SystemView.tsx` (or the system health panel) +- **description**: Report the following through the existing + metrics/diagnostics: + - accepted and dropped observations by reason; + - evidence backlog and failures; + - retained bytes per camera; + - the oldest recoverable cursor and its time; + - publication pending, failed and lag; + - the budget-exhausted flag. + + Add a per-camera "latest eligible observation + evidence state" probe for + YA-WAMF setup diagnostics. It is exposed via + `GET /api/observations?camera_uuid=X&limit=1` (no new endpoint), and + distinguishes "no detections" from transport failure. +- **validation**: Unit tests on metric serialization. The metrics endpoint + shows the counters move under an L3 saturation test. When the feature is + disabled, the values are zeros and no queue exists. +- **status**: Completed +- **log**: + - RED: `test_observation_diagnostics` failed to link (every + `observation_diagnostics_*`, `api_metrics_render_observations` and + `observation_snapshot_retry_after_seconds` undefined). The new relay test + `test_relay_sidecar_is_the_shared_l4_cursor_head` failed (`Expected 0 Was + -1`: the relay's own writer could not create a missing directory). + `test_handle_get_system_info_includes_inactive_observation_pipeline` + failed with no `observation_pipeline` member. GREEN in `build-int01-L10` + and `build-int01-L10-asan` (ASan/UBSan clean): diagnostics 5/5, relay + 13/13, retention 8/8, evidence 14/14, observations 25/25, metrics 2/2, + system 32/32. Web: `npm run typecheck`, eslint, and jest + `observationPipeline`/`locale-coverage`/`i18nLiteralRule`/`systemHealth` + (53 passed). + - Builds ran from a detached worktree of HEAD plus only L10's files, because + parallel agents' uncommitted `src/video` and test files are globbed into + `lightnvr_lib` and broke the shared tree's build. + - **Module** `core/observation_diagnostics.{h,c}`: + - `observation_diagnostics_collect(&d, now_ms)` reads L3/L5 stats, L4's + per-camera status and flag count, and the new read-only + `db_observation_diagnostics_get()`. It never starts the evidence worker. + - `observation_diagnostics_to_json()` builds the system-info JSON. + - `observation_diagnostics_relay_lag_seconds()` returns the lag. + - `observation_diagnostics_free()` releases the snapshot. + - **DB** `db_observation_diagnostics_get()` (`db_observations.c`) runs one + WAL read transaction and returns: + - the meta (oldest/head seq, epoch); + - `MIN(observed_at_ms)` and the observation count; + - publication counts for `pending`, `failed` and `expired`; + - the oldest pending change's `created_at_ms`, via the partial pending + index. + + Lag is computed from that DB value, not from the relay's last-pass stat. + - **`/api/metrics`** (`api_metrics_render_observations()`, pure, in + `api_handlers_metrics.c`). Every family is present with zero values while + evidence is unused. + - Admission: `lightnvr_observation_accepted_total` and + `lightnvr_observation_dropped_total{reason=queue_full|budget_exhausted|disabled|untracked}`. + - Evidence, all under `lightnvr_observation_evidence_`: + - `unavailable_total`, `ready_total`, `bytes_written_total`; + - `failures_total{reason=encode_failed|write_failed|db_error|disabled|interrupted}`; + - `backlog_items`, `backlog_bytes`, `drop_ring_depth`; + - `high_water_items`, `high_water_bytes`; + - `queue_allocated`, `worker_running`; + - per camera: `retained_bytes{camera_uuid}`, `budget_bytes{camera_uuid}` + and `budget_exhausted{camera_uuid}`, plus the total + `budget_exhausted_cameras`. + - History, all under `lightnvr_observation_history_`: `observations`, + `oldest_seq` (cursor = seq − 1), `head_seq`, + `oldest_observed_timestamp_seconds` (no sample when empty) and + `epoch_regenerations_total`. + - Publications and relay: `lightnvr_observation_publications{state=pending|failed|expired}`, + `lightnvr_observation_relay_lag_seconds`, + `lightnvr_observation_relay_running` and + `lightnvr_observation_relay_events_total{outcome=relayed|duplicate|no_route|expired|failed|outbox_full|enqueue_error|mark_error|read_error|cap_pause|backoff|sidecar_error}`. + - The history and publication families are omitted when the DB read + fails. + - **`/api/system/info`** carries `observation_pipeline`: + - Top level: `active`, `accepted`, `dropped{…}`, + `budget_exhausted_cameras` and `cameras[]` (`camera_uuid`, + `retained_bytes`, `max_bytes`, `max_age_days`, `budget_exhausted`). + - `evidence`: `queue_allocated`, `worker_running`, backlog, high-water, + `failures{…}` and more. + - `history`: `oldest_seq`, `head_seq`, `oldest_observed_at` (ISO or + null) and `epoch_regenerations`. + - `publications`: `pending`, `failed`, `expired`, `relay_lag_seconds` + and `relay_running`. + + `active: false` means the feature is unused. The System page shows the + new `system/ObservationPipeline.tsx` card, with keys + `system.observations.*` in all 18 locales. + - **Retry-After**: `observation_snapshot_retry_after_seconds(age_ms, + items_ahead)` returns 1 under 5 s, otherwise `min(30, 2 × ahead)`, always + 1..30. L3 has no per-asset position, so the handler passes + `backlog_items` as the upper bound. The contract §8.2 is updated and no + longer provisional. + - The handler hunk in `api_handlers_observations.c` was swept into L14's + commit `9e0e8ecd`, because L14 staged the whole file. HEAD did not + build between `9e0e8ecd` and this commit. + - **Sidecar consolidation**: the relay's own path, writer and reader + (about 80 lines) are deleted. + - `observation_relay_write_cursor_head()` now calls + `observation_cursor_head_write()`. + - `observation_relay_check_cursor_head()` now calls + `observation_cursor_head_reconcile()` and updates the relay stats. The + relay keeps its once-per-second dirty-write cadence. + - The public names and `OBSERVATION_CURSOR_HEAD_FILE` are unchanged. + - Contract §15 now lists the metric names. + - **OpenAPI follow-up (not done)**: `SystemInformation` has + `additionalProperties: true`, so validation passes and the UI uses a + hand-written `ObservationPipelineDiagnostics` type in `types/system.ts`. + L14 had uncommitted `openapi.json` edits at the time, so the schema was + left alone. A follow-up should add an optional + `observation_pipeline: ObservationPipelineDiagnostics` component that + mirrors `observation_diagnostics_to_json()`, then regenerate. + - **Gotchas**: + - **X2**: per-camera rows come from L4's last pass, so they can be up to + 5 minutes old and cover only cameras that hold `ready` media. For live + numbers during saturation, scrape `backlog_*`, `high_water_*` and + `dropped_total`. High-water marks are per process run. Count failed and + expired publications from the DB gauges; the relay counters reset on + restart. + - **The in-flight item counts toward `per_camera_max_items`**. With depth + 1, a second submit while the worker holds the first one is + `DROPPED_QUEUE_FULL`. + - **W8**: the setup probe stays `GET /api/observations?camera_uuid=X&limit=1`. + An empty `items` means no detections yet, and a 403 or network error + means a transport failure. `/api/metrics` and `/api/system/info` need + `system.admin`, so YA-WAMF's scoped token cannot read them. + - **L11**: the `failed`/`expired` publication counts scan + `observation_publications`. That is cheap at the retention bound, but + a scrape costs four small queries. +- **files edited/created**: `include/core/observation_diagnostics.h` (new), `src/core/observation_diagnostics.c` (new), `include/database/db_observations.h`, `src/database/db_observations.c`, `include/web/api_handlers_metrics.h`, `src/web/api_handlers_metrics.c`, `src/web/api_handlers_system.c`, `src/web/api_handlers_observations.c` (landed in `9e0e8ecd`), `include/core/observation_relay.h`, `src/core/observation_relay.c`, `tests/unit/test_observation_diagnostics.c` (new), `tests/unit/test_observation_relay.c`, `tests/unit/test_api_handlers_system.c`, `tests/unit/CMakeLists.txt`, `web/js/components/preact/system/ObservationPipeline.tsx` (new), `web/js/components/preact/SystemView.tsx`, `web/js/types/system.ts`, `web/tests/observationPipeline.spec.js` (new), `web/public/locales/*.json`, `docs/integrations/OBSERVATIONS_CONTRACT.md` (commit `9f6012a6`) + +#### L11: lightNVR contract and reliability tests (A1–A3, A5–A8 lightNVR side) +- **depends_on**: [L5, L6, L7, L8, L9, L10, C3] +- **location**: `tests/contracts/observations.api.spec.ts` (new), `tests/contracts/fixtures/observations/seed.ts`, `tests/contracts/permissionCases.ts` (add cases), `tests/reliability/observations_crash_boundaries.*` (new), `tests/contracts/eventsDelivery.api.spec.ts` (extend with `mqtt-sink.ts`) +- **description**: + - **Playwright API contracts**: + - Camera-scoped token isolation on list, get and snapshot (A7). + - Rename, delete and name-reuse fixtures (reuse + `seedHistoricalNameReuse`) never transfer access. + - Distinct outcomes for pending, expired, unavailable, unknown and expired + cursor (A8). + - Cursor pagination with pending→ready recovery. + - MQTT sink receives one message ID per revision, stable across redelivery. + - **Reliability**: kill the process (SIGKILL) at each boundary, then restart + and assert that no `ready` asset points at an unfinished file and that + every committed change is eventually relayed (A6). The boundaries are: + - after the batch insert; + - after tmp write; + - after rename, before commit; + - after commit, before relay; + - after relay, before outbox delivery. + + Also assert the startup order: reconciliation, then worker, then relay. + - **DB restore**: a cursor taken before a restore returns `410` + (epoch or seq check). + - **Broker down**: stop the broker, generate 100 observations, restart, and + verify all 100 are recoverable via the outbox and the cursor (A5, + lightNVR half). +- **validation**: The new specs pass in CI next to the existing contract + suite, and the existing `evidence.*.spec.ts` lists of operation IDs are + untouched. +- **status**: Completed +- **log**: + - Commit `ddea7404`. Built in `build-int01-L11` (mirrors `build-int01-L14`: + MQTT+SOD, LiteRT off, apt FFmpeg header dir via `CMAKE_C_FLAGS`) and + `build-int01-L11-asan` (Debug ASan/UBSan). Both were configured from a + scratch worktree that has since been removed, so re-run cmake from the repo + before reusing them. + - **Results** (against a freshly built `build-int01-L11/bin/lightnvr`): + - `test_observation_crash_boundaries` 6/6, `test_observation_relay` 13/13 + and `test_observation_evidence` 14/14, in both builds, ASan clean. + - Playwright (`LIGHTNVR_CONTRACT_MQTT=1`), the new specs together with + `observations`, `evidence`, `eventsDelivery`, `recordingsRead` and + `streams`: 44 passed, 0 skipped, in 4.9 min. + - `node --test tests/contracts/fixture-server.test.mjs` 4/4, + `tsc -p tests/contracts/tsconfig.json` clean, tracked tooling tests + 58/58. + - The `evidence.*.spec.ts` operation-ID lists are untouched. + - **Mutation checks.** Every new test passed on first run, so each was run + against a deliberately broken build, then the change was reverted: + - Startup sweep keeps stale final files: `after_rename_before_commit` + fails ("every final file has a ready row"). + - Relay sends a random message ID instead of the deterministic one: all 6 + crash boundaries fail, as do the A6 drain, redelivery and broker-down + specs. + - Restore epoch check disabled (`observation_cursor_head_reconcile`): the + D9 spec fails (no "epoch regenerated"). + - `authorized_cameras` filter skipped: both A7 specs fail. + - Expired evidence served as unavailable: A8 fails (404, expected 410). + - **A-ID → test map**: + + | A-ID | Test | + | --- | --- | + | A1 | `observationsPipeline`: one frame becomes two siblings with one asset | + | A2 | `observationsPipeline`: the HTTP variant (the snapshot is byte-equal to one detector input) | + | A5 | `observationsHistory` "A5" (cursor walk, filter, dedup, `limit`, pending→ready recovery); `observationsReliability` "broker down, 100 observations"; `observationsPipeline` (message ID per revision) | + | A6 | `test_observation_crash_boundaries` (6 boundaries); `observationsReliability` "after SIGKILL…" (startup order and drain) and "relayed but unacknowledged…" (same IDs on redelivery) | + | A7 | `observationsHistory` "isolated per camera" and "rename, delete and name reuse"; annotate stays in `observations.api.spec.ts` (L14) | + | A8 | `observationsHistory` "A8" | + | D9 | `observationsReliability` "after a DB restore…" (old cursors return 410 by epoch, and a beyond-head cursor also returns 410) | + + - **Crash-hook gating.** The task asked for an env-gated hook. It is + stricter than that: + - The kill points are stage hooks: `observation_evidence_set_stage_hook` + (new `AFTER_INSERT` and `BEFORE_COMMIT` beside the existing + `BEFORE_RENAME`) and the new `observation_relay_set_stage_hook` + (`BEFORE_ENQUEUE`, `AFTER_ENQUEUE`, `AFTER_MARK`). + - Only code linked into the process can install them. No configuration, + environment variable or API reaches them, and nothing in `src/` calls + the setters. A production binary therefore has no way to trigger them. + - The only production cost is a mutex-guarded NULL check per stage. + - The header comments and `tests/reliability/README.md` document this. + - **How the boundaries are hit**: + - The C test forks a "generation 1" that runs the real worker, relay, + router and outbox on a migrated DB, then SIGKILLs it from a hook. The + test checks the on-disk and DB state at the crash. + - It then forks a "generation 2" running the `main.c` order (reconcile → + evidence init → relay start), and a third start to prove idempotence. + - It asserts no pending asset, no `.jpg.tmp`, final files = ready rows, + the ready file byte-equal to the submission, revision 2 everywhere, and + every change in the outbox exactly once and still pending (there is no + broker). + - Six boundaries are covered: the five in the plan, plus "after outbox + accept, before mark". "After relay, before delivery" is `AFTER_MARK`. + - At binary level, the Playwright suite kills the real process at the + boundaries an outside process can hit deterministically: committed but + not relayed (rows staged while it is down), relayed but not delivered + (the sink withholds PUBACK). + - **Real end-to-end was feasible**: + - `observationsPipeline` streams an ffmpeg-generated HTTP MPEG-TS source + into a detection stream. + - Its HTTP API detector is an owned loopback server. + - There is no RTSP and no go2rtc; ffmpeg must have libx264, which the CI + `ffmpeg` package does. + - The history and reliability specs SQL-seed rows shaped exactly as L2 + writes them (`fixtures/observations/seed.ts`). The live relay and API + then process them. + - **Startup order** is asserted from the restart's own server log, in this + order: `Observation reconciliation:`, `Observation evidence initialized`, + `Observation relay drained`, `Observation publication relay started`. + - **Harness changes**: + - `fixture-server.mjs` adds `kill(signal)` and `relaunch()` (same + directory, config and port). Readiness is judged only on the new + generation's log bytes, and an expected kill signal is not reported as + a native failure. + - `mqtt-sink.ts` takes an optional port, so a broker that is "down" can + come back on its reserved address. + - `permissionCases.ts` and `eventsDelivery.api.spec.ts` were not changed. + The MQTT redelivery cases live in `observationsReliability`. + - Run: + - `make -C build-int01-L11 test_observation_crash_boundaries && build-int01-L11/bin/test_observation_crash_boundaries` + - `LIGHTNVR_CONTRACT_BIN=build-int01-L11/bin/lightnvr LIGHTNVR_CONTRACT_MQTT=1 npx playwright test -c tests/contracts/playwright.config.ts --project=api tests/contracts/observations*.api.spec.ts` + - **Bugs found**: none in production code. One mutation attempt was a + no-op: keeping pending assets' files in the sweep had no effect, because + a pending row has no `path` yet. + - **Gotchas for L12/X1**: + - Observation routes must list + `io.lightnvr.detection.observation_changed.v1` with no label predicate. + The specs use a `camera_uuid` selector scope. + - lightNVR has no stream rename API. The rename case edits + `streams.name` while the server is stopped (SIGTERM), then relaunches. + - A permanent DELETE leaves observation rows in place but makes them + unreachable for everyone (`404 observation_not_found`). They never + attach to a recreated camera with the same name. + - Restore detection relies on the sidecar + `recordings/observations/.cursor_head.json`. Wait until the relay has + written it (it writes at most once per second) before taking or + restoring a backup in X1. A restore newer than the last sidecar write + stays the same-epoch case: such cursors are beyond the head and also + return 410. + - Redelivery after SIGKILL takes about 30–40 s (MQTT worker reconnect + backoff), and broker-down recovery of 200 messages about 30 s. Keep + the timeouts generous. + - The `lightnvr-rs` job runs the api project with 4 workers and without + MQTT. The MQTT-gated specs skip there; `observationsHistory` runs. +- **files edited/created**: `include/core/observation_evidence.h`, + `include/core/observation_relay.h`, `src/core/observation_evidence.c`, + `src/core/observation_relay.c`, `tests/unit/test_observation_crash_boundaries.c` + (new), `tests/unit/CMakeLists.txt`, `tests/contracts/fixtures/observations/seed.ts` + (new), `tests/contracts/observationsHistory.api.spec.ts` (new), + `tests/contracts/observationsReliability.api.spec.ts` (new), + `tests/contracts/observationsPipeline.api.spec.ts` (new), + `tests/contracts/fixture-server.mjs`, `tests/contracts/fixture-server.d.mts`, + `tests/contracts/mqtt-sink.ts`, `tests/reliability/README.md` + +#### L12: lightNVR documentation and deployment recipe +- **depends_on**: [L11] +- **location**: `docs/EVENT_CONTRACT.md`, `docs/MQTT_INTEGRATION.md`, `docs/DETECTION_ENGINES.md`, `docs/integrations/YA_WAMF.md` (new: the recipe covering scoped token creation, broker routes, a bird-capable detector, retention/budgets and diagnostics), `docs/prd/README.md` (status), `docs/prd/INT_01_Event_Enrichment_YAWAMF.md` (status) +- **description**: Document the new event type and topic, the observation + API, evidence semantics (low-resolution inference image, not a recording + frame), retention and cursor expiry, and rollback (disable per camera; + evidence and annotations are kept; the 0095 down-migration drops data, so + take a backup first). Write the supported-version matrix. +- **validation**: Doc links resolve. The recipe is executed verbatim in X1/A14. +- **status**: Completed (commit `271f3191`, not pushed) +- **log**: + - `reason_not_testable` (docs part): documentation only; the recipe itself is + executed by X1/A14. Static verification instead: + - a link/anchor checker (GitHub heading slugs, duplicate suffixes, fenced + code skipped) over the 7 touched docs: 125 relative links, 0 broken. A + negative sample (bad anchor, missing file) is reported as 2 broken. + - a name check of every backticked env var, route, event type, metric, + action and reason code in the four docs against lightNVR `src`, + `include`, migrations, OpenAPI and `en.json`, and YA-WAMF `HEAD` + (`backend/app`, `apps/ui/src`, docs): all found. YA-WAMF routes were + checked against their router prefixes (`/api/setup/source`, + `/api/setup/lightnvr/test`, `/api/diagnostics/source`, + `/api/backfill/source-recovery`); every named test file exists. + - **Docs**: new `docs/integrations/YA_WAMF.md` (recipe + matrix). The event + route example uses `mqtt:default` with a `camera_uuid` selector and no + predicate; with zero stored routes no route is needed (publish-all). + `EVENT_CONTRACT.md` gains the `observation_changed.v1` data schema and the + predicate rule; `MQTT_INTEGRATION.md` an "Observation change events" section; + `DETECTION_ENGINES.md` an "Observation evidence (INT_01)" engine table. + Contract §15 names the schema and §18 points at the published matrix. + - **OpenAPI (L10 follow-up)**: RED: the new + `observations-contract.test.mjs` case failed (`observation_pipeline` + undefined). GREEN after adding `ObservationPipelineDiagnostics` (every + member the C serializer always emits is required; integers ≥ 0; + `oldest_observed_at` null or `ObservationTime`; camera UUID pattern + case-insensitive) as an optional `SystemInformation` property and to + `x-lightnvr-validators`. The test also parses + `observation_diagnostics_to_json()` and asserts the documented key set + equals the emitted one. `generate.mjs` and `--check` OK, `lint.mjs` valid + (same 5 warnings), `coverage.mjs --check` OK, tooling tests 61/61 + (includes the untracked `native-embedding.test.mjs`). + `build-int01-L14` (cmake re-run from the repo): `test_api_handlers_system` + 32/32, `test_api_handlers_openapi` 8/8. `system.api.spec.ts` 10/10 against + `build-int01-L11/bin/lightnvr`, which Ajv-validates the real + `/api/system/info` against the updated canonical schema. + - `web/js/types/system.ts`: `failures` widened to `Record`, + because the generated type's index signature made `npm run typecheck` + fail; typecheck clean, jest `observationPipeline` 3/3. + - **Unverified in the docs** (for X1/A14): the whole recipe end to end; + Mosquitto; a real bird model (tests used synthetic detectors); the + user → role → policy → token sequence as one flow (each API exists and is + tested separately); running an older lightNVR against a 0095/0096 DB; + the history retention setting has no API, so the doc says so. +- **files edited/created**: `docs/integrations/YA_WAMF.md` (new), + `docs/EVENT_CONTRACT.md`, `docs/MQTT_INTEGRATION.md`, + `docs/DETECTION_ENGINES.md`, `docs/integrations/OBSERVATIONS_CONTRACT.md`, + `docs/prd/INT_01_Event_Enrichment_YAWAMF.md`, `docs/prd/README.md`, + `docs/api/openapi.json`, `include/web/openapi_document_generated.h`, + `web/generated/api/{schema.d.ts,validators.d.ts,validators.js}`, + `web/js/types/system.ts`, `tests/contracts/tooling/observations-contract.test.mjs` + +### P1 — YA-WAMF (repo `../YetAnother-WhosAtMyFeeder`, branch `feature/lightnvr-provider`) + +#### W1: Source-provider boundary and Frigate provider extraction +- **depends_on**: [W0] +- **location**: `backend/app/services/sources/__init__.py`, `base.py` (the `NvrSourceProvider` Protocol + `Capabilities` dataclass), `frigate_provider.py`, `registry.py` (new); `backend/app/services/frigate_client.py` (unchanged API, wrapped); `backend/app/main.py` (construct the provider from config); `backend/tests/pure/test_source_capabilities.py` +- **description**: + - The provider interface (PRD T1) covers: + - `identity()` → `(kind, instance)`; + - `list_cameras()`; + - `capabilities()`; + - `parse_live_message(topic, payload)` → a list of `SourceObservation` + (common input carrying optional lifecycle `new|update|end` only when + supplied); + - `history(cursor, filters)`; + - `fetch_snapshot(external_id, crop_region?)`; + - optional `fetch_clip`, `fetch_recording_frame` and `write_classification` + operations guarded by capabilities. + - `FrigateProvider` delegates to `FrigateClient` and preserves the exact + current behavior. + - Keep `FrigateClient` as is. No plugin framework. +- **validation**: W0 is green. Pure tests cover the capability declarations. + `pytest` and the pure lane pass. +- **status**: Completed (YA-WAMF commit `ed8b400f` on `feature/lightnvr-provider`, not pushed) +- **log**: + - RED: the three new pure test modules failed at collection with + `ModuleNotFoundError: app.services.sources`. The startup test failed with + `assert isinstance(provider, FrigateProvider)` while `main.py` was + stashed. GREEN: 60 pure tests plus 1 startup test pass, and the 4 later + identity cases bring it to 64. + - Gates: W0 + `test_dead_modules` + the startup test give 53 passed. The + pure lane (excluding W2's in-flight `test_source_identity.py`) gives 2004 + passed. Full suite: 7523 passed and 6 failed. The 6 failures were + `inspect.getsource` tests on `detection_repository.py`/`database.py`, + which W2 was editing during the run. All 24 tests in those files pass when + rerun. Ruff check and format are clean on every W1 file. + - Interface (W3/W4/W5 use these exact names), in `app/services/sources/base.py`: + - `NvrSourceProvider` (runtime-checkable Protocol): + - `identity() -> SourceIdentity(kind, instance)` + - `capabilities() -> Capabilities` + - `live_topics() -> list[str]` + - `parse_live_message(topic, payload: bytes) -> list[SourceObservation]` + - `async list_cameras() -> list[SourceCamera(camera_id, display_name)]` + - `async history(cursor, filters: HistoryFilters) -> HistoryPage | RecoveryRequired` + - `async fetch_snapshot(external_id, *, crop=True, quality=95, timeout=30.0) -> SnapshotResult` + - optional, check capabilities first: + - `fetch_clip(external_id, dest_path, *, timeout=20.0) -> (bool, reason)` (event_clip) + - `fetch_range_clip(camera, after, before, *, timeout=20.0) -> (bytes|None, reason)` (range_clip) + - `fetch_recording_frame(camera, frame_time, *, timeout=5.0) -> (bytes|None, reason)` (recorded_frame) + - `write_classification(external_id, label, *, score=None) -> bool` (annotate) + - `Capabilities` is a frozen dataclass. Every field defaults to False: + `history`, `snapshots`, `annotate`, `track_lifecycle`, `recorded_frame`, + `event_clip`, `range_clip`, plus `reasons`. Methods: + - `supports(name)` raises `KeyError` on an unknown name. + - `unsupported_reason(name)`. + - `require(name)` raises `UnsupportedCapabilityError(capability, reason)`. + - `Capabilities.from_contract_operations(doc["operations"])` treats an + operation as usable only when `supported is True and authorized is True`. + A missing or malformed entry gives the reason `not_declared`. + - `CONTRACT_OPERATION_KEYS` maps each field to its contract operation key: + `history→observations.history`, `snapshots→observations.snapshot`, + `annotate→observations.annotate`. The other four map to themselves. + - `SourceObservation` (frozen): + - Always set: `source_kind`, `source_instance`, `external_id`, + `event_key` (the `detections.frigate_event` value), `camera_name`, + `source_camera_id`, `label`, `observed_at` (unix float or None). + - `lifecycle` (`new|update|end` or None) and `is_false_positive`. + - lightNVR-only, None for Frigate: `revision`, `message_id`, + `change_cause`, `evidence_state`, `evidence_reason`, `asset_id`. + - `raw`: the decoded source payload. + - Other types: + - `HistoryFilters(after, before, cameras: tuple, labels: tuple, has_snapshot=True, limit=100)` + - `HistoryPage(observations, next_cursor, has_more)` + - `RecoveryRequired(oldest_cursor, reason="cursor_expired")` + - `SnapshotOutcome` (`READY`, `PENDING`, `UNAVAILABLE`, `EXPIRED`, + `MISSING`, `TEMPORARILY_UNREACHABLE`) + - `SnapshotResult(outcome, image, reason, cropped, retry_after_seconds)` + - `SourceUnavailableError` + - Registry, in `app/services/sources/registry.py`: + - `build_source_provider(kind="frigate", *, app_settings=None)`; an + unknown kind raises `ValueError`. + - `install_source_provider(p)`. + - `active_source_provider()` lazily falls back to Frigate, so callers that + run before startup, or in tests without the lifespan, still get a + provider. + - `main.py` lifespan calls + `install_source_provider(build_source_provider(app_settings=settings))` + right after `_log_startup_diagnostics`, in test mode too. + - W5 adds the `kind` selection from settings. + - In `frigate_provider.py`: + - `FrigateProvider(client, app_settings)` exposes `.client` (the + `FrigateClient`) for Frigate-only work during W3. + - `FRIGATE_CAPABILITIES` declares all seven capabilities. + - `SOURCE_KIND_FRIGATE = "frigate"`, `frigate_source_instance(url)`. + - Gotchas for W2–W5: + - **Instance derivation is duplicated.** W2's uncommitted + `app/utils/source_identity.frigate_source_instance` returns `host:port` + or None, and its callers substitute `"unbound"`. W1's copy returns + `"unbound"` directly. The two produce identical results on 16 probe + inputs, including default ports made explicit, bare hosts, IPv6, + credentials, a trailing dot and bad ports. W1 cannot import W2's + uncommitted file, so once both land W3 should make the provider call + W2's function and delete the copy. + - Frigate `parse_live_message`: + - reads only `{main_topic}/events`; + - treats a missing `type` as `new`, matching `EventData`; + - returns `[]` for an unrecognised type unless the message is a false + positive, which gets `lifecycle=None`; + - leaves label, camera and ingest gating to the pipeline; + - never adds `__received_at_ts`. W4 keeps adding it before + `_process_event_payload(obs.raw)`. + - Frigate `history` is time-windowed, so the cursor must be None, and it + takes at most one camera and one label (`ValueError` otherwise). + Backfill keeps its own `before`-cursor paging. `has_more` is + `len(events) >= limit`. `FrigateEventsFetchError` propagates. + History items have `lifecycle=None`. + - Frigate `fetch_snapshot` keeps the original `FrigateClient` reason + string. Outcome mapping: + - `snapshot_not_found` → `MISSING`; + - timeout, request error or HTTP 5xx → `TEMPORARILY_UNREACHABLE`; + - anything else → `UNAVAILABLE`. + + `cropped` equals the `crop` argument. lightNVR returns the full image + with `cropped=False`, and the caller crops by box. + - Frigate-only calls have no protocol method yet: thumbnail, clean and + alignment snapshot, `get_event`, config/version and motion recording. + W3 either adds capability-gated methods or keeps them behind + `FrigateProvider.client`. + - Clip and recording user settings (`clips_enabled`, + `recording_clip_enabled`) are not capabilities. Check both. + - YA-WAMF `test_dead_modules` requires every new module to be reachable. + The `main.py` import covers `sources/*`, and W5's `lightnvr_provider.py` + must be imported from the registry. + - The full suite has `inspect.getsource` tests that fail while another + agent is editing repository files in the same worktree. Rerun them + before treating a failure as real. + - No changelog fragment was added: W1 changes no user-visible behaviour, + setting, endpoint or label (`changelog.d/README.md`). +- **files edited/created**: + - YA-WAMF `backend/app/services/sources/__init__.py` (new) + - YA-WAMF `backend/app/services/sources/base.py` (new) + - YA-WAMF `backend/app/services/sources/frigate_provider.py` (new) + - YA-WAMF `backend/app/services/sources/registry.py` (new) + - YA-WAMF `backend/app/main.py` (installs the provider at startup) + - YA-WAMF `backend/tests/pure/test_source_capabilities.py` (new) + - YA-WAMF `backend/tests/pure/test_frigate_source_provider.py` (new) + - YA-WAMF `backend/tests/pure/test_source_registry.py` (new) + - YA-WAMF `backend/tests/test_source_provider_startup.py` (new) + +#### W2: Alembic migration — source identity, recovery cursor, notification intent +- **depends_on**: [W0] +- **location**: `backend/migrations/versions/_add_source_identity.py`, `backend/app/db_schema.py`, repositories under `backend/app/repositories/*` that look up by `frigate_event`, `backend/tests/test_source_identity_migration.py`, `MIGRATION.md` +- **description**: + - **New columns** (nullable or server-defaulted, so older binaries' inserts + still succeed; see design decision 8): add `source_kind`, + `source_instance`, `external_observation_id` and `source_camera_id` to + `detections`. + - Other tables (media cache, `processing_job_state`, favourites, + corrections, `bird_observations`) keep keying on `frigate_event`, which + is `UNIQUE NOT NULL`; the PK is `id`. + - They only gain source columns where a lookup does not go through + `detections`. + - **Backfill**: legacy rows get `('frigate', , + frigate_event)`. The instance is derived from the configured + `frigate_url` host, normalized; document the derivation. + - Add a startup lazy backfill for rows inserted with a NULL source by an + older binary after the upgrade. + - **Constraints**: add a partial `UNIQUE(source_kind, source_instance, + external_observation_id) WHERE source_kind IS NOT NULL`. Keep the + `frigate_event` unique column and its FK web intact. + - **New table** `source_cursors(source_kind, source_instance, cursor, + epoch, updated_at, last_error)`. Notification state reuses or extends the + existing `mark_notified` mechanism (W7), and durable work reuses + `processing_job_state`. Neither gets a new table. + - **Schema check**: `_verify_schema` deliberately lets older builds run + against a newer DB and only warns. Do not claim refusal. Document in + `MIGRATION.md` that downgrading the binary after enabling lightNVR is + unsupported without restoring the backup. + - **Batch mode**: use batch mode and keep `PRAGMA foreign_keys` handling + consistent with existing migrations. + - Document backup and rollback in `MIGRATION.md`. +- **validation**: + - Migration tests: `upgrade → downgrade → upgrade` is a no-op; a seeded + legacy DB keeps IDs, photos, favourites, corrections and queued jobs. + - Duplicate `(frigate, inst, id)` is rejected. + - An insert by the old code path (no source columns) still succeeds and is + lazily backfilled. + - Changing the configured source does not rewrite old rows. + - Single Alembic head check passes. W0 is green. +- **status**: Completed (YA-WAMF commit `d3079b63` on `feature/lightnvr-provider`, not pushed) +- **log**: + - The revision is `d4e1a7c93b52` (it revises `c1d734b9e201`) and is the + single head. P1 needs no other YA-WAMF revision. + - **`detections` columns**, all nullable: + - `source_kind` VARCHAR(32), `source_instance` VARCHAR(255), + `external_observation_id` VARCHAR(255) and `source_camera_id` + VARCHAR(255). + - Partial unique index `uq_detections_source_identity(source_kind, + source_instance, external_observation_id) WHERE source_kind IS NOT NULL`. + - For W7: `notification_intent`, constrained by + `ck_detections_notification_intent` to + `dispatch|suppressed_recovery|suppressed_policy`, and + `notification_intent_at`. + - For W7: `notification_outcome`, constrained by + `ck_detections_notification_outcome` to + `sent|failed|unknown|skipped`, and `notification_outcome_at`. + - NULL intent means a legacy row or no intent recorded. `notified_at` + still marks a successful send. + - **`source_cursors` table**: + - `source_kind` and `source_instance` form the primary key + (`pk_source_cursors`). + - Other columns: `cursor` TEXT, `epoch`, `filter_fingerprint`, + `last_error`, and `updated_at` (default `CURRENT_TIMESTAMP`). + - `filter_fingerprint` was added for the contract §16.5 camera and label + filter fingerprint. + - `recovery_state` is NOT NULL, defaults to `current`, and is constrained + by `ck_source_cursors_recovery_state` to `current|recovery_required`. + - **Choice of instance**: placeholder plus a startup bind, chosen as the + safer option. + - No existing migration reads settings, and the resolved `frigate_url` + depends on environment overrides, the config file and defaults. + - The migration therefore backfills `('frigate', 'unbound', + frigate_event)`. + - `init_db()` then calls `_bind_unbound_source_identity` after + `_verify_schema`. That runs `bind_unbound_frigate_detections` + (`app/repositories/source_identity_repository.py`), which: + - gives NULL-source rows (old-binary inserts) the kind `frigate`, + `external_observation_id = frigate_event` and the configured instance; + - replaces `unbound` with the configured instance; + - never touches a row that already has a real instance, so a later + `frigate_url` change does not reassign history. + - The instance comes from `app/utils/source_identity.frigate_source_instance`: + - the lowercased host, with any trailing dot dropped, plus the port; + - the scheme's default port (80 or 443) is made explicit; + - credentials, path and query are dropped; + - IPv6 hosts are bracketed; + - a URL with no scheme is treated as http. + - Examples: `http://Frigate.LAN:5000/` → `frigate.lan:5000`, and the + default `http://frigate:5000` → `frigate:5000`. + - An unusable URL returns None. The row keeps `unbound`, which can never + equal a derived value because a derived value always has a `:port`. + The bind is retried on the next start. + - The bind never blocks startup. + - **No table recreation**: the migration uses raw `ALTER TABLE ADD/DROP + COLUMN`, not batch recreation. Batch recreation would drop the four + `detections` triggers (initial-classification capture and media cleanup) + and rebuild the parent of eight foreign keys. As a result, downgrade + needs SQLite 3.35 or newer; it raises a clear error otherwise. + - **RED → GREEN**: + - New tests first failed at collection (`ModuleNotFoundError`). + - With the implementation present and the migration file removed, 8 of 8 + migration tests failed. + - With the migration restored, all pass. + - The first full-suite run found two more problems: + - 29 failures in hand-built `CREATE TABLE detections` fixtures + (`test_detection_repository.py`, `test_canonical_identity_repair.py`), + fixed by adding the columns to them; + - 1 real regression: SQLite reports the source-tuple conflict *before* + the `frigate_event` conflict, which broke `create()`'s idempotent + duplicate skip. + - The regression got its own RED test, then a fix: + `_DUPLICATE_DETECTION_KEYS` treats either key as a duplicate. + - **Final results**: + - Full suite: 7556 passed, 60 skipped. + - W0 gate, all migration tests, `scripts/ci_migration_path_check.py`, + `ruff check` and `ruff format --check` from the repo root, and + `docs_consistency_check.py` are all green. + - **Gotchas for W4**: + - Any `Detection` with `source_kind=None` is written as a Frigate row for + the configured instance (`_source_identity_params`). lightNVR rows must + set `source_kind='lightnvr'`, `source_instance` (the installation UUID), + `external_observation_id` and `source_camera_id`. + - A NULL `source_instance` would escape the unique index. + - `INSERT OR IGNORE` paths (`insert_if_not_exists`, + `upsert_if_higher_score`) silently ignore a source-tuple conflict. + - `DETECTION_SELECT_COLUMNS` and `_row_to_detection` deliberately do not + read the source columns, because callers append columns positionally. + Only `DetectionRepository.get_by_source_identity()` fills them; extend + deliberately. + - **Gotchas for W6**: + - There is one cursor row per `(kind, instance)`. A changed filter must + reset the row to absent (§7.3). + - A `410` sets `recovery_state='recovery_required'`. + - `REQUIRED_COLUMNS` now lists the `source_cursors` columns. + - **Gotchas for W7**: + - Use the intent and outcome vocabularies above. Changing them needs a + new revision, and adding a CHECK later would force a table recreation, + so avoid that. + - The migration does not backfill intent on legacy rows. NULL means + legacy. +- **files edited/created** (YA-WAMF): + - `backend/migrations/versions/d4e1a7c93b52_add_source_identity.py` (new) + - `backend/app/utils/source_identity.py` (new) + - `backend/app/repositories/source_identity_repository.py` (new) + - `backend/app/db_schema.py`, `backend/app/database.py`, + `backend/app/repositories/detection_repository.py` + - `backend/tests/test_source_identity_migration.py` (new), + `backend/tests/pure/test_source_identity.py` (new), + `backend/tests/test_detection_repository.py`, + `backend/tests/test_canonical_identity_repair.py` + - `MIGRATION.md`, `changelog.d/lightnvr-source-identity.md` (new) + +#### W3: Route every Frigate call through the provider and gate by capability +- **depends_on**: [W1] +- **location**: every current importer of `frigate_client`. Routers: + - `routers/ai.py`, `routers/events.py`, `routers/proxy.py`, + `routers/settings.py`. + + Services: + - `archive_service.py`, `auto_video_classifier_service.py`, + `backfill_service.py`, `classification_input_provenance.py`; + - `full_visit_clip_service.py`, `high_quality_snapshot_service.py`, + `recording_snapshot_input.py`, `video_*`, `visit_film_service.py`; + - `garden_automatic_review.py`, `garden_capture_service.py`, + `garden_crop_service.py`, `garden_identification.py`, + `garden_media_cache.py`, `garden_motion.py`; + - `inaturalist_service.py`, `media_integrity_scan.py`, `media_probe.py`, + `notification_orchestrator.py`, `timezone_repair_service.py`. + + Also `media_cache.py`, and a new test `backend/tests/pure/test_no_direct_frigate_calls.py`. +- **description**: + - Replace direct `FrigateClient` and `/api/events/...` usage in shared + workflows with provider calls. + - Media cache keys are resolved by source identity; legacy Frigate keys stay + readable. + - Add neutral media routes `/api/media/{source_kind}/{external_id}/snapshot` + (and thumbnail) while preserving existing Frigate proxy URLs. + - HQ snapshot, recording snapshot, clip, full-visit, garden-capture and + video-classifier services run only when the provider declares that + capability. Otherwise they record a "skipped: unsupported by source" + reason and never infer support from an HTTP error. + - Add a static test (AST or import scan) that fails if any module outside + `services/sources/frigate_provider.py` and `frigate_client.py` imports + `frigate_client` or builds `/api/events/` paths. +- **validation**: W0 stays green (A10). The static test passes. With a fake + snapshot-only provider, a test asserts that zero clip, HQ or + recording-snapshot jobs run and that no `/api/events/` request is made (A9 + backend half). +- **status**: Completed (YA-WAMF commit `ca1ee246` on `feature/lightnvr-provider`, not pushed) +- **log**: + - RED: `tests/pure/test_no_direct_frigate_calls.py` listed 36 violations + (every importer plus 7 `/api/events` builders in `proxy.py`, + `media_probe.py` and `notification_orchestrator.py`); its self-test of the + scanner passed. `tests/test_snapshot_only_source.py` failed 3/3: fake + Frigate received `snapshot.jpg?crop=0`, `POST sub_label` and so on, and + `high_quality_snapshot_service.enabled()` was True. GREEN: both pass, as do + the new `test_source_media_routes.py` (12) and + `pure/test_source_event_keys.py` (14). These two were written alongside the + implementation, so they have no RED run. + - Gates: W0 is 46/46 green after every service. Full suite: 7727 passed, + 60 skipped, 0 failed (W2 and W5 had committed by then, so the run is clean). + `ruff check .` and `ruff format --check .` are clean repo-wide. OpenAPI + and the frontend types were regenerated and `--check` passes. The + docs-consistency and changelog checks pass. `npm run check` was not run, + because `apps/ui/node_modules` is not installed; only generated types + changed there. + - New in `app/services/sources/`: + - `access.py` is the only way shared code reaches the NVR: + - `resolve_event_source(event_key)` returns `(provider, external_id)`, + or None when the key's source kind or instance is not the active one; + - `fetch_event_snapshot`, `fetch_event_snapshot_bytes` (the old + `(bytes, reason)` shape), `fetch_event_thumbnail`, + `external_snapshot_url`, `fetch_event_record` (needs `track_lifecycle`), + `fetch_event_clip_bytes` and `download_event_clip` (`event_clip`), + `fetch_range_clip` (`range_clip`), `fetch_recording_frame` + (`recorded_frame`), `write_event_classification` (`annotate`); + - `active_frigate_client()` is None unless Frigate is the active + source; `configured_frigate_client()` is for owner tools about the + configured Frigate itself. + - `capability_gate.py`: `source_allows(workflow, *caps)` is true when the + source declares any of the caps. Otherwise it records a `CapabilitySkip` + (`status="skipped: unsupported by source"`, the source's reason such as + `not_implemented`, a count and the time). It also bumps the Prometheus + counter `source_capability_skips_total{workflow,capability,source_kind}` + and logs at info once, then at debug. Related helpers: `record_skip`, + `record_source_not_active`, `recorded_capability_skips()` (for W8 + health) and `clear_recorded_capability_skips()`. Reason constants are + `unsupported_by_source` and `source_not_active`. + - `event_keys.py`: + - `event_key_for(kind, instance, id)` gives the Frigate id itself, or + `lnvr::` for lightNVR; `parse_event_key` reads it back. + - `media_cache_key`: a Frigate key keeps its legacy name, byte for + byte. A lightNVR key becomes `lnvr..`. It is validated, + never stripped, so `lnvr:ab:c` and `lnvr:a:bc` cannot collide. A + Frigate id that sanitises to `lnvr.` is refused. + - `event_key_from_media_key` inverts it, so `cache_file_event_id` + resolves qualified owners. + - `media_cache.sanitize_event_id`, which the archive also uses, now + delegates to `media_cache_key`. + - Protocol additions (W5 already implements them): + - required: `fetch_thumbnail` and `external_snapshot_url`; + - optional: `fetch_event` (Frigate-shaped event document, needs + `track_lifecycle`) and `fetch_clip_bytes` (`event_clip`). + - `FrigateProvider()` built without a client resolves the + `frigate_client` singleton at call time. Tests can therefore patch + `app.services.frigate_client.frigate_client`, and the old per-module + `X.frigate_client` patches were rewritten to that. + - It has `aclose()`, and `registry.close_active_source_provider()` + replaces `frigate_client.close` in `main.py`. + - `frigate_event_media_url(id, asset)` is the only builder of Frigate + `/api/events` URLs outside the client. + - `FrigateEventsFetchError` now subclasses `SourceUnavailableError`. + - The duplicated instance derivation was removed: `frigate_source_instance` + now calls W2's `app/utils/source_identity.frigate_source_instance` and + falls back to `unbound`. + - Capability gating, by workflow name: + - `high_quality_snapshot`: `enabled()` needs `event_clip` or `range_clip`. + - `full_visit` (`range_clip`): both the event-processor trigger and + `FullVisitClipService._auto_generation_enabled`. + - `video_classification` (`event_clip` or `range_clip`): in + `trigger_classification` and `queue_classification`. The latter returns + the new `"unsupported"`, which the manual video reclassify maps to HTTP + 409 (`errors.events.video_analysis_unsupported_by_source`) and batch + analysis stops on. + - `recording_snapshot` (`recorded_frame`). + - `recording_frame_fallback` (`range_clip`). + - `write_classification` (`annotate`). + - `garden_capture` (`track_lifecycle`), checked at the top of + `GardenCaptureService.process`. + - `visit_film` (`range_clip` and `track_lifecycle`). + - `garden_motion` (`range_clip` plus a Frigate client). + - `backfill` (`history`; time-window replay is Frigate-only and is + recorded as `time_window_history_is_frigate_only`). + - `media_integrity_scan` (Frigate-only, returns `disabled` with a message). + - Missing-media purges and batch analysis return `failed` or + `unsupported` with plain messages when Frigate is not the source. + - The archive and Garden media treat `unsupported_by_source` and + `source_not_active` as unavailable or missing, not retryable. + - Neutral routes in `routers/proxy.py`, with the same auth, share-link and + guest rate-limit rules: + - `GET /api/media/{source_kind}/{external_id}/snapshot` and `/thumbnail`. + - `frigate` delegates to the bodies of the existing `/frigate/{id}/...` + routes, now `_serve_frigate_snapshot` and `_serve_frigate_thumbnail`. + - Another kind must be the active source. It is served from the cache + (`lnvr.` key), then the archive, then `provider.fetch_snapshot(crop=False)`, + cached as `source_snapshot`. + - Outcomes: PENDING gives 202 with `Retry-After` (1–30) and + `retry_after_seconds`; EXPIRED gives 410; MISSING and UNAVAILABLE give + 404; TEMPORARILY_UNREACHABLE gives 502. + - The thumbnail is derived from the snapshot. + - Documented in `docs/api.md`. New i18n keys are added in all nine + locales. + - Remaining Frigate-only exceptions, all through `active_frigate_client()` + or `configured_frigate_client()`, never imports: + - The `/frigate/...` streaming proxies (clip, recording clip, HLS, + previews, `HEAD` checks) and `full_visit_clip_service`'s streamed + recording fetch. They stream Frigate URLs with their own `httpx` + clients and Frigate response semantics. W0's swapped-`httpx` harness is + untouched. + - The clean and alignment snapshot (HQ final frame, + `recording_snapshot_input`, Garden crops): only Frigate's + `snapshot-clean` fixes native coordinates. + - Garden: event metadata, snapshots, clips and motion scanning follow + Frigate's lifecycle and keep the original client calls. Garden capture + is gated by `track_lifecycle`. + - Retention and missing-media checks, timezone repair, the video-sample + probe, and the owner tools for Frigate itself (connection test, config, + camera status, latest frame, recording-clip capability, version). + - Gotchas for W4/W5/W8: + - The default Frigate provider resolves the client lazily. Tests that + install a provider use `monkeypatch.setattr(registry, "_active_provider", p)`. + The suite `conftest.py` now restores `_active_provider` and clears the + recorded skips around every test. + - The snapshot-only test's fake is Frigate-kind, because events are still + Frigate-shaped until W4. W9 should repeat it with a lightNVR-kind + provider once W4 routes `lnvr:` keys through the pipeline. + - `resolve_event_source` refuses a key whose kind or instance differs from + the active source, so W4 must store `lnvr::` exactly as + `event_key_for` builds it. W5 has its own identical `lightnvr_event_key` + in `lightnvr_provider.py`; collapse it onto `event_keys`. + - `notification_orchestrator` sends `snapshot_url=""` when the source has + no external link (lightNVR). W7 owns links through the provider. + - `enabled()` and the other gates record a skip on every check, including + status polls, so the counter measures refusals, not distinct jobs. W8 + should read `recorded_capability_skips()` for its "unsupported" copy + rather than the counter. + - `tests/pure/test_auto_video_classifier_pressure.py` fails at `HEAD` when + run alone (a pre-existing `derive_archive_state` stub gap). Its module + reimport used to leak into later tests; it is now restored at teardown. +- **files edited/created**: + - YA-WAMF `backend/app/services/sources/access.py`, `capability_gate.py`, `event_keys.py` (new) + - YA-WAMF `backend/app/services/sources/base.py`, `frigate_provider.py`, `registry.py`, `backend/app/services/frigate_client.py`, `backend/app/main.py` + - YA-WAMF routers: `ai.py`, `events.py`, `proxy.py` (neutral media routes), `settings.py` + - YA-WAMF services: `archive_service`, `auto_video_classifier_service`, `backfill_service`, `classification_input_provenance`, `event_processor`, `full_visit_clip_service`, `garden_automatic_review`, `garden_capture_service`, `garden_crop_service`, `garden_identification`, `garden_media_cache`, `garden_media_policy`, `garden_motion`, `high_quality_snapshot_service`, `inaturalist_service`, `media_cache`, `media_integrity_scan`, `media_probe`, `notification_orchestrator`, `recording_snapshot_input`, `timezone_repair_service`, `visit_film_service` + - YA-WAMF `backend/locales/*.json`, `backend/openapi.json`, `apps/ui/src/lib/api/generated/openapi.ts`, `docs/api.md`, `changelog.d/source-capability-gating.md` + - YA-WAMF tests (new): `tests/pure/test_no_direct_frigate_calls.py`, `tests/pure/test_source_event_keys.py`, `tests/test_snapshot_only_source.py`, `tests/test_source_media_routes.py` + - YA-WAMF tests (patch targets moved to the shared client): `tests/conftest.py`, `tests/pure/conftest.py` and 40 existing test modules + +#### W4: Common ingestion pipeline (provider parsing → shared admission/idempotency) +- **depends_on**: [W1, W2, W3, C3] +- **location**: `backend/app/services/mqtt_service.py`, `backend/app/services/event_processor.py` (`process_mqtt_message`, `_process_event_payload`, `_save_detection`, `_handle_terminal_event_enrichment`), `classification_admission.py`, `backend/tests/pure/test_observation_admission.py` +- **description**: + - **MQTT**: `mqtt_service` subscribes to the topics the active provider + declares and dispatches to `provider.parse_live_message`. Frigate + availability and stall logic stays Frigate-only. + - **Common pipeline**: + - Dedupe by `source + message_id` (persist a recent-message table or + reuse a durable job key). + - Admission by `source + observation_id`; a stale revision or a + `change_cause=classification` is ignored. + - Classify only when `evidence_state=ready`. + - Per-object aligned region crop from box plus transform; multiple objects + in one frame are classified separately (A3). + - **Lifecycle hooks**: Frigate `new/update/end` and end-refinement run only + when lifecycle metadata is present. Never synthesize `end`. + - **Visits**: visit grouping is unchanged; it uses a camera-and-time window + only and never claims identity. + - **Multi-bird**: implement the decision 8 mapping (one observation, one + detection; sibling detections share an asset). Verify that visit counts + and the species leaderboard do not double-count siblings relative to the + Frigate multi-bird model, and document any intended difference. + - **Camera identity**: store `source_camera_id` (the lightNVR camera UUID). + `camera_name` is display-only for lightNVR rows. +- **validation**: Pure tests cover admission (duplicate, stale revision, + pending, classification-only) and per-object crop math against the C3 + fixture. W0 is green. +- **status**: Completed (YA-WAMF commit `710de873` on `feature/lightnvr-provider`, not pushed) +- **log**: + - **RED→GREEN**: all three new test modules failed at collection + (`ModuleNotFoundError: app.services.sources.evidence_crop`, then + `observation_admission`, and `ImportError: mqtt_topic_matches`). After the + change they give 141 passed together with `test_lightnvr_parse.py`. One test + bug was fixed on the way: a fake watchdog recorded its topic only when the + coroutine ran, and the task was cancelled before that. + - **Gates**: W0, `pure/test_mqtt_service.py`, + `test_live_notification_integration.py`, `test_dead_modules.py` and + `pure/test_no_direct_frigate_calls.py` give 126 passed. Pure lane: 2165 + passed, 4 skipped. Rest of the suite (`--ignore=tests/pure`): 5651 passed, + 56 skipped, 0 failed, run with W8's in-flight hunks in the tree. + `dead_modules_check.py`, `docs_consistency_check.py`, the changelog check, + and `ruff check`/`ruff format` on every W4 file are clean. No Alembic + revision was added; W2's `d4e1a7c93b52` is still the single head. + - **Entry points (W6/W7 call these)**: + - `EventProcessor.process_live_message(topic, payload)`: the MQTT service + calls this for a non-Frigate source. It parses with the active provider, + then admits each observation with `origin="live"`. + - `EventProcessor.process_source_observation(obs, *, origin="live"|"recovery") -> AdmissionDecision`. + This is the one admission path for live and recovery. W6 passes history + items straight in: `change_cause=None` means the item already is the + authoritative state, so it is not fetched again. + - `AdmissionDecision(action, reason, detail)`, in + `app/services/sources/observation_admission.py`: + - `action` is `classify|wait|skip|retry`; + - `.settled` is False only for `retry`. **W6 advances the cursor only + past settled decisions.** + - Reasons: `classified`, `duplicate_message`, `classification_only`, + `stale_revision`, `already_admitted`, `evidence_pending`, + `evidence_unavailable` / `evidence_expired` (`detail` = the lightNVR + reason), `observation_missing`, `source_unreachable`, `source_refused` + (a permanent `LightNvrApiError`), `source_not_active`, + `label_not_ingested`, `classification_unavailable`, `filtered` + (`detail` = the filter reason), `not_saved`. + - Pure helpers: `ObservationLedger.screen/settle` and + `decide_evidence(current, *, already_admitted)`. In + `sources/evidence_crop.py`: `parse_normalized_box`, + `parse_evidence_transform`, `inference_point_to_stored`, `pixel_box`, + `classification_crop_region` and `evidence_regions`. + - Orchestration lives in `app/services/source_ingestion.py` + (`SourceIngestion`, `SourceObservationEvent`). + `DetectionService.save_detection` gained the optional keyword arguments + `source_kind`, `source_instance`, `external_observation_id` and + `source_camera_id`. `_save_detection` forwards + `event.detection_save_options` (which also carries + `report_to_birdweather=False` for recovery). A Frigate save passes + nothing new, so its call is identical. + - **Dedupe storage**: there is no new table and no revision. + `processing_job_state.event_id` has a foreign key to + `detections.frigate_event`, so it cannot hold anything before a detection + exists. The **`detections` row is the durable admission record**: + - The row is unique on `frigate_event` and on W2's source tuple, and it is + checked under a per-observation lock before any fetch. + - Once a row exists, any later message, revision or recovery item is + `already_admitted`, even after a restart. + - Pending, unavailable and expired evidence writes nothing durable, as + contract §16.2 requires. + - `ObservationLedger` is a bounded in-process LRU of 4096 entries. It holds + settled `(source, message_id)` pairs and the highest settled revision per + observation, so a replay costs no request. Losing it only costs a refetch. + - **Pipeline behaviour**: + - A live trigger is followed by `fetch_observation`, which supplies the box, + the transform, `stream_name_at_capture` and the current state. + - Evidence goes through `access.fetch_event_snapshot(crop=False, workflow="source_ingest")`, + so it is capability-gated. Images are cached per + `(instance, asset_id)` (16 entries), which means siblings fetch once. + - A `202` after the observation already said `ready` is a **retry**, not a + wait, because no later ready change will arrive. + - The crop is a square of the box's longer side × 1.1, centred and kept + inside the image. Frigate's 300 px floor is dropped, because on a small + frame it would swallow the sibling. + - C3 crops: bird 0 is `(14,14,50,50)` and bird 1 is `(93,17,146,70)` on the + 160×80 JPEG. The letterbox box maps to `(512,90,768,234)` and its crop is + `(499,21,781,303)`. The transform is audited but never re-applied. A null + box classifies the whole image (`lightnvr_evidence`, `is_cropped=False`). + - Classifier input: `input_source="lightnvr_evidence_crop"`, + `is_cropped=True`, `source_image_sha256` = the evidence image. The stored + `classification_input` carries the crop box, frame size and frame time, + and a `frame_sha256` shared by siblings, which records the shared asset. + - The cached snapshot (media key `lnvr..`) is the crop. The + notification attachment is the same crop. + - `frigate_score` holds lightNVR's detector confidence. The event `type` is + `new`. `camera_name` is `stream_name_at_capture`, and + `source_camera_id` is the camera UUID. + - Skips, waits and retries appear in `recent_outcomes` as + `"skipped: "`, `"waiting: …"` or `"retry: …"`, with `detail`. + - **Not run for lightNVR**: the Frigate lifecycle (`new/update/end`), end + refinement, `_handle_terminal_event_enrichment`, recording waits, the + staleness drop, Garden capture and domestic-bird routing. The last two are + Frigate- and lifecycle-only Garden features. No `end` is synthesized. + - **MQTT**: + - With a non-Frigate provider the service subscribes only to + `live_topics()` (plus BirdNET). + - It does not subscribe to `frigate/events` or `frigate/available`, starts + no watchdog and runs no stall reconnects. + - Messages are matched with `mqtt_topic_matches` (`+` and `#`) and + dispatched as `_schedule_source_message`, under the shared semaphore and + the 90 s timeout. + - `_refresh_source_capabilities()` runs at `start()` (also when no broker is + configured) and before every reconnect. It is bounded at 30 s, and a + failure is logged without blocking. + - The Frigate path is byte-identical. Frigate's `parse_live_message` stays + off the hot path, because per-event serialization and pending-payload + merging need the raw payload. + - **Multi-bird (verified by test)**: two same-species siblings give + 2 detections, 1 visit with `capture_count` 2, and a leaderboard window with + `window_count` 2 and `window_visit_count` 1. Visits and leaderboard visits + do not double count. + - **Intended difference** (contract §16.3): a Frigate two-bird event is + 1 detection, while lightNVR yields 2. + - **Deviation from decision 8**: no `bird_observations` row is written. + Those rows need bird-scan candidate media (`candidate_id`, + `clip_variant`, `frame_index`), and synthetic rows would feed the HQ and + bird-scan machinery. A lightNVR detection therefore counts like a + Frigate detection that has never been bird-scanned: `counted_birds` and + `counted_captures` in the daily summary exclude it. W9 should check the + multi-bird display, and decide whether to add index-0 rows with a real + candidate. + - **Other changes**: + - `lightnvr_provider.lightnvr_event_key` is now `event_keys.lightnvr_event_key` + (re-exported). + - **Bug fixed**: `LightNvrSettings.source_instance` allowed `.` and `_`, + which `event_keys` rejects (and `.` is the media-key separator). It is now + `^[A-Za-z0-9][A-Za-z0-9-]{0,63}$`. The docs and a test case were updated. + - **Gotchas**: + - **W6**: a live `retry` is not retried by itself. Recovery must replay it, + so only advance the cursor past settled decisions. A detection the owner + hard-deletes would be admitted again if the cursor is reset. Hidden rows + still count as admitted. History items have no `message_id` collision + with live items for the same revision (W5 recomputes the ID), so the + ledger dedupes the overlap. + - **W7**: + - Live admission enqueues `_enqueue_notification_flow` with + `type="new"`, `changed=True` and `was_inserted=True`. `standard` and + default modes therefore notify, but `final` does not yet, because it + still waits for an `end` (W7 owns that). + - Recovery (`origin="recovery"`) enqueues **no** notification; W7 + records `suppressed_recovery`. + - Siblings notify separately, subject to cooldown. + - **W8**: the UI or validation for `lightnvr_source_instance` must match + the new regex. + - **W9**: repeat the snapshot-only test with this pipeline. + `write_frigate_sublabel` makes `write_event_classification` record an + `annotate` capability skip on every save. + - **W10**: replace the `annotate` skip through + `access.write_event_classification`, which is already called after save. +- **files edited/created** (YA-WAMF): + - `backend/app/services/source_ingestion.py` (new), + `backend/app/services/sources/observation_admission.py` (new), + `backend/app/services/sources/evidence_crop.py` (new) + - `backend/app/services/mqtt_service.py`, `event_processor.py`, + `detection_service.py`, `sources/lightnvr_provider.py`, + `backend/app/config_models.py` + - `backend/tests/pure/test_observation_admission.py` (new), + `backend/tests/pure/test_source_live_dispatch.py` (new), + `backend/tests/test_lightnvr_ingestion.py` (new), + `backend/tests/pure/test_lightnvr_parse.py` + - `docs/setup/environment-variables.md`, `changelog.d/lightnvr-ingestion.md` (new) + +#### W5: lightNVR client and provider +- **depends_on**: [W1, C3] +- **location**: `backend/app/services/sources/lightnvr_client.py`, `lightnvr_provider.py` (new), `backend/app/config_models.py` (`LightNvrSettings`: `api_url`, `token` (secret, redacted), `mqtt` topic prefix, `camera_uuids`, `source_instance` override), `backend/tests/fixtures/lightnvr/` (vendored C3), `backend/tests/test_lightnvr_provider.py` +- **description**: + - Implement `identity` from `/api/integrations/capabilities` `source`. + - Implement `list_cameras` with existing lightNVR camera discovery, + non-admin. + - Implement `capabilities`, mapping from the capability doc. + - Implement `parse_live_message` for `observation_changed.v1`. + - Implement `history(cursor)`: on `410 cursor_expired`, return a + `RecoveryRequired(oldest_cursor)` result. This includes the case where + lightNVR's DB was restored and the epoch changed. + - Implement `fetch_snapshot`: + - `202` → retry with bounded backoff honoring `Retry-After`; + - `410` → `Expired`; + - `404` with reason → `Unavailable`; + - network failure → `TemporarilyUnreachable`. + - Implement `write_classification` as a stub that raises Unsupported until + W10. + - Reuse httpx with timeouts. Redact the token in logs. + - Sanitize IDs before use in any cache path, per YA-WAMF CLAUDE.md §1. +- **validation**: Tests against a mocked httpx transport serving the C3 + fixtures cover every outcome class. The secret is redacted in the settings + API response. The pure parser test passes on every fixture. +- **status**: Completed (YA-WAMF commit `88e09e4d` on `feature/lightnvr-provider`, not pushed) +- **log**: + - **RED→GREEN**: both new test modules failed at collection with + `ImportError: LightNvrSettings` (and the missing provider modules). After + the change, 137 pass, then 138 with a protocol-conformance check. Two real + bugs surfaced on the way: + - httpx copies an `httpx.Cookies` object into a fresh jar and drops its + policy, so a `Set-Cookie` came back on the next request. The client now + passes a `CookieJar` with a reject-all policy directly. + - Float milliseconds truncated `.481` to `.480` in `since`. The client now + rounds to integer milliseconds. + - **Gates**, run on the exact commit content in an isolated worktree at + HEAD, because W3's in-flight edits break 46 pure tests in the shared tree: + - W0, `test_dead_modules`, the startup test and the new tests: 191 passed. + - Pure lane: 2101 passed. + - Rest of the suite: 5593 passed, 56 skipped, 0 failed. + - `export_openapi.py --check`, `export_openapi_types.py --check`, + `dead_modules_check.py`, `docs_consistency_check.py`, the changelog + check, and repo-wide `ruff check` and `ruff format --check`: clean. + - **Public API**, `app/services/sources/lightnvr_client.py`: + - `LightNvrClient(api_url, token, *, timeout=10.0, transport=None)`. + Methods: `get(path, *, params, accept, max_bytes, timeout)`, + `get_capabilities()`, `list_streams(page=, page_size=)`, + `list_observations(params)`, `get_observation(id)`, + `get_snapshot(id, timeout=)` and `aclose()`. It returns + `LightNvrResponse(status_code, headers, body)`, with `.json_object()` + and `.content_type`. + - Errors: `LightNvrUnreachableError(reason, msg)` subclasses + `SourceUnavailableError`, with reason `not_configured|timeout|request_error`. + `LightNvrResponseTooLargeError` is raised past the limits (JSON 8 MiB, + media 20 MiB, both instance-adjustable). + - The client sends `Authorization: Bearer`, stores no cookies, never + follows redirects, and redacts the token from any logged exception + text. + - **Public API**, `app/services/sources/lightnvr_provider.py`: + - `LightNvrProvider(client, config, *, sleep=asyncio.sleep, clock=time.monotonic)`: + - `identity()` returns `("lightnvr", source_instance override or + capabilities.source.installation_uuid)`. Without an override it raises + `SourceUnavailableError` until capabilities load. + - `capabilities()` reads `capabilities_not_loaded` on every field until + the document loads. + - `apply_capabilities(doc)` is sync and pure-testable. It raises + `SourceUnavailableError` and keeps the last good state when the doc + is not `kind: lightnvr`, when `contract_versions.observations ≠ 1`, + when `source.id` ≠ `urn:lightnvr:`, or when there + is no `operations` block. + - `async refresh_capabilities()`. + - `capability_info` returns `LightNvrCapabilityInfo`: `source_urn`, + `installation_uuid`, `product_version`, `writer_namespace`, + `mqtt_enabled`, `delivery_topic`, the history page and filter limits, + and `camera_uuids`. + - `writer_namespace`. + - `live_topics()` and `parse_live_message(topic, payload)`. + - `observation_from_api(item)`. + - `async list_cameras()`, `async history(cursor, filters)`, + `async fetch_observation(id) -> SourceObservation | None` and + `async fetch_snapshot(...)`. + - `fetch_thumbnail` returns None and `external_snapshot_url` returns + None. + - `fetch_event`, `fetch_clip`, `fetch_clip_bytes`, `fetch_range_clip`, + `fetch_recording_frame` and `write_classification` raise + `UnsupportedCapabilityError`. + - `aclose()`. + - `LightNvrApiError(status_code, code)` means a permanent refusal. It is + **not** a `SourceUnavailableError`. + - Helpers: `lightnvr_event_key(instance, obs_id)` returns + `lnvr::`, `observation_message_id(source, obs_id, rev)` + (the §5.4 UUIDv5), `is_canonical_uuid`, `build_lightnvr_provider(config)`. + Constants: `SOURCE_KIND_LIGHTNVR`, `OBSERVATION_CHANGED_EVENT_TYPE`, + `MESSAGE_ID_NAMESPACE`. + - `registry.configured_source_kind(app_settings)`. + `build_source_provider(kind=None, *, app_settings=None)` now picks + lightNVR when `settings.lightnvr.enabled`, so `main.py` is unchanged. + - **Settings**: `Settings.lightnvr: LightNvrSettings` has these fields: + - `enabled` (False); + - `api_url` (http/https only, no credentials, query or fragment); + - `token` (`repr=False`); + - `mqtt_topic_prefix` (`lightnvr`; plain levels, no wildcards); + - `camera_uuids` (accepts a list or a comma string; lowercased and + deduped, invalid entries dropped with a warning); + - `source_instance` (`^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$`, otherwise a + validation error); + - `request_timeout_seconds` (10, from 1 to 60). + + Environment variables `LIGHTNVR__ENABLED|API_URL|TOKEN|MQTT_TOPIC_PREFIX|CAMERA_UUIDS|SOURCE_INSTANCE|REQUEST_TIMEOUT_SECONDS` + are documented in `docs/setup/environment-variables.md`. An environment + variable overrides the file per key. The settings API fields are + `lightnvr_enabled|api_url|token|mqtt_topic_prefix|camera_uuids|source_instance`. + The GET response returns `lightnvr_token` as `***REDACTED***`. A POST + with the placeholder keeps the stored token (`should_update_secret`). The + section is revalidated as a whole, and a bad value returns `422` without + being stored. The token is also covered by `configured_secrets()`, through + the `token` key. + - **Mapping decisions**: + - Live messages: + - Accepted only on `{prefix}/v1/events/io.lightnvr.detection.observation_changed.v1/` + and only after capabilities load. + - The envelope must have `specversion` 1.0, the exact `type`, + `source` equal to the capabilities URN, and `subject` equal to + `camera/` and to `data.camera_uuid`. + - The `id` must equal the recomputed UUIDv5. + - Payloads are capped at 64 KiB. + - Unknown types, causes or states are dropped. An unknown + `unavailable`/`expired` reason is kept as `[a-z0-9_]`. + - Cameras outside `camera_uuids` are dropped. + - Live `camera_name` is the camera UUID, because the event carries no + stream name. History and `fetch_observation` items use + `stream_name_at_capture`, with control characters stripped and capped + at 63. + - History items have `change_cause=None` and a recomputed `message_id` + for `(source, id, current revision)`, so live/recovery dedup keys match. + `lifecycle` is always None. + - History outcomes: + - `410 cursor_expired` → `RecoveryRequired(oldest_cursor)` (None if the + cursor is unsafe); + - `400 invalid_cursor` → `RecoveryRequired(None, "invalid_cursor")`; + - 5xx, network error or an unreadable page → `SourceUnavailableError`; + - other 4xx (for example `403` on a camera filter) → `LightNvrApiError`. + - History filters: + - comma-joined cameras and labels; a label containing a comma is a + `ValueError`, sent before any request; + - `since`/`until` in RFC 3339 with milliseconds; + - `limit` clamped to `history_page_max`; + - empty `filters.cameras` falls back to the configured `camera_uuids`; + - more than 10 cameras → no server filter, filtered client-side; + - `has_snapshot` is ignored, because lightNVR has no such filter. + - Snapshot outcomes: + - `200` image/jpeg → `READY` (`cropped=False`); + - `202` → poll on Retry-After (header, then body + `retry_after_seconds`, then a default of 2), clamped to 1–30. The + budget is the `timeout` argument capped at 120 s, with at most 12 + attempts, then `PENDING` with `retry_after_seconds`; + - `404 evidence_unavailable` → `UNAVAILABLE(reason)`; + - any other `404` → `MISSING("observation_not_found")`; + - `410` → `EXPIRED(reason)`; + - `403` → `UNAVAILABLE("forbidden")`; + - `501` → `UNAVAILABLE("not_implemented")`; + - 5xx → `TEMPORARILY_UNREACHABLE` (`service_unavailable` or + `http_5xx`, with Retry-After); + - timeout or connect error → `TEMPORARILY_UNREACHABLE` + (`timeout`/`request_error`); + - a non-JPEG body → `UNAVAILABLE("unexpected_media_type")`; an + oversized one → `UNAVAILABLE("snapshot_too_large")`; + - a non-canonical ID → `MISSING("invalid_observation_id")` with no + request. + - Capabilities are narrowed to what YA-WAMF implements. `annotate`, clips, + recordings and lifecycle stay False even if lightNVR declares them, with + the reason `not_implemented_by_consumer`. + - **Gotchas for W4/W6/W8/W10**: + - **W4/W6: call `await provider.refresh_capabilities()` at startup and on + MQTT reconnect.** Until it runs, `parse_live_message` returns `[]` and + `identity()` raises (unless `source_instance` is set). `history()`, + `fetch_observation()` and `list_cameras()` load it lazily, but `main.py` + does not. A failed refresh keeps the last good document. + - **W4**: a live observation is only a trigger. Call + `fetch_observation(external_id)` for the box, transform, + `stream_name_at_capture` and the current evidence state (contract + §16.2.3), and drop `change_cause == "classification"`. Siblings share + `asset_id`. Cache the full image once per asset and crop each + observation by its own box with `cropped=False`. + - **W4/W8: MQTT broker.** lightNVR events use the existing broker + settings (`frigate.mqtt_*`); only the topic comes from + `lightnvr.mqtt_topic_prefix`. Frigate availability and stall logic must + stay Frigate-only. `capability_info.delivery_topic` lets W8 warn when + the configured prefix disagrees with lightNVR. + - **W3/W4: event key characters.** The event key contains `:`. + `media_cache.sanitize_event_id` strips it, which is unique for + UUID-based keys, but W3's source-identity cache keys should not rely on + that. + - **W6**: `RecoveryRequired.oldest_cursor` can be None, meaning restart + without a cursor. `cursor="head"` is passed through for a live-only + start. Malformed history items are skipped with a warning count and the + cursor still advances. If W6 needs per-item skip reasons it must extend + `HistoryPage`, which is in W1's `base.py`. A `LightNvrApiError` is + permanent and a `SourceUnavailableError` is retryable. + - **W8**: + - Settings changes do not rebuild the active provider; restart, or + add a rebuild. + - `lightnvr_enabled` is already in the settings API. The UI selector + should write it, and a failure must never flip it. + - `list_cameras()` uses `GET /api/streams?summary=true&surface=live`, + paged by 100 for up to 20 pages, which needs only `live.view`. + - **W10**: read `provider.writer_namespace` (D3) and never hard-code + `yawamf`. Replace the `write_classification` stub and drop `annotate` + from `_consumer_capabilities`' unimplemented set. + - **Shared worktree**: `routers/settings.py` and `registry.py` held W3's + unstaged hunks. W5's versions were staged as HEAD plus W5 edits + (`git hash-object -w` + `update-index --cacheinfo`), and W3's hunks were + left in place. W3's registry hunk (`FrigateProvider(app_settings=…)` + without a client) still applies on top. +- **files edited/created**: + - YA-WAMF `backend/app/services/sources/lightnvr_client.py` (new) + - YA-WAMF `backend/app/services/sources/lightnvr_provider.py` (new) + - YA-WAMF `backend/app/services/sources/registry.py` (lightNVR kind selection) + - YA-WAMF `backend/app/config_models.py` (`LightNvrSettings`), `backend/app/config.py`, `backend/app/config_loader.py` + - YA-WAMF `backend/app/routers/settings.py`, `backend/app/services/settings_update.py` (API fields, redaction) + - YA-WAMF `backend/openapi.json`, `apps/ui/src/lib/api/generated/openapi.ts` (regenerated) + - YA-WAMF `docs/setup/environment-variables.md`, `changelog.d/lightnvr-provider.md` + - YA-WAMF `backend/tests/pure/test_lightnvr_parse.py` (new), `backend/tests/test_lightnvr_provider.py` (new) + +#### W6: Cursor recovery and backfill for providers +- **depends_on**: [W2, W4, W5] +- **location**: `backend/app/services/backfill_service.py`, `frigate_event_absence.py` / `frigate_missing_policy.py` (keep these Frigate-only), new `source_recovery_service.py`, `backend/app/routers/backfill.py`, tests +- **description**: + - On start and reconnect, page through `provider.history(saved_cursor)`. + - Feed each page through the same admission path as live messages, so + live/recovery overlap shares dedup. + - Advance `source_cursors` only after each item is durably accepted or + recorded as skipped with a reason. + - Persist retryable failures in `processing_job_state` with a dedicated + `pipeline` value. + - Recovery is silent: no standard notifications for historical items by + default, enforced via notification intent. + - Distinguish missing, expired, unsupported and temporarily unreachable in + stored outcomes and the backfill UI messages. +- **validation**: Tests cover: + - 100 fixture observations with interleaved pending→ready recover to + exactly 100 detections; + - a crash mid-page resumes without duplicates; + - an expired cursor yields a recovery-required state shown in health; + - live+recovery overlap with each message replayed 10× gives one detection + and one intent (A4, A5 YA-WAMF half). +- **status**: Completed (YA-WAMF commit `119d9f48` on `feature/lightnvr-provider`, not pushed) +- **log**: + - **Entry points** (`app/services/source_recovery_service.py`, singleton + `source_recovery_service`): + - `request(trigger, processor=None)` coalesces requests and lazily starts + the loop. `mqtt_service._request_source_recovery` calls it after + subscribing (`start`, then `reconnect`), and also on the no-broker + return. It is a no-op for Frigate. + - The loop runs every 300 s. While held up (`blocked`, `unreachable`, + `failed`) it backs off 30 s → 600 s; after `page_budget` it re-runs + at once. + - `run_once(trigger) -> RecoveryRun(result, detail, start, pages, items, + outcomes)`. Results: `caught_up|page_budget|blocked|unreachable|refused|unsupported|not_applicable|failed`. + Runs are serialized by a lock. + - Other methods: `history_window(after=, before=, cameras=)`, an async + iterator for the date-range backfill that never touches the cursor; + `acknowledge_gap()`; `get_status()`; `handles_backfill()`; `stop()` + (`main.py` shutdown, after MQTT stops). + - Pure rules live in `app/services/sources/recovery_policy.py`: + `filter_fingerprint`, `start_point`, `page_may_advance`, + `backfill_outcome` and `outcome_code`. The repository is + `app/repositories/source_cursor_repository.py`. + - **Initial cursor decision**: when no `source_cursors` row exists, start at + `cursor=head` (contract D10), so the first start is live-only. + - Paging all retained history (30 days by default) on first connection + would classify and store every retained sighting unasked. Scan History + (date-range backfill) imports retained history on request instead. + - A row whose cursor is NULL means "start of retained history". A changed + filter fingerprint resets the row to that, as §7.3 requires. + `RecoveryRequired(oldest_cursor=None)` from `invalid_cursor` or an + unsafe cursor also resets to it. + - The fingerprint covers the configured `camera_uuids` with no labels. + Recovery reads every label, and admission skips labels that are not + ingested, with a reason. + - **Retry persistence choice**: there is no `processing_job_state` row, + because its FK to `detections.frigate_event` cannot reference a + pre-admission item. + - The cursor is saved only after a page in which every decision is + `settled`. Cursors are opaque, so page boundaries are the only safe + resume points. + - The run still processes the rest of a blocked page. On the next run the + page is re-fetched; settled items come back as `already_admitted` and + only the retried item does work. + - `source_cursors.last_error` holds `retry: xN`, + `source_unreachable: …`, `source_refused: …` or `failed: …`, and is + cleared on the next save. + - Live `retry` decisions are replayed by the next interval or reconnect + run, because the cursor trails until recovery passes them. + - A permanently failing item blocks cursor progress, visibly in health. + Live ingestion continues meanwhile. + - **Expired cursor**: + - The run calls `mark_recovery_required`, which stores + `cursor=oldest_cursor` (or NULL), sets + `recovery_state=recovery_required`, records an + `error_diagnostics_history` entry (`source=source_recovery`) and logs a + warning. + - It then resumes in the same run, with at most 2 expiries per run. + - The state is sticky until `POST /api/backfill/source-recovery/acknowledge`. + `/health.source_recovery` and `GET /api/backfill/source-recovery` show + `recovery_required`, `gap_reason`, `last_error` and `last_run`. + - `/health` top-level status is not degraded by it. + - The `epoch` column stays NULL, because cursors are opaque and lightNVR + answers `410` on an epoch change. + - **Backfill (`routers/backfill.py`, additive)**: when the active source is + not Frigate, both `POST /backfill` and `/backfill/async` go through + `history_window` with `origin="recovery"`. + - `backfill_outcome` reasons: `already_exists`, `evidence_pending`, + `observation_missing`, `evidence_expired`, `evidence_unavailable`, + `unsupported_by_source` (evidence `not_implemented`), and + `label_not_ingested` or the filter detail. The error reasons are + `source_unreachable`, `source_refused`, `evidence_not_ready`, + `classification_unavailable` and `not_saved`. + - The English messages name each reason class. + - Whole-run outcomes: + - no history capability → `failed` with `unsupported_by_source`; + - `SourceUnavailableError` → `failed` with `source_unreachable` and + nothing marked skipped. + - Camera filters accept lightNVR UUIDs only. + - The labels are `settings.data.backfill_reasons.*` in all 9 locales, + which keeps the audit test's key parity. + - `backfill_service.py`, `frigate_event_absence.py` and + `frigate_missing_policy.py` are unchanged. + - **RED→GREEN**: + - Both new test modules first failed at collection (`ModuleNotFoundError: + app.services.sources.recovery_policy` and + `app.repositories.source_cursor_repository`). + - After the change, 27 pure and 19 integration tests pass. The + integration tests use a fake lightNVR on `httpx.MockTransport` that + implements the §7.3 page algorithm (watermark, `has_more`, `head`, + 410 with `oldest_cursor`, pruning, epoch change) over observations + built from the vendored C3 `single-ready.json`. Cases: + - 100 observations with pending→ready flips between pages give exactly + 100 detections and 100 classifier calls, with zero notifications; + - a crash on item 15 keeps the cursor at seq 10, and a fresh processor + and service resume to 30 detections and 30 classifier calls; + - a 503 snapshot blocks the page and is replayed; + - an expired epoch gives `recovery_required` in + `build_health_payload()` and is then acknowledged; + - 20 observations × 10 live replays, overlapped with recovery, give 20 + detections, 20 classifier calls and at most one notify per key; + - head bootstrap, filter reset, unreachable, unsupported, the Frigate + no-op, and the sync and async backfill outcome reasons. + - A mutation check, making `page_may_advance` always True, fails the + retry test and the pure test. + - 3 MQTT hook tests were added to `pure/test_source_live_dispatch.py`. + An autouse recorder stops that file from starting the real loop. + - **Gates**: W0, `test_dead_modules`, `test_lightnvr_ingestion`, + `test_backfill_integration` and the new tests give 158 passed. Pure lane: + 2220 passed, 4 skipped. Rest of the suite: 5687 passed, 56 skipped, 0 + failed, on the exact commit content. `export_openapi.py --check`, + `export_openapi_types.py --check`, `dead_modules_check`, + `docs_consistency_check`, the changelog check, repo-wide `ruff + check`/`ruff format --check`, `npm run check` (0 errors, 0 warnings) and + the i18n vitest suite (177) are all clean. + - **Shared-tree note**: W7's commit `7fb42e41` left `backend/app/main.py` + unparseable. The `notification_intent` import and the `reconcile` phase + were spliced inside the `registry` import and an `await + _run_lifecycle_phase(` call. W6's commit carries the corrected ordering; + the working tree already had it. Check this when reviewing `7fb42e41` on + its own. + - **Gotchas for W9/X1**: + - Recovery needs an `EventProcessor`. MQTT start attaches it, and + `history_window` falls back to `EventProcessor()`. Tests should build + `SourceRecoveryService(processor=…, page_limit=…)` rather than use the + singleton, whose loop task outlives a test. + - A first start never imports history. For A4 acceptance, seed a + `source_cursors` row (cursor NULL or a real cursor with + `filter_fingerprint(camera_uuids, ())`), or drive Scan History. + - Setting `camera_uuids` later changes the fingerprint, which replays all + retained history once. It is idempotent but costs fetches. + - W8's `GET /api/diagnostics/source` and SourceHealthCard do not show + `source_recovery` yet. The data is in `/health.source_recovery` and + `GET /api/backfill/source-recovery`, and acknowledging needs a UI button + (a follow-up for W8/W9). + - The Scan History UI copy still says "Analyzing Frigate…" for lightNVR. + - X1: a lightNVR restore (epoch change) should show `recovery_required` + and a diagnostics entry, followed by a full catch-up from + `oldest_cursor`. +- **files edited/created** (YA-WAMF): + - `backend/app/services/source_recovery_service.py` (new), + `backend/app/services/sources/recovery_policy.py` (new), + `backend/app/repositories/source_cursor_repository.py` (new) + - `backend/app/routers/backfill.py`, `backend/app/services/mqtt_service.py`, + `backend/app/main.py` + - `backend/openapi.json`, `apps/ui/src/lib/api/generated/openapi.ts` + (regenerated), `apps/ui/src/lib/i18n/locales/*/settings.json` + - `backend/tests/test_source_recovery.py` (new), + `backend/tests/pure/test_source_recovery_policy.py` (new), + `backend/tests/pure/test_source_live_dispatch.py` + - `docs/api.md`, `changelog.d/lightnvr-recovery.md` (new) + +#### W7: Durable notification intent +- **depends_on**: [W2, W4] +- **location**: `backend/app/services/notification_orchestrator.py`, `notification_dispatcher.py`, `notification_links.py` (build links through the provider), tests +- **description**: + - Reconcile the existing `mark_notified` / `notify_mode` mechanism rather + than adding a parallel one. + - Record the notification intent durably in the same transaction as the + detection save, by extending the existing notified state with an + intent and outcome. Dispatch only when the intent is newly recorded. + - **Sources without lifecycle**: `notify_mode="final"` currently fires + only on `event_type=="end"`, which lightNVR never sends. + - For providers without `track_lifecycle`, "final" means the first + admitted ready classification, bounded by the existing cooldown or + visit rules (decision 8). + - Add a test that lightNVR users on "final" are notified. + - Mark the outcome after the delivery attempt. An ambiguous external + delivery state (timeout after send) is recorded as `unknown` and surfaced + rather than retried blindly. + - Notification attachments fetch evidence through the provider. + - Preserve existing Frigate notification behavior (W0). +- **validation**: A replay test sends the same observation 10 times and gets + one intent and one dispatch. A simulated crash after intent and before + dispatch is recoverable and visible. W0 is green. +- **status**: Completed (YA-WAMF commit `7fb42e41` on `feature/lightnvr-provider`, not pushed) +- **log**: + - **Intent lifecycle** (W2's columns and vocabularies; no Alembic revision): + - `SourceIngestion` computes the intent with + `notification_intent.single_shot_intent(origin, notify_mode, notify_on_insert)`: + - `recovery` always gives `suppressed_recovery`; + - `silent` gives `suppressed_policy`; + - `final`, `standard` and `realtime` give `dispatch`; + - any other mode follows `notify_on_insert`. + - `DetectionService.save_detection(notification_intent=…)` forwards it + to `upsert_if_higher_score`. Only the **insert** branch writes + `notification_intent` and `notification_intent_at`, before the same + commit, guarded by `notification_intent IS NULL`. So `was_inserted` + means the intent is newly recorded, and only then does a `dispatch` + enqueue. A Frigate save passes no intent and its upsert call is + unchanged. + - The orchestrator sees `notification_intent` on the event and takes + `_deliver_recorded_intent`, never the lifecycle `_should_notify`. It: + - re-reads `get_notification_state`; anything except + `dispatch` with no outcome is a no-op, so a double enqueue cannot + double send; + - records `skipped` for a hidden or unconfirmed row; + - does not wait for video, because a snapshot-only source has no + clip; + - sends with a `NotificationReport`, then calls + `record_outcome` once (`WHERE notification_intent IS NOT NULL AND + notification_outcome IS NULL`). `sent` also sets `notified_at` + (`DetectionRepository.record_notification_outcome`, which extends + `mark_notified`). + - Outcomes: + - `notify_detection` filters and cooldown give `skipped`; + - any channel sent gives `sent`; + - an ambiguous error gives `unknown`: `ReadTimeout`, `WriteTimeout`, + `RemoteProtocolError`, `ReadError`, `TimeoutError`, + `SMTPServerDisconnected`, or `CancelledError` (the dispatcher's + `wait_for` deadline hitting a send in flight); + - otherwise `failed` (for example `ConnectError` or an HTTP status); + - a queue-full drop records `failed` (`not_queued`). + - Transport results reach the report through a `ContextVar` that is set + only while the channel tasks are created. `_note_delivery` is called + in each `_send_*`. Frigate and Garden calls pass no report and are + unaffected. + - Every `unknown` is logged and recorded in `error_diagnostics_history` + (`reason_code=notification_delivery_unknown`, severity `error`) and + never retried. + - **Crash-recovery choice: surface, never resend** (delivery is at most + once, and exactly once is not promised). + - The lifecycle phase `notification_intent_reconcile` runs in `main.py` + after `db_init` and before the dispatcher, MQTT and source recovery. + - It calls `reconcile_interrupted_intents(started_before=)`, + which marks every `dispatch` intent with no outcome and recorded before + this start as `unknown`. + - Each one gets a diagnostics entry + (`notification_intent_interrupted`, severity `warning`), and the count + appears in `/health` under `notification_intents` + (`interrupted_at_startup`, `delivery_unknown`, `last_reconciled_at`). + - Rows recorded after the cutoff are left alone. A row cannot say + whether the crash came before or during the send, so resending could + duplicate the alert. A job dropped by `dispatcher.stop()` at shutdown + therefore also ends up `unknown`, although it was never sent. + - **Other changes**: + - **Frigate is untouched**: rows keep NULL intent, and `final` still + waits for `end`. A test asserts no `report` kwarg on the Frigate path. + - For a single-shot sighting, `notify_detection` gets `event_type="end"`, + so **email only on end** still emails: the first admission is also + the end. + - `notification_link`: with `link_target="frigate"`, a non-Frigate key + (`lnvr:`) falls back to the YA-WAMF detection link. + - Attachments: ingestion already passes the cached crop. The fallback + stays `media_cache`, then `source_access.fetch_event_snapshot_bytes`, + which goes through the provider. `snapshot_url` stays `""` for + lightNVR. + - **RED → GREEN**: + - `tests/test_lightnvr_notification_intent.py` (17 tests) first failed + at collection (`ImportError: notification_intent`). + - With a stub module, 16 of 17 failed. `final` mode got "awaited 0 + times", intent columns were NULL, and the recovery, silent, crash, + timeout, deadline, refusal, sibling-cooldown and link tests all failed. + Only the Frigate-unchanged test passed, as intended. + - After the change, all 17 pass, plus `tests/pure/test_notification_intent_policy.py` + (25 cases). + - Covered: + - 10× replay gives one intent and one dispatch, also from a fresh + processor and with recovery overlap; + - `final` notifies once; + - recovery gives `suppressed_recovery` and no dispatch, including on a + later live message; + - `silent` gives `suppressed_policy`; + - a crash before dispatch gives `unknown` plus a diagnostic, with no + resend on a second sweep or a redelivery; + - the sweep ignores intents recorded after the cutoff; + - a queue drop gives `failed`; + - real `notify_detection` against a fake Telegram: `ReadTimeout` + gives `unknown` plus a diagnostic and no resend; a 0.2 s dispatcher + deadline mid-send gives `unknown`; `ConnectError` gives `failed`; + OK gives `sent` with `notified_at`; + - siblings with a 5 min cooldown give `sent` and `skipped`, and + without a cooldown both are `sent`. + - **Gates**: + - W0 46/46. + - Notification, ingestion and live-notification modules: 186 passed. + - Pure lane: 2217 passed, 4 skipped. + - Rest of the suite: 5686 passed, 56 skipped, 0 failed, run with W6's + and W8's in-flight hunks in the tree. + - `ruff check`/`ruff format` are clean on every W7 file. The repo-wide + `ruff check` flags only W6's in-flight `source_recovery_service.py` + (unused `HEAD_CURSOR`). + - `docs_consistency_check.py` and `dead_modules_check.py` are clean. + - **Commit hygiene**: the shared index already held W8's staged files. The + commit was therefore built in a private index + (`GIT_INDEX_FILE`, `read-tree HEAD`, `git add` W7 files, and + `git apply --cached` with only the three W7 hunks of `main.py`). W6's + `source_recovery` hunks in `main.py` stay unstaged, and W8's staged set + is untouched. + - **Gotchas**: + - **W9**: assert intent and outcome rows with the `INTENT` query in + `test_lightnvr_notification_intent.py`. Its fixtures are reused from + `test_lightnvr_ingestion` by module attribute (a direct import trips + ruff F811). `intent_status()` counters are process-global, so assert + deltas. For A4, check one `dispatch` row and one notify call. + - **W6**: recovery must call `process_source_observation(origin="recovery")`. + That is what records `suppressed_recovery`; nothing else is needed. A + recovered item for an already admitted row leaves the live row's + intent and outcome alone. + - **W8**: `/health.notification_intents` and the diagnostics reason + codes `notification_delivery_unknown` and + `notification_intent_interrupted` are there to surface. No UI was + added. + - **W10 / A13**: write-back retries never touch + `notification_intent`, and a re-save of an existing row cannot record + one (insert only). Do not call `_enqueue_notification_flow` from + write-back. + - **X1**: an unknown outcome is expected after a kill -9 during a send, + and also for jobs that were still queued at a graceful stop. Do not + treat it as a bug. Ambiguity is detected only from error type names, + so a new transport must return `DeliveryResult.error_type` with the + exception class name. +- **files edited/created** (YA-WAMF): + - `backend/app/services/notification_intent.py` (new) + - `backend/app/services/notification_orchestrator.py`, + `notification_service.py`, `notification_links.py`, + `source_ingestion.py`, `event_processor.py`, `detection_service.py`, + `backend/app/repositories/detection_repository.py`, `backend/app/main.py` + (W7 hunks only) + - `backend/tests/test_lightnvr_notification_intent.py` (new), + `backend/tests/pure/test_notification_intent_policy.py` (new) + - `docs/features/notifications.md`, + `changelog.d/lightnvr-notification-intent.md` (new) + +#### W8: Settings, onboarding and health UI for source selection +- **depends_on**: [W3, W5] +- **location**: `backend/app/routers/settings.py`, `routers/setup.py`, `routers/diagnostics.py`, `backend/app/services/setup_state_service.py`, `apps/ui/src/**` (settings and onboarding Svelte views, health panel), `backend/locales/*` and UI i18n +- **description**: + - Add a source selector; the default stays Frigate for existing configs, and + a failure never switches providers. + - For lightNVR: API URL, scoped token and MQTT connection, then a camera + picker using lightNVR discovery. + - The connection test reports API access, event connectivity, evidence + access and available features as separate results. + - A diagnostic shows the latest eligible observation and its photo state + (ready, missing or expired), and states "no bird detections yet" as + distinct from an error. + - Explain that a bird-capable detector is required. + - Unsupported video features are disabled with a short explanation and do + not affect health status. +- **validation**: Backend API tests for the connection test outcomes. UI: + `npm run check`, `npm test`, and a browser test that a snapshot-only source + shows disabled clip controls with an explanation (A9 UI half). +- **status**: Completed (YA-WAMF commit `3cda8dfe` on `feature/lightnvr-provider`, not pushed) +- **log**: + - **RED→GREEN**: + - `tests/test_lightnvr_setup_api.py` (26 tests) failed at collection with + `ImportError: source_setup_service`. Three new `test_setup_state.py` + cases failed on the Frigate-only connection rules. After the change, + both files pass (26 and 25). + - `browser-tests/source-selection.spec.ts` failed 3/3 before the UI + existed: the clip switch was not `aria-disabled`, and there was no + source radio or lightNVR field. It now passes 5/5, with two Health + cases added. `utils/source-features.test.ts` passes 8. + - **Gates**, run on the exact commit content in an isolated worktree, because + W6 and W7 had unstaged edits in the shared tree: + - Full backend suite: 7816 passed, 60 skipped, 0 failed. This includes W0 + (`test_frigate_regression_baseline.py`), `test_dead_modules`, the pure + lane (2165 passed in the shared tree) and the startup test. + - Clean: `export_openapi.py --check`, `export_openapi_types.py --check`, + `docs_consistency_check.py`, `assemble_changelog.py`, and repo-wide + `ruff check` and `ruff format --check`. + - Frontend: `npm run check` 0/0, `npm test` 2353 passed, + `npm run lint:dead` clean. + - Playwright chromium, full suite in the shared tree: 638 passed, 2 + skipped. `explorer-search:258` and `family-tree:595` failed under load + and pass when rerun alone. `setup-wizard.spec.ts` passes 28/28. + - `npm ci` was run in `apps/ui`, and `playwright install + chromium-headless-shell` added build 1248, which `@playwright/test` + 1.64.1 needs. + - **Endpoints**: all owner-only, all in `docs/api.md`. + - `GET /api/setup/source` returns `SourceStatus`: `active_kind`, + `configured_kind`, `restart_required`, `capabilities_loaded`, + `capabilities_error` and `features[{name,supported,reason}]`. + - `restart_required` is true when the saved kind differs from the + running one, or when the running lightNVR client's URL differs from + the saved `api_url`. + - A lightNVR provider whose capabilities never loaded is refreshed once + here. + - `POST /api/setup/lightnvr/test` takes `{api_url, token, mqtt_topic_prefix, + camera_uuids, mqtt_server/port/auth/username/password}` and returns + `LightNvrConnectionTest{ok, results[], features[], latest_observation}`. + - Each result is `{id, status: ok|warning|error|skipped, code, message, + values}`, with ids `api_access`, `event_connectivity`, + `evidence_access` and `features`, in that order. + - Nothing is stored. A blank or `***REDACTED***` token means the saved + token, and only when the on-screen URL normalises to the saved URL. + - `POST /api/setup/lightnvr/cameras` takes `{api_url, token}` and returns + `{cameras:[{camera_uuid,name}]}` from `provider.list_cameras()`. It + answers `502` with a plain message when the list cannot be read. + - `GET /api/diagnostics/source` returns `SourceDiagnostic{status, + latest_observation, skipped_workflows[]}`. The skipped workflows come + from `capability_gate.recorded_capability_skips()`. + - **Check codes** (`app/services/source_setup_service.py`): + - `api_access`: + - from the capabilities GET: `unreachable`, `not_configured`, + `unauthorized` (401), `forbidden` (403), `not_supported` (404), + `http_error`, `invalid_document` (rejected by + `apply_capabilities`); + - `history_not_authorized`; + - `camera_forbidden` (cameras in `values.cameras`): the contract setup + probe `GET /api/observations?camera_uuid=X&limit=1`, run per selected + camera (up to 10, or once unfiltered), answered 403. It is an error, + never an empty page. + - `event_connectivity`: + - `broker_not_configured`; + - `broker_unreachable`, from `mqtt_service.test_connection` with the + shared `frigate.mqtt_*` broker; + - `topic_unchecked` (warning; the API failed); + - `lightnvr_mqtt_disabled`; + - `topic_unknown` (warning); + - `topic_prefix_mismatch` (values `configured` and `advertised`). The + advertised prefix is `delivery.topic` before + `/v1/events//`. + - `evidence_access`: + - `snapshot_not_supported` or `snapshot_not_authorized`, read from the + raw `operations["observations.snapshot"]`; + - `cameras_without_photos` (warning): a selected camera that is absent + from `cameras[]`, has `evidence_enabled` false, or lacks + `snapshot.create`. + - `features` is always `ok` (`listed`). Unsupported features never fail + the test. + - `ok` means `api_access` is ok and no result is an error. + - **Latest eligible observation**: + - lightNVR has no newest-first order, so the search widens `since` + windows (1 h, 24 h, 7 d, 30 d). It reads up to 3 pages each at + `history_page_max` through `provider.history()` and takes the newest + `observed_at` in the first non-empty window. + - The labels are `frigate.ingest_labels`, at most 8, without commas. + - The search covers only cameras whose probe passed. + - `state` values: + - `found`, with `photo_state`: ready, pending, missing (lightNVR + `unavailable`, with `photo_reason`) or expired; + - `none_yet`; + - `error`: `unreachable`, `forbidden`, `refused` or `invalid_filter`; + - `not_checked`. + - **UI**: + - New `components/settings/SourceSettings.svelte`, the top card on the + Connection tab: + - a Frigate/lightNVR radio that binds `lightnvr_enabled`; + - address, token (`SecretInput`) and topic prefix fields; + - the bird-capable detector note (`data-source-bird-detector`); + - a test button that opens `DiagnosticDialog` with the 4 checks plus a + latest-detection stage (`none_yet` counts as passed); + - a camera picker (`data-lightnvr-cameras`) that keeps chosen-but- + unlisted UUIDs visible; + - a restart note (`data-source-restart`) for an unsaved change or a + saved-but-not-running source. + - `Settings.svelte` loads and saves `lightnvr_*`, sends the token only + when typed, includes it in the dirty checks, and fetches + `/api/setup/source` with each settings load. + - `ConnectionSettings` (Fetch Video Clips, full-visit) and + `DetectionSettings` (auto video) take an optional `sourceStatus` prop. + An unsupported control is `aria-disabled`, shown unchecked without + changing the saved value, and gets a neutral + `SourceUnsupportedNote` (`data-source-unsupported=event_clip|range_clip| + video_analysis`). An unread status disables nothing. + - New `components/settings/SourceHealthCard.svelte` on the Health page + (`Errors.svelte`). It renders only when lightNVR is active or + configured, so Frigate installs look unchanged. It shows the latest + detection and photo state, says "no bird detections yet" as its own + sentence, and lists unsupported features with their skip counts and + "do not affect health". + - Wizard: + - `ConnectionStep` has the source radio and the lightNVR fields, saves + and then runs the lightNVR test inline. It writes `lightnvr_enabled` + only when lightNVR is chosen or was saved, so the Frigate payload is + unchanged. + - `CamerasStep` uses lightNVR discovery and saves + `lightnvr_camera_uuids`. + - `ReviewStep` knows `connection_lightnvr_ready`, `lightnvr_url` and + `lightnvr_token`. + - `setup_state_service`: with lightNVR enabled, the connection section + needs `api_url`, the token and the MQTT broker (no Frigate URL), and the + cameras section counts `camera_uuids`. + - The `settings.source.*` i18n block is in all 9 locales; French uses + U+202F before `:`. + - The test-only `browser-tests/settings-page-mock.ts` `openSettings` now + takes a third `handlers` map. Playwright gives the last-registered route + priority, so extra `page.route`s registered before `openSettings` are + shadowed by its catch-all. + - **Restart, not rebuild**: changing the source or the lightNVR settings + still needs a restart (W5's design). The UI says so, and no hot provider + rebuild was added. + - **Gotchas for W9/X1**: + - The connection test builds its own `LightNvrProvider` per call + (`source_setup_service.make_lightnvr_client` is the patch point for a + mocked transport). `probe_mqtt_broker` is the broker patch point. The + active provider is never touched, except `/api/setup/source`, which + lazily runs `refresh_capabilities()` once. + - `/health` does not read capability skips. W8 adds a test that recorded + skips leave `build_health_payload()["status"]` unchanged. Keep it that + way. + - A9 UI half: the browser fixture serves a snapshot-only `SourceStatus` + through the `handlers` map. W9's lightNVR-kind acceptance can reuse + `SNAPSHOT_ONLY` from `source-selection.spec.ts`. + - The latest-observation search is bounded (at most 12 history requests) + and approximate when a window has more than 3 pages. That is fine for + a diagnostic; do not reuse it for recovery. + - Live MQTT delivery is not observed end to end: event connectivity means + the broker accepts a session and the prefixes agree. X1 should prove an + actual `observation_changed.v1` arrives. + - Shared worktree: W6 and W7 had unstaged hunks in the same + `locales/*/settings.json` and `docs/api.md`. W8's index blobs were built + as HEAD plus the W8 block (`hash-object` + `update-index --cacheinfo`), + and their hunks were left unstaged in the tree. +- **files edited/created**: + - YA-WAMF `backend/app/services/source_setup_service.py` (new) + - YA-WAMF `backend/app/routers/setup.py`, `backend/app/routers/diagnostics.py`, `backend/app/services/setup_state_service.py` + - YA-WAMF `backend/openapi.json`, `apps/ui/src/lib/api/generated/openapi.ts` (regenerated) + - YA-WAMF `backend/tests/test_lightnvr_setup_api.py` (new), `backend/tests/test_setup_state.py` + - YA-WAMF `apps/ui/src/lib/api/source.ts` (new), `apps/ui/src/lib/api/settings.ts` + - YA-WAMF `apps/ui/src/lib/utils/source-features.ts` and `source-features.test.ts` (new) + - YA-WAMF `apps/ui/src/lib/components/settings/SourceSettings.svelte`, `SourceHealthCard.svelte`, `SourceUnsupportedNote.svelte` (new); `ConnectionSettings.svelte`, `DetectionSettings.svelte` + - YA-WAMF `apps/ui/src/lib/components/setup/ConnectionStep.svelte`, `CamerasStep.svelte`, `ReviewStep.svelte` + - YA-WAMF `apps/ui/src/lib/pages/Settings.svelte`, `apps/ui/src/lib/pages/Errors.svelte` + - YA-WAMF `apps/ui/src/lib/i18n/locales/{de,en,es,fr,it,ja,pt,ru,zh}/settings.json` + - YA-WAMF `apps/ui/browser-tests/source-selection.spec.ts` (new), `apps/ui/browser-tests/settings-page-mock.ts` + - YA-WAMF `docs/api.md`, `changelog.d/lightnvr-setup.md` + +#### W9: YA-WAMF acceptance suite (A4, A9, A10, A1/A3 consumer side) +- **depends_on**: [W3, W6, W7, W8] +- **location**: `backend/tests/test_lightnvr_acceptance.py`, `backend/tests/test_frigate_regression_baseline.py` +- **description**: Combine W0 with fake-lightNVR scenarios driven by the C3 + fixtures and a fake MQTT broker: + - a single bird is classified and labelled with the source camera; + - two birds become two detections with correct crops; + - 10× replay plus overlapping recovery gives one detection and one intent; + - a snapshot-only source runs no clip jobs; + - every Frigate regression case passes post-migration. +- **validation**: The full `pytest`, `npm run check` and `npm test` pass on + the branch. +- **status**: Completed (YA-WAMF commits `1cbc05c0` acceptance suite and + `3d89116a` Health recovery status and Scan History wording, on + `feature/lightnvr-provider` after W10's `de28e1e4`; not pushed) +- **log**: + - **Harness**: a `FakeBroker` replaces `aiomqtt.Client` in the real + `MQTTService`. It replays the vendored C3 envelopes byte for byte, one + list per connection, so a dropped session reconnects, with hooks between + deliveries. The rest is reused by module attribute: + `test_lightnvr_ingestion.pipeline` (A1, A3, A9), + `test_source_recovery.recovery` (A4), the W0 baseline's `FakeFrigate` + and pipeline (A10), and `test_lightnvr_setup_api.nvr/broker` (A9 setup). + - **A-ID → test** (`backend/tests/test_lightnvr_acceptance.py`): + - A1 `test_a1_one_bird_published_over_mqtt_…`: + - pending → ready → classification; + - a reconnect redelivery plus a stream rename keep the camera-UUID + identity; + - the crop is a 1.1× square centred on the box; + - one notify; zero Frigate calls; only `live_topics()` is subscribed. + - A3 `test_a3_two_birds_in_one_frame_…`: + - 3× sibling replay gives 2 detections; + - each crop contains its own box and misses the sibling's; + - classifier input and cached photo centre colours match each bird; + - both share one `frame_sha256`, with at most 2 snapshot reads; + - one visit with `capture_count` 2 and no `bird_observations` rows. + This confirms W4's deviation, which is kept: no index-0 rows. + - A4: + - `test_a4_ten_mqtt_replays_overlapping_api_recovery_…`: 12 sightings + × 10 deliveries over 2 connections, with a real `run_once` started on + each connect, give 12 detections, 12 classifier calls and 12 intents. + Each is `dispatch/sent` or `suppressed_recovery/None`, and the + notified keys equal the dispatched keys. + - `test_a4_a_live_sighting_replayed_ten_times_…`: exactly one + `dispatch/sent` and one notify. + - A9 backend: + - `test_a9_a_lightnvr_source_classifies_from_its_photo_…`: + - classification comes from the photo; there are no + `processing_job_state` rows, and the HQ, full-visit and video + schedulers are never called; + - lightNVR requests are exactly capabilities, the observation and + the snapshot, with no `/api/events/`; + - direct HQ, full-visit and video calls refuse before any request; + - `source_status` reports `history` and `snapshots` only; + - `/health` status is unchanged by the recorded skips. + - `test_a9_setup_completes_for_a_snapshot_only_lightnvr_…`: all 4 + checks are ok and the video features are named unsupported. + - A9 browser: + - W8's clip and full-visit cases; + - new `source-selection.spec.ts` "a snapshot-only source disables auto + video analysis…": Auto Video Analysis is `aria-disabled` and + unchecked, the note is info-toned, and a forced click writes nothing. + - A10 `TestFrigateHistoryFromThePreviousRelease` (3 tests): Alembic + upgrades a DB to `c1d734b9e201`, inserts with the v2.23.1 statement + plus a favourite, then upgrades to head. Startup binds the row to + `frigate.test:5000`. `update`/`end` refine that one row without + reclassifying; a false positive hides it with zero Frigate calls; a new + event gets the same identity. The full A10 gate stays + `test_frigate_regression_baseline.py`. + - **Mutation evidence** (scratch worktree at HEAD, each reverted): + - A1/A3: + - `source_camera_id=camera_name` fails A1 and A3; + - `CROP_CONTEXT` 1.1 → 2.5 fails A1 and A3; + - classifying the whole frame fails A1 and A3; + - caching the whole frame instead of the crop fails A1, A3, both A4 + tests and A9. + - A9: `source_allows` always True fails the A9 backend test. Browser: + DetectionSettings `disabled={false}` fails the new auto-video case. + - A10: skipping the configured-instance bind fails 2 of the 3 A10 tests. + The false-positive test does not assert identity. + - A4: single-layer removals survive, by design. These were removing the + ledger screen, the DB already-admitted check, the `was_inserted` guard, + or the orchestrator outcome re-check; replays never get past the first + two. With the ledger and the pre-check both removed, both A4 tests fail + (plus A1/A3). With all four removed, the exactly-once test fails on + "Expected mock to have been awaited once. Awaited 10 times." + - Flake check: A4 passed 15/15 consecutive runs. + - **UI follow-ups** (RED first): + - `source-features.test.ts` (5 new cases) failed on the missing + `recoveryNotice`. In `source-recovery.spec.ts` the gap, waiting and + lightNVR Scan History cases failed before the change, and Scan History + failed again against HEAD's DataSettings. + - **Health card**: `SourceHealthCard` fetches + `GET /api/backfill/source-recovery` itself instead of adding a field to + `/api/diagnostics/source`, so no OpenAPI regeneration was needed and + W10's diagnostics hunks were not touched. It shows + `data-source-recovery=gap|waiting|stopped|caught_up`: + - `gap` (`recovery_required`) is an amber notice with an Acknowledge + button (`POST …/acknowledge`, and the returned status replaces it); + - `last_error` `retry:`/`source_unreachable:` show as waiting, and + `source_refused:`/`failed:` as stopped, with "Reason: "; + - none of these is a `role=alert`, and an unreadable status hides only + this block. + - **Scan History**: `backfill_desc` and `backfill_scanning` take + `{source}` (`DataSettings` gets `sourceKind` from Settings) in all + 9 locales. The new `settings.source.health.recovery_*` keys are in all + 9 locales too; French uses U+202F before `:` and `;`. + - **Bugs found**: none in shipped code. One W6 limitation is noted for X1: + `get_status()` reads only the cached `source_cursors` row, which is loaded + once a run resolves the provider identity. After a restart with lightNVR + unreachable, a sticky `recovery_required` is therefore not shown (in + `/health`, the API or the card) until lightNVR answers. + - **Gates**: + - Full backend suite on the exact commit content: 7987 passed, + 60 skipped, 0 failed, on W10's HEAD plus the acceptance file. A run on + the pre-W10 HEAD also passed, with 0 failures in 7976. + - `ruff check` and `ruff format --check` on the new file. + - `npm run check`: 0 errors, 0 warnings. + - `npm test`: 2358 passed. + - `npm run lint:dead` clean; `docs_consistency_check` and + `assemble_changelog` (9 fragments) clean. + - Playwright chromium full suite: 648 passed, 2 skipped. + - **Gotchas for X1**: + - The Scan History card is inside the collapsed "Backup & maintenance + tools" `AdvancedSection` on the Data tab; expand it first. + - The `openSettings` catch-all answers `{}` for unmocked paths. The card + treats a body without `state` as no recovery data. + - A fake broker must let in-flight dispatch tasks settle + (`service._in_flight_tasks`) and join the dispatcher queue before + asserting. The W0 `asyncio.sleep` patch shortens reconnect backoff. + - A4 end to end: on a first start, recovery begins at `head` (W6). + Seed a cursor or use Scan History so recovery overlaps live replays. + - The A10 previous-release fixture shells out to `alembic`, so it needs + `PATH=.venv/bin:$PATH`, as the W0 migration tests do. + - Commits: W10 committed mid-task. W9 commits were built with + `hash-object` + `commit-tree` + compare-and-swap `update-ref HEAD + `, so a concurrent commit cannot be overwritten. +- **files edited/created** (YA-WAMF): + - `backend/tests/test_lightnvr_acceptance.py` (new), + `apps/ui/browser-tests/source-selection.spec.ts` + - `apps/ui/browser-tests/source-recovery.spec.ts` (new), + `apps/ui/src/lib/api/source.ts`, + `apps/ui/src/lib/utils/source-features.ts` and `source-features.test.ts`, + `apps/ui/src/lib/components/settings/SourceHealthCard.svelte`, + `DataSettings.svelte`, `apps/ui/src/lib/pages/Settings.svelte` + - `apps/ui/src/lib/i18n/locales/{de,en,es,fr,it,ja,pt,ru,zh}/settings.json` + - `changelog.d/lightnvr-recovery-health.md` (new) + +### P1 — Cross-repo gates + +#### X1: End-to-end P1 suite and A14 pilot recipe +- **depends_on**: [L11, L12, W9] +- **location**: `tests/integration/yawamf_e2e/` in lightNVR (docker-compose: mosquitto + lightNVR + YA-WAMF branch image + fixture RTSP via go2rtc), plus the README +- **description**: Run the documented recipe (L12) verbatim with a recorded + feeder fixture and a pinned bird-capable detector model: + - A1: identity and labels. + - A2: moving-bird alignment, for local and HTTP detectors. + - A3: two birds. + - A4: replay ×10. + - A5: broker stopped for 100 observations, then both apps restarted. + - A6: kill points. + - A8: expired, unavailable and temporary outcomes, end to end. + + Record the tested source and model versions into the supported-version + matrix. A14 is the operator pilot on a real camera using this recipe; it is + a manual checklist that records outcomes. +- **validation**: The compose suite passes in CI or nightly. The A14 checklist + is completed and committed with versions. +- **status**: Completed for the automated suite. lightNVR commit `def9367c`; + YA-WAMF fixes `b89846d0`, `fcf449f2`, `37c0877f` and `1cbbae74`. Nothing + is pushed. The validation is only partly met: the compose (container) mode + has not run in CI, and A14 is a checklist still open for an operator. +- **log**: + - **Mode executed: host processes.** Docker exists, but X1 ran real + Mosquitto 2.0.22, the `build-int01-X1` lightNVR binary (built from + `e294f956` with the L14 options: MQTT+SOD on, LiteRT off, the apt FFmpeg + header symlink dir) and the YA-WAMF backend from its venv, all as host + processes driven by `run_e2e.py`. The `compose/` Dockerfile and + docker-compose file (one container that owns every process, because the + suite SIGKILLs and SIGSTOPs them and reads both DBs) are committed as the + CI path but **were not built or run**. + - **Pinned models**: + - Detector: loopback HTTP `x1-colour-detector 1.0`, in the + light-object-detect format. It finds the red and blue fixture squares + in the submitted JPEG and reports them only for the frames a scenario + arms. + - Optional A2-local: the real SOD VOC `tiny20.sod` (local file) on a + real cardinal photo. The photo is from Wikimedia, CC BY-SA, so it is not + committed; the README gives its URL and SHA-256. + - YA-WAMF classifier: `x1-colour-rule-v1`. The launcher replaces only + `ClassifierService.classify_async_live`: ≥10% saturated-red pixels is + Prunella, otherwise ≥10% blue is Erithacus, otherwise no result. + - The real YA-WAMF model and a real HTTP or LiteRT bird model are still + pending, and the matrix says so. + - **Results** (`results/x1-host-20261108.json`, 286 s, all pass against + YA-WAMF `1cbbae74`): + + | A-ID | Result and evidence | + | --- | --- | + | A1 | 1 observation. `created`/`pending`, then `evidence`/`ready`, on the real broker with the UUIDv5 message IDs. 1 row `lnvr::` with the camera UUID and Prunella 0.93, from 1 classifier call. | + | A2 | HTTP, 3 frames: each snapshot is byte-equal to a detector input and its box equals the detector box. Crops taken across frames miss the bird. Local SOD: IoU 0.50–0.53 with the bird found in the stored image, origin `inference_input`, transform `resize` 640×360→416×416. | + | A3 | 2 siblings share one asset; the red box is identified as Prunella and the blue as Erithacus, from 2 classifier calls. | + | A4 | 60 replays of 6 captured messages with backfill overlapping. Still 3 rows, 0 extra classifier calls, intents unchanged, backfill `new_detections: 0`. | + | A5 | 100 observations with the broker down: the outbox held 200 pending deliveries. After restarting the broker, lightNVR and YA-WAMF, all 100 rows appeared within 6.6 s, all 200 revisions were delivered, and intents were `suppressed_recovery` ×100. | + | A6 | lightNVR SIGKILL after 4 frames: startup order held, no pending asset or tmp file, ready rows equal files, all 24 converged 12 s after restart. YA-WAMF SIGKILL mid-burst: 44/44 rows, no duplicates, converged in 4 s. | + | A8 | Temporary: lightNVR SIGSTOPped while YA-WAMF started, so 0 rows and status `unreachable`; after SIGCONT, converged in 16 s. Expired: `410 evidence_expired`. Unavailable: `404 evidence_unavailable(write_failed)`. Recovery outcomes and backfill report both reasons and admit neither. Restore: the old cursor gets `410 cursor_expired`, YA-WAMF shows `recovery_required`/`cursor_expired` plus a `source_recovery` diagnostics event, and acknowledging clears it. | + + - **Bugs found and fixed** (all in YA-WAMF, RED first, no trailer): + - `b89846d0`: YA-WAMF did not start with `LIGHTNVR__CAMERA_UUIDS` set as + documented. pydantic-settings JSON-decoded the list (`Extra data`). + Fix: `Annotated[list[str], NoDecode]`. Pure test with two UUIDs. + - `fcf449f2`: after recovery skipped an observation for an expired or + unavailable photo, a replay or Scan History saw it as a duplicate or + stale revision and counted it as "already existed" (136 vs 132 real + rows). Fix: the ledger keeps the skip decision for expired, + unavailable and missing observations. + - `37c0877f`: a live message that hit an unreachable lightNVR (the A6 + SIGKILL) was retried only at the 300 s recovery interval; the pre-fix + run, kept as `results/x1-host-20261108-prefix-a6.json`, took 297 s. + Fix: a live RETRY requests recovery. + - `1cbbae74`: a follow-up to the previous fix. A burst of live retries + woke recovery once each, and every held-up run doubled the backoff + (30→120 s, measured at 121 s). Fix: live retries do not cut a scheduled + backoff short and are folded into a held-up run. + - YA-WAMF gates: full backend 7995 passed / 60 skipped; ruff clean; + `export_openapi` and type checks up to date; the changelog fragments + are well formed. The UI was untouched, so `npm` checks were not rerun. + - No lightNVR product bug. lightNVR C code is untouched, so `rust/` is + unaffected. + - **Recipe corrections** (`docs/integrations/YA_WAMF.md`): + - the `Feeder East` example name is invalid (only `[A-Za-z0-9._-]`); + - PUT `/api/streams` answers 202 and applies asynchronously; + - role and policy writes need `expected_policy_version`, now with a + role body; + - `detection_threshold` in the stream API is a percentage; + - the minimum YA-WAMF commit for `LIGHTNVR__CAMERA_UUIDS`; + - status and matrix rewritten, and contract §18 rows updated. + - **Pending**: the container mode in CI; a real HTTP bird model and + LiteRT end to end; YA-WAMF's real classifier; A14 + (`A14_PILOT_CHECKLIST.md`, unchecked). + - Run: + `../YetAnother-WhosAtMyFeeder/backend/.venv/bin/python tests/integration/yawamf_e2e/run_e2e.py --lightnvr-bin build-int01-X1/bin/lightnvr [--sod-model ~/tiny20.sod --bird-image cardinal.jpg]` + - **Gotchas for X3**: + - Mosquitto runs as `mosquitto -p `. Ubuntu's AppArmor profile + blocks configs outside `/etc/mosquitto`. + - `mosquitto_sub` exits when the broker stops, so resubscribe after a + restart. + - Use `detection_threshold: 50`, not `0.5`. + - YA-WAMF ignores live messages until its capabilities fetch succeeds. + When it starts with lightNVR down, recovery catches up. + - The YA-WAMF connection test needs `api_url` in the body. + - YA-WAMF writes a `speciesid.pre-migration-*.db` copy on every start. + - Both sides back off MQTT reconnects for about 35 s. + - The suite's `Detector.arm()` and the `LightNvr`/`YaWamf` start, stop, + kill and SQL helpers can be reused for A12/A13: start YA-WAMF with + `LIGHTNVR__WRITEBACK_ENABLED=true` and read + `observation_classifications` directly. +- **files edited/created**: `tests/integration/yawamf_e2e/{README.md, + A14_PILOT_CHECKLIST.md, run_e2e.py, harness/components.py, + harness/yawamf_launcher.py, compose/Dockerfile, compose/docker-compose.yml, + results/x1-host-20261108.{json,mqtt-capture.jsonl,detector.jsonl,classifier.jsonl}, + results/x1-host-20261108-prefix-a6.json}` (new), + `docs/integrations/YA_WAMF.md`, `docs/integrations/OBSERVATIONS_CONTRACT.md`; + YA-WAMF `backend/app/config_models.py`, + `backend/app/services/sources/observation_admission.py`, + `backend/app/services/source_ingestion.py`, + `backend/app/services/event_processor.py`, + `backend/app/services/source_recovery_service.py`, + `backend/tests/pure/{test_lightnvr_parse,test_observation_admission,test_source_live_dispatch}.py`, + `changelog.d/lightnvr-{camera-uuids-env,backfill-lost-photo-reasons,live-retry-recovery}.md` + +#### X2: Saturation and performance release gate (A11) +- **depends_on**: [C4, L6, L7, L10] +- **location**: `tests/performance/observations/` +- **description**: + - Rerun the C4 workload with evidence enabled on every stream at maximum + cadence. + - Saturate the queue and byte budgets. Assert that limits hold, drops carry + reasons, there are no new recording gaps, p95 recording-path latency + rises by no more than 5%, and producer enqueue never waits on I/O + (submit-latency histogram). + - Record memory, CPU, disk and queue high-water marks. + - Finalize the C1 provisional defaults from the results. If the threshold + fails, reduce admission (lower the per-camera cap, the queue bytes or the + batch commit rate) and rerun. +- **validation**: A committed results JSON meets the thresholds, and the C1 + defaults are updated from "provisional" to final. +- **status**: Completed. The gate passes under C4's rule (DB probe advisory). + The DB-probe tail rise is an open finding. Commits: `9ed02bb4` (product fix + plus harness) and `66270dd6` (results plus docs). Nothing is pushed. +- **log**: + - **Setup.** + - Build: `build-int01-X2`, Release, from HEAD (LiteRT on, MQTT and SOD, + `BUILD_TESTS=ON`, `PKG_CONFIG_LIBDIR` set to the apt pkgconfig dirs). + `CMAKE_C_FLAGS=-I/ffinc` points at the apt FFmpeg header + symlink dir. + - Clip: `make-bird-clip.sh`. The pinned cardinal (sha `85bf66b1…`, the + same as X1) bounces across a textured 1280×720 background at 15 fps + with a GOP of 15. The clip is deterministic (sha `5bf26bbc…`). The + photo is CC BY-SA and is not committed. + - Every SOD inference detected `bird`: 2,404–2,408 detections per 600 s + window, written to the DB. + - **Product bug found by the gate, RED then GREEN, fixed in `9ed02bb4`.** + - In a saturated smoke run, 10 of 120 submits took 1–4.4 ms. Temporary + phase timing (not committed) showed the time was in `free()` of the + rejected 2.7 MB RGB frame on the detection thread (an munmap, 1.5–2.6 + ms). That breaks L3's "never blocks, under 1 ms" contract. + - Fix: a tracked drop hands the frame to the worker through the drop + record. The worker frees it. Pending deferred frames are bounded by + max(global queue bytes, 32 MiB); past that bound, or when the ring is + full, the frame is freed inline. + - RED: + - `test_rejected_frames_are_released_by_the_worker` and + `test_drop_ring_overflow_counts_untracked` failed on the + deferred-frame counters (expected 12 and 256 deferred, got 0). + - The new stats fields first failed to compile in + `test_observation_evidence` and `test_observation_diagnostics`. + - GREEN: + - In `build-int01-X2`: `test_observation_evidence` 16/16 (3 runs), + diagnostics 5/5, `test_api_handlers_system` 32/32, + `test_detection_observation_evidence` 5/5, + `test_api_detection_observation_evidence` 7/7 and + `test_observation_crash_boundaries` 6/6. + - In the new `build-int01-X2-asan` (Debug, ASan/UBSan, LiteRT off): + evidence, diagnostics, crash boundaries and detection evidence are + clean. + - Afterwards, the smoke run's window submits were all ≤ 50 µs. + - **New observability (contract §15):** + - `lightnvr_observation_submit_latency_seconds` histogram (1 µs … 10 ms + buckets) and `_max_seconds`; + - `lightnvr_observation_evidence_limit_items{scope}` and + `_limit_bytes{scope=global|camera|discard}`; + - `_camera_high_water_items`; + - `_discards_total{mode=deferred|inline}` and `_discard_pending_bytes`. + - **Harness additions:** + - `run-workload.mjs --system-setting K=V`: writes the row, then restarts + lightNVR once (the queue bytes are read only at startup). + - `results.feature_metrics` and a post-shutdown `results.feature_db` + (read with `node:sqlite`). + - `metrics.mjs`: `evidenceGate`, `observationFeatureSummary`, + `assetReasonSummary`, the histogram helpers and `pooledComparison`. + RED was missing exports; the tooling tests are now 67/67. + - `compare.mjs gate`: requires the same binary and runs the evidence + checks. Also `--advisory KEY`, `--require-saturation`, + `--max-submit-seconds`, and a `pool` command. + - Showing the gate can fail: `gate … --max-pct -1` and + `--max-submit-seconds 0.00001 --require-saturation` both exit 1. The + exit codes are recorded in `results/2026-11-08-x2-gate.json`. + - **Runs.** Seven runs of 600 s, each with 30 s warm-up, all on binary + `9ed02bb4` and the same clip. + - Order: disabled-run1, enabled-defaults, enabled-saturated (4 MiB + queue), disabled-run2, disabled-run3, enabled-defaults2, + enabled-saturated2. + - Load average: 2.8–7.8. Present on the host: the user's lightNVR, + uvicorn, Mosquitto, a `llama-server` using one core, Chrome and other + desktop work. disabled-run1 (end), both first enabled runs, and + enabled-defaults2 (start) ran at load about 7; the rest at about 3. + - **Results (disabled → defaults / saturated):** + - Recording gaps: 0 everywhere. + - `segment_interval_ms` p95: 10008.9–10016.1 → 10008.5–10013.5 / + 10009.3–10012.9. + - Accepted, dropped, ready: + - defaults: 2,407–2,408 accepted, 0 dropped, all ready; + - saturated: 1,063–1,073 accepted, 1,331–1,341 `queue_full`, 0 + untracked; the DB holds 1,395–1,408 `unavailable:queue_full` rows. + - High-water marks: + - items/bytes: 4 / 11.1 MB of 64 / 32 MiB (defaults); 1 / 2.77 MB of + 64 / 4 MiB (saturated); + - per camera: 1 of 8; + - drop ring: 2 of 256; + - deferred discards pending: 5.5 MB. + - Submit latency: mean about 4.3 µs, p99 bucket ≤ 50 µs, run maximum + 341–405 µs. + - CPU: 149–156% → 154–158% / 151–155% of one core. + - RSS high-water: 873–888 → 891–892 / 882–929 MB. + - Disk writes: 2.06 → 3.3 / 2.9 MB/s. + - Relay lag: ≤ 2 ms. + - No encode, write or DB failures. + - **Gate verdict.** + - With `--advisory latency.db_probe_ms.p95`, all four pairs pass: + run1 against defaults and saturated, and run3 against defaults2 and + saturated2. + - The DB probe fails its own repeatability (disabled pairs 2.61%, 5.03% + and 2.42% against the 2% criterion). Following C4's handoff rule it is + advisory, and §13 and §19 record that decision. + - Strict per pair: +13.4% and +8.8% (the first pair, under load) and + +4.4% and +2.2% (the second pair). + - **Pooled DB-probe p95: +9.9% (defaults) and +6.5% (saturated)**, with + the median unchanged. This is an open finding, not attributed to noise. + The likely cause is WAL autocheckpoints inside commits that hold the + global DB mutex, driven by the worker's extra writes. + - Reducing admission, as the plan suggests, cannot fix it: drops still + insert `unavailable` rows, and a 56% cut in admission only moved + +9.9% to +6.5%. So the admission defaults were kept. + - **Final defaults (contract §13, with an X2 basis column):** + - Global queue: 32 MiB / 64 frames. + - Per camera: 8 frames. + - Drop ring: 256 records. + - Encoder worker concurrency: 1. + - Media: 7 days and 2 GiB per camera (64 MiB..1 TiB). + - History retention: 30 days. + - Relay batch: 64. Outbox share: 2,000. Scan cap: 5,000. + - New derived bound: deferred-release bytes = max(queue bytes, 32 MiB). +- **gotchas / follow-ups**: + - **Follow-up for the DB-probe tail.** Two candidate fixes: checkpoint WAL + outside the global mutex (passive checkpoints on a background + connection, or a raised `wal_autocheckpoint`), or batch the worker's + insert, transition and drop commits per drain. Re-gate with `compare.mjs + pool` over at least 3 runs per mode on a quiet host. + - **Not exercised by X2:** media eviction (about 100 KB JPEGs left at most + 45 MB per camera, under the 64 MiB budget), `budget_exhausted` (needs + protected assets), the relay outbox cap (no broker in the fixture) and + the history scan cap. L4, L11 and X1 cover these. + - **Detection timing:** detection threads sample at the wall-clock second + boundary, so all cameras submit within about 50 ms. That burst is why a + single-frame queue drops more than half of the submissions. + - **Fixture and tooling notes:** + - The fixture's lightNVR has no broker, so publications resolve without + routing and none stay pending. + - The Monitor tool cannot start in this sandbox (seccomp/userns). Use a + background `until grep` wait instead. + - Migration 0095's "Provisional budget bounds" comment was left as it is, + because migration files are treated as immutable. +- **files edited/created**: `include/core/observation_evidence.h`, + `src/core/observation_evidence.c`, `src/web/api_handlers_metrics.c`, + `tests/unit/test_observation_evidence.c`, `tests/unit/test_observation_diagnostics.c`, + `tests/performance/observations/{run-workload.mjs,metrics.mjs,compare.mjs,README.md,make-bird-clip.sh}` (new script), + `tests/performance/observations/results/2026-11-08-x2-{disabled-run1,disabled-run2,disabled-run3,enabled-defaults,enabled-defaults2,enabled-saturated,enabled-saturated2,gate}.json`, + `tests/contracts/tooling/observation-perf-metrics.test.mjs`, + `docs/integrations/{OBSERVATIONS_CONTRACT.md,YA_WAMF.md}`, `docs/DETECTION_ENGINES.md`, + `include/database/db_observations.h` (comment) + +### P2 — Classification feedback + +#### L13: Migration 0096, classification DB layer and authz action +- **depends_on**: [L2] +- **location**: `db/migrations/0096_add_observation_classifications.sql`, regenerated `include/database/db_embedded_migrations.h`, `src/database/db_observations.c` (or a new `db_observation_classifications.c`), `tests/unit/test_db_observation_classifications.c`. The authz action is mirrored in C and catalog order defines the bit, so also append to: + - the enum in `include/core/authorization.h`; + - the catalog in `src/core/authorization.c` (around line 22); + - the OpenAPI action enums in `docs/api/openapi.json` (three places), then + regenerate; + - the token-policy UI and locales (`web/tests/apiTokenPolicy.spec.js`); + - `tests/unit/test_authorization.c`. +- **description**: + - **Table** `observation_classifications`: + - Columns: `observation_uuid`, `key`, `writer_principal`, `classifier`, + `model_version`, `label`, `taxonomy_id`, `confidence`, `classified_at`, + `input_asset_uuid`, `input_revision`, `kind`, `annotation_revision`, + `created_at`, `updated_at`. + - Primary key `(observation_uuid, key)`. + - **Authz action** `observations.annotate`: bit 19, camera-scoped, + Administrator only, same pattern as 0078. + - **DB upsert**: + - Same payload → no-op that returns the current row. + - Different payload → requires `expected_revision == annotation_revision`. + - Reject when `input_asset_uuid` differs from the observation's asset. + - Writer mismatch on an existing key is rejected. + - An `owner_confirmed` row from the owner path cannot be overwritten by + `automated`. + - Each change bumps the observation revision with cause `classification`, + plus the change and publication rows, in the same transaction. + - Original detector fields are never modified. +- **validation**: Unit tests for each rule, plus a migration up/down/up test + where bit 19 does not collide with existing bit indexes. Query + `authz_actions` for the max `bit_index` first; if anything after 0078 used + 19, take the next free bit. +- **status**: Completed +- **log**: + - Commit `3b86a6db`. Before the change, bit 19 was free (the max + `bit_index` was 18, from 0078) and 0096 was the next migration number. + - RED: `test_authorization` failed 2 of 52 (catalog vs DB and the bit + layout), and `test_db_observation_classifications` did not link. + - GREEN in `build-int01-L13` and `build-int01-L13-asan` (ASan + UBSan): + - `test_db_observation_classifications` 12/12 + - `test_authorization` 52/52 + - `test_observation_migration` 4/4 + - `test_db_observations` 15/15 + - `test_sqlite_migration_boundaries` 21/21 + - `test_audit_log` 42/42 + - `test_api_handlers_observations` 18/18 + - `test_db_auth` 36/36 + - Contract tooling: `generate.mjs` and `generate.mjs --check` pass, + `lint.mjs` is valid (only the existing warnings), and the + `tests/contracts/tooling` tests pass 59/59. Web: jest + `apiTokenPolicy.spec.js` passes 16/16. + - **Schema.** Columns follow the contract rather than the plan's + `writer_principal`: + - `writer_namespace` plus `writer_user_id` (D3), with no FK to users. + - Times are `_ms` integers: `classified_at_ms`, `created_at_ms` and + `updated_at_ms`. + - `observation_uuid` has an FK to `object_observations` with ON DELETE + CASCADE, so pruning removes annotations. + - A CHECK requires the key to be prefixed with `writer_namespace/`. + - `model_version` and `taxonomy_id` are stored as NULL when absent (the + API maps "" to null). + - **DB API** (`include/database/db_observation_classifications.h`): + - `db_observation_classification_put(obs_uuid, &input, &result)`: + - The input carries `writer_namespace`, `writer_user_id`, the `name` + segment, the request fields, `kind` and `expected_revision` (0 means + absent). + - The result holds `has_annotation`, the annotation (current or + stored), `observation_revision`, `change_seq`, + `observation_asset_uuid`, `evidence_state` and `limit`. + - `db_observation_classifications_list(obs_uuid, &set)` returns + automated and owner_confirmed lists, each ordered by `updated_at` DESC. + It uses a read-only connection. + - **Rule order:** observation found → writer check → evidence check + (`input_asset_id`, `1 ≤ input_revision ≤ revision`, state ready or + expired) → identical retry → owner lock → CAS → 8-key limit → store, + then bump with `classification` and call `observation_relay_notify()` + after COMMIT. + - **Mapping for L14:** + + | DB result | HTTP response | + | --- | --- | + | `CREATED` | 201 | + | `UPDATED` | 200 `changed:true` | + | `NOOP` | 200 `changed:false` | + | `REVISION_CONFLICT` | 409 `revision_conflict` + `current` when `has_annotation` | + | `OWNER_LOCKED` | 409 `owner_decision_locked` + `current` | + | `EVIDENCE_MISMATCH` | 422 + `observation_asset_id`, `evidence_state` | + | `WRITER_MISMATCH` | 403 `namespace_forbidden` (the annotation is withheld) | + | `LIMIT` | 409 `annotation_limit` + `limit` | + | `INVALID` | 400 | + | `NOT_FOUND` | 404 | + | `ERROR` | 500 | + + The audit `result` strings match one to one. + - **Enforcement flag (deviation from the contract tuple).** The catalog + entry is currently `{..., true, false, false}`, meaning *unenforced*: + per `authorization.h`, an action is not reported enforced until a handler + routes it through the evaluator. As a result, the token UI hides it. L14 + must: + - flip the last field to `true`; + - update `web/tests/apiTokenPolicy.spec.js`, where the catalog entry is + `enforced: false`; + - move the key from `unenforcedKeys` to `enforcedKeys` in + `tests/integration/specs/api-tokens.ui.spec.ts`. + - **OpenAPI.** Only two OpenAPI action enums exist: + - `AuthorizationAction`; + - the `AdminSimulationRequest` camera-scoped action enum. + + The third `lpr.delete` hit is an operation requirement, not an enum. + - **Migration test.** The 0095 up/down test in + `test_observation_migration.c` now migrates through 0095 explicitly, not + to the latest migration. + - **L15 decision: no `observation_uuid` column on `detections`.** L6 is + asynchronous: observation UUIDs are generated by the L3 worker's + `db_observation_insert_batch`, after `store_detections_in_db_for_camera` + has already written the detection rows on a separate path. Populating the + column cheaply is therefore not possible. L15 should join on: + - `detections.camera_uuid = object_observations.camera_uuid`; + - `object_observations.observed_at_ms / 1000 BETWEEN detections.timestamp + - 1 AND detections.timestamp + 1`, because `observed_at_ms` is the + frame second × 1000, or wall-clock ms when that falls in the same + second; + - case-insensitive equal labels. + + Use `idx_object_observations_camera_time`, and pick the nearest + `observed_at_ms` when siblings tie. +- **files edited/created**: + - `db/migrations/0096_add_observation_classifications.sql` + - `include/database/db_embedded_migrations.h` + - `include/database/db_observation_classifications.h` + - `src/database/db_observation_classifications.c` + - `include/core/authorization.h` + - `src/core/authorization.c` + - `docs/api/openapi.json` + - `include/web/openapi_document_generated.h` + - `web/generated/api/schema.d.ts` + - `web/generated/api/validators.js` + - `tests/unit/test_db_observation_classifications.c` + - `tests/unit/test_authorization.c` + - `tests/unit/test_observation_migration.c` + - `tests/unit/CMakeLists.txt` + - `web/tests/apiTokenPolicy.spec.js` + - `tests/integration/specs/api-tokens.ui.spec.ts` + +#### L14: PUT annotation API +- **depends_on**: [L13, L8, L5] +- **location**: `src/web/api_handlers_observations.c`, `docs/api/openapi.json` (remove the P2 stub note, add the authz requirement `observations.annotate`; the action enum already lists it via L13), regenerated projections and `docs/api/contracts/coverage.json`, `tests/contracts/observations.api.spec.ts`, `tests/contracts/permissionCases.ts` +- **description**: + - Replace the 501 stub: parse and bound the body, then authorize with + `observations.annotate` on the capture-time camera. + - Derive the writer namespace from the token or principal; a body claiming + another namespace returns `403`. + - Map the DB outcomes to the C1 error catalog. + - Audit the mutation, without credentials or bytes. + - `GET /api/observations/{id}` includes classifications, with automated and + owner decisions separated. + - Capabilities now advertise `annotate`. +- **validation**: Contract tests for A12 (retry no-op, stale revision `409`, + owner overwrite `409`, evidence mismatch `422`) and A7-annotate (another + camera's observation gives `404`/`403` and no row is written). The MQTT sink + sees a `classification` change whose message ID is stable. +- **status**: Completed +- **log**: + - Commit `9e0e8ecd`. Built in `build-int01-L14` (mirrors `build-int01-L13`) + and `build-int01-L14-asan` (Debug ASan/UBSan, `-DENABLE_LITERT=OFF`). + `libuv_api_handlers.c` did not change, so `coverage.json` did not move + (`coverage.mjs --check` OK). + - **Rule order in the handler** (contract §10.4): + 1. Authenticate, parse the raw path (a malformed ID is `400 field:id`, not + audited), then load the observation. + 2. Evaluate `observations.annotate` on the capture camera with + `httpd_evaluate_camera_identity_action`. On a deny it re-checks + `live.view`: held → `403 forbidden`, otherwise `404 + observation_not_found`, byte-identical to an unknown ID. This runs + before the namespace and body checks, so a hidden observation with a + bad namespace and a broken body is still that same `404`. Holding + annotate without `live.view` is enough to write. + 3. Namespace: `observation_writer_namespace(username)` must succeed and + equal the path namespace. A body `writer_namespace` or `writer` + naming anyone else is `403 namespace_forbidden`; naming the caller is + accepted and ignored. A non-string writer field is `400`. + 4. Body: at most `OBS_PUT_BODY_MAX` (4096) bytes, a JSON object, closed. + Text must be valid UTF-8 without control characters. Optional + `model_version` and `taxonomy_id` may be absent or null, and `""` is + stored as null. `input_asset_id` is lowercased; integers must be + integral and ≤ `INT_MAX`; `expected_revision: null` is `400`. Every + 400 carries `field` (an unknown key names itself; an invalid name + segment gives `field: "name"`). + 5. `db_observation_classification_put`, with the L13 mapping exactly. + `WRITER_MISMATCH` withholds the stored values. + - **Audit**: `audit_log_operation(..., "observations.annotate", + "observation", id, "annotate", outcome, context)`. The context holds + `camera_uuid`, `key`, `kind`, `result`, `annotation_revision_before` and + `annotation_revision_after` (null when unknown). `audit_events.outcome` + only allows `allowed|denied|success|failure|error`, so the contract + result lives in `context.result` and maps to an outcome: + - `success`: created, updated, noop; + - `denied`: forbidden, namespace_forbidden, and the hidden 404; + - `error`: internal_error; + - `failure`: everything else. + + The result set is the §12.2 list plus `forbidden`, + `observation_not_found`, `invalid_request`, `annotation_limit` and + `internal_error`. Contract §9, §11 and §12.2 are updated to match. + - **Reads**: `observation_record_to_json(record, source, set)` gained a + third argument (NULL means empty lists). The new + `observation_classification_to_json()` never emits `writer_user_id`. + GET uses `db_observation_classifications_list`. History pages use the + new `db_observation_classifications_list_many(ids, n, visit, ctx)`, which + uses one read-only connection and one prepared statement per page rather + than one connection per item. + - **Capabilities**: `observations.annotate` is `supported: true`. + `authorized` requires both a writer namespace and the action on at least + one camera. Otherwise `reason` is `reserved_writer_namespace` or + `invalid_writer_namespace` (these win) or `not_authorized`. `cameras` + now includes cameras where only annotate is held, and `actions` appends + `observations.annotate`. + - **Authz**: the catalog entry is now `{..., true, false, true}` + (enforced). `test_authorization` asserts this. + `web/tests/apiTokenPolicy.spec.js` and + `tests/integration/specs/api-tokens.ui.spec.ts` now list it as enforced + (the checkbox count went from 16 to 17). + - **OpenAPI**: the PUT is camera `observations.annotate` (raw). The `501` + and the P2 note are gone, and the 403/404/409/422 descriptions are + tightened. The `not_implemented` code and the + `not_available_in_this_version` reason stay in the enums (the P1 + capability fixture uses them). `observations-contract.test.mjs` now + expects no `501` on any operation and checks + `validateObservationClassificationWrite`. + - **RED→GREEN**: + - RED: with the extended tests and only the signature change, + `test_api_handlers_observations` failed 11/25. The failures were the + serializer against the annotated fixtures, both capabilities tests, + and every PUT test (`501`). + - GREEN: 25/25 in both builds, ASan and UBSan clean. + - Also green in both builds: `test_authorization` 52, + `test_db_observation_classifications` 12, `test_api_handlers_openapi` + 8, `test_openapi_projection` 21, `test_db_observations` 15, + `test_event_envelope` 13, `test_observation_relay` 13, `test_audit_log` + 42 and `test_db_auth` 36. `test_authorization_snapshot` 10 passes in + `build-int01-L14`. + - **Unit coverage**: + - C3 fixtures 01–05 run as one chain. Fixture IDs are mapped to the seeded + ones; server `created_at`/`updated_at` are checked, then aligned, so + the rest is exact `cJSON_Compare` equality. + - Fixtures 06–08: no rows, no revision bump and no publication. + - The serializer is exact against all 10 observation fixtures, including + the classified and owner-confirmed ones. + - A7 denial matrix: 404 identical to an unknown ID, 403, annotate-only + allowed, and an evaluation failure is 500. + - Namespace cases: reserved, invalid, body writer fields, and D3 reuse by + user id 43. + - 34 body-field cases plus whole-document, oversize and name cases. + - The absent-key CAS (`409` without `current`), a 200 update and the + 9th-key `annotation_limit`. + - GET and list return the annotations. + - The staged publication has cause `classification` and message ID + `observation_message_id(source, id, 3)`. Envelopes built twice match, + and a no-op retry stages nothing. + - Audit context fields are checked, and the body never appears in them. + - **Playwright** (runnable locally): new + `tests/contracts/observations.api.spec.ts`, 3/3 with + `LIGHTNVR_CONTRACT_BIN=build-int01-L14/bin/lightnvr + LIGHTNVR_CONTRACT_MQTT=1 npx playwright test -c + tests/contracts/playwright.config.ts --project=api + tests/contracts/observations.api.spec.ts`. Against the old + `build-int01-L8` binary it fails 3/3. + - It seeds ready observations by direct SQL into the isolated fixture DB. + - It covers A12, and A7-annotate with a policy user `yawamf`, the fixture + viewer, and scoped tokens with and without the action. + - It covers the MQTT case: a real sink receives exactly one + `classification` envelope whose `id` equals the §5.4 UUIDv5, and a + no-op retry adds nothing. + + `discovery.api.spec.ts`, `baseline.spec.ts` and + `eventsDelivery.api.spec.ts` still pass (18/18). `permissionCases.ts` + needed no change. + - **Other validation**: `generate.mjs` and `--check` pass; `lint.mjs` is + valid with the same 5 warnings; `node --test + tests/contracts/tooling/*.test.mjs` 59/59; `tsc -p + tests/contracts/tsconfig.json` is clean; jest `apiTokenPolicy.spec.js` + 16/16. + - **Gotchas for later tasks**: + - **L15**: read annotations from `GET /api/observations/{id}` (or a + history page), never from the PUT. Each list is already ordered by + `updated_at` desc. Show `owner_confirmed[0]` over `automated[0]`, and + keep the top-level `label`/`confidence` (the detector's) visible. + `observation_classification_to_json()` is the C serializer if a + detection API join needs it. + - **W10**: read `principal.writer_namespace` and + `operations["observations.annotate"].authorized` from capabilities. + The recipe user needs a role with `observations.annotate`. Built-in + Administrator has it; nothing else does by default, and legacy + user/viewer roles never do. A scoped token must list the action + explicitly. A `403 namespace_forbidden` on an existing key means + another user id created it, which is permanent. A `409 revision_conflict` + without `current` means `expected_revision` was non-zero on an absent + key. A retry after a lost response is a `200 changed:false` whatever + `expected_revision` says. + - **L11 / X3**: `observations.api.spec.ts` seeds rows by SQL, bypassing + L3, and its MQTT case is skipped unless `LIGHTNVR_CONTRACT_MQTT=1`. + L11 may fold it into the broader suite. Observation events need a + route listing `io.lightnvr.detection.observation_changed.v1` with no + label predicate (L5 gotcha). Classification events carry the + detector `label`, not the species. + - **Audit consumers**: filter on `action = observations.annotate` and read + `details.context.result`. The `outcome` column is the coarse 5-value + enum. +- **files edited/created**: + - `src/web/api_handlers_observations.c`, `include/web/api_handlers_observations.h` + - `src/database/db_observation_classifications.c`, `include/database/db_observation_classifications.h` (`_list_many`) + - `src/core/authorization.c`, `tests/unit/test_authorization.c` + - `docs/api/openapi.json`; generated `include/web/openapi_document_generated.h`, `web/generated/api/schema.d.ts` + - `docs/integrations/OBSERVATIONS_CONTRACT.md` (§9, §11 and §12.2 only) + - `tests/unit/test_api_handlers_observations.c`, `tests/unit/CMakeLists.txt` (`--wrap=audit_log_operation`) + - `tests/contracts/observations.api.spec.ts` (new), `tests/contracts/tooling/observations-contract.test.mjs` + - `web/tests/apiTokenPolicy.spec.js`, `tests/integration/specs/api-tokens.ui.spec.ts` + +#### L15: Minimal classification display in lightNVR +- **depends_on**: [L14] +- **location**: the detection-detail surfaces that already render detections: `web/js/components/preact/timeline/TimelinePlayer.tsx`, `web/js/components/preact/recordings/recordingsAPI.tsx`, `web/js/types/timeline.ts`, `web/js/types/core.ts`; i18n strings; web tests +- **description**: + - Where a detection links to an observation, show the species label, + classifier confidence, classifier/source and an owner-confirmed badge, + with a link to the observation. + - Keep the original `bird` label and detector confidence visible. + - No dashboard and no taxonomy browser. + - If detections do not carry `observation_uuid` today, add a light join in + the detection results API: match by `camera_uuid` + timestamp + label, + or better, store `observation_uuid` on the detection row in 0096. Decide + in L13. +- **validation**: Web unit tests and a Playwright UI spec show both the + original and classified labels. Run i18n literal baseline checks. +- **status**: Completed +- **log**: + - Commit `119fad5b`. Built in `build-int01-L15` (mirrors `build-int01-L14`) + and `build-int01-L15-asan` (Debug ASan/UBSan). `libuv_api_handlers.c` did + not change, so `coverage.json` did not move (`coverage.mjs --check` OK). + - **Server-side join** (no N+1, authz stays in C): in + `handle_get_detection_results`, after the existing `live.view` check on + the stream's camera, `db_observation_link_detections(camera_uuid, refs, n, + out)` runs one prepared match plus one annotation statement on a single + read-only connection. The match is: + - `camera_uuid = ?` (only the authorized camera, so another camera's + observation can never be linked; legacy NULL-UUID detection rows link + through the authorized camera's UUID); + - `observed_at_ms BETWEEN (ts-1)*1000 AND (ts+1)*1000+999`, which + `EXPLAIN QUERY PLAN` shows as a range search on + `idx_object_observations_camera_time`; + - `label = ? COLLATE NOCASE` (ASCII). + + The order is `abs(observed_at_ms/1000 - ts)`, then `observed_at_ms`, + then `uuid`, so ties are deterministic and the earliest wins. A DB failure + logs a warning and returns the detections without links. + - **API (additive)**: `EvidenceDetection` gains the optional + `observation` → `EvidenceDetectionObservation` `{id, owner_confirmed, + classification: ObservationClassification | null}`, which is closed. + `classification` is `owner_confirmed[0]` (newest `updated_at`) before + `automated[0]`. Unlinked detections carry no member; it is never + `null`. The C serializer is `observation_detection_link_to_json()` in + `api_handlers_observations.c` and reuses + `observation_classification_to_json()`, so it never emits + `writer_user_id`. + - **UI**: the new `DetectionClassifications.tsx` provides pure helpers + (`detectionClassificationView`, `detectionOverlayLabel`, + `classifiedDetections`, `renderDetectionClassifications`) and a + component. One row per linked observation shows `Detector: bird (82%)`, + the classified label and confidence, `via `, an + `Owner confirmed` badge (from the shown annotation's `kind`), or + `Not classified`. Each row has an `Observation` link to + `/api/observations/{id}`. + - It appears under the TimelinePlayer toolbar and in the recording + video modal (`UI.tsx`) below the detection status. + - Both canvas overlays draw `bird (82%) · Carolina Chickadee (100%)`. + - `recordingsAPI.tsx` needed only the type, since `Detection` now + carries `observation?`. + - Seven `detections.classification.*` keys were added to all 18 + locales. The i18n literal baseline is unchanged; lint is clean. + - **RED→GREEN**: + - RED with stubs: `test_db_observation_classifications` failed 4 of 16, + `test_api_handlers_observations` failed 1 of 26, and the jest spec + could not resolve the component. `observations.api.spec.ts` "L15" + fails against the `build-int01-L14` binary (`observation` undefined). + - GREEN in both builds, ASan and UBSan clean: + `test_db_observation_classifications` 16/16 and + `test_api_handlers_observations` 26/26. The cases cover nearest + second, the ±1 s edges (inside and outside), the case-insensitive + label, a wrong label, a NULL label, a deterministic tie, never + crossing cameras, no annotation (summary with `classification: null`), + owner over a newer automated key, and bad arguments. + - Web: jest `detectionClassifications.spec.js` 5/5 and the full suite + 87 suites / 1027 tests; `npm run typecheck` and `npm run lint` are + clean. + - Tooling: `node --test tests/contracts/tooling/*.test.mjs` 60/60, with a + new validator case. `generate.mjs --check` is OK, and `lint.mjs` is + valid with the same 5 warnings. + - Contracts (`LIGHTNVR_CONTRACT_BIN=build-int01-L15/bin/lightnvr`): + `observations.api.spec.ts`, `evidence.api.spec.ts` and + `recordingsRead.api.spec.ts` give 24 passed and 1 skipped (MQTT). The + new L15 case SQL-seeds same-second observations on A and B plus + detections, checks that the `null` classification becomes the owner + decision after two PUTs, checks the detector `label`/`confidence` + are unchanged, and checks that B's observation never appears. + - Playwright UI: the new + `tests/integration/specs/detection-classification.ui.spec.ts` passes + 1/1 (`LIGHTNVR_TEST_BIN=build-int01-L15/bin/lightnvr + LIGHTNVR_SKIP_GO2RTC=1`, after `npm run build`). It mocks the + timeline APIs. `timeline-boundary.ui` and `recordings.ui` pass 29/30; + the mobile-scroll case is flaky and passes on its own. + - **Gotchas for X3 / later**: + - The link is heuristic (same second ±1 and label). Two same-label + objects in one frame both link to the earliest observation in that + second, because the detection row has no object index. If exact + per-object identity is needed, store `observation_uuid` on detections + once L3 can write it synchronously. + - `/api/investigations/search` (`results[].detection`) and the + investigations timeline are **not** enriched; only + `/api/detection/results` is. + - The link only exists while the observation is retained (history + pruning cascades annotations), and only within the detection's own + second ±1. ONVIF/API detections whose `observed_at_ms` came from + wall-clock time more than 1 s after the detection timestamp will not + link. + - An X3 end-to-end check can call `GET + /api/detection/results/?start=&end=` after the YA-WAMF + write-back and expect `observation.classification.kind == + "owner_confirmed"` once an owner decision exists. + - A `/tmp/lightnvr-test` left by an earlier run makes the integration + global setup fail with `health UI viewer fixture (500)`. Use the + default `LIGHTNVR_TEST_DIR`, which cleans it; the test ini hardcodes + `/tmp/lightnvr-test` paths. +- **files edited/created**: + - `include/database/db_observation_classifications.h`, `src/database/db_observation_classifications.c` (`db_observation_link_detections`) + - `include/web/api_handlers_observations.h`, `src/web/api_handlers_observations.c` (`observation_detection_link_to_json`) + - `src/web/api_handlers_detection.c` + - `docs/api/openapi.json`; generated `include/web/openapi_document_generated.h`, `web/generated/api/schema.d.ts`, `web/generated/api/validators.js` + - `tests/unit/test_db_observation_classifications.c`, `tests/unit/test_api_handlers_observations.c` + - `tests/contracts/observations.api.spec.ts`, `tests/contracts/tooling/observations-contract.test.mjs` + - `web/js/components/preact/DetectionClassifications.tsx` (new), `web/js/components/preact/timeline/TimelinePlayer.tsx`, `web/js/components/preact/UI.tsx`, `web/js/types/core.ts`, `web/js/types/timeline.ts` + - `web/public/locales/*.json` (18 locales) + - `web/tests/detectionClassifications.spec.js` (new), `tests/integration/specs/detection-classification.ui.spec.ts` (new) + +#### W10: YA-WAMF classification write-back with correction precedence +- **depends_on**: [W4, W5, W7, C3] +- **location**: `backend/app/services/sources/lightnvr_provider.py` (`write_classification`), new `classification_writeback_service.py`, durable jobs via the existing `processing_job_state` with `pipeline='lightnvr_writeback'` (no new table, ideally no Alembic revision), settings toggle (UI and backend), corrections flow hook, tests +- **description**: + - After a lightNVR detection is classified, or an owner correction is saved, + enqueue a durable write-back job. + - The worker PUTs to `…/classifications/yawamf/species`, sending + `expected_revision` and `kind`: + - an owner correction sends `owner_confirmed`; + - an automated result never overwrites a local owner correction; + - `409` → refetch and reconcile; + - `422` → drop with a reason. + - Retries never create detections or notifications. + - A write-back outage leaves the YA-WAMF result usable and visible as + "sync pending". + - Incoming `classification` changes are ignored for classification (W4 + rule). + - Off by default, with a settings toggle. +- **validation**: Tests against the C3 PUT fixtures cover retry idempotency, a + `409` reconcile, owner precedence, and an outage that creates no + re-notification (A13). +- **status**: Completed (YA-WAMF `de28e1e4`) +- **log**: + - **Job lifecycle** (`processing_job_state`, `pipeline='lightnvr_writeback'`, + keyed by the detection's `frigate_event`; no Alembic revision): + - `classification_writeback_service.request(event_key)` enqueues with + `force=True` (status `queued`, revision+1). It is a no-op returning + False when the toggle is off or the key is not `lnvr:`. Callers: + `EventProcessor` after a lightNVR detection is classified (the `elif` + keeps Frigate sub-label write-back exactly as before), and + `PATCH /api/events/{id}` and bulk manual tag in `routers/events.py` + (owner decisions). Queuing never fails the save. + - The worker (`start`/`stop` in `main.py` lifespan, a 60 s poll plus a + wake on enqueue) runs `run_due`. Nothing is sent while the toggle is + off; queued jobs wait. + - Jobs carry no payload. Each attempt re-reads the detection, then + `GET /api/observations/{id}`, plans with the pure + `sources/lightnvr_writeback.plan_write`, and PUTs with + `expected_revision`. `record_success` and `record_failure` pass the job + revision, so an attempt that finishes after a newer request cannot + close it. + - Outcomes: `succeeded:{stored|unchanged}`. `dropped:` goes + terminal with no retry: `owner_decision_locked`, `evidence_mismatch`, + `namespace_forbidden`, `forbidden`, `observation_not_found`, + `annotation_limit`, `invalid_request`, `no_species_result`, `hidden`, + `detection_missing` or `source_not_active`. + `retry:{source_unreachable|annotate_unavailable|revision_conflict|internal_error}` + backs off 30 s → 12 h over 9 attempts (about 2 days), then goes + terminal. + - `409 revision_conflict` re-reads and replans, at most 3 plans per + attempt. + - A retry after a lost answer resends lightNVR's stored fields unchanged, + so it gets `200 changed:false`. + - Owner precedence: `manual_tagged` → `owner_confirmed` at confidence 1 + with no model version. An automated result is dropped locally when + lightNVR already holds `owner_confirmed`, and on a `409 + owner_decision_locked`. + - An automated "Unknown Bird" (or a configured unknown label) is not + written. + - The worker never saves a detection, records an intent or notifies. + Admission still ignores incoming `classification` changes (W4). + - **Settings keys**: + - `lightnvr.writeback_enabled` (default `false`) and env + `LIGHTNVR__WRITEBACK_ENABLED`. + - Settings API `lightnvr_writeback_enabled` (GET and POST). + - UI switch "Send species to lightNVR" in Settings → Connection. + - **Status**: + - `GET /api/diagnostics/source` adds + `writeback{enabled,pending,synced,not_synced,last_outcome,last_outcome_at}` + (null for Frigate-only). + - `/health.classification_writeback` gives the outcome counters. + - The Health card shows "N results are waiting to sync" as information, + not an alert. + - **Provider and client**: + - `LightNvrProvider.put_classification(external_id, name, body, *, expected_revision)` + uses `writer_namespace` from capabilities. It raises + `UnsupportedCapabilityError` before any request when annotate or the + namespace is missing. + - `write_classification` now raises `durable_writeback_only`. + - `annotate` is in the consumer-implemented set, so the provider's + annotate mask is removed. + - The client adds `put_json` and `put_classification`. + - **RED→GREEN**: + - The tests are 30 integration tests in + `test_lightnvr_classification_writeback.py`, driving the real + pipeline with an httpx MockTransport answering from the vendored + `fixtures/lightnvr/classifications/01–08`, plus 41 pure plan tests. + All pass. + - The original RED run was lost with the stopped agent, so + meaningfulness was shown by 13 temporary mutations. Each failed its + target test: + - toggle ignored; + - owner guard removed; + - correction sent as automated; + - 409 not replanned; + - idempotent resend removed; + - 422 or `namespace_forbidden` made retryable; + - outage dropped instead of retried; + - the worker calling `notify_detection`; + - the worker touching the detection row; + - namespace hard-coded; + - the correction not queued. + - Playwright: with the UI hunks reverted, both new + `source-selection.spec.ts` cases fail. + - **Gates** (exact commit content, isolated worktree): + - Full backend suite: 7978 passed, 60 skipped (includes the W0 + baseline). + - Pure lane: 2261 passed. + - Clean: `export_openapi.py --check`, `export_openapi_types.py --check`, + `dead_modules_check`, `docs_consistency_check`, + `assemble_changelog --preview`, `ruff check` and `ruff format --check`. + - Frontend: `npm run check` 0/0 and `npm test` 2353 passed. + - Playwright `source-selection.spec.ts`: 7/7. + - **Gotchas for X3**: + - The lightNVR token's user needs `observations.annotate`. Only + Administrator has it by default, and a scoped token must list it. + Without it, results stay "sync pending" (`retry:annotate_unavailable`) + and go terminal after about 2 days. + - Turning the toggle on does not backfill detections saved while it was + off. + - A correction to a detection whose earlier job was dropped re-queues + it, because `force=True` is used. + - `model_version` is the active model ID, and the comparison ignores it. + - lightNVR `classification` change events are ignored, so there is no + loop. +- **files edited/created**: YA-WAMF: + - New: `backend/app/services/classification_writeback_service.py`, + `backend/app/services/sources/lightnvr_writeback.py`, + `backend/tests/test_lightnvr_classification_writeback.py`, + `backend/tests/pure/test_lightnvr_writeback_plan.py` and + `changelog.d/lightnvr-writeback.md`. + - Backend: `config_models.py`, `config_loader.py`, `routers/settings.py`, + `services/settings_update.py`, `routers/events.py`, + `services/event_processor.py`, `main.py`, + `repositories/processing_job_repository.py` (`status_counts`), + `services/source_setup_service.py`, `sources/lightnvr_client.py`, + `sources/lightnvr_provider.py`, `tests/pure/test_lightnvr_parse.py`, + `openapi.json`, `docs/api.md` and + `docs/setup/environment-variables.md`. + - UI: `apps/ui` `generated/openapi.ts`, `api/settings.ts`, + `SourceSettings.svelte`, `SourceHealthCard.svelte`, `Settings.svelte`, + 9 `settings.json` locales and `browser-tests/source-selection.spec.ts`. + +#### X3: P2 end-to-end +- **depends_on**: [X1, L14, L15, W10] +- **location**: `tests/integration/yawamf_e2e/` (extend) +- **description**: + - With write-back enabled, the species appears in both products. + - Replays and stale revisions keep exactly one annotation. + - An owner correction in YA-WAMF propagates as `owner_confirmed`, and a + later automated result does not override it. + - The classification change does not trigger another classification or + notification cycle. + - A lightNVR API outage leaves the YA-WAMF detection intact. +- **validation**: A12 and A13 pass end to end. The supported-version matrix is + updated. +- **status**: Completed. lightNVR commit `817796c7`. No product fix was + needed in either repo. Nothing is pushed. +- **log**: + - **Mode**: host processes, as in X1. The lightNVR binary is + `build-int01-X3`, built from `bcf5290f` with X1's options (MQTT+SOD on, + LiteRT off, the apt FFmpeg header symlink dir via `CMAKE_C_FLAGS`). + YA-WAMF runs at `1cbbae74` with `LIGHTNVR__WRITEBACK_ENABLED=true`. The + new scenarios are in `p2_scenarios.py`, a mixin for `run_e2e.Suite`. + Two cameras were added: `feeder-wb` and `feeder-wb-outage`. + - **RED**: `--no-writeback --scenarios A1,A12,A13,A7-annotate`. A1 passed. + A12 and A13 timed out waiting for the annotation on lightNVR. A7-annotate + first failed on a harness assumption (see the gotchas). Kept as + `results/x3-host-20261108-red-nowriteback.json`. + - **GREEN**: the full suite passes 10/10 in 454 s + (`results/x3-host-20261108.json`): A1–A6 and A8 with write-back on, plus + the three below. + + | A-ID | Evidence | + | --- | --- | + | A12 | **Both products:** YA-WAMF has `Dunnock` 0.93. lightNVR has `yawamf/species` `automated` with `input_revision` 2 and `annotation_revision` 1, in both the DB and GET. Detection results show `observation.classification`, while the detector keeps `bird` 0.90. **No loop:** the classification change is revision 3 and its UUIDv5 is on the broker. After it, there are 0 extra classifier calls, the intent columns are unchanged, and the job revision is unchanged. **Replays:** 30 MQTT replays and 5 identical PUTs give `200 changed:false`. A stale or future revision gives `409 revision_conflict`. There is still 1 row at revision 1, the observation stays at revision 3, and nothing new is published. **Owner:** `PATCH /api/events/{id}` to *Erithacus rubecula* gives `owner_confirmed` `European Robin` 1.0, revision 2. **Later automated result:** a PUT gives `409 owner_decision_locked`. YA-WAMF's detection was reverted to automated by SQL and its job re-queued; the job ended `terminal` with `dropped:owner_decision_locked`, and lightNVR is unchanged. | + | A13 | lightNVR was SIGTERMed for 47 s. The correction was saved, served by `GET /api/events?event_id=`, and its job was `retryable` with `retry:source_unreachable:request_error`. The row did not change during the outage. The job succeeded 14 s after restart, with `owner_confirmed` on lightNVR and in the enrichment. No classifier call, no duplicate row, and the intent is unchanged. | + | A7-annotate | A token without the action gets `403 forbidden` on both an existing and a new key. A token scoped to another camera gets `404 observation_not_found`. Neither writes a row or bumps the revision. The recipe token gets `201` and then `200 changed:false`. The audit results match. | + + - **Docs**: the `YA_WAMF.md` matrix now has a P2 row marked tested and the + YA-WAMF minimum for write-back (`de28e1e4`). The P2 section notes the + known gap. Contract §18 rows are updated, and the README has the + scenarios and gotchas. + - **Not covered**: the "later automated result" in YA-WAMF is simulated. + No YA-WAMF path produces an automated result for a detection that is + already saved and also writes it back. + - **Finding (not fixed, follow-up for YA-WAMF W10)**: + `POST /api/events/{id}/reclassify` (owner, sets `manual_tagged`) and the + video classifier change the species without queuing a write-back. Only + `EventProcessor` and the PATCH/bulk manual-tag paths call + `classification_writeback_service.request`. lightNVR keeps the old + annotation until the owner edits or confirms. + - Run: + `../YetAnother-WhosAtMyFeeder/backend/.venv/bin/python tests/integration/yawamf_e2e/run_e2e.py --lightnvr-bin build-int01-X3/bin/lightnvr [--scenarios A1,A12,A13,A7-annotate] [--no-writeback]` + - **Gotchas**: + - A token's camera selector must *equal* one of its user's grant + selectors. A subset gives `403 API token scope exceeds live.view + authority`. The other-camera probe is therefore a second user, + `yawamf-east`. + - YA-WAMF has no `GET /api/events/{id}` (it answers 405); use + `?event_id=`. + - The write-back worker polls every 60 s and wakes on enqueue. The first + retry comes 30 s after a failure, and the second 120 s after that. + - `results/*.json` records `dirty_tracked: true`, because the harness + edits were not yet committed during the run. +- **files edited/created**: `tests/integration/yawamf_e2e/p2_scenarios.py` + and `results/x3-host-20261108{.json,.classifier.jsonl,.detector.jsonl,.mqtt-capture.jsonl,-red-nowriteback.json}` + (new); `tests/integration/yawamf_e2e/run_e2e.py`, `README.md`; + `docs/integrations/YA_WAMF.md`, `docs/integrations/OBSERVATIONS_CONTRACT.md` + +## Parallel Execution Groups + +| Wave | Tasks | Can Start When | +|------|-------|----------------| +| 1 | C1, C4, W0 | Immediately | +| 2 | C2, C3, L1, W1, W2 | C1 (C2, C3, L1); W0 (W1, W2) | +| 3 | L2, W3, W5 | L1 (L2); W1 (W3); W1+C3 (W5) | +| 4 | L3, L4, L5, L8, L9, L13, W4, W8 | L2 (L3, L4, L13); L2+C3 (L5); L2+C2 (L8, L9); W1+W2+W3+C3 (W4); W3+W5 (W8) | +| 5 | L6, L10, L14, W6, W7 | L2+L3 (L6); L3+L4+L5+L8 (L10); L13+L8+L5 (L14); W2+W4+W5 (W6); W2+W4 (W7) | +| 6 | L7, L15, W9, W10 | L6+C3 (L7); L14 (L15); W3+W6+W7+W8 (W9); W4+W5+W7+C3 (W10) | +| 7 | L11, X2 | L5–L10+C3 (L11); C4+L6+L7+L10 (X2) | +| 8 | L12 | L11 | +| 9 | X1 | L11+L12+W9 | +| 10 | X3 | X1+L14+L15+W10 | + +These pairs touch the same files, so give them to one agent or merge them in +sequence to avoid conflicts: + +| Shared file(s) | Tasks | +| --- | --- | +| `src/web/api_handlers_observations.c` | L8 → L14 | +| `db_observations.c` | L2 → L13 | +| `event_processor.py` | W3 → W4 | +| `docs/api/openapi.json` | C2 → L8 / L9 → L13 → L14 | +| `src/web/libuv_api_handlers.c` + `docs/api/contracts/coverage.json` (hash/line-pinned; regenerate on every edit) | C2 → L8 → L14 | +| `src/video/unified_detection_thread.c` | L6 → L7 | +| `include/core/config.h` / `db_streams.c` | L2 → L9 | +| Alembic head | W2 (W10 only if it ends up needing a revision) | + +## Testing Strategy + +- **Unit tests** (`tests/unit`, ASan build for L3/L4/L5/L6): each L* task ships + its own tests (idempotency, CAS transitions, cursor/watermark, atomic + finalization, non-blocking admission, relay crash recovery). +- **Contract tests** (`tests/contracts`): OpenAPI projection lint; C3 fixtures + validated against schemas; Playwright API specs for authorization isolation, + name reuse and distinct error outcomes; MQTT sink message-ID stability. +- **YA-WAMF**: W0 Frigate regression baseline green at every step (A10); pure + tests for admission, crop math and capability gating; static "no direct + Frigate calls" test; migration up/down/up test. +- **Reliability**: SIGKILL at each persistence boundary (A6); broker outage + with 100 observations (A5). +- **Performance**: C4 baseline vs X2 enabled run (A11, ≤5% p95, zero new + gaps). +- **E2E**: X1/X3 docker-compose suite running the documented recipe; A14 + manual pilot with recorded versions. + +## Risks & Mitigations + +- **Detection-thread latency.** The L3 worker performs every observation + insert. The detection thread only hands off an RGB buffer it already owns + (with no extra copy where possible). X2 confirms this. +- **Memory held by queued frames.** + - Queue items are owned RGB24 buffers, not `AVFrame` references, so no + decoder or hwaccel surfaces are pinned. + - Bound the queue by bytes. + - Run ASan and a shutdown test. +- **Stale local FFmpeg headers give false crash signals** (project memory). + Run evidence/encoder tests in the project Docker toolchain. +- **Global DB mutex contention.** Evidence worker commits share the + non-recursive mutex with recording-path writes. Keep transactions small and + batched, and have X2 measure mutex hold time. +- **Migration number or authz bit collisions with concurrent PRs.** Reserve + 0095/0096 and bit 19 in C1, and re-check before merging L1 and L13. +- **The YA-WAMF identity migration rebuilds large tables in batch mode.** + - Document the backup step. + - Test on a large seeded DB for runtime. + - The schema guard refuses older binaries. +- **Hidden Frigate calls remain.** The W3 static test plus the fake + snapshot-only provider test, which asserts zero `/api/events/` requests. +- **Evidence expires before recovery.** + - Advertise the oldest cursor and remaining availability. + - YA-WAMF caches admitted photos. + - Recovery reports expired items as distinct outcomes. +- **Upstream YA-WAMF declines the integration.** + - Keep the changes upstreamable: small, provider-scoped, preserving Frigate + behavior. + - Name a fork maintenance owner before production release. +- **Annotation authority is ambiguous.** C1 freezes writer namespace + derivation and owner precedence before L13 and L14 start. +- **Motion-only or non-bird detectors.** Setup diagnostics (L10, W8) report + "no eligible observations" distinctly from failures, and the docs require a + bird-capable detector. diff --git a/rust/MANIFEST.json b/rust/MANIFEST.json index 2705a6267..b9a2c84a3 100644 --- a/rust/MANIFEST.json +++ b/rust/MANIFEST.json @@ -17,16 +17,16 @@ "modules": [ { "c": "src/core/authorization.c", - "c_sha256": "8d6f8baa06f5b332d45dda8bc5021fb19a29e6d5354326b1e6dcd24f29ce020e", + "c_sha256": "98b86206d9a50c76669cf49aa0a04fd182e90747510a32811ce0c375f00e2050", "rust": "generated/src__core__authorization.rs", - "rust_sha256": "a64072fb602d955e9f3c37f3556ad50c53807381e34844f4d1c77372fa6aa4cd", + "rust_sha256": "5b6e2672950b4ba91f9fbb97ac3a8335b3b5fdeb526518ab81427dafb9a2b073", "adapted": false }, { "c": "src/core/camera_collection_filter.c", "c_sha256": "54d963d65e6657db4d9c9d9b72be5fd8ebe256a49b4fa26930faa7446d1dc86f", "rust": "generated/src__core__camera_collection_filter.rs", - "rust_sha256": "69cf1ed2fc1518180e8ee84f7833d4f0324293679cfc9149c0bd76bbb6f24490", + "rust_sha256": "2f1125785faef30145891ec0be89dd425eb2087170244f146152f0de733cc811", "adapted": false }, { @@ -38,9 +38,9 @@ }, { "c": "src/core/config.c", - "c_sha256": "6869d6b6240027fd6265d11ae4677fc402f697624ce6a1af433173a5ea90d862", + "c_sha256": "863c5aad291d74e76dfae03172967d94bf089d92d57a1779d0fc374231f1a3f7", "rust": "generated/src__core__config.rs", - "rust_sha256": "75fbd914348984850be864cd90d88619ff975be667bde40a91f7b62fab43b8ed", + "rust_sha256": "ca9b0c9887dfc58692b9f0427e81e3174fdb5288ca1b9bfebe2431d0d915d930", "adapted": false }, { @@ -66,9 +66,9 @@ }, { "c": "src/core/event_envelope.c", - "c_sha256": "f228b7e2da69d3532d92b2a31174f5406f40cc45bedb1d820b891069424733de", + "c_sha256": "f0c3436f0ad9a874a385845beeebdbdae900f581cbd490c9b00491c38bca2692", "rust": "generated/src__core__event_envelope.rs", - "rust_sha256": "ec058c64cb883ff867a77e025fca2ac364b8cdbdcda0e855825421bab6697142", + "rust_sha256": "68c2f9bd197083837c87962222858003e1108c9cadbf9dca572ba08ba3c72f29", "adapted": false }, { @@ -80,9 +80,9 @@ }, { "c": "src/core/event_producers.c", - "c_sha256": "b2de27bc0cc2065a70532a968b8107fab261ccce3ffc106f2db2b440568cc6b6", + "c_sha256": "bb354d44fc33a46b161f6fc588f0f96a31ef2759f1aad4d94844ce3ec03adcae", "rust": "generated/src__core__event_producers.rs", - "rust_sha256": "dbeada688a95dc02198a9819e3b68285a3bfd4b84994d52717abbea212208b51", + "rust_sha256": "f24ae5a6dd01fc2d410001e79ea5858d6bc95097f82c8304d8e28685d22a1cc9", "adapted": false }, { @@ -108,16 +108,16 @@ }, { "c": "src/core/main.c", - "c_sha256": "a9518b68d9a8c63f6b7b1592d5a390c9524ff1af20f8b7b875f0e3b1330baa01", + "c_sha256": "59908b4c043fd28ec02d3579e0a231c718b897fdd85e1cbf1937c01204a5ee4e", "rust": "generated/src__core__main.rs", - "rust_sha256": "af1047390978c7c91c87e095c0bb981a0e78c94efc8c36bfc67000b1d569c9a7", + "rust_sha256": "efc023df43ba422f2b8dc5256be3950c3b3e37395c22aadd4d094529ec67020e", "adapted": true }, { "c": "src/core/mqtt_client.c", "c_sha256": "f7477e6e8ff86a1c49b829d9022f563995bb83b041266233957ed946002154eb", "rust": "generated/src__core__mqtt_client.rs", - "rust_sha256": "bd076b97030de7af1cc41310e9cb56882227229bb02562449ab35abe67a6a6fa", + "rust_sha256": "27274503fc9212604f926473bd3ee441b768f0c503db77788d29554149f6c714", "adapted": false }, { @@ -138,7 +138,7 @@ "c": "src/core/mqtt_event_adapter.c", "c_sha256": "7fda2a34a28d5c647f02b457167e8687511cecece6ae3d110de9fb5e64dc8610", "rust": "generated/src__core__mqtt_event_adapter.rs", - "rust_sha256": "a1287b67dcb66b9bdfcd2d53da72d4487110c057fa8ffd9a47d928a5031b378e", + "rust_sha256": "e2b1e687a48e64f065ab30983bc486a6a4c40ff5bf92ba132fc8e75c921d6606", "adapted": false }, { @@ -148,6 +148,34 @@ "rust_sha256": "3e37bc0ba7d7f3049b2654a37506600ef74f88d9a1ec2166e489f37045c96354", "adapted": false }, + { + "c": "src/core/observation_diagnostics.c", + "c_sha256": "e5506f3b03195422129909fb88f28754b66827852415a513e352dd9b6ec6bc82", + "rust": "generated/src__core__observation_diagnostics.rs", + "rust_sha256": "da9bcf199adf9b7513d7580f173893662741d9a5f203793abac60bcfee8dbf73", + "adapted": false + }, + { + "c": "src/core/observation_evidence.c", + "c_sha256": "fc21a2eca26a98a8f4b1dcacdb7e7f78e73df9cac328778cfec73d9143faca4f", + "rust": "generated/src__core__observation_evidence.rs", + "rust_sha256": "553b1b8ee480897e2b08256a14b6af7378cfa9e780e967a9f11b21f7b0523315", + "adapted": false + }, + { + "c": "src/core/observation_relay.c", + "c_sha256": "ae905bb25a980bb6bf247234036b946e9c8598ca8384fecf8fa8596335a966da", + "rust": "generated/src__core__observation_relay.rs", + "rust_sha256": "d67100592fa6902d197c0d1c9e60aee6b04c6ba8712c4799e98265022ddd0b22", + "adapted": false + }, + { + "c": "src/core/observation_retention.c", + "c_sha256": "8e31957087aff3961646bcdc7e32f5f8ff8ecfbf8f70154dae2ab3bb66b0d7fd", + "rust": "generated/src__core__observation_retention.rs", + "rust_sha256": "321033efcb475e2f8b273181fa3d684dd4a9267d3bca824ceb71794731fbf263", + "adapted": false + }, { "c": "src/core/path_utils.c", "c_sha256": "6a1f48d250ab758f5869cfc4e25503e228cb557009f2e18b1c86e36e02cb8e7c", @@ -187,42 +215,42 @@ "c": "src/database/db_api_tokens.c", "c_sha256": "dfbc98ab32a45e622c97fd27e38df7c523d70bf46153e22343846f3c69b10d63", "rust": "generated/src__database__db_api_tokens.rs", - "rust_sha256": "ceadc8ac06b5f34169dbd12c825abdf9b87d0e799ceea02a9d25ddb540353475", + "rust_sha256": "146c7435578c90e3e6e5a62269be66e55d3ec0bc6a84288e180a5b416d1225ca", "adapted": false }, { "c": "src/database/db_audit.c", "c_sha256": "9e7d86b01fdf6df9934180d6cfe53c6670527cdaae76ad240bdb8fb0e5c7a350", "rust": "generated/src__database__db_audit.rs", - "rust_sha256": "8a47ef5b25dca775f582a76f34ab1ab04c8aec2cb46e0eba72b2373270856ae8", + "rust_sha256": "1f1811415c106fd88c750a9f760df8360579159ab486da743296027f0817e307", "adapted": false }, { "c": "src/database/db_auth.c", "c_sha256": "55c01731435a1427fd206c26e6fb2fcb984c36d6115ae857897b37904dacf95f", "rust": "generated/src__database__db_auth.rs", - "rust_sha256": "a2c273f574decde721b94661138f52d04d1b88e766fb5230be1340b8f11c6a17", + "rust_sha256": "faabb430eed0c0ce3da5555cd9d19d7ae0518fba2affd7ede3f8e467b410e974", "adapted": false }, { "c": "src/database/db_authorization.c", "c_sha256": "be17d7866b0b595027a66996413e055b1bfb75ca745e35d83e51df51e2dd9d0e", "rust": "generated/src__database__db_authorization.rs", - "rust_sha256": "15a1923f666c66f2452646f52d56429e8090528674daa2bc0bf0a8d4107a9c9d", + "rust_sha256": "014ebc5fdac386523c13e42d13aee6403f69f8bc613a1a9f63f623750f409027", "adapted": false }, { "c": "src/database/db_authorization_snapshot.c", "c_sha256": "568bafd52eae0dfb1f482688acc4f67a97f6ff46790450a93f9e284ff502e1c8", "rust": "generated/src__database__db_authorization_snapshot.rs", - "rust_sha256": "9a95628bd66131da1b7412e700aae0d4a7e45ecf20fcdfe57a956ad1f008c4a2", + "rust_sha256": "286e3d299dedf2fe74f3e459b3c31b2e61794cb60c55932bd97641b9c74a2751", "adapted": false }, { "c": "src/database/db_backup.c", "c_sha256": "6e5444470a6eb66d2010ca41d3afa14770d6e3e964970e25e8917a10e3347ac4", "rust": "generated/src__database__db_backup.rs", - "rust_sha256": "6b6b735af3d8e652ea6bb2a6cc346b1749806335ec0f1c383d401168e21e1c9c", + "rust_sha256": "7a6d309f9cf85e7afdd8cbdc09dd3854c88e140dd2de8aa674236850c435d870", "adapted": true }, { @@ -255,9 +283,9 @@ }, { "c": "src/database/db_core.c", - "c_sha256": "550e25ec88aed4c54f5f0d5fedad4e9dc11218f9bf8273a081da448f55bf6c65", + "c_sha256": "52fa628b05e76a038c2d90882772702769a84205267693d8ac66b4db581af3ad", "rust": "generated/src__database__db_core.rs", - "rust_sha256": "2da5c3b1cba70930d22040c6fcdab4cbe306fe8c3c86844818178f6fb6bb2f17", + "rust_sha256": "d684746572afdbc1fd872ff2ce7f22c09e0b9496f7a0dc1ba4f4cf8390d7d353", "adapted": true }, { @@ -283,9 +311,9 @@ }, { "c": "src/database/db_event_outbox.c", - "c_sha256": "46129dd5f3b5cd8c7aaa5dc2c7542f29203c4d7fb1d9cc7ef7660462165ba9e3", + "c_sha256": "aefa00ec1fb2194a133f4b22bfe2f62bf963fde3a4b94a566449e3b5f5f0e51f", "rust": "generated/src__database__db_event_outbox.rs", - "rust_sha256": "126243c3f29395a46577a61c6f05945367ff3e0659ea6a5dabc33a63350114dd", + "rust_sha256": "8d188a3f23a1a410a7512829e7c2da7dde6ca4abdddb89793593a6fd501e378b", "adapted": false }, { @@ -369,7 +397,7 @@ "c": "src/database/db_migrations.c", "c_sha256": "b78ed025c94d3dd9f07a0512e67c4174f02dab07d9061a1cfcb10be5dcf1f52f", "rust": "generated/src__database__db_migrations.rs", - "rust_sha256": "e332e314f44e08e00b36056bd089e14c764dc6d686777eb8b032833dafbcd138", + "rust_sha256": "d8609169825a20e1ce23e61c2fe9730710d9f3195c48d58cef192f20489af731", "adapted": false }, { @@ -379,6 +407,20 @@ "rust_sha256": "e75a707bf90e92e69d321c998d7c1e62caf1b30113b64244b29d0ef183fca013", "adapted": false }, + { + "c": "src/database/db_observation_classifications.c", + "c_sha256": "4f9cfb19ebc55330f8509685f63b406d7a415a9515e4ed3f62ca0286919e4fc7", + "rust": "generated/src__database__db_observation_classifications.rs", + "rust_sha256": "6ef3281bcbf423af3dda97a8f2a61aa71e223d36073e4baa227ca0902bf83162", + "adapted": false + }, + { + "c": "src/database/db_observations.c", + "c_sha256": "8596e2ca284aaf629289e450b14ff20fd3e21fd1849f96b570de166709e604d9", + "rust": "generated/src__database__db_observations.rs", + "rust_sha256": "875e2d9a31005f426e5da551f4c7bc783093b22defc34888a58894ec60b27015", + "adapted": false + }, { "c": "src/database/db_onvif_discovery_inventory.c", "c_sha256": "1b3f7c131c3b3357a270d3066d2cb41a7370ac09239121c39c9c6176961d988c", @@ -472,9 +514,9 @@ }, { "c": "src/database/db_streams.c", - "c_sha256": "6eb8c7af1890641ebd8986b4d17a0282e0df1ae494a3cd3206e418d34d27a3bd", + "c_sha256": "2369c8924c34455b44f5b07913a5152228813862774394431e874f0240d363a9", "rust": "generated/src__database__db_streams.rs", - "rust_sha256": "66cb0e45369dc8e8cb949d12484e9d9a829bcfe70876973fa6b81adff4101142", + "rust_sha256": "8c177daf272d5a677c052026d0d5721c718783fc65fad3d0de8ef536dde78e49", "adapted": false }, { @@ -537,14 +579,14 @@ "c": "src/storage/storage_deletion.c", "c_sha256": "a7e6af61dbcd3f6deac2dc082debe899e3976c5e0409b39eedb688024c65fc01", "rust": "generated/src__storage__storage_deletion.rs", - "rust_sha256": "67d08a4d9c246c3c8536d0c63908dd3f8c8aa61bcce84c160828bb104e284477", + "rust_sha256": "96f99b3347075774d02beffba9cd5ef14c4c029da5d9a284a144b22892f295c3", "adapted": false }, { "c": "src/storage/storage_manager.c", - "c_sha256": "fe8ed749eccd6f0a9846c1561da42dd5cca94df6af1a1e91256605f922008758", + "c_sha256": "added0d7697901ba3f3da9f912980cadd22dc01ebfdce0f367d6c941d5a39b9e", "rust": "generated/src__storage__storage_manager.rs", - "rust_sha256": "0e45dc5f7e04a037c43f588abbf2d6dbdc2517d3bfdefa9ae8129a89049a73cb", + "rust_sha256": "6429f930db24a1eb0cd13eeb48d7b7fe0362bc538629cea6cf656cbc54d94d6c", "adapted": false }, { @@ -558,7 +600,7 @@ "c": "src/storage/storage_manager_streams_cache.c", "c_sha256": "05567406419fbf744c5103337c5cc49e0b786d764b218ef32c7728ca33e81039", "rust": "generated/src__storage__storage_manager_streams_cache.rs", - "rust_sha256": "1a8176bf46e0d95b173f5c28773860ee5a55ed187d75a35d2aa9294ff3134004", + "rust_sha256": "4a4494654dd25325892652c54d77f9775b0cee833bec7a05f568609f4109502c", "adapted": false }, { @@ -586,7 +628,7 @@ "c": "src/storage/storage_source.c", "c_sha256": "89417b0ced2831061c5611974d6961fd80098cf2fcd4e086179bc75a0c2d615b", "rust": "generated/src__storage__storage_source.rs", - "rust_sha256": "ee57405ef06f5927375facb7273c62cb14cdb54d63f4badaed2cc2e6f7559337", + "rust_sha256": "236846d270bb2ce5ec645282ff338a9dcb4b0a02722a993dc653c7c4c2000be5", "adapted": true }, { @@ -759,9 +801,9 @@ }, { "c": "src/utils/base64.c", - "c_sha256": "06e8bf1cfcc46457e2cea6b801231e6625b35e1ef5133bc6673eb22648cb21b4", + "c_sha256": "69f6a626463686f17eb83fc9ece5a9fa7b1de26c91e8d49a7185c1b719580a3e", "rust": "generated/src__utils__base64.rs", - "rust_sha256": "0dca05a392e308527fa20beda90f7af71a1cf7291c83767ea93e29766ee2385c", + "rust_sha256": "4388dec0c70c6801db5d95764f656801acec9d34733b81541c036fc9309ec00b", "adapted": false }, { @@ -801,9 +843,16 @@ }, { "c": "src/utils/uuid.c", - "c_sha256": "8f238e3dbeb696f338a19f6666fc854404b2846c6c62591b5b1e4b34a37d643d", + "c_sha256": "a0d118746a4296bd18af8ea2556403879bd7924652323b1cfeaafcab7f107796", "rust": "generated/src__utils__uuid.rs", - "rust_sha256": "4c1b5faeed9357be4a74b60b66d379e5ae91a5f5d5545e04d7a479a0625f9804", + "rust_sha256": "7a2962924780c52479fb3ed8237f6b6c8f0c29fd755b2f77342c0052b1cdddff", + "adapted": false + }, + { + "c": "src/utils/webrtc_candidates.c", + "c_sha256": "961c8789cdf82f307d0b2eee249cc5c21c7c4449bf01fc74dcbfb53ffb9d1b12", + "rust": "generated/src__utils__webrtc_candidates.rs", + "rust_sha256": "ab4d35cb737d0f5a66833ac0687cf57a532ce14d802d443aec4224db5283a9cc", "adapted": false }, { @@ -822,23 +871,23 @@ }, { "c": "src/video/api_detection.c", - "c_sha256": "d365a78c5d525df5b17e9325eb7df1182eeb658c9b313e7e06e637973f099c7f", + "c_sha256": "0a467133e694a5370960ed9dee2e3fbb8ce4cd40e82f35a489b26219e16c78ae", "rust": "generated/src__video__api_detection.rs", - "rust_sha256": "5e0a5eb37fd0c6e99d0198ca60e791b5f2fd70cdbd5f182f3ac20bb649df7229", + "rust_sha256": "6fd4153ad2383d1544bf3cb169bc9867beb19944602980b06b66cac6d9eed860", "adapted": false }, { "c": "src/video/cross_stream_motion_trigger.c", "c_sha256": "35c4fb6d33b96686ad31034a05401ad3d74b06798714f0da36e020c8ea5cea23", "rust": "generated/src__video__cross_stream_motion_trigger.rs", - "rust_sha256": "2864a25c6d80db981b3cf16f3e40144806f3aca2745d844ff1845850631f6570", + "rust_sha256": "76657ea382ad9127ba2ca9b678cd1c33b554cc828a83be4093597e40ad2d7a4e", "adapted": false }, { "c": "src/video/detection.c", "c_sha256": "81ded80cf90d6a18a8259d0948a0ccb67d50320f51ea37ac05141c0740b0fa0c", "rust": "generated/src__video__detection.rs", - "rust_sha256": "2ce4c4c2344f6dca8c9b321700d4b1d1e17bb078a489bca0acb329717a54c1de", + "rust_sha256": "190d931fc1b8f1860d9c2f11b601fa7691a235e35e2b7a7e19f090a57a326183", "adapted": false }, { @@ -855,6 +904,13 @@ "rust_sha256": "36b58e32e465030156598a4915adb347a3c02f6f95303579fbd2a60f932e30c3", "adapted": false }, + { + "c": "src/video/detection_evidence.c", + "c_sha256": "61aa598040777a165952d3466959a2f1441bafff4b2ba338a0ed58b4adfe7ccd", + "rust": "generated/src__video__detection_evidence.rs", + "rust_sha256": "661966e623bc5ebcc63672fafdcd461294c0aebacf1da416ddb25f75a283129a", + "adapted": false + }, { "c": "src/video/detection_integration.c", "c_sha256": "5f49ecc488c90b8d09bf510ff19d2ac57afaad762e7a634452732af44a19d346", @@ -873,7 +929,7 @@ "c": "src/video/detection_stream.c", "c_sha256": "b4e9241c764cbbe22d836a828ca84de16b36988c91e7536370e87a0cff9714ac", "rust": "generated/src__video__detection_stream.rs", - "rust_sha256": "6096fe04c0d79b4362657fd247a651197ce5bc9fa2fab434b3a72abfe75f110f", + "rust_sha256": "450cade93aea65e5684f3f5b0e296628c503e5e321f29db773ff3093287eb18e", "adapted": false }, { @@ -894,7 +950,7 @@ "c": "src/video/ffmpeg_utils.c", "c_sha256": "146f6495d7b911a79eb51cc7fa58499b0c4983f4b2a324ac44f1cf1f89206a7d", "rust": "generated/src__video__ffmpeg_utils.rs", - "rust_sha256": "4c2e4f48bfee86183e01b73ce08fd36fdc57089da48e0a2c66b016dd24bcf47d", + "rust_sha256": "264e3d17c261609e83b35551445eeedac934a9bd16399ee3bbf2ff9e81a5b944", "adapted": false }, { @@ -915,14 +971,14 @@ "c": "src/video/go2rtc/go2rtc_consumer.c", "c_sha256": "34e57bb4ce1efe764e1a4c66c68dc14193c038cb945efd4f15877d015aef318d", "rust": "generated/src__video__go2rtc__go2rtc_consumer.rs", - "rust_sha256": "3e5683629e1b73fd6947ce7f262c48d1c83be69b2cdd351082b7450ce2a02081", + "rust_sha256": "53dc0afe9655a419449ac5b2c9ae7fe9290198a3e6462deae5bed9481212f641", "adapted": false }, { "c": "src/video/go2rtc/go2rtc_integration.c", "c_sha256": "88e4f629e606a2c9fd7f5f1fcd35b87d0d8994f6f23205de00143880cc510993", "rust": "generated/src__video__go2rtc__go2rtc_integration.rs", - "rust_sha256": "354541a4a23a64e443241e12ba6eea12e0c8594332261141ea5d361a99887731", + "rust_sha256": "dd2795221c0f00e72d4ac50e86d8a8c9ee7b1dcb628e2932624f8c53ee14f2e6", "adapted": false }, { @@ -934,9 +990,9 @@ }, { "c": "src/video/go2rtc/go2rtc_process.c", - "c_sha256": "8eee5a536a71cd062a75e762a40a62aa277917ff1c7cf55a270d3015a5505f98", + "c_sha256": "344729f507e07ef96a241c8792d42983cf25754e045d3ebf72ca85d77437de2f", "rust": "generated/src__video__go2rtc__go2rtc_process.rs", - "rust_sha256": "4b8198d04a3b896a5fd4ac1776554b12abf7cab44c0c3dab0ef16789dd30f42e", + "rust_sha256": "0567802515d0a74aa9db498f01d49e2ef6e8575e2259445076bbc10be748c3ac", "adapted": false }, { @@ -950,7 +1006,7 @@ "c": "src/video/go2rtc/go2rtc_stream.c", "c_sha256": "99771db2618d331fe7d2b48091b959666d0862a25cbffa6299139e98b35ce441", "rust": "generated/src__video__go2rtc__go2rtc_stream.rs", - "rust_sha256": "6bb8ff416c234e7bc9e4db4d5ae88956d81abd0c1ac39da04f8a476845b27aac", + "rust_sha256": "2290c77078753b6ad74ff14c2baea091cddec61593f725078144dccf0ac10823", "adapted": false }, { @@ -971,21 +1027,21 @@ "c": "src/video/hls/hls_context.c", "c_sha256": "8a703bd9db66f7c4fec96dc3f33b27fc7fbbfaad78b5f385d803413372165259", "rust": "generated/src__video__hls__hls_context.rs", - "rust_sha256": "37d16b41c61b0167fc011eaef1280886f0a9e9a985c60b5d56f9dbdd11f362e3", + "rust_sha256": "a421a39bad7e0a3bc12940d65a76499b4c05fae94783ceee2c867ffe1c223eda", "adapted": false }, { "c": "src/video/hls/hls_directory.c", "c_sha256": "d1c38b65dd453366317cd0921fa5d63b3d7e5609019733a61581d3e75043eb11", "rust": "generated/src__video__hls__hls_directory.rs", - "rust_sha256": "5c9aeb85206332848407fbb8b94cbed92fbd4d417adb234ca1586a91e7e5b680", + "rust_sha256": "bcff229f17ad939a62448d5aaae5995159925578c38face77e076bd094c7eaff", "adapted": false }, { "c": "src/video/hls/hls_unified_thread.c", "c_sha256": "099b40bb2f32002ebafca40d1b76c8058511762d71d1966f9a12be2bc8f5e357", "rust": "generated/src__video__hls__hls_unified_thread.rs", - "rust_sha256": "71551cbedf7f3ec61b6e10de5b30174eec3883d5572632ab75d815a3457b6bcd", + "rust_sha256": "672b5ea6f607b6fa4786021c911f5665c65fe839ba29d6bb911867e4d626e316", "adapted": true }, { @@ -999,7 +1055,7 @@ "c": "src/video/hls_streaming.c", "c_sha256": "8495c2a2a73c3e859bcf38b2ce0d67ce9892c7dc92a6000b31ac9e63e535400b", "rust": "generated/src__video__hls_streaming.rs", - "rust_sha256": "969ace281f167ed9cb6e832c8331b0666093f9442aa12f7071f2ded659bb938c", + "rust_sha256": "639f6e035b2c4a0cdc36c029c1c8227bdf695f9beeb71b49a5c1a7567c53e627", "adapted": false }, { @@ -1020,28 +1076,28 @@ "c": "src/video/mp4_recording_core.c", "c_sha256": "8ee2ab76dae2bf9f5aa88fd857f5eeb625142b5abaf75378b14114387cab1d44", "rust": "generated/src__video__mp4_recording_core.rs", - "rust_sha256": "0615f7f140a4fdc1139321b97c42dc577fa7095bd93272aa7074c6c0aedc6b63", + "rust_sha256": "16c8f5573786d98f6b0c5c36f551f27de28ab2d10580e1aa14ab8083106d0df7", "adapted": false }, { "c": "src/video/mp4_recording_writer.c", "c_sha256": "fccf132d2ca713231d856107e2732254d6f4afe7e191927b172bf975cf3f0e79", "rust": "generated/src__video__mp4_recording_writer.rs", - "rust_sha256": "b9e4a273a25f66aeca81af30160099ca2261bee6000bc9f51e438ab636baac87", + "rust_sha256": "8f9800e24f9101044a393577947dcc497f70eed9b03026a522f428606819d537", "adapted": false }, { "c": "src/video/mp4_segment_recorder.c", "c_sha256": "8de48216fb3ff4ef73e88b23b86325c4fdff5cc8f2b48bf13b1bd76922d55393", "rust": "generated/src__video__mp4_segment_recorder.rs", - "rust_sha256": "1a218d178f920f2617dfa51618d7caa01171beccb73f8d6b80412487c77e2470", + "rust_sha256": "47869d84eb4b1f6141ebaacd732136e990f72f8e9841ba9414b5c50417de51d4", "adapted": false }, { "c": "src/video/mp4_writer.c", "c_sha256": "9da5c4754aa383dfedf9dc72e859b06dc77e136477ec89ca9e0a32713ef648fd", "rust": "generated/src__video__mp4_writer.rs", - "rust_sha256": "cc09023b2feb4f6e75a9b8088849802f267077f86a56871f7c1d6fe2d3567ba1", + "rust_sha256": "5788ab954c6e6c2d80c9523063df34472d2c7a672081024bf3937c747b28ce11", "adapted": false }, { @@ -1055,35 +1111,35 @@ "c": "src/video/mp4_writer_thread.c", "c_sha256": "a295174f4d26fe83af9979a84189fe00ba7ed6b9b1d35b4c49df0e3c1c11b442", "rust": "generated/src__video__mp4_writer_thread.rs", - "rust_sha256": "f3ba6ce540915eb68c8f47ca9f89dcdfad408e82dc0dbdeee444c3642e64e003", + "rust_sha256": "b73b395b0033f1934aeaf901bdb351f947e3ae431000087699ff934bf73d5c28", "adapted": false }, { "c": "src/video/mp4_writer_utils.c", "c_sha256": "159b983b061e1090597003cffae2ad6937acc373c45585f064fe27bce27c0a23", "rust": "generated/src__video__mp4_writer_utils.rs", - "rust_sha256": "33da1c9787eeef676a8a7154c3daf6af258c1dc3615cea2d5ded46d5ec367fa8", + "rust_sha256": "39fc6a3aee7c2f73eef69e930da7754197a70cab1b8e0a49f80c6d3c62663dd8", "adapted": false }, { "c": "src/video/onvif_detection.c", "c_sha256": "f53d0fc20ed3654a2550c3515ec9e6f919f4e16d4d0261970c52d6b6fe649100", "rust": "generated/src__video__onvif_detection.rs", - "rust_sha256": "ed95a41fc6cf18dd18b9b672265a454d3fc610d717e01b577726b35bdfcc0a0b", + "rust_sha256": "947361f55fa1309a18d6a256d7882afd20c897b48dd2418cbf2047892a540fe5", "adapted": true }, { "c": "src/video/onvif_device_management.c", - "c_sha256": "3de946329dadbc992a8ee96f385a5a4d4333fd9efecb933583b43b05b0373236", + "c_sha256": "03675d19724215c86418b8e120c89dd1ae5a54fd95b1c3fb6443bbecdb18a99c", "rust": "generated/src__video__onvif_device_management.rs", - "rust_sha256": "c176440453a2976e8aebd3387049794e9d189df9fb5669cb511640da11314c38", + "rust_sha256": "43c2bed250a0e30b82ed3a541d5665f26beac74c61d29874a599670c6516764b", "adapted": false }, { "c": "src/video/onvif_discovery.c", "c_sha256": "02cc39322d1442db985ea3577b7521f4f4507ee012bc279782e7a37663c90aef", "rust": "generated/src__video__onvif_discovery.rs", - "rust_sha256": "9c8f2a34a4b2ab7cc9a509a98aa6fef444cc91d8114251e0bf20ae492f37ffb4", + "rust_sha256": "00f0633dc53f7ac297af0b9fe8e71aee60cce5c1121d830d9fb503d722e921d8", "adapted": false }, { @@ -1153,14 +1209,14 @@ "c": "src/video/packet_buffer.c", "c_sha256": "3c9f630b333153c7cbae7128d06d41b59df7e35c16167252204d499c1754ef2d", "rust": "generated/src__video__packet_buffer.rs", - "rust_sha256": "675e6b50eddd9a39cc56c920bf2703f707a98cf6efb52a0d662af93122bc6848", + "rust_sha256": "f401e20f290c802b42596d5cb663e6a15518067bb759b724e3a2c59ca0a822d2", "adapted": false }, { "c": "src/video/recording.c", "c_sha256": "89379d8e75720fab2fedea3104906adc1ec8d9c29a6ec4e4244cfb1069c71ebd", "rust": "generated/src__video__recording.rs", - "rust_sha256": "8bb9a032c25396e770a69070e8c759928159cc55b45a037f2316fbbfbcc45699", + "rust_sha256": "6cbfd6763335842b6e3b41047e7f2ce647c463e328e45451f4a7fb7bfdd03691", "adapted": false }, { @@ -1181,7 +1237,7 @@ "c": "src/video/recording_path.c", "c_sha256": "1ce1a0a2fdcd5d513701f4247940161d9e7bdb7cd1a94c88d8ef38ba16ab3911", "rust": "generated/src__video__recording_path.rs", - "rust_sha256": "b4bd7b428003c5c2cc8f196800fccae3c0b26a631d921ce811039bbf0b038097", + "rust_sha256": "1379c06ad39ffa1f2a0ca46aac4535a703a97d3b6b97dca62bb49a149d247451", "adapted": false }, { @@ -1200,16 +1256,16 @@ }, { "c": "src/video/sod_detection.c", - "c_sha256": "a1e0bed1f719d71f9a08397b3aa7baa4dbefa92f0d3e17fa3e5fab08b4ba0a3c", + "c_sha256": "0e477dba0273eb92f033ef3ca7cb29aabd292ef49c5278f853d0f16f2f79d976", "rust": "generated/src__video__sod_detection.rs", - "rust_sha256": "a57af90514f5f817a5a762c6a5c8dcc999e4585c53f38bfc5fd947779924b05d", + "rust_sha256": "f6dc70cd11779b0b358d413aa960bc85ee0e48e25b23a0fdd34164677d0bde47", "adapted": false }, { "c": "src/video/sod_integration.c", "c_sha256": "17fd9138a7fefa18dd4354a241196dd9d17be78a66084ae648689392c0d6305a", "rust": "generated/src__video__sod_integration.rs", - "rust_sha256": "b17c0e51147633491555117278d3574b70d109fe0b0aaa71d8c0d5c93e159904", + "rust_sha256": "15c8a4d128c5fba80ed95073349959d62a89952012bb8ca92e18d5dd02c8f2e7", "adapted": false }, { @@ -1230,7 +1286,7 @@ "c": "src/video/stream_manager.c", "c_sha256": "8a7ba155f5171768bd19288b575872e1c62e8c6b47c900652ea48e053c551911", "rust": "generated/src__video__stream_manager.rs", - "rust_sha256": "ed1c5b3dc1e02941629a666f5994db515a4e1f11d8af2eef636476c88c7b6add", + "rust_sha256": "71d9f40b9e3e68260aadd14b865d726189ccb82526b7b3ea4e056d5a490b8a2b", "adapted": false }, { @@ -1244,14 +1300,14 @@ "c": "src/video/stream_state.c", "c_sha256": "32a1160d7e7167388721676f34d3be78e4d6e7baa1de71ef16f746a3031548fc", "rust": "generated/src__video__stream_state.rs", - "rust_sha256": "5b6334caaa4d8cc2e77d0ee4a677c6b2032e6831dec10e591c6418b803858270", + "rust_sha256": "543bc3448ea082e2bd0a3f0336ddf41ea29f57310c2e746f825a4738ecab6e8e", "adapted": false }, { "c": "src/video/stream_state_adapter.c", "c_sha256": "daa0554f87ec8df147e1fcb2dc3bcce3c25661b35b17442c708f91d4c13d40ee", "rust": "generated/src__video__stream_state_adapter.rs", - "rust_sha256": "a826212bfda504fd3639bca3c02f8d2b9c9c2d3de1b0d32d9990111a3d96f15e", + "rust_sha256": "0d409aa5e7f00976a6037bc3a9fe7fb800fe0c03902bd902d14955a86ea99a7c", "adapted": false }, { @@ -1265,7 +1321,7 @@ "c": "src/video/streams.c", "c_sha256": "126c40870b5f0e6bcc30b941918bd5612c6958498dab8c2614db18d593024d61", "rust": "generated/src__video__streams.rs", - "rust_sha256": "067e5653cad3fd4787cf6e07544d7efe14d5feb099773d9e5ec8ed25c02d5230", + "rust_sha256": "6988014b2e50e7a30ef9fa9dc444d32b2c3a92b6f7d07f3445857c03c346d7c8", "adapted": false }, { @@ -1284,114 +1340,114 @@ }, { "c": "src/video/unified_detection_thread.c", - "c_sha256": "9647f5150042ebe59329d78283a09228dbb06b4c4d26179162c0dedd14a02648", + "c_sha256": "155f29b4deb13cfeb85b0fe0b85fca85c7ffc2213b6c22a6eabbe5abfcc1b5d7", "rust": "generated/src__video__unified_detection_thread.rs", - "rust_sha256": "0bcc8634e73383c9d6ffe2a7a74ed257129d469d969d368a98303c19210706dd", + "rust_sha256": "c12385a382443a31acad23c99e23707734e58539b870efda98c7f1879ac9a6de", "adapted": true }, { "c": "src/video/zone_filter.c", "c_sha256": "0bbc993ebd8e4dced6d4e0d102342e22af412321e85a779251dc2799f1cf79ef", "rust": "generated/src__video__zone_filter.rs", - "rust_sha256": "e216cfbb967297dddeaad2a6a46f11d3a93db3ec8d7e6fc2e14fc9fccb073ef2", + "rust_sha256": "e4848932406bafd9dc474afc7eed11334a5971dfee38a5cf4e257841e67e281c", "adapted": false }, { "c": "src/web/api_handlers_audit.c", "c_sha256": "b959dddfd14e89a99e8f806771cd82c4a14f1c4e739f6fbd44409d7967db4aae", "rust": "generated/src__web__api_handlers_audit.rs", - "rust_sha256": "45237d8a4efc80e65188c05b0607a5ca0c6940609a7378bc6edc6bc5dcde1975", + "rust_sha256": "3179e5d6f73350ff52bd34273f671395ff068cd89fbd36479b03097d3c188ee2", "adapted": false }, { "c": "src/web/api_handlers_auth_backend_agnostic.c", "c_sha256": "7ae4d842e0ba335e6bbb2eae91d4d0b70765095fd01bda3ec49ac1bb7d88f305", "rust": "generated/src__web__api_handlers_auth_backend_agnostic.rs", - "rust_sha256": "c537fc4d4a3997fc9499ae53704c1ffd9696464d5ce36bfc76c033ec86a932cc", + "rust_sha256": "71bbd685d57d403d0fb772e7f577ae5b1864d06b76061331bf50e4f70f2e14a4", "adapted": false }, { "c": "src/web/api_handlers_authorization.c", "c_sha256": "0a52714e4a1563eecf8d4adf461a135dafcd9a8e9c75d6acd55822be1c8149ed", "rust": "generated/src__web__api_handlers_authorization.rs", - "rust_sha256": "29e1571b61daaae31ec8230b1cf23e4c1ac5f72ac2f7e2c2d35cb090c8074960", + "rust_sha256": "40d9378353a0eeded43bac637455040d0f2fa6c7fe73ae8023a019db347b5318", "adapted": false }, { "c": "src/web/api_handlers_camera_collections.c", "c_sha256": "b28fdb51772a79e4429c248a18cc3865327922a67012e536e67ee8ebb5b0062b", "rust": "generated/src__web__api_handlers_camera_collections.rs", - "rust_sha256": "e4910f87c3b3c0d0bd55c0b786f6db90ebe4d7c86c2755a563776bd3c2989ea3", + "rust_sha256": "6e11d80a212b31ac1e29ff4e06a14042dcd4eb81477cb906e7aef6710cc3fb31", "adapted": false }, { "c": "src/web/api_handlers_camera_favorites.c", "c_sha256": "a33a03d69925078584d57bc73ecd2070c981f028c0a816b69e1b45ad62a4ed90", "rust": "generated/src__web__api_handlers_camera_favorites.rs", - "rust_sha256": "3a18d58cb302b937d3d8b7acf0a4e1060a1559b7975b6d23fb7b0ae32b32bb3b", + "rust_sha256": "8a369084e4877eec774d75c4dd90b6ddeb5e7c225099a856752c463bc1ef2dc8", "adapted": false }, { "c": "src/web/api_handlers_camera_tags.c", "c_sha256": "938e87084f56300f67dc68ec63d40261c9616d8f05f017a02c5e1ef38a36c3bf", "rust": "generated/src__web__api_handlers_camera_tags.rs", - "rust_sha256": "0e384d9b3c300197800ea883c5d9a6a788abf0875839f78506363600ce78e371", + "rust_sha256": "d81b09f274804bc7e2d7685d221b3a8a7782ba42411eac5c14fc4ef70cc3cb86", "adapted": false }, { "c": "src/web/api_handlers_common_utils.c", "c_sha256": "a24f4c1f72eb92edd25ec639377097524c129ac2620b1b3e57a036bbcdb6b7c8", "rust": "generated/src__web__api_handlers_common_utils.rs", - "rust_sha256": "d68aca907ad530a0a25dc144a858e7ec22e609263b56462e1ad903701546556f", + "rust_sha256": "9c418713e41bf254463ceaa5a85eff9253d09b343219dd20e83e2bb38c942cca", "adapted": false }, { "c": "src/web/api_handlers_detection.c", - "c_sha256": "bd98067a96584ca60b4cf595faa9c6b6881b32df94b2c022710d03cc0f542416", + "c_sha256": "d4022b39c7b73dda3379d9985efc78ba659322f39acdb452fa1802682dc52207", "rust": "generated/src__web__api_handlers_detection.rs", - "rust_sha256": "dc6f8365d19705a521b05397b08a3c8832c319306ca1559c30e814c33b644073", + "rust_sha256": "daecb53bb59ffb95c61c5d67c3005b0ed2cf8f3acb1d52580edeedc905848a5f", "adapted": false }, { "c": "src/web/api_handlers_detection_engines.c", "c_sha256": "43a959d6a3ff5c3046e3769e647afb73177694ea350df7636c0c0b5e0b68a547", "rust": "generated/src__web__api_handlers_detection_engines.rs", - "rust_sha256": "f0ecdc757ae75c5b650af7c13a91c39339575c05d4e6b522cfcd53afb5ac60da", + "rust_sha256": "03855c130068ae50e876b72ff672a556380cde5771932c5caa7294db7d662721", "adapted": false }, { "c": "src/web/api_handlers_detection_models.c", "c_sha256": "add47f300aa6551ad3c91be377872910710df10a4bd9300a8e02006bdaf3f48f", "rust": "generated/src__web__api_handlers_detection_models.rs", - "rust_sha256": "0f3551847076d199d250dd55b19d546f402d638e3c436cfa3c78ceea1d74f96c", + "rust_sha256": "85e5f575af5b6efe6105b016d1c4056bc1e84e41c14e2fbb5ad94456e3b2de5a", "adapted": false }, { "c": "src/web/api_handlers_detection_results.c", "c_sha256": "852b0f19869c510c2a2bea77ba3adeaf29ecb1ae2845a99e5740109c86850804", "rust": "generated/src__web__api_handlers_detection_results.rs", - "rust_sha256": "6f52e17cb33a13e5bf3967ae00a5a25a47653ce97f775a8bacaf9f328405abf1", + "rust_sha256": "e85014e9de48fd02fc2b14b95fa3ababcd15f9358f625bb155b224c8be937e09", "adapted": false }, { "c": "src/web/api_handlers_event_destinations.c", "c_sha256": "78ae8a04c0d0e63536e5cb4a08ded1b5cbcf89f518a3c0268fcd271bd6a5b694", "rust": "generated/src__web__api_handlers_event_destinations.rs", - "rust_sha256": "1e3036ee6b15146da8a49dc4aa1c541c1ad769a3c98244d52a88bf252122fbf7", + "rust_sha256": "dff4fec1334858ca493c0f57d19f3bbf2e2c20a77657a8a7b38bba29d8a71875", "adapted": false }, { "c": "src/web/api_handlers_event_routes.c", "c_sha256": "06b6086aaae565b89cec0eaeeab055a3c4a994a31d684bc025568a3f3fb99231", "rust": "generated/src__web__api_handlers_event_routes.rs", - "rust_sha256": "c338379f1e7bec0c9fc423861f7ce07b7a6f7fbd14f0a5b371295b69adfb9a70", + "rust_sha256": "fa3de1413442ea7889617aca83ac3abb21a3b17dce942d7be1b3f6d25d937226", "adapted": false }, { "c": "src/web/api_handlers_fleet.c", "c_sha256": "e086c66bc4d0712523d0f4b9870613c2e7dd44d64ba54046178c727ab6822102", "rust": "generated/src__web__api_handlers_fleet.rs", - "rust_sha256": "c7f34e66115f7b6cd1ae41f27f0b920e924b9ee2e252d87b7cce2708298aa98a", + "rust_sha256": "3beffef2d03377c4c76982552df41313c9945cec7e41a4ea24686c743523f118", "adapted": false }, { @@ -1405,322 +1461,329 @@ "c": "src/web/api_handlers_go2rtc_proxy.c", "c_sha256": "3cd588b03b131988006f5d0eb6295f48f7a3197df86fed4a7903c7708bb9c6f0", "rust": "generated/src__web__api_handlers_go2rtc_proxy.rs", - "rust_sha256": "7cf33b16d774e659630f9b94bfa7a1fdf98a3b934b876b95d269f3936b44030b", + "rust_sha256": "81b4178d61f39726a8cef6fc804c8f456fc631c9ec53de1165999584614dd9f2", "adapted": false }, { "c": "src/web/api_handlers_health.c", "c_sha256": "814f4c33ac27af38ede1c2fa4210388e613496cf7042fda37ddeda2ff0882d10", "rust": "generated/src__web__api_handlers_health.rs", - "rust_sha256": "bfb1fd947d9541e917ff4c597899fa120382a39e2f73f87f009309d0ad4cb1c3", + "rust_sha256": "b063d2dcc1b12a274138d3da0e0277faaa7cf7492e471c73805433f17ef90b8f", "adapted": false }, { "c": "src/web/api_handlers_ice_servers.c", "c_sha256": "5a208facbaa0d95cdb365e7e6e8d2627dd7c13eccb265d642a6ac153c2ddfa3c", "rust": "generated/src__web__api_handlers_ice_servers.rs", - "rust_sha256": "b66ba8b087b7fdf2bcd8a54e2a9d46cc36a9a5c430ee0ea341ad3def4d3d19c6", + "rust_sha256": "1345e87e76797eccf0144b5250ce073acb653467b80d5ca4c2b319ba4e98ee42", "adapted": false }, { "c": "src/web/api_handlers_imaging.c", "c_sha256": "93994412a080fb70f7f3be636132c3b45c4cdaa23e8266e5adfb6d443a6e899b", "rust": "generated/src__web__api_handlers_imaging.rs", - "rust_sha256": "74ffa519269468a609991c206b1b3bda7a18be3d740797cd3b6c22bc7bb5afdd", + "rust_sha256": "5b01768613f8305a32fb25354f1b4e03611e7c76678adb39c329d40cc8db850e", "adapted": false }, { "c": "src/web/api_handlers_investigation_bookmarks.c", "c_sha256": "9738d20ecc80b54bc8aa15758ed760f4f48968301c49835caf30652e8f76748d", "rust": "generated/src__web__api_handlers_investigation_bookmarks.rs", - "rust_sha256": "19a5725cd51bf58a800655e50357fc3cdaef45c9025acfcf8a508bf91a0e15ad", + "rust_sha256": "8dd3842186e4f3fb42665e1d0d2bdccdbca074dfd3e0ea846c8ecfacd9bd57f3", "adapted": false }, { "c": "src/web/api_handlers_investigations.c", "c_sha256": "9ac45032f028e295560a8fa04b99599ecb1d7bbd01a1ee8f7490d35fedff9558", "rust": "generated/src__web__api_handlers_investigations.rs", - "rust_sha256": "7dd0befe4cd030dbaf2eec5925255fc0807fc7784d325229f9bdb3f1f9f1fb52", + "rust_sha256": "7735ec5a13d2f2ada26477bced68ad925011275158c3bdc956c30218e47f8cae", "adapted": false }, { "c": "src/web/api_handlers_live_layouts.c", "c_sha256": "830257bfa68623a5408dbcfa462ec7557a5bbff1d8d3ce7255704907cf30c623", "rust": "generated/src__web__api_handlers_live_layouts.rs", - "rust_sha256": "b37786de944fbae01e6c5a1316c7d1d49013e665f400fa2143e8a6fb273e794b", + "rust_sha256": "6ebef1f2cfc2850b7abcbb6854576d48260c8dc6259a6e49ce523fabb20ee970", "adapted": false }, { "c": "src/web/api_handlers_locations.c", "c_sha256": "43a7435ef61db3e0adf98fcc736258984d200d2bf7f78dbc2c65501d47b80085", "rust": "generated/src__web__api_handlers_locations.rs", - "rust_sha256": "87cceac7a12e356ffc344e5fc3c979a09dab9c58bc95fbc6edb99cc305b2c2eb", + "rust_sha256": "8b321a6bf85237fbff5a82d0f185a534a52c5c8a83dd29073183461efc14e2bf", "adapted": false }, { "c": "src/web/api_handlers_lpr.c", "c_sha256": "41325b22534db05bf8e7132a94ebc9a6dc648429062ad7c86f108165a85df4d8", "rust": "generated/src__web__api_handlers_lpr.rs", - "rust_sha256": "5c37b2e9d021aec3ccc3e844f9816daa144e5bbaa2d5659bc82099696188868c", + "rust_sha256": "933a7174829f2d1b3dcc340e9762c1a975c255e6d3ba5e3e6dc9ada6c468fbf7", "adapted": false }, { "c": "src/web/api_handlers_metrics.c", - "c_sha256": "597ca91756ed84a329a753914b7b50ad38a504b9dd815a1bdbcfff89ae7d5574", + "c_sha256": "5ad20627f891796b7998ddd122e76a6c751f5c82a78adbc1ed9373bf32ebd464", "rust": "generated/src__web__api_handlers_metrics.rs", - "rust_sha256": "38d9265c7be1f71ef790bbb68512e050ff0ec4240635dc25a9ee6b5a666becfa", + "rust_sha256": "21de2017d1d264bf3aeff96d4f8e86b9062a28d4d846600f4aee77872f272aeb", "adapted": false }, { "c": "src/web/api_handlers_motion.c", "c_sha256": "f45e8b794bf1fa7a17b1ec994bf7459d556a746eb715e7011573584a10fcaf6b", "rust": "generated/src__web__api_handlers_motion.rs", - "rust_sha256": "0d6d8de7fc0799e802ff3a7cf22a50ec56b8cbb8d4dfa0295db22098681a8cbb", + "rust_sha256": "8b52d9d3d6fb6754b3d1916cef1b30372248620b67abc3b7a4b22b18497af2fc", + "adapted": false + }, + { + "c": "src/web/api_handlers_observations.c", + "c_sha256": "4c274aef79b6a4c73a9ad11e85c06a0e255b6608b863adc7ba39755764926d6d", + "rust": "generated/src__web__api_handlers_observations.rs", + "rust_sha256": "fcd979c2d6e4587136dbe45b2cc3ac650162084059feb9934e2905ba0efa1356", "adapted": false }, { "c": "src/web/api_handlers_onvif_backend_agnostic.c", - "c_sha256": "10aee61ec5d39abb0eb566cf5eb25abc8de15746def3fbf545ef77262c20ac95", + "c_sha256": "0b19ad5757f06b0d5c14cde320394a0b01078e74365e26c5e1f04550118a55e2", "rust": "generated/src__web__api_handlers_onvif_backend_agnostic.rs", - "rust_sha256": "47909008ebcad3022634e672c7e8c3f31836b453ab25f760b1123d019840b98f", + "rust_sha256": "86357f22d91dec054f3beb1aa71835c599b36f2f8fbfba468ccbe57a3be367ac", "adapted": false }, { "c": "src/web/api_handlers_openapi.c", "c_sha256": "24a7f45bda510214f89f5d12cf395fe8df2b97b28dfc920fa73705fe18d3fbe5", "rust": "generated/src__web__api_handlers_openapi.rs", - "rust_sha256": "b6f545fe89566ab740378848dd9b42088e8dd3cf2e1dd8e28a6901007748ae54", + "rust_sha256": "30d6d09441cf196a04e4230c5b32a001c1541a7d098f3b3bf11889bacf268d37", "adapted": false }, { "c": "src/web/api_handlers_operator_floor_plans.c", "c_sha256": "62ffcfa7c612ad925bf0b50194c512524d4bcf1a7aa9cbfc0d8cb2201b1cf8c3", "rust": "generated/src__web__api_handlers_operator_floor_plans.rs", - "rust_sha256": "7e4daeb1b189a41b4db5ba8e5f5b6cdc8773f60efa981ba8de37edfe187f03fd", + "rust_sha256": "b0d3c3e1c936705cb33ddd6481abd9f074d7837c0abd127c0dd8f33073b81664", "adapted": false }, { "c": "src/web/api_handlers_ptz.c", "c_sha256": "2eac34726c0419eeac0048e55ddfbc4f1fd8cb20675b07ec1975c17ff442f2d2", "rust": "generated/src__web__api_handlers_ptz.rs", - "rust_sha256": "bf2a8bf9267917851831da751e3fc0bbb8c4a8697edbe17218a289e5abe921c0", + "rust_sha256": "f09841f76811ad4293ca668e0eb35ff1551a1010a32d1e40c926f0a03d34735a", "adapted": false }, { "c": "src/web/api_handlers_recording_control.c", "c_sha256": "e13ad69f4a331bb3b373ec08db72bdaed4bde904aaccdd83eecfa5c408f3b01d", "rust": "generated/src__web__api_handlers_recording_control.rs", - "rust_sha256": "3e60d3a0c2a03d300d74b91b63f10324aacf5b1288f0371b22d82645b45095e6", + "rust_sha256": "d186a9ae8325b8f747b4e3d33223dba0b8540dbf12be538fd3ab7dd3024f5c70", "adapted": false }, { "c": "src/web/api_handlers_recording_tags.c", "c_sha256": "0efe74715f6ff05bfe41fe532fd8d8d16471e0d39ee58760e6b06f83b98f5c4d", "rust": "generated/src__web__api_handlers_recording_tags.rs", - "rust_sha256": "12f6f1dd3040752a9b259597654c6814db35af947dcd32ea96179705e234562d", + "rust_sha256": "afcc5fa3ee1eb3597f211874b6570909e7274fd3129ecb46b797191a901a584f", "adapted": false }, { "c": "src/web/api_handlers_recordings_backend_agnostic.c", "c_sha256": "0a493c562c0a879f892029adfde79f9c6ed0219dfecde074ffb4ab438764e4eb", "rust": "generated/src__web__api_handlers_recordings_backend_agnostic.rs", - "rust_sha256": "2866b7f1adc0110fa3627d7c27b0a9fc1db51d25a64e80c525a6bcffdda4a723", + "rust_sha256": "716f149c0ab3d2e269ce542614153e0f69597e5369effe9c1748dcc38e42401c", "adapted": false }, { "c": "src/web/api_handlers_recordings_batch_download.c", "c_sha256": "036acc396cd9c9a752ec63aa10a2698427876b6bfcdf3e347192df64e36ef44e", "rust": "generated/src__web__api_handlers_recordings_batch_download.rs", - "rust_sha256": "15037ca2be91c818fb5bb6d22e593f2e4b4e20f52e15e1b75bd3dd47968b25e1", + "rust_sha256": "82f49fe4061e174e6887881ad84590654f4d5488a69507a9077c150a87f4ffa8", "adapted": false }, { "c": "src/web/api_handlers_recordings_download.c", "c_sha256": "4999ed5a484648efed3ae1570bf51d9067aec50f005cece348bec9ea9aaccd0d", "rust": "generated/src__web__api_handlers_recordings_download.rs", - "rust_sha256": "727e62e84513de1df6d015dbe252b8481a1df418b6174da39fc92b35fc708195", + "rust_sha256": "ed35e33ac79843ea67d1e65f90cb2320b9078e8fa7f88e10b1e1049273dd140f", "adapted": false }, { "c": "src/web/api_handlers_recordings_files_backend_agnostic.c", "c_sha256": "361dab8bf2954181bbc974c030e7718b88fc030bcfb54d111958390d7d2077be", "rust": "generated/src__web__api_handlers_recordings_files_backend_agnostic.rs", - "rust_sha256": "c17e214975967d4fdefef2605f73d36f98ecb98bd526d7fa3642025b881149df", + "rust_sha256": "1c9326f0a5a852bf37de587b8dd0b78bf90ca6f54013954df5c09e98df8bfb3f", "adapted": false }, { "c": "src/web/api_handlers_recordings_list.c", "c_sha256": "69c98644f2c0c3660b0ac7e646d413705adc2a679549fb34d20ddc8d6883cf34", "rust": "generated/src__web__api_handlers_recordings_list.rs", - "rust_sha256": "663f530b0e608f3de42e8769ecdba11f7022bd0e0744c5761e46cd94e277e7a6", + "rust_sha256": "0be58c00e1e5afb39c5ed8ffcb5f4821aaee9ace6eec1175756881293b0d1146", "adapted": false }, { "c": "src/web/api_handlers_recordings_playback.c", "c_sha256": "f40e0dbec796aa86e595da29d48a3c4a747c1664fbf3f5eacc53e2c1f0790c27", "rust": "generated/src__web__api_handlers_recordings_playback.rs", - "rust_sha256": "d00f01737f276668d79c7ff0de40befa7f2d7bc97f8310609f5b6e21e20d3351", + "rust_sha256": "01bf55edca28d0ad7c87f2ba08d9aa414ad96c8d13b3667dc232fa77b7b1dda8", "adapted": false }, { "c": "src/web/api_handlers_recordings_sync.c", "c_sha256": "8f9ca84397bed3f3c4fb3a4aa37a424573aac272dfaac7cb7fa5159f65f7caf7", "rust": "generated/src__web__api_handlers_recordings_sync.rs", - "rust_sha256": "4c2d0d53effe6fc48d8de8a255816bfeecfb20f1cb73a851818d14ccf15652ed", + "rust_sha256": "4934f5bae185116f69f8c1e576767bd2d38c6682cf53dea0cdb6d9cea56dadd7", "adapted": false }, { "c": "src/web/api_handlers_recordings_thumbnail.c", "c_sha256": "fffb6c4c7a4ad421bb6a0d3c6949afb9587c112f3de19c1b2739cd2af5fe05e3", "rust": "generated/src__web__api_handlers_recordings_thumbnail.rs", - "rust_sha256": "f94542018f1487f38b0253fa8735e87ad8dae876e4e874c3531b26e7e136a27f", + "rust_sha256": "1e12f55600d9efbb355df7062934c9a84aa4c79fcfd71521039a50d27ddb5165", "adapted": false }, { "c": "src/web/api_handlers_retention.c", "c_sha256": "d549561f9717b5287507be7bcd66e0615261911b3a4ededebe2f829803ba849f", "rust": "generated/src__web__api_handlers_retention.rs", - "rust_sha256": "ccb8bd7368969d55db48bd0128e92837f73771170c73b57e13770984e581e00e", + "rust_sha256": "d4064c6afd4c160330ccc03054df8c159240c1e89d0f900fdf466045d6e49fa1", "adapted": false }, { "c": "src/web/api_handlers_settings.c", - "c_sha256": "08a36e773d1c39d674021c06770ba0796d6b3b7bbe0b9767b9cc90bc1d8c100f", + "c_sha256": "c17256f8cf3da99eb081d920e66002ee5ae893fa1d2e883189e8b46a115ca273", "rust": "generated/src__web__api_handlers_settings.rs", - "rust_sha256": "fb96ff08f6fe1f11c569ca9dbd1827ba2ab1c9ffc1e913b94f68a361bc7f92cc", + "rust_sha256": "30a31236bdc7f12f226801221897627007f93ddd5065f9d6b120059105613818", "adapted": false }, { "c": "src/web/api_handlers_setup.c", "c_sha256": "02cee1b93e089855ee94a1f7ca30294d8fb5861c1701bc913093dc4c0868dc0e", "rust": "generated/src__web__api_handlers_setup.rs", - "rust_sha256": "6f4354410af28ed011bc8ab85747f043db33975122429dc74d2d9da6879c46e4", + "rust_sha256": "43e7d837297ef8e723b88ba33bad7cfec6a82571f16c7101c89b382704cc3da0", "adapted": false }, { "c": "src/web/api_handlers_storage.c", "c_sha256": "bc1c927ac3d3123dc573a9c7db73c7816a5682df4510ad4a390961dbbd46c19d", "rust": "generated/src__web__api_handlers_storage.rs", - "rust_sha256": "8c1cc14f59af98778fcb1e09b95dc196a47570f7c87d5223242f69d0f24373fd", + "rust_sha256": "cb04736ab6cafd632c01194204f5c2731a719852fe6b9329a5c263a592c75e29", "adapted": false }, { "c": "src/web/api_handlers_storage_compliance.c", "c_sha256": "432ee38dc314987e68da163204dbcf41516ec02b79054d19a8ff66ba4b8dbad5", "rust": "generated/src__web__api_handlers_storage_compliance.rs", - "rust_sha256": "dd0059a7c5914a240f2f6e38728123202882535896ad687f93605e0c3f9672bc", + "rust_sha256": "f12300a2946a8a140bbf949a9413ce4ccf51a349cf813ea9f573e221cb26beac", "adapted": false }, { "c": "src/web/api_handlers_storage_migrations.c", "c_sha256": "132f2c01d25d22f6ddf22b00ba80741b148378d78c616f84a43a865f3425b60f", "rust": "generated/src__web__api_handlers_storage_migrations.rs", - "rust_sha256": "f9af49c7ce9e513119661d6f56e1a2cf1a351594a1000fc28eeb877cb622b4e1", + "rust_sha256": "51f67238289547d05e0fa9fde42db785be684117b3656ed36e30cf500de9fd58", "adapted": false }, { "c": "src/web/api_handlers_storage_policies.c", "c_sha256": "d5b8fc24544ca078e4bf591e4e8a69e80c0da90bccb162ae89a7368ea2a64530", "rust": "generated/src__web__api_handlers_storage_policies.rs", - "rust_sha256": "848fceecc4162dcecaa6d06c972307f8c4241aee3a702daa322113d7bd9a2ee4", + "rust_sha256": "449af3cc2053ba449a40fcb0485f3bf0efee07de149f1dc52f4608528d7f34ce", "adapted": false }, { "c": "src/web/api_handlers_storage_pools.c", "c_sha256": "015d900c42a8f04a73d8fb33dc3b88e954f0bfa257c327fd2d808c4747fac9e3", "rust": "generated/src__web__api_handlers_storage_pools.rs", - "rust_sha256": "86d009c174f66943ceab3a67e960063cdef3960cf3056cd53616d5397aa50dd9", + "rust_sha256": "d5ef77d7e7c90c2e9e7f8a53fb9cb72c3c37aa2abb2d4cf1f58c1b44d4799a5b", "adapted": false }, { "c": "src/web/api_handlers_storage_targets.c", "c_sha256": "131deb4fd91b1d95f1e1dfd88053373944f2f7c1206acdd23227410fd8e81d21", "rust": "generated/src__web__api_handlers_storage_targets.rs", - "rust_sha256": "56c80c5b2fff590b38df51ee002165174f08df67d7eb0ea17733a2691f33bbce", + "rust_sha256": "f663eb6d61610782240c653bb638b606eb9643120d4967f70b0e27ce8bfcb9ce", "adapted": false }, { "c": "src/web/api_handlers_streaming.c", "c_sha256": "215f04d2319f4dbcf4e34b45cbce55f8c32e15a818e6b518caa34a7f5f11d6f0", "rust": "generated/src__web__api_handlers_streaming.rs", - "rust_sha256": "2e279e2f75e65df357ac64dcb255f1e954a3d803b7d22f0259474f0eac807c87", + "rust_sha256": "82c6aee0ee0e06318cbbc8b0e26402c5ccd320caec5e6f7dc0f7cdc8f37bfa2f", "adapted": false }, { "c": "src/web/api_handlers_streams_get.c", - "c_sha256": "09b34f6e47dd885dbe9b951854987779e9da27705bb2db4575ef7fd1755cdecd", + "c_sha256": "0b9de269c92ea55051b1e0e2c144435f0706b98b1d9e3b15fc387442c97ecab0", "rust": "generated/src__web__api_handlers_streams_get.rs", - "rust_sha256": "514977c688027e3a36864e7562f3966095b45503d6024c92be48e9f9f995ec6a", + "rust_sha256": "bce9a30465dcbdea49d7998552a065080196669ac30f94d6428ff425e830db91", "adapted": false }, { "c": "src/web/api_handlers_streams_modify.c", - "c_sha256": "a4f3105368eeb7a540ba8396899b25b55806b9d5baaba16faa5c8982f83b7074", + "c_sha256": "aa206ab944c6c2d3520a88b4be83cce735ab132e27e90357ddbac8b32bd6d2c4", "rust": "generated/src__web__api_handlers_streams_modify.rs", - "rust_sha256": "6e3bcfe64202aa8fd3d5dec22d55aacb62dc3e1372bfcb60101891effe8f3383", + "rust_sha256": "07007e007b588b62625e98ac6d71e3f43ac6108534fceca20ae2dcfad963915a", "adapted": false }, { "c": "src/web/api_handlers_streams_test.c", "c_sha256": "a90f77589d835bdf1a93c0baaacbe2cdd6142f3b2e620bf00042cc76e9712a7e", "rust": "generated/src__web__api_handlers_streams_test.rs", - "rust_sha256": "b07950fbd16eb104b87334a8d5a7e8e996e22f72437958eb3cd975e144638c7e", + "rust_sha256": "b3f912aff9d5e80876fec6da917f91706b0cecfc9081d8171843b78a18536c4a", "adapted": false }, { "c": "src/web/api_handlers_system.c", - "c_sha256": "8c16db861de418cae5cccfe7ae31b88b01333e375cc0c8a2e447d562bda07344", + "c_sha256": "d1ace8b40b7b50385014fd926a778c20d19a0bde2ba59ab9043712b3b2913b17", "rust": "generated/src__web__api_handlers_system.rs", - "rust_sha256": "e62ca81215a5a7d64fba195c4d990a9f3a8a8fbbbc0f0c16af140bd52754a455", + "rust_sha256": "f41d8f8ecf79188e123fdd33a838df2fb7dac44613ba3b1af17237ab73589fa3", "adapted": true }, { "c": "src/web/api_handlers_system_go2rtc.c", "c_sha256": "f0ec0f14716e0d3316c0aeeaa01c4c66b2f06e344fc73bc880f804e43f973712", "rust": "generated/src__web__api_handlers_system_go2rtc.rs", - "rust_sha256": "94e382f9abfdfdcf072a39604a149a80f74c64d2a6757e1052114a75d382cc43", + "rust_sha256": "0ecc19d277ce8f1b46f48392eccf0afa6ad68e2d72de8e906400f43331dda759", "adapted": false }, { "c": "src/web/api_handlers_system_health.c", "c_sha256": "d8c626b7f0049276f734e1132d0438c9282260e442125eba236eede6b9c3f449", "rust": "generated/src__web__api_handlers_system_health.rs", - "rust_sha256": "736dd7a6f4e2d8992f3f4db62f9cd8903457e502ca09ecf79bcb5405a75d71fc", + "rust_sha256": "50381f12a9b37efd8254698c1d1a73a2f1bc0bd2652577d0510daac6a3d6e1be", "adapted": false }, { "c": "src/web/api_handlers_system_logs.c", "c_sha256": "49c5b576170d28d0d910a01c04e67c5591183c32eb1cec4939a56bccf8399c5b", "rust": "generated/src__web__api_handlers_system_logs.rs", - "rust_sha256": "df85a38d96e7b2ee456c810b9e79e9ca34757b39ef55316cceb5fa35eb56b61d", + "rust_sha256": "3178b46e7fcd860e42ac6db95f6809c7f0672c48e531cc0e04ed096f32b478f6", "adapted": false }, { "c": "src/web/api_handlers_system_logs_tail.c", "c_sha256": "c1f2f1e3b012a58e36fd53c33b2b319d3403feda234237e56ec4e1e42cede8f2", "rust": "generated/src__web__api_handlers_system_logs_tail.rs", - "rust_sha256": "3323dac67c1ff576fae0658c5c413f8f4475cc38e8d497b8b3fdb4400c64d74a", + "rust_sha256": "94a0f686461c40e65d5f85f54601528f7ea5c21cbef446b084292847dc14e0c5", "adapted": false }, { "c": "src/web/api_handlers_timeline.c", "c_sha256": "fcf56fbafa4a16c3b81af9b1141bea2abeda5f3de28038f5ee78748db895cb6f", "rust": "generated/src__web__api_handlers_timeline.rs", - "rust_sha256": "237fcf79de6f14f649ebc92705e4e3148807a066c5205322616cb33416caced7", + "rust_sha256": "1e490b1e2f90e04bdf36cfd1c864658e4777db396b8f2d2a9ae2e329e5194ec8", "adapted": false }, { "c": "src/web/api_handlers_totp.c", "c_sha256": "efd240dcbede81ace4d7f9ac90e0510211a536ebc161141431bde41dd55fb05b", "rust": "generated/src__web__api_handlers_totp.rs", - "rust_sha256": "fa899e423a12100b29eac28a822c19d8e6e935e0b747215f62e341156fe0f045", + "rust_sha256": "d388a658432a59e0e30ff1131110c44beba0cd1d04068da68aa9f98790fc1189", "adapted": false }, { "c": "src/web/api_handlers_users_backend_agnostic.c", "c_sha256": "0afdba138a7d5b9ae635af77e376ed2eabd75580dcd2d08bfad660ead422fc97", "rust": "generated/src__web__api_handlers_users_backend_agnostic.rs", - "rust_sha256": "1d2cf164203f16f11800bf110ec6807e9c75ea7329fc93bc95e42d60ed5a9e5c", + "rust_sha256": "66744057879940f46ff532787a4cb623206b3eb4188dcec2c039778d2b7519cd", "adapted": false }, { @@ -1734,14 +1797,14 @@ "c": "src/web/api_handlers_zones.c", "c_sha256": "6d8366422e7bac601b4726b08fe52cddd30c5d83bc115adb058118207890c7fa", "rust": "generated/src__web__api_handlers_zones.rs", - "rust_sha256": "16d0b80ea3d2ee55dd5a9c0465bfa04c4d447c043f446d82af020c657271443a", + "rust_sha256": "f70f1ea2d066aa59f2ce69abe649a258db2f79e891c42e0c43d73241915e9f47", "adapted": false }, { "c": "src/web/audit_log.c", "c_sha256": "2a61d982c6d0b0c846e3466b5d9c3dfd329413ade0884ce2640e5e06379d8617", "rust": "generated/src__web__audit_log.rs", - "rust_sha256": "e27b2de840bead56bce807541218e10d99b492ec2060c5493c11bdccc0080dcb", + "rust_sha256": "13d6a881512809bebf2bb03bd0efd82da86ed4f66fde47a96210670458af45de", "adapted": false }, { @@ -1783,21 +1846,21 @@ "c": "src/web/httpd_utils.c", "c_sha256": "b21bd27c4ac2f048dae3bea42a54562d2d158f8e81c07fc32b29363e3311f35c", "rust": "generated/src__web__httpd_utils.rs", - "rust_sha256": "0c5296ac4bc5970dbf7a0c10e84dcfa6e21f079513211c1d3318e7410ca73dcd", + "rust_sha256": "24e62876e63d4d490f4dc0a835d4a8996f809429668d3c13dfb79e025d0df10f", "adapted": false }, { "c": "src/web/libuv_api_handlers.c", - "c_sha256": "17c0b9896b310c308858d2360e80bdb23e50056cb5387909490126966ae62e8a", + "c_sha256": "5abcb74cf897b88acb15337307b4f1b2962dfb123f6a9b416274692deb556c5e", "rust": "generated/src__web__libuv_api_handlers.rs", - "rust_sha256": "ba246d3f00c34ed26c95849128ee4ecae92658c565cbe43da8ec2f6d9e2e9815", + "rust_sha256": "fb06801952f7c36f8e018a61c1a4610464cbf81e1be99c9edfa21d67f37a3d5f", "adapted": false }, { "c": "src/web/libuv_connection.c", "c_sha256": "8df28ebc43ee7f06f6e19fca9164b3182c7c54de6bbef8de1f7e076c9beb3e94", "rust": "generated/src__web__libuv_connection.rs", - "rust_sha256": "ea3fb39f7968e7fc47ade73b23992ba2effe7b2a3c276ccb4cdcce8f92fcc9a9", + "rust_sha256": "5b9bad6406942526dc3ea55c058fc4c75ccb2d7f64f8b165c7d510d54f868edc", "adapted": false }, { @@ -1818,7 +1881,7 @@ "c": "src/web/libuv_server.c", "c_sha256": "b8dbbda395605d7259de64af339c526383507047116340658c6a0312d93b6eab", "rust": "generated/src__web__libuv_server.rs", - "rust_sha256": "47ab7415e25da9d7524c96a954fb619555d8add965930b0385463e4c58918b29", + "rust_sha256": "6c41cca3001f3907e23f1fb42d887b3c59ba3cd2dc00c625f1332dda8ef50a4c", "adapted": false }, { @@ -1832,7 +1895,7 @@ "c": "src/web/openapi_projection.c", "c_sha256": "2a16052d105355c7b29b78cd698eb56d89f90d2426aebc01b8d0fe02cd135e05", "rust": "generated/src__web__openapi_projection.rs", - "rust_sha256": "29bb9008abadcdc071b18af625f3d8561fb2b063d6b75de1b7e2575a404f1a30", + "rust_sha256": "7a716dd7375e6f361d9253c51ca7a36006578524d32ee462f8b7a826e22a0dc4", "adapted": false }, { @@ -1870,6 +1933,13 @@ "rust_sha256": "f7dbd2fc8026ea6063aef5b3b92f667cd4d8f36726d6469b9405da97b1b9a5f5", "adapted": false }, + { + "c": "src/web/stream_observation_settings.c", + "c_sha256": "f93cc236d28469cb1ff5e47a358dcf141eda7095af2bde4d46ebca58fe35c170", + "rust": "generated/src__web__stream_observation_settings.rs", + "rust_sha256": "8d5472722c168e8b9533b88b79c3180b782842512f578df63daac890808c3550", + "adapted": false + }, { "c": "src/web/thumbnail_thread.c", "c_sha256": "7ca4d644a006b3011ab2d4f523a991c2573e67d24b0d78d9dacdcdfef0e8e56a", diff --git a/rust/generated/src__core__authorization.rs b/rust/generated/src__core__authorization.rs index 882b2a913..833a13677 100644 --- a/rust/generated/src__core__authorization.rs +++ b/rust/generated/src__core__authorization.rs @@ -202,6 +202,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -307,7 +311,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -452,7 +456,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -615,7 +620,7 @@ impl camera_collection_filter_result_t { pub const CAMERA_COLLECTION_FILTER_OUT_OF_MEMORY: Self = Self(-3); pub const CAMERA_COLLECTION_FILTER_DATABASE_ERROR: Self = Self(-4); } -static mut action_catalog: [authorization_action_metadata_t; 19] = [ +static mut action_catalog: [authorization_action_metadata_t; 20] = [ authorization_action_metadata_t { action: authorization_action_t::AUTHZ_LIVE_VIEW, key: b"live.view\0".as_ptr() as *const ::core::ffi::c_char, @@ -795,6 +800,16 @@ static mut action_catalog: [authorization_action_metadata_t; 19] = [ destructive: r#true != 0, enforced: r#true != 0, }, + authorization_action_metadata_t { + action: authorization_action_t::AUTHZ_OBSERVATIONS_ANNOTATE, + key: b"observations.annotate\0".as_ptr() as *const ::core::ffi::c_char, + category: b"Integrations\0".as_ptr() as *const ::core::ffi::c_char, + description: b"Annotate detection observations with classifier results\0".as_ptr() + as *const ::core::ffi::c_char, + camera_scoped: r#true != 0, + destructive: r#false != 0, + enforced: r#true != 0, + }, ]; #[unsafe(no_mangle)] pub unsafe extern "C" fn authorization_action_catalog( diff --git a/rust/generated/src__core__camera_collection_filter.rs b/rust/generated/src__core__camera_collection_filter.rs index 102abdba3..a92a4a75f 100644 --- a/rust/generated/src__core__camera_collection_filter.rs +++ b/rust/generated/src__core__camera_collection_filter.rs @@ -207,7 +207,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__core__config.rs b/rust/generated/src__core__config.rs index db6cfeac2..29bfdb21f 100644 --- a/rust/generated/src__core__config.rs +++ b/rust/generated/src__core__config.rs @@ -183,6 +183,7 @@ pub type __blksize_t = ::core::ffi::c_long; pub type __blkcnt_t = ::core::ffi::c_long; pub type __syscall_slong_t = ::core::ffi::c_long; pub type mode_t = __mode_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct timespec { @@ -627,6 +628,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -732,7 +737,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -831,6 +836,9 @@ pub const DB_STARTUP_CHECK_FULL: ::core::ffi::c_int = 2 as ::core::ffi::c_int; pub const DB_BACKUP_VERIFY_OFF: ::core::ffi::c_int = 0 as ::core::ffi::c_int; pub const DB_BACKUP_VERIFY_QUICK: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const DB_BACKUP_VERIFY_FULL: ::core::ffi::c_int = 2 as ::core::ffi::c_int; +pub const OBSERVATION_DEFAULT_MAX_AGE_DAYS: ::core::ffi::c_int = 7 as ::core::ffi::c_int; +pub const OBSERVATION_DEFAULT_MAX_BYTES: ::core::ffi::c_longlong = + 2147483648 as ::core::ffi::c_longlong; pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); @@ -939,7 +947,7 @@ pub static mut g_config: config_t = config_t { go2rtc_webrtc_listen_port: 0, go2rtc_stun_enabled: false, go2rtc_stun_server: [0; 256], - go2rtc_external_ip: [0; 64], + go2rtc_external_ip: [0; 256], go2rtc_ice_servers: [0; 512], webrtc_connection_timeout_ms: 0, webrtc_ice_recovery_timeout_ms: 0, @@ -1113,7 +1121,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"TURN_ENABLED\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_BOOL, - offset: 10240 as size_t, + offset: 10432 as size_t, size: 0 as size_t, default_str_value: ::core::ptr::null::<::core::ffi::c_char>(), default_int_value: 0 as ::core::ffi::c_int, @@ -1122,7 +1130,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"TURN_SERVER_URL\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_STRING, - offset: 10241 as size_t, + offset: 10433 as size_t, size: 256 as size_t, default_str_value: b"\0".as_ptr() as *const ::core::ffi::c_char, default_int_value: 0 as ::core::ffi::c_int, @@ -1131,7 +1139,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"TURN_USERNAME\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_STRING, - offset: 10497 as size_t, + offset: 10689 as size_t, size: 64 as size_t, default_str_value: b"\0".as_ptr() as *const ::core::ffi::c_char, default_int_value: 0 as ::core::ffi::c_int, @@ -1140,7 +1148,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"TURN_PASSWORD\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_STRING, - offset: 10561 as size_t, + offset: 10753 as size_t, size: 64 as size_t, default_str_value: b"\0".as_ptr() as *const ::core::ffi::c_char, default_int_value: 0 as ::core::ffi::c_int, @@ -1186,7 +1194,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_name: b"GO2RTC_EXTERNAL_IP\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_STRING, offset: 9653 as size_t, - size: 64 as size_t, + size: 256 as size_t, default_str_value: b"\0".as_ptr() as *const ::core::ffi::c_char, default_int_value: 0 as ::core::ffi::c_int, default_bool_value: r#false != 0, @@ -1194,7 +1202,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"GO2RTC_ICE_SERVERS\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_STRING, - offset: 9717 as size_t, + offset: 9909 as size_t, size: 512 as size_t, default_str_value: b"\0".as_ptr() as *const ::core::ffi::c_char, default_int_value: 0 as ::core::ffi::c_int, @@ -1212,7 +1220,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"WEBRTC_CONNECTION_TIMEOUT_MS\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_INT, - offset: 10232 as size_t, + offset: 10424 as size_t, size: 0 as size_t, default_str_value: ::core::ptr::null::<::core::ffi::c_char>(), default_int_value: 30000 as ::core::ffi::c_int, @@ -1221,7 +1229,7 @@ static mut env_config_mappings: [env_config_mapping_t; 33] = [ env_config_mapping_t { env_name: b"WEBRTC_ICE_RECOVERY_TIMEOUT_MS\0".as_ptr() as *const ::core::ffi::c_char, r#type: config_field_type_t::CONFIG_TYPE_INT, - offset: 10236 as size_t, + offset: 10428 as size_t, size: 0 as size_t, default_str_value: ::core::ptr::null::<::core::ffi::c_char>(), default_int_value: 5000 as ::core::ffi::c_int, @@ -1903,6 +1911,13 @@ pub unsafe extern "C" fn load_default_config(mut config: *mut config_t) { (*(*config).streams.offset(i as isize)).tier_important_multiplier = 2.0f64; (*(*config).streams.offset(i as isize)).tier_ephemeral_multiplier = 0.25f64; (*(*config).streams.offset(i as isize)).storage_priority = 5 as ::core::ffi::c_int; + (*(*config).streams.offset(i as isize)).observation_evidence_enabled = r#false != 0; + (*(*config).streams.offset(i as isize)).observation_labels[0usize] = + '\0' as ::core::ffi::c_char; + (*(*config).streams.offset(i as isize)).observation_max_age_days = + OBSERVATION_DEFAULT_MAX_AGE_DAYS; + (*(*config).streams.offset(i as isize)).observation_max_bytes = + OBSERVATION_DEFAULT_MAX_BYTES as int64_t; i += 1; } (*config).mqtt_enabled = r#false != 0; @@ -3642,7 +3657,7 @@ unsafe extern "C" fn config_ini_handler( safe_strcpy( &raw mut (*config).go2rtc_external_ip as *mut ::core::ffi::c_char, value, - ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), 0 as size_t, ); } else if strcmp( diff --git a/rust/generated/src__core__event_envelope.rs b/rust/generated/src__core__event_envelope.rs index ee25a46df..f4554896c 100644 --- a/rust/generated/src__core__event_envelope.rs +++ b/rust/generated/src__core__event_envelope.rs @@ -40,6 +40,14 @@ unsafe extern "C" { __format: *const ::core::ffi::c_char, ... ) -> ::core::ffi::c_int; + unsafe fn observation_asset_state_parse( + name: *const ::core::ffi::c_char, + state: *mut observation_asset_state_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_reason_is_valid( + state: observation_asset_state_t, + reason: *const ::core::ffi::c_char, + ) -> bool; unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; unsafe fn lightnvr_uuid_generate_v4(output: *mut ::core::ffi::c_char) -> ::core::ffi::c_int; unsafe fn memset( @@ -171,6 +179,15 @@ pub struct event_envelope_t { } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint); impl C2Rust_Unnamed { pub const _ISupper: Self = Self(256); @@ -206,11 +223,12 @@ unsafe extern "C" fn tolower(mut __c: ::core::ffi::c_int) -> ::core::ffi::c_int __c }; } +pub const OBS_LABEL_SIZE: ::core::ffi::c_int = 32 as ::core::ffi::c_int; pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; pub const INT_MAX: ::core::ffi::c_int = __INT_MAX__; -static mut EVENT_TYPES: [event_type_definition_t; 13] = [ +static mut EVENT_TYPES: [event_type_definition_t; 14] = [ event_type_definition_t { r#type: b"io.lightnvr.detection.object.v1\0".as_ptr() as *const ::core::ffi::c_char, family: b"detection\0".as_ptr() as *const ::core::ffi::c_char, @@ -226,6 +244,22 @@ static mut EVENT_TYPES: [event_type_definition_t; 13] = [ subject_kind: event_subject_kind_t::EVENT_SUBJECT_CAMERA, default_expiry_seconds: 3600 as ::core::ffi::c_int, }, + event_type_definition_t { + r#type: b"io.lightnvr.detection.observation_changed.v1\0".as_ptr() + as *const ::core::ffi::c_char, + family: b"detection\0".as_ptr() as *const ::core::ffi::c_char, + description: b"A durable object observation was created or changed\0".as_ptr() + as *const ::core::ffi::c_char, + severity: event_severity_t::EVENT_SEVERITY_INFO, + dynamic_severity: false, + minimum_severity: event_severity_t::EVENT_SEVERITY_INFO, + maximum_severity: event_severity_t::EVENT_SEVERITY_INFO, + sensitivity: event_sensitivity_t::EVENT_SENSITIVITY_OPERATIONAL, + media_policy: event_media_policy_t::EVENT_MEDIA_REFERENCE_ALLOWED, + expected_rate: event_expected_rate_t::EVENT_RATE_HIGH, + subject_kind: event_subject_kind_t::EVENT_SUBJECT_CAMERA, + default_expiry_seconds: 3600 as ::core::ffi::c_int, + }, event_type_definition_t { r#type: b"io.lightnvr.recognition.license_plate.v1\0".as_ptr() as *const ::core::ffi::c_char, @@ -901,6 +935,321 @@ unsafe extern "C" fn system_subject_matches_scope( } return r#false != 0; } +unsafe extern "C" fn valid_rfc3339_utc(mut value: *const ::core::ffi::c_char) -> bool { + if value.is_null() { + return r#false != 0; + } + let mut length: size_t = strnlen(value, 40 as size_t); + if length < 20 as size_t || length >= 40 as size_t { + return r#false != 0; + } + let mut pattern: *const ::core::ffi::c_char = + b"dddd-dd-ddTdd:dd:dd\0".as_ptr() as *const ::core::ffi::c_char; + let mut index: size_t = 0 as size_t; + while index < 19 as size_t { + if *pattern.offset(index as isize) as ::core::ffi::c_int == 'd' as ::core::ffi::c_int { + if *(*__ctype_b_loc()).offset(*value.offset(index as isize) as ::core::ffi::c_uchar + as ::core::ffi::c_int as isize) as ::core::ffi::c_int + & C2Rust_Unnamed::_ISdigit.0 as ::core::ffi::c_int as ::core::ffi::c_ushort + as ::core::ffi::c_int + == 0 + { + return r#false != 0; + } + } else if *value.offset(index as isize) as ::core::ffi::c_int + != *pattern.offset(index as isize) as ::core::ffi::c_int + { + return r#false != 0; + } + index = index.wrapping_add(1); + } + let mut cursor: size_t = 19 as size_t; + if *value.offset(cursor as isize) as ::core::ffi::c_int == '.' as ::core::ffi::c_int { + let mut digits: size_t = 0 as size_t; + cursor = cursor.wrapping_add(1); + while *(*__ctype_b_loc()).offset( + *value.offset(cursor as isize) as ::core::ffi::c_uchar as ::core::ffi::c_int as isize + ) as ::core::ffi::c_int + & C2Rust_Unnamed::_ISdigit.0 as ::core::ffi::c_int as ::core::ffi::c_ushort + as ::core::ffi::c_int + != 0 + && digits < 9 as size_t + { + cursor = cursor.wrapping_add(1); + digits = digits.wrapping_add(1); + } + if digits == 0 as size_t { + return r#false != 0; + } + } + return *value.offset(cursor as isize) as ::core::ffi::c_int == 'Z' as ::core::ffi::c_int + && *value.offset(cursor.wrapping_add(1 as size_t) as isize) as ::core::ffi::c_int + == '\0' as ::core::ffi::c_int; +} +unsafe extern "C" fn valid_observation_label(mut value: *const ::core::ffi::c_char) -> bool { + if value.is_null() { + return r#false != 0; + } + let mut length: size_t = strnlen(value, OBS_LABEL_SIZE as size_t); + if length == 0 as size_t || length >= OBS_LABEL_SIZE as size_t { + return r#false != 0; + } + let mut cursor: *const ::core::ffi::c_uchar = value as *const ::core::ffi::c_uchar; + while *cursor != 0 { + if *(*__ctype_b_loc()).offset(*cursor as ::core::ffi::c_int as isize) as ::core::ffi::c_int + & C2Rust_Unnamed::_IScntrl.0 as ::core::ffi::c_int as ::core::ffi::c_ushort + as ::core::ffi::c_int + != 0 + { + return r#false != 0; + } + cursor = cursor.offset(1); + } + return r#true != 0; +} +unsafe extern "C" fn validate_observation_changed( + mut event: *const event_envelope_t, + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, +) -> ::core::ffi::c_int { + let mut data: *const cJSON = (*event).data; + let mut observation_id: *const cJSON = required_field( + data, + b"observation_id\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + let mut revision: *const cJSON = required_field( + data, + b"revision\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_Number, + error, + error_size, + ); + let mut camera_uuid: *const cJSON = required_field( + data, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + let mut label: *const cJSON = required_field( + data, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + let mut observed_at: *const cJSON = required_field( + data, + b"observed_at\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + let mut cause: *const cJSON = required_field( + data, + b"change_cause\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + let mut state: *const cJSON = required_field( + data, + b"evidence_state\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + let mut asset_id: *const cJSON = required_field( + data, + b"asset_id\0".as_ptr() as *const ::core::ffi::c_char, + cJSON_String, + error, + error_size, + ); + if observation_id.is_null() + || revision.is_null() + || camera_uuid.is_null() + || label.is_null() + || observed_at.is_null() + || cause.is_null() + || state.is_null() + || asset_id.is_null() + { + return -1 as ::core::ffi::c_int; + } + let mut reason: *const cJSON = cJSON_GetObjectItemCaseSensitive( + data, + b"evidence_reason\0".as_ptr() as *const ::core::ffi::c_char, + ); + if reason.is_null() { + set_error( + error, + error_size, + b"event data is missing required field 'evidence_reason'\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if !lightnvr_uuid_is_valid((*observation_id).valuestring) + || !lightnvr_uuid_is_valid((*asset_id).valuestring) + || !lightnvr_uuid_is_valid((*camera_uuid).valuestring) + { + set_error( + error, + error_size, + b"observation identifiers must be UUIDs\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if strcmp( + (&raw const (*event).subject as *const ::core::ffi::c_char) + .offset(7 as ::core::ffi::c_int as isize), + (*camera_uuid).valuestring, + ) != 0 as ::core::ffi::c_int + { + set_error( + error, + error_size, + b"observation camera_uuid must match the event subject\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if (*revision).valuedouble < 1 as ::core::ffi::c_int as ::core::ffi::c_double + || (*revision).valuedouble > INT_MAX as ::core::ffi::c_double + || (*revision).valuedouble != (*revision).valueint as ::core::ffi::c_double + { + set_error( + error, + error_size, + b"observation revision must be an integer >= 1\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if !valid_observation_label((*label).valuestring) { + set_error( + error, + error_size, + b"observation label must be 1..31 bytes\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if !valid_rfc3339_utc((*observed_at).valuestring) { + set_error( + error, + error_size, + b"observation observed_at must be RFC 3339 UTC\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if strcmp( + (*cause).valuestring, + b"created\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + && strcmp( + (*cause).valuestring, + b"evidence\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + && strcmp( + (*cause).valuestring, + b"classification\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + set_error( + error, + error_size, + b"observation change_cause is unknown\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut evidence_state: observation_asset_state_t = + observation_asset_state_t::OBS_ASSET_PENDING; + if observation_asset_state_parse((*state).valuestring, &raw mut evidence_state) + != 0 as ::core::ffi::c_int + { + set_error( + error, + error_size, + b"observation evidence_state is unknown\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if cJSON_IsNull(reason) == 0 && cJSON_IsString(reason) == 0 { + set_error( + error, + error_size, + b"observation evidence_reason must be a string or null\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut reason_text: *const ::core::ffi::c_char = if cJSON_IsString(reason) != 0 { + (*reason).valuestring + } else { + ::core::ptr::null_mut::<::core::ffi::c_char>() + }; + if !reason_text.is_null() + && *reason_text.offset(0isize) as ::core::ffi::c_int == '\0' as ::core::ffi::c_int + { + set_error( + error, + error_size, + b"observation evidence_reason must not be empty\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + if !observation_reason_is_valid(evidence_state, reason_text) { + set_error( + error, + error_size, + b"observation evidence_reason is inconsistent with evidence_state\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut snapshot: *const cJSON = cJSON_GetObjectItemCaseSensitive( + data, + b"snapshot_url\0".as_ptr() as *const ::core::ffi::c_char, + ); + if evidence_state.0 == observation_asset_state_t::OBS_ASSET_READY.0 { + let mut expected: [::core::ffi::c_char; 96] = [0; 96]; + snprintf( + &raw mut expected as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 96]>(), + b"/api/observations/%s/snapshot\0".as_ptr() as *const ::core::ffi::c_char, + (*observation_id).valuestring, + ); + if cJSON_IsString(snapshot) == 0 + || strcmp( + (*snapshot).valuestring, + &raw mut expected as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + set_error( + error, + error_size, + b"ready observation events need their own snapshot_url\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + } else if !snapshot.is_null() { + set_error( + error, + error_size, + b"snapshot_url is only allowed when evidence is ready\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} unsafe extern "C" fn validate_type_data( mut event: *const event_envelope_t, mut error: *mut ::core::ffi::c_char, @@ -909,6 +1258,13 @@ unsafe extern "C" fn validate_type_data( let mut r#type: *const ::core::ffi::c_char = &raw const (*event).r#type as *const ::core::ffi::c_char; let mut data: *const cJSON = (*event).data; + if strcmp( + r#type, + b"io.lightnvr.detection.observation_changed.v1\0".as_ptr() as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + { + return validate_observation_changed(event, error, error_size); + } if strcmp( r#type, b"io.lightnvr.detection.object.v1\0".as_ptr() as *const ::core::ffi::c_char, @@ -1777,7 +2133,7 @@ pub unsafe extern "C" fn event_registry_all( mut count: *mut ::core::ffi::c_int, ) -> *const event_type_definition_t { if !count.is_null() { - *count = ::core::mem::size_of::<[event_type_definition_t; 13]>() + *count = ::core::mem::size_of::<[event_type_definition_t; 14]>() .wrapping_div(::core::mem::size_of::()) as ::core::ffi::c_int; } diff --git a/rust/generated/src__core__event_producers.rs b/rust/generated/src__core__event_producers.rs index 94ba953d9..13cef8576 100644 --- a/rust/generated/src__core__event_producers.rs +++ b/rust/generated/src__core__event_producers.rs @@ -6,24 +6,6 @@ unsafe extern "C" { __tp: *const tm, ) -> size_t; unsafe fn gmtime_r(__timer: *const time_t, __tp: *mut tm) -> *mut tm; - unsafe fn system_health_scope_name(scope: system_health_scope_t) -> *const ::core::ffi::c_char; - unsafe fn system_health_capability_name( - capability: system_health_capability_t, - ) -> *const ::core::ffi::c_char; - unsafe fn system_health_unit_name(unit: system_health_unit_t) -> *const ::core::ffi::c_char; - unsafe fn system_health_severity_name( - severity: system_health_severity_t, - ) -> *const ::core::ffi::c_char; - unsafe fn system_health_condition_code( - condition: system_health_condition_t, - ) -> *const ::core::ffi::c_char; - unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; - unsafe fn snprintf( - __s: *mut ::core::ffi::c_char, - __maxlen: size_t, - __format: *const ::core::ffi::c_char, - ... - ) -> ::core::ffi::c_int; unsafe fn cJSON_Delete(item: *mut cJSON); unsafe fn cJSON_CreateObject() -> *mut cJSON; unsafe fn cJSON_AddItemToArray(array: *mut cJSON, item: *mut cJSON) -> cJSON_bool; @@ -51,7 +33,6 @@ unsafe extern "C" { object: *mut cJSON, name: *const ::core::ffi::c_char, ) -> *mut cJSON; - static mut g_config: config_t; unsafe fn event_registry_find( r#type: *const ::core::ffi::c_char, ) -> *const event_type_definition_t; @@ -68,6 +49,25 @@ unsafe extern "C" { error_size: size_t, ) -> ::core::ffi::c_int; unsafe fn event_envelope_clear(event: *mut event_envelope_t); + unsafe fn system_health_scope_name(scope: system_health_scope_t) -> *const ::core::ffi::c_char; + unsafe fn system_health_capability_name( + capability: system_health_capability_t, + ) -> *const ::core::ffi::c_char; + unsafe fn system_health_unit_name(unit: system_health_unit_t) -> *const ::core::ffi::c_char; + unsafe fn system_health_severity_name( + severity: system_health_severity_t, + ) -> *const ::core::ffi::c_char; + unsafe fn system_health_condition_code( + condition: system_health_condition_t, + ) -> *const ::core::ffi::c_char; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + static mut g_config: config_t; unsafe fn event_bus_publish( event: *const event_envelope_t, error: *mut ::core::ffi::c_char, @@ -131,6 +131,91 @@ pub struct tm { pub type uint8_t = u8; pub type uint32_t = u32; pub type uint64_t = u64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +pub type cJSON_bool = ::core::ffi::c_int; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_severity_t(pub ::core::ffi::c_uint); +impl event_severity_t { + pub const EVENT_SEVERITY_INFO: Self = Self(0); + pub const EVENT_SEVERITY_WARNING: Self = Self(1); + pub const EVENT_SEVERITY_ERROR: Self = Self(2); + pub const EVENT_SEVERITY_CRITICAL: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_sensitivity_t(pub ::core::ffi::c_uint); +impl event_sensitivity_t { + pub const EVENT_SENSITIVITY_OPERATIONAL: Self = Self(0); + pub const EVENT_SENSITIVITY_INTERNAL: Self = Self(1); + pub const EVENT_SENSITIVITY_RESTRICTED: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_media_policy_t(pub ::core::ffi::c_uint); +impl event_media_policy_t { + pub const EVENT_MEDIA_FORBIDDEN: Self = Self(0); + pub const EVENT_MEDIA_REFERENCE_ALLOWED: Self = Self(1); + pub const EVENT_MEDIA_BINARY_OPT_IN: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_expected_rate_t(pub ::core::ffi::c_uint); +impl event_expected_rate_t { + pub const EVENT_RATE_LOW: Self = Self(0); + pub const EVENT_RATE_MEDIUM: Self = Self(1); + pub const EVENT_RATE_HIGH: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_subject_kind_t(pub ::core::ffi::c_uint); +impl event_subject_kind_t { + pub const EVENT_SUBJECT_CAMERA: Self = Self(0); + pub const EVENT_SUBJECT_STORAGE: Self = Self(1); + pub const EVENT_SUBJECT_SYSTEM: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct event_type_definition_t { + pub r#type: *const ::core::ffi::c_char, + pub family: *const ::core::ffi::c_char, + pub description: *const ::core::ffi::c_char, + pub severity: event_severity_t, + pub dynamic_severity: bool, + pub minimum_severity: event_severity_t, + pub maximum_severity: event_severity_t, + pub sensitivity: event_sensitivity_t, + pub media_policy: event_media_policy_t, + pub expected_rate: event_expected_rate_t, + pub subject_kind: event_subject_kind_t, + pub default_expiry_seconds: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct event_envelope_t { + pub specversion: [::core::ffi::c_char; 4], + pub id: [::core::ffi::c_char; 37], + pub r#type: [::core::ffi::c_char; 96], + pub source: [::core::ffi::c_char; 128], + pub subject: [::core::ffi::c_char; 96], + pub time: [::core::ffi::c_char; 32], + pub datacontenttype: [::core::ffi::c_char; 32], + pub occurred_at: time_t, + pub expires_at: time_t, + pub severity: event_severity_t, + pub data: *mut cJSON, +} #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] pub struct system_health_scope_t(pub ::core::ffi::c_uint); @@ -313,169 +398,16 @@ pub struct detection_result_t { pub count: ::core::ffi::c_int, pub detections: [detection_t; 20], } -#[derive(Copy, Clone)] -#[repr(C)] -pub struct cJSON { - pub next: *mut cJSON, - pub prev: *mut cJSON, - pub child: *mut cJSON, - pub r#type: ::core::ffi::c_int, - pub valuestring: *mut ::core::ffi::c_char, - pub valueint: ::core::ffi::c_int, - pub valuedouble: ::core::ffi::c_double, - pub string: *mut ::core::ffi::c_char, -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct event_severity_t(pub ::core::ffi::c_uint); -impl event_severity_t { - pub const EVENT_SEVERITY_INFO: Self = Self(0); - pub const EVENT_SEVERITY_WARNING: Self = Self(1); - pub const EVENT_SEVERITY_ERROR: Self = Self(2); - pub const EVENT_SEVERITY_CRITICAL: Self = Self(3); -} -#[derive(Copy, Clone)] -#[repr(C)] -pub struct event_type_definition_t { - pub r#type: *const ::core::ffi::c_char, - pub family: *const ::core::ffi::c_char, - pub description: *const ::core::ffi::c_char, - pub severity: event_severity_t, - pub dynamic_severity: bool, - pub minimum_severity: event_severity_t, - pub maximum_severity: event_severity_t, - pub sensitivity: event_sensitivity_t, - pub media_policy: event_media_policy_t, - pub expected_rate: event_expected_rate_t, - pub subject_kind: event_subject_kind_t, - pub default_expiry_seconds: ::core::ffi::c_int, -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct event_subject_kind_t(pub ::core::ffi::c_uint); -impl event_subject_kind_t { - pub const EVENT_SUBJECT_CAMERA: Self = Self(0); - pub const EVENT_SUBJECT_STORAGE: Self = Self(1); - pub const EVENT_SUBJECT_SYSTEM: Self = Self(2); -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct event_expected_rate_t(pub ::core::ffi::c_uint); -impl event_expected_rate_t { - pub const EVENT_RATE_LOW: Self = Self(0); - pub const EVENT_RATE_MEDIUM: Self = Self(1); - pub const EVENT_RATE_HIGH: Self = Self(2); -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct event_media_policy_t(pub ::core::ffi::c_uint); -impl event_media_policy_t { - pub const EVENT_MEDIA_FORBIDDEN: Self = Self(0); - pub const EVENT_MEDIA_REFERENCE_ALLOWED: Self = Self(1); - pub const EVENT_MEDIA_BINARY_OPT_IN: Self = Self(2); -} #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] -pub struct event_sensitivity_t(pub ::core::ffi::c_uint); -impl event_sensitivity_t { - pub const EVENT_SENSITIVITY_OPERATIONAL: Self = Self(0); - pub const EVENT_SENSITIVITY_INTERNAL: Self = Self(1); - pub const EVENT_SENSITIVITY_RESTRICTED: Self = Self(2); -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct event_bus_result_t(pub ::core::ffi::c_int); -impl event_bus_result_t { - pub const EVENT_BUS_OK: Self = Self(0); - pub const EVENT_BUS_NOT_RUNNING: Self = Self(-1); - pub const EVENT_BUS_INVALID_EVENT: Self = Self(-2); - pub const EVENT_BUS_QUEUE_FULL: Self = Self(-3); - pub const EVENT_BUS_ALLOCATION_FAILED: Self = Self(-4); -} -#[derive(Copy, Clone)] -#[repr(C)] -pub struct event_envelope_t { - pub specversion: [::core::ffi::c_char; 4], - pub id: [::core::ffi::c_char; 37], - pub r#type: [::core::ffi::c_char; 96], - pub source: [::core::ffi::c_char; 128], - pub subject: [::core::ffi::c_char; 96], - pub time: [::core::ffi::c_char; 32], - pub datacontenttype: [::core::ffi::c_char; 32], - pub occurred_at: time_t, - pub expires_at: time_t, - pub severity: event_severity_t, - pub data: *mut cJSON, -} -pub type cJSON_bool = ::core::ffi::c_int; -#[derive(Copy, Clone)] -#[repr(C)] -pub struct stream_config_t { - pub camera_uuid: [::core::ffi::c_char; 37], - pub location_uuid: [::core::ffi::c_char; 37], - pub name: [::core::ffi::c_char; 256], - pub url: [::core::ffi::c_char; 512], - pub enabled: bool, - pub width: ::core::ffi::c_int, - pub height: ::core::ffi::c_int, - pub fps: ::core::ffi::c_int, - pub codec: [::core::ffi::c_char; 16], - pub priority: ::core::ffi::c_int, - pub record: bool, - pub segment_duration: ::core::ffi::c_int, - pub detection_based_recording: bool, - pub detection_model: [::core::ffi::c_char; 512], - pub detection_interval: ::core::ffi::c_int, - pub detection_threshold: ::core::ffi::c_float, - pub pre_detection_buffer: ::core::ffi::c_int, - pub post_detection_buffer: ::core::ffi::c_int, - pub detection_api_url: [::core::ffi::c_char; 512], - pub detection_object_filter: [::core::ffi::c_char; 16], - pub detection_object_filter_list: [::core::ffi::c_char; 256], - pub streaming_enabled: bool, - pub protocol: stream_protocol_t, - pub record_audio: bool, - pub audio_voice_enhancement: bool, - pub onvif_username: [::core::ffi::c_char; 64], - pub onvif_password: [::core::ffi::c_char; 64], - pub onvif_profile: [::core::ffi::c_char; 64], - pub onvif_port: ::core::ffi::c_int, - pub onvif_discovery_enabled: bool, - pub is_onvif: bool, - pub backchannel_enabled: bool, - pub retention_days: ::core::ffi::c_int, - pub detection_retention_days: ::core::ffi::c_int, - pub max_storage_mb: ::core::ffi::c_int, - pub tier_critical_multiplier: ::core::ffi::c_double, - pub tier_important_multiplier: ::core::ffi::c_double, - pub tier_ephemeral_multiplier: ::core::ffi::c_double, - pub storage_priority: ::core::ffi::c_int, - pub ptz_enabled: bool, - pub ptz_max_x: ::core::ffi::c_int, - pub ptz_max_y: ::core::ffi::c_int, - pub ptz_max_z: ::core::ffi::c_int, - pub ptz_has_home: bool, - pub record_on_schedule: bool, - pub recording_schedule: [uint8_t; 168], - pub detection_record_on_schedule: bool, - pub detection_recording_schedule: [uint8_t; 168], - pub tags: [::core::ffi::c_char; 256], - pub admin_url: [::core::ffi::c_char; 512], - pub privacy_mode: bool, - pub motion_trigger_source: [::core::ffi::c_char; 256], - pub go2rtc_source_override: [::core::ffi::c_char; 8192], - pub sub_stream_url: [::core::ffi::c_char; 512], - pub detection_url: [::core::ffi::c_char; 512], - pub publish_url: [::core::ffi::c_char; 512], - pub playback_transport: [::core::ffi::c_char; 24], - pub eptz_config: [::core::ffi::c_char; 1024], -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct stream_protocol_t(pub ::core::ffi::c_uint); -impl stream_protocol_t { - pub const STREAM_PROTOCOL_TCP: Self = Self(0); - pub const STREAM_PROTOCOL_UDP: Self = Self(1); +pub struct event_producer_enqueue_result_t(pub ::core::ffi::c_int); +impl event_producer_enqueue_result_t { + pub const EVENT_PRODUCER_ENQUEUE_FAILED: Self = Self(-1); + pub const EVENT_PRODUCER_ENQUEUE_ENQUEUED: Self = Self(0); + pub const EVENT_PRODUCER_ENQUEUE_DUPLICATE: Self = Self(1); + pub const EVENT_PRODUCER_ENQUEUE_NO_ROUTE: Self = Self(2); + pub const EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED: Self = Self(3); + pub const EVENT_PRODUCER_ENQUEUE_FULL: Self = Self(4); } #[derive(Copy, Clone)] #[repr(C)] @@ -602,7 +534,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -634,6 +566,79 @@ pub struct detection_engine_config_t { pub num_threads: ::core::ffi::c_int, pub delegate: [::core::ffi::c_char; 16], } +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stream_config_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub location_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub url: [::core::ffi::c_char; 512], + pub enabled: bool, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub fps: ::core::ffi::c_int, + pub codec: [::core::ffi::c_char; 16], + pub priority: ::core::ffi::c_int, + pub record: bool, + pub segment_duration: ::core::ffi::c_int, + pub detection_based_recording: bool, + pub detection_model: [::core::ffi::c_char; 512], + pub detection_interval: ::core::ffi::c_int, + pub detection_threshold: ::core::ffi::c_float, + pub pre_detection_buffer: ::core::ffi::c_int, + pub post_detection_buffer: ::core::ffi::c_int, + pub detection_api_url: [::core::ffi::c_char; 512], + pub detection_object_filter: [::core::ffi::c_char; 16], + pub detection_object_filter_list: [::core::ffi::c_char; 256], + pub streaming_enabled: bool, + pub protocol: stream_protocol_t, + pub record_audio: bool, + pub audio_voice_enhancement: bool, + pub onvif_username: [::core::ffi::c_char; 64], + pub onvif_password: [::core::ffi::c_char; 64], + pub onvif_profile: [::core::ffi::c_char; 64], + pub onvif_port: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub is_onvif: bool, + pub backchannel_enabled: bool, + pub retention_days: ::core::ffi::c_int, + pub detection_retention_days: ::core::ffi::c_int, + pub max_storage_mb: ::core::ffi::c_int, + pub tier_critical_multiplier: ::core::ffi::c_double, + pub tier_important_multiplier: ::core::ffi::c_double, + pub tier_ephemeral_multiplier: ::core::ffi::c_double, + pub storage_priority: ::core::ffi::c_int, + pub ptz_enabled: bool, + pub ptz_max_x: ::core::ffi::c_int, + pub ptz_max_y: ::core::ffi::c_int, + pub ptz_max_z: ::core::ffi::c_int, + pub ptz_has_home: bool, + pub record_on_schedule: bool, + pub recording_schedule: [uint8_t; 168], + pub detection_record_on_schedule: bool, + pub detection_recording_schedule: [uint8_t; 168], + pub tags: [::core::ffi::c_char; 256], + pub admin_url: [::core::ffi::c_char; 512], + pub privacy_mode: bool, + pub motion_trigger_source: [::core::ffi::c_char; 256], + pub go2rtc_source_override: [::core::ffi::c_char; 8192], + pub sub_stream_url: [::core::ffi::c_char; 512], + pub detection_url: [::core::ffi::c_char; 512], + pub publish_url: [::core::ffi::c_char; 512], + pub playback_transport: [::core::ffi::c_char; 24], + pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_protocol_t(pub ::core::ffi::c_uint); +impl stream_protocol_t { + pub const STREAM_PROTOCOL_TCP: Self = Self(0); + pub const STREAM_PROTOCOL_UDP: Self = Self(1); +} #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] pub struct event_router_result_t(pub ::core::ffi::c_int); @@ -643,11 +648,21 @@ impl event_router_result_t { pub const EVENT_ROUTER_MATCH: Self = Self(1); pub const EVENT_ROUTER_DEFAULT: Self = Self(2); } +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_bus_result_t(pub ::core::ffi::c_int); +impl event_bus_result_t { + pub const EVENT_BUS_OK: Self = Self(0); + pub const EVENT_BUS_NOT_RUNNING: Self = Self(-1); + pub const EVENT_BUS_INVALID_EVENT: Self = Self(-2); + pub const EVENT_BUS_QUEUE_FULL: Self = Self(-3); + pub const EVENT_BUS_ALLOCATION_FAILED: Self = Self(-4); +} +pub const EVENT_SUBJECT_MAX: ::core::ffi::c_int = 96 as ::core::ffi::c_int; +pub const EVENT_TIME_MAX: ::core::ffi::c_int = 32 as ::core::ffi::c_int; pub const SYSTEM_HEALTH_ID_LENGTH: ::core::ffi::c_uint = 64 as ::core::ffi::c_uint; pub const MAX_DETECTIONS: ::core::ffi::c_int = 20 as ::core::ffi::c_int; pub const MAX_STREAM_NAME: ::core::ffi::c_int = 256 as ::core::ffi::c_int; -pub const EVENT_SUBJECT_MAX: ::core::ffi::c_int = 96 as ::core::ffi::c_int; -pub const EVENT_TIME_MAX: ::core::ffi::c_int = 32 as ::core::ffi::c_int; pub const EVENT_ROUTE_DEFAULT_DESTINATION: [::core::ffi::c_char; 13] = unsafe { ::core::mem::transmute::<[u8; 13], [::core::ffi::c_char; 13]>(*b"mqtt:default\0") }; pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); @@ -833,11 +848,50 @@ unsafe extern "C" fn publish_event( error_size, ); } -unsafe extern "C" fn durably_enqueue_event( +#[unsafe(no_mangle)] +pub unsafe extern "C" fn event_producer_enqueue_result_name( + mut result: event_producer_enqueue_result_t, +) -> *const ::core::ffi::c_char { + match result { + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED => { + return b"failed\0".as_ptr() as *const ::core::ffi::c_char; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_ENQUEUED => { + return b"enqueued\0".as_ptr() as *const ::core::ffi::c_char; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_DUPLICATE => { + return b"duplicate\0".as_ptr() as *const ::core::ffi::c_char; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_NO_ROUTE => { + return b"no_route\0".as_ptr() as *const ::core::ffi::c_char; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED => { + return b"mqtt_disabled\0".as_ptr() as *const ::core::ffi::c_char; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FULL => { + return b"full\0".as_ptr() as *const ::core::ffi::c_char; + } + _ => {} + } + return b"unknown\0".as_ptr() as *const ::core::ffi::c_char; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn event_producer_enqueue_durable( mut event: *const event_envelope_t, mut error: *mut ::core::ffi::c_char, mut error_size: size_t, -) -> ::core::ffi::c_int { +) -> event_producer_enqueue_result_t { + if !error.is_null() && error_size > 0 as size_t { + *error.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if event.is_null() { + set_error( + error, + error_size, + b"event is required\0".as_ptr() as *const ::core::ffi::c_char, + ); + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED; + } let mut plan: event_route_delivery_plan_t = event_route_delivery_plan_t { event_id: [ 0 as ::core::ffi::c_char, @@ -893,12 +947,12 @@ unsafe extern "C" fn durably_enqueue_event( error_size, b"event route evaluation failed\0".as_ptr() as *const ::core::ffi::c_char, ); - return -1 as ::core::ffi::c_int; + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED; } if route_result.0 == event_router_result_t::EVENT_ROUTER_DEFAULT.0 { if !g_config.mqtt_enabled { event_route_delivery_plan_clear(&raw mut plan); - return 0 as ::core::ffi::c_int; + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED; } let mut result: event_outbox_enqueue_result_t = mqtt_delivery_worker_enqueue( event, @@ -906,23 +960,33 @@ unsafe extern "C" fn durably_enqueue_event( ::core::ptr::null_mut::(), ); event_route_delivery_plan_clear(&raw mut plan); - if result.0 == event_outbox_enqueue_result_t::EVENT_OUTBOX_ENQUEUED.0 - || result.0 == event_outbox_enqueue_result_t::EVENT_OUTBOX_DUPLICATE.0 - { - return 0 as ::core::ffi::c_int; + if result.0 == event_outbox_enqueue_result_t::EVENT_OUTBOX_ENQUEUED.0 { + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_ENQUEUED; + } + if result.0 == event_outbox_enqueue_result_t::EVENT_OUTBOX_DUPLICATE.0 { + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_DUPLICATE; } set_error( error, error_size, b"durable event outbox enqueue failed\0".as_ptr() as *const ::core::ffi::c_char, ); - return -1 as ::core::ffi::c_int; + return event_producer_enqueue_result_t( + if result.0 == event_outbox_enqueue_result_t::EVENT_OUTBOX_FULL.0 { + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FULL.0 + } else { + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED.0 + }, + ); } if route_result.0 == event_router_result_t::EVENT_ROUTER_NO_MATCH.0 { event_route_delivery_plan_clear(&raw mut plan); - return 0 as ::core::ffi::c_int; + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_NO_ROUTE; } let mut failed: bool = r#false != 0; + let mut any_error: bool = r#false != 0; + let mut any_enqueued: bool = r#false != 0; + let mut any_duplicate: bool = r#false != 0; let mut destination_accepted: [bool; 512] = [ r#false != 0, false, @@ -1456,7 +1520,7 @@ unsafe extern "C" fn durably_enqueue_event( previous = previous.wrapping_add(1); } } - 's_82: { + 's_113: { if !duplicate_destination { let mut result_0: event_outbox_enqueue_result_t = event_outbox_enqueue_result_t::EVENT_OUTBOX_ENQUEUED; @@ -1466,7 +1530,7 @@ unsafe extern "C" fn durably_enqueue_event( ) == 0 as ::core::ffi::c_int { if !g_config.mqtt_enabled { - break 's_82; + break 's_113; } else { result_0 = mqtt_delivery_worker_enqueue( event, @@ -1486,8 +1550,16 @@ unsafe extern "C" fn durably_enqueue_event( && result_0.0 != event_outbox_enqueue_result_t::EVENT_OUTBOX_DUPLICATE.0 { failed = r#true != 0; + if result_0.0 != event_outbox_enqueue_result_t::EVENT_OUTBOX_FULL.0 { + any_error = r#true != 0; + } } else { destination_accepted[index] = r#true != 0; + if result_0.0 == event_outbox_enqueue_result_t::EVENT_OUTBOX_ENQUEUED.0 { + any_enqueued = r#true != 0; + } else { + any_duplicate = r#true != 0; + } } } } @@ -1522,6 +1594,7 @@ unsafe extern "C" fn durably_enqueue_event( ) != 0 as ::core::ffi::c_int { failed = r#true != 0; + any_error = r#true != 0; } index_0 = index_0.wrapping_add(1); } @@ -1533,9 +1606,34 @@ unsafe extern "C" fn durably_enqueue_event( error_size, b"durable event route enqueue failed\0".as_ptr() as *const ::core::ffi::c_char, ); - return -1 as ::core::ffi::c_int; + return event_producer_enqueue_result_t(if any_error as ::core::ffi::c_int != 0 { + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED.0 + } else { + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FULL.0 + }); } - return 0 as ::core::ffi::c_int; + if any_enqueued { + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_ENQUEUED; + } + if any_duplicate { + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_DUPLICATE; + } + return event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED; +} +unsafe extern "C" fn durably_enqueue_event( + mut event: *const event_envelope_t, + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, +) -> ::core::ffi::c_int { + let mut result: event_producer_enqueue_result_t = + event_producer_enqueue_durable(event, error, error_size); + return if result.0 == event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED.0 + || result.0 == event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FULL.0 + { + -1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }; } unsafe extern "C" fn camera_subject_for_stream( mut stream_name: *const ::core::ffi::c_char, @@ -1610,6 +1708,10 @@ unsafe extern "C" fn camera_subject_for_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name(stream_name, &raw mut stream) != 0 as ::core::ffi::c_int || !lightnvr_uuid_is_valid(&raw mut stream.camera_uuid as *mut ::core::ffi::c_char) @@ -2003,6 +2105,10 @@ pub unsafe extern "C" fn event_producer_publish_detection_for_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name(stream_name, &raw mut stream) != 0 as ::core::ffi::c_int || !lightnvr_uuid_is_valid(&raw mut stream.camera_uuid as *mut ::core::ffi::c_char) diff --git a/rust/generated/src__core__main.rs b/rust/generated/src__core__main.rs index 0fa026eee..19e71904b 100644 --- a/rust/generated/src__core__main.rs +++ b/rust/generated/src__core__main.rs @@ -193,6 +193,28 @@ unsafe extern "C" { unsafe fn mqtt_cleanup(); unsafe fn mqtt_delivery_worker_start() -> ::core::ffi::c_int; unsafe fn mqtt_delivery_worker_shutdown(); + unsafe fn sqlite3_shutdown() -> ::core::ffi::c_int; + unsafe fn sqlite3_release_memory(_: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn observation_evidence_init( + config_0: *const observation_evidence_config_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_evidence_shutdown(drain_timeout_ms: ::core::ffi::c_int); + unsafe fn observation_evidence_set_enabled_probe( + r#fn: observation_evidence_enabled_fn, + ctx: *mut ::core::ffi::c_void, + ); + unsafe fn observation_evidence_stream_enabled_probe( + camera_uuid: *const ::core::ffi::c_char, + ctx: *mut ::core::ffi::c_void, + ) -> bool; + unsafe fn observation_relay_start( + options: *const observation_relay_options_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_relay_stop(); + unsafe fn observation_retention_startup( + storage_path: *const ::core::ffi::c_char, + result: *mut observation_reconcile_result_t, + ) -> ::core::ffi::c_int; unsafe fn mkdir_recursive(path: *const ::core::ffi::c_char) -> ::core::ffi::c_int; unsafe fn ensure_path(path: *const ::core::ffi::c_char) -> ::core::ffi::c_int; unsafe fn safe_strcpy( @@ -297,8 +319,6 @@ unsafe extern "C" { ) -> ::core::ffi::c_int; unsafe fn go2rtc_integration_full_start() -> bool; unsafe fn go2rtc_integration_cleanup(); - unsafe fn sqlite3_shutdown() -> ::core::ffi::c_int; - unsafe fn sqlite3_release_memory(_: ::core::ffi::c_int) -> ::core::ffi::c_int; unsafe fn init_database_ex( db_path: *const ::core::ffi::c_char, flags: ::core::ffi::c_uint, @@ -315,6 +335,11 @@ unsafe extern "C" { interval_seconds: ::core::ffi::c_int, ) -> ::core::ffi::c_int; unsafe fn stop_recording_sync_thread() -> ::core::ffi::c_int; + unsafe fn db_get_system_setting( + key: *const ::core::ffi::c_char, + out: *mut ::core::ffi::c_char, + out_len: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; unsafe fn db_get_system_setting_alloc( key: *const ::core::ffi::c_char, out: *mut *mut ::core::ffi::c_char, @@ -364,6 +389,11 @@ unsafe extern "C" { __endptr: *mut *mut ::core::ffi::c_char, __base: ::core::ffi::c_int, ) -> ::core::ffi::c_long; + unsafe fn strtoll( + __nptr: *const ::core::ffi::c_char, + __endptr: *mut *mut ::core::ffi::c_char, + __base: ::core::ffi::c_int, + ) -> ::core::ffi::c_longlong; unsafe fn free(__ptr: *mut ::core::ffi::c_void); unsafe fn exit(__status: ::core::ffi::c_int) -> !; unsafe fn getenv(__name: *const ::core::ffi::c_char) -> *mut ::core::ffi::c_char; @@ -394,6 +424,7 @@ pub type pid_t = __pid_t; pub type ssize_t = isize; pub type clockid_t = __clockid_t; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct __sigset_t { @@ -842,6 +873,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -947,7 +982,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -989,6 +1024,35 @@ impl component_state_t { pub const COMPONENT_STOPPING: Self = Self(1); pub const COMPONENT_STOPPED: Self = Self(2); } +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_config_t { + pub storage_path: *const ::core::ffi::c_char, + pub global_max_bytes: int64_t, + pub global_max_items: ::core::ffi::c_int, + pub per_camera_max_items: ::core::ffi::c_int, + pub per_camera_max_bytes: int64_t, +} +pub type observation_evidence_enabled_fn = + Option bool>; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_options_t { + pub batch_size: ::core::ffi::c_int, + pub poll_interval_ms: ::core::ffi::c_int, + pub outbox_cap: ::core::ffi::c_int, + pub startup_drain_ms: ::core::ffi::c_int, + pub storage_path: *const ::core::ffi::c_char, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_reconcile_result_t { + pub tmp_files_deleted: ::core::ffi::c_int, + pub stale_files_deleted: ::core::ffi::c_int, + pub pending_interrupted: ::core::ffi::c_int, + pub ready_missing: ::core::ffi::c_int, + pub epoch_regenerated: bool, +} pub type stream_handle_t = *mut stream_handle_s; #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -1141,6 +1205,7 @@ unsafe extern "C" fn signal_safe_write(mut msg: *const ::core::ffi::c_char) { } *__errno_location() = saved_errno; } +pub const OBS_EVIDENCE_DEFAULT_DRAIN_MS: ::core::ffi::c_int = 2000 as ::core::ffi::c_int; #[unsafe(no_mangle)] pub static mut running: bool = r#true != 0; #[unsafe(no_mangle)] @@ -1247,7 +1312,7 @@ pub static mut config: config_t = config_t { go2rtc_webrtc_listen_port: 0, go2rtc_stun_enabled: false, go2rtc_stun_server: [0; 256], - go2rtc_external_ip: [0; 64], + go2rtc_external_ip: [0; 256], go2rtc_ice_servers: [0; 512], webrtc_connection_timeout_ms: 0, webrtc_ice_recovery_timeout_ms: 0, @@ -3024,6 +3089,118 @@ unsafe fn main_0( _log_stream_name, b"Storage manager initialized\0".as_ptr() as *const ::core::ffi::c_char, ); + let mut observation_reconcile: observation_reconcile_result_t = + observation_reconcile_result_t { + tmp_files_deleted: 0, + stale_files_deleted: 0, + pending_interrupted: 0, + ready_missing: 0, + epoch_regenerated: false, + }; + if observation_retention_startup( + &raw mut config.storage_path as *mut ::core::ffi::c_char, + &raw mut observation_reconcile, + ) != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation evidence reconciliation incomplete; evidence eviction disabled\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + } + let mut evidence_config: observation_evidence_config_t = + observation_evidence_config_t { + storage_path: ::core::ptr::null::<::core::ffi::c_char>(), + global_max_bytes: 0, + global_max_items: 0, + per_camera_max_items: 0, + per_camera_max_bytes: 0, + }; + let mut queue_bytes: [::core::ffi::c_char; 32] = [ + 0 as ::core::ffi::c_char, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + evidence_config.storage_path = + &raw mut config.storage_path as *mut ::core::ffi::c_char; + if db_get_system_setting( + b"observation_global_queue_bytes\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut queue_bytes as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>() as ::core::ffi::c_int, + ) == 0 as ::core::ffi::c_int + && queue_bytes[0usize] as ::core::ffi::c_int != 0 + { + let mut value: ::core::ffi::c_longlong = strtoll( + &raw mut queue_bytes as *mut ::core::ffi::c_char, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + 10 as ::core::ffi::c_int, + ); + if value > 0 as ::core::ffi::c_longlong { + evidence_config.global_max_bytes = value as int64_t; + } + } + if observation_evidence_init(&raw mut evidence_config) != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to initialize observation evidence; evidence admission disabled\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + } else { + observation_evidence_set_enabled_probe( + Some( + observation_evidence_stream_enabled_probe + as unsafe extern "C" fn( + *const ::core::ffi::c_char, + *mut ::core::ffi::c_void, + ) -> bool, + ), + NULL, + ); + } + if observation_relay_start(::core::ptr::null::()) + != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to start observation publication relay; staged observation changes stay pending\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + } health_overrides = ::core::ptr::null_mut::<::core::ffi::c_char>(); health_overrides_length = 0 as size_t; health_overrides_result = db_get_system_setting_alloc( @@ -4713,6 +4890,8 @@ unsafe fn main_0( b"Cleaning up detection resources...\0".as_ptr() as *const ::core::ffi::c_char, ); cleanup_detection_resources(); + observation_relay_stop(); + observation_evidence_shutdown(OBS_EVIDENCE_DEFAULT_DRAIN_MS); _log_message_ctx( log_level_t::LOG_LEVEL_INFO, LOG_COMPONENT, @@ -4929,6 +5108,8 @@ unsafe fn main_0( cleanup_mp4_recording_backend(); cleanup_hls_streaming_backend(); cleanup_transcoding_backend(); + observation_relay_stop(); + observation_evidence_shutdown(OBS_EVIDENCE_DEFAULT_DRAIN_MS); event_bus_shutdown(r#true != 0); mqtt_event_adapter_unregister(); event_identity_shutdown(); diff --git a/rust/generated/src__core__mqtt_client.rs b/rust/generated/src__core__mqtt_client.rs index dc0636b4f..9bc403613 100644 --- a/rust/generated/src__core__mqtt_client.rs +++ b/rust/generated/src__core__mqtt_client.rs @@ -823,6 +823,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -928,7 +932,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -2457,6 +2461,10 @@ pub unsafe extern "C" fn mqtt_publish_detection( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let snapshot_identity_valid: bool = get_stream_config_by_name(stream_name, &raw mut snapshot_camera) == 0 as ::core::ffi::c_int @@ -2532,6 +2540,10 @@ pub unsafe extern "C" fn mqtt_publish_detection( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name(stream_name, &raw mut current_camera) == 0 as ::core::ffi::c_int diff --git a/rust/generated/src__core__mqtt_event_adapter.rs b/rust/generated/src__core__mqtt_event_adapter.rs index 414a36fb2..e356089b1 100644 --- a/rust/generated/src__core__mqtt_event_adapter.rs +++ b/rust/generated/src__core__mqtt_event_adapter.rs @@ -162,6 +162,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -267,7 +271,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__core__observation_diagnostics.rs b/rust/generated/src__core__observation_diagnostics.rs new file mode 100644 index 000000000..c95f1f13a --- /dev/null +++ b/rust/generated/src__core__observation_diagnostics.rs @@ -0,0 +1,633 @@ +unsafe extern "C" { + unsafe fn cJSON_CreateNull() -> *mut cJSON; + unsafe fn cJSON_CreateString(string: *const ::core::ffi::c_char) -> *mut cJSON; + unsafe fn cJSON_CreateObject() -> *mut cJSON; + unsafe fn cJSON_AddItemToArray(array: *mut cJSON, item: *mut cJSON) -> cJSON_bool; + unsafe fn cJSON_AddItemToObject( + object: *mut cJSON, + string: *const ::core::ffi::c_char, + item: *mut cJSON, + ) -> cJSON_bool; + unsafe fn cJSON_AddBoolToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + boolean: cJSON_bool, + ) -> *mut cJSON; + unsafe fn cJSON_AddNumberToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + number: ::core::ffi::c_double, + ) -> *mut cJSON; + unsafe fn cJSON_AddStringToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_AddObjectToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_AddArrayToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn db_observation_diagnostics_get( + out: *mut observation_db_diagnostics_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_evidence_get_stats(out: *mut observation_evidence_stats_t); + unsafe fn gmtime_r(__timer: *const time_t, __tp: *mut tm) -> *mut tm; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn observation_relay_get_stats(stats: *mut observation_relay_stats_t); + unsafe fn observation_retention_budget_exhausted_count() -> ::core::ffi::c_int; + unsafe fn observation_retention_camera_status( + out: *mut observation_retention_camera_status_t, + max: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __time_t = ::core::ffi::c_long; +pub type __clockid_t = ::core::ffi::c_int; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct tm { + pub tm_sec: ::core::ffi::c_int, + pub tm_min: ::core::ffi::c_int, + pub tm_hour: ::core::ffi::c_int, + pub tm_mday: ::core::ffi::c_int, + pub tm_mon: ::core::ffi::c_int, + pub tm_year: ::core::ffi::c_int, + pub tm_wday: ::core::ffi::c_int, + pub tm_yday: ::core::ffi::c_int, + pub tm_isdst: ::core::ffi::c_int, + pub tm_gmtoff: ::core::ffi::c_long, + pub tm_zone: *const ::core::ffi::c_char, +} +pub type uint32_t = u32; +pub type uint64_t = u64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +pub type cJSON_bool = ::core::ffi::c_int; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_db_diagnostics_t { + pub meta: observation_meta_t, + pub has_oldest_observed_at: bool, + pub oldest_observed_at_ms: int64_t, + pub observations: int64_t, + pub publications_pending: int64_t, + pub publications_failed: int64_t, + pub publications_expired: int64_t, + pub oldest_pending_created_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_stats_t { + pub initialized: bool, + pub worker_running: bool, + pub allocated: bool, + pub accepted: uint64_t, + pub dropped_queue_full: uint64_t, + pub dropped_budget: uint64_t, + pub dropped_disabled: uint64_t, + pub dropped_untracked: uint64_t, + pub unavailable_submitted: uint64_t, + pub ready: uint64_t, + pub failed_encode: uint64_t, + pub failed_write: uint64_t, + pub failed_db: uint64_t, + pub disabled_at_encode: uint64_t, + pub interrupted: uint64_t, + pub bytes_written: uint64_t, + pub backlog_items: uint32_t, + pub backlog_bytes: uint64_t, + pub drop_ring_depth: uint32_t, + pub high_water_items: uint32_t, + pub high_water_bytes: uint64_t, + pub budget_exhausted_cameras: uint32_t, + pub limit_items: uint32_t, + pub limit_camera_items: uint32_t, + pub limit_bytes: uint64_t, + pub limit_camera_bytes: uint64_t, + pub camera_high_water_items: uint32_t, + pub submit_latency_count: uint64_t, + pub submit_latency_sum_ns: uint64_t, + pub submit_latency_max_ns: uint64_t, + pub submit_latency_bucket: [uint64_t; 10], + pub discard_deferred: uint64_t, + pub discard_inline: uint64_t, + pub discard_pending_bytes: uint64_t, + pub limit_discard_bytes: uint64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_stats_t { + pub running: bool, + pub passes: uint64_t, + pub relayed: uint64_t, + pub duplicates: uint64_t, + pub relayed_no_route: uint64_t, + pub expired: uint64_t, + pub failed: uint64_t, + pub enqueue_full: uint64_t, + pub enqueue_errors: uint64_t, + pub mark_errors: uint64_t, + pub read_errors: uint64_t, + pub cap_pauses: uint64_t, + pub backoffs: uint64_t, + pub sidecar_writes: uint64_t, + pub sidecar_errors: uint64_t, + pub epoch_regenerations: uint64_t, + pub last_relayed_seq: int64_t, + pub oldest_pending_created_at_ms: int64_t, + pub last_pass_pending: ::core::ffi::c_int, + pub outbox_type_pending: int64_t, + pub consecutive_failures: ::core::ffi::c_int, + pub backoff_until_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_retention_camera_status_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub retained_bytes: int64_t, + pub max_bytes: int64_t, + pub max_age_days: ::core::ffi::c_int, + pub budget_exhausted: bool, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_diagnostics_snapshot { + pub collected_at_ms: int64_t, + pub evidence: observation_evidence_stats_t, + pub relay: observation_relay_stats_t, + pub db_available: bool, + pub db: observation_db_diagnostics_t, + pub cameras: *mut observation_retention_camera_status_t, + pub camera_count: ::core::ffi::c_int, + pub budget_exhausted_cameras: ::core::ffi::c_int, +} +pub type observation_diagnostics_t = observation_diagnostics_snapshot; +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const OBS_SNAPSHOT_RETRY_AFTER_MAX: ::core::ffi::c_int = 30 as ::core::ffi::c_int; +unsafe extern "C" fn wall_ms() -> int64_t { + let mut ts: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_REALTIME, &raw mut ts); + return ts.tv_sec as int64_t * 1000 as int64_t + ts.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn collect_cameras(mut out: *mut observation_diagnostics_t) { + let mut capacity: ::core::ffi::c_int = 64 as ::core::ffi::c_int; + let mut attempt: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while attempt < 8 as ::core::ffi::c_int { + let mut rows: *mut observation_retention_camera_status_t = calloc( + capacity as size_t, + ::core::mem::size_of::(), + ) + as *mut observation_retention_camera_status_t; + if rows.is_null() { + return; + } + let mut count: ::core::ffi::c_int = observation_retention_camera_status(rows, capacity); + if count < capacity || attempt == 7 as ::core::ffi::c_int { + if count <= 0 as ::core::ffi::c_int { + free(rows as *mut ::core::ffi::c_void); + return; + } + (*out).cameras = rows; + (*out).camera_count = count; + return; + } + free(rows as *mut ::core::ffi::c_void); + capacity *= 2 as ::core::ffi::c_int; + attempt += 1; + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_diagnostics_collect( + mut out: *mut observation_diagnostics_t, + mut now_ms: int64_t, +) -> ::core::ffi::c_int { + if out.is_null() { + return -1 as ::core::ffi::c_int; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*out).collected_at_ms = if now_ms > 0 as int64_t { + now_ms + } else { + wall_ms() + }; + observation_evidence_get_stats(&raw mut (*out).evidence); + observation_relay_get_stats(&raw mut (*out).relay); + (*out).db_available = + db_observation_diagnostics_get(&raw mut (*out).db) == C2Rust_Unnamed::DB_OBS_OK.0; + collect_cameras(out); + (*out).budget_exhausted_cameras = observation_retention_budget_exhausted_count(); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_diagnostics_free( + mut diagnostics: *mut observation_diagnostics_t, +) { + if diagnostics.is_null() { + return; + } + free((*diagnostics).cameras as *mut ::core::ffi::c_void); + (*diagnostics).cameras = ::core::ptr::null_mut::(); + (*diagnostics).camera_count = 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_diagnostics_relay_lag_seconds( + mut diagnostics: *const observation_diagnostics_t, +) -> ::core::ffi::c_double { + if diagnostics.is_null() || !(*diagnostics).db_available { + return 0.0f64; + } + let mut oldest: int64_t = (*diagnostics).db.oldest_pending_created_at_ms; + if oldest <= 0 as int64_t || (*diagnostics).collected_at_ms <= oldest { + return 0.0f64; + } + return ((*diagnostics).collected_at_ms - oldest) as ::core::ffi::c_double / 1000.0f64; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_snapshot_retry_after_seconds( + mut asset_age_ms: int64_t, + mut items_ahead: uint64_t, +) -> ::core::ffi::c_int { + if asset_age_ms < 5000 as int64_t { + return 1 as ::core::ffi::c_int; + } + if items_ahead >= (OBS_SNAPSHOT_RETRY_AFTER_MAX / 2 as ::core::ffi::c_int) as uint64_t { + return OBS_SNAPSHOT_RETRY_AFTER_MAX; + } + let mut seconds: ::core::ffi::c_int = + (2 as uint64_t).wrapping_mul(items_ahead) as ::core::ffi::c_int; + return if seconds < 1 as ::core::ffi::c_int { + 1 as ::core::ffi::c_int + } else { + seconds + }; +} +unsafe extern "C" fn add_u64( + mut object: *mut cJSON, + mut name: *const ::core::ffi::c_char, + mut value: uint64_t, +) { + cJSON_AddNumberToObject(object, name, value as ::core::ffi::c_double); +} +unsafe extern "C" fn time_or_null(mut present: bool, mut ms: int64_t) -> *mut cJSON { + if !present || ms <= 0 as int64_t { + return cJSON_CreateNull(); + } + let mut seconds: time_t = (ms / 1000 as int64_t) as time_t; + let mut tm: tm = tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: ::core::ptr::null::<::core::ffi::c_char>(), + }; + if gmtime_r(&raw mut seconds, &raw mut tm).is_null() { + return cJSON_CreateNull(); + } + let mut text: [::core::ffi::c_char; 64] = [0; 64]; + snprintf( + &raw mut text as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + b"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ\0".as_ptr() as *const ::core::ffi::c_char, + tm.tm_year + 1900 as ::core::ffi::c_int, + tm.tm_mon + 1 as ::core::ffi::c_int, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + (ms % 1000 as int64_t) as ::core::ffi::c_int, + ); + return cJSON_CreateString(&raw mut text as *mut ::core::ffi::c_char); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_diagnostics_to_json( + mut d: *const observation_diagnostics_t, +) -> *mut cJSON { + if d.is_null() { + return ::core::ptr::null_mut::(); + } + let mut e: *const observation_evidence_stats_t = &raw const (*d).evidence; + let mut root: *mut cJSON = cJSON_CreateObject(); + if root.is_null() { + return ::core::ptr::null_mut::(); + } + let mut active: bool = (*e).allocated as ::core::ffi::c_int != 0 + || (*e).accepted != 0 + || (*e).dropped_queue_full != 0 + || (*e).dropped_budget != 0 + || (*e).dropped_untracked != 0 + || (*e).unavailable_submitted != 0 + || (*d).db_available as ::core::ffi::c_int != 0 && (*d).db.observations > 0 as int64_t; + cJSON_AddBoolToObject( + root, + b"active\0".as_ptr() as *const ::core::ffi::c_char, + active as cJSON_bool, + ); + add_u64( + root, + b"accepted\0".as_ptr() as *const ::core::ffi::c_char, + (*e).accepted, + ); + let mut dropped: *mut cJSON = + cJSON_AddObjectToObject(root, b"dropped\0".as_ptr() as *const ::core::ffi::c_char); + if !dropped.is_null() { + add_u64( + dropped, + b"queue_full\0".as_ptr() as *const ::core::ffi::c_char, + (*e).dropped_queue_full, + ); + add_u64( + dropped, + b"budget_exhausted\0".as_ptr() as *const ::core::ffi::c_char, + (*e).dropped_budget, + ); + add_u64( + dropped, + b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + (*e).dropped_disabled, + ); + add_u64( + dropped, + b"untracked\0".as_ptr() as *const ::core::ffi::c_char, + (*e).dropped_untracked, + ); + } + let mut evidence: *mut cJSON = + cJSON_AddObjectToObject(root, b"evidence\0".as_ptr() as *const ::core::ffi::c_char); + if !evidence.is_null() { + cJSON_AddBoolToObject( + evidence, + b"queue_allocated\0".as_ptr() as *const ::core::ffi::c_char, + (*e).allocated as cJSON_bool, + ); + cJSON_AddBoolToObject( + evidence, + b"worker_running\0".as_ptr() as *const ::core::ffi::c_char, + (*e).worker_running as cJSON_bool, + ); + add_u64( + evidence, + b"backlog_items\0".as_ptr() as *const ::core::ffi::c_char, + (*e).backlog_items as uint64_t, + ); + add_u64( + evidence, + b"backlog_bytes\0".as_ptr() as *const ::core::ffi::c_char, + (*e).backlog_bytes, + ); + add_u64( + evidence, + b"high_water_items\0".as_ptr() as *const ::core::ffi::c_char, + (*e).high_water_items as uint64_t, + ); + add_u64( + evidence, + b"high_water_bytes\0".as_ptr() as *const ::core::ffi::c_char, + (*e).high_water_bytes, + ); + add_u64( + evidence, + b"drop_ring_depth\0".as_ptr() as *const ::core::ffi::c_char, + (*e).drop_ring_depth as uint64_t, + ); + add_u64( + evidence, + b"ready\0".as_ptr() as *const ::core::ffi::c_char, + (*e).ready, + ); + add_u64( + evidence, + b"unavailable_submitted\0".as_ptr() as *const ::core::ffi::c_char, + (*e).unavailable_submitted, + ); + add_u64( + evidence, + b"bytes_written\0".as_ptr() as *const ::core::ffi::c_char, + (*e).bytes_written, + ); + let mut failures: *mut cJSON = cJSON_AddObjectToObject( + evidence, + b"failures\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !failures.is_null() { + add_u64( + failures, + b"encode_failed\0".as_ptr() as *const ::core::ffi::c_char, + (*e).failed_encode, + ); + add_u64( + failures, + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + (*e).failed_write, + ); + add_u64( + failures, + b"db_error\0".as_ptr() as *const ::core::ffi::c_char, + (*e).failed_db, + ); + add_u64( + failures, + b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + (*e).disabled_at_encode, + ); + add_u64( + failures, + b"interrupted\0".as_ptr() as *const ::core::ffi::c_char, + (*e).interrupted, + ); + } + } + let mut history: *mut cJSON = + cJSON_AddObjectToObject(root, b"history\0".as_ptr() as *const ::core::ffi::c_char); + if !history.is_null() { + cJSON_AddBoolToObject( + history, + b"available\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db_available as cJSON_bool, + ); + cJSON_AddNumberToObject( + history, + b"observations\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.observations as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + history, + b"oldest_seq\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.meta.oldest_seq as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + history, + b"head_seq\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.meta.head_seq as ::core::ffi::c_double, + ); + cJSON_AddItemToObject( + history, + b"oldest_observed_at\0".as_ptr() as *const ::core::ffi::c_char, + time_or_null( + (*d).db_available as ::core::ffi::c_int != 0 + && (*d).db.has_oldest_observed_at as ::core::ffi::c_int != 0, + (*d).db.oldest_observed_at_ms, + ), + ); + add_u64( + history, + b"epoch_regenerations\0".as_ptr() as *const ::core::ffi::c_char, + (*d).relay.epoch_regenerations, + ); + } + let mut publications: *mut cJSON = cJSON_AddObjectToObject( + root, + b"publications\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !publications.is_null() { + cJSON_AddNumberToObject( + publications, + b"pending\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.publications_pending as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + publications, + b"failed\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.publications_failed as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + publications, + b"expired\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.publications_expired as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + publications, + b"relay_lag_seconds\0".as_ptr() as *const ::core::ffi::c_char, + observation_diagnostics_relay_lag_seconds(d), + ); + cJSON_AddBoolToObject( + publications, + b"relay_running\0".as_ptr() as *const ::core::ffi::c_char, + (*d).relay.running as cJSON_bool, + ); + add_u64( + publications, + b"relayed\0".as_ptr() as *const ::core::ffi::c_char, + (*d).relay.relayed, + ); + add_u64( + publications, + b"relayed_no_route\0".as_ptr() as *const ::core::ffi::c_char, + (*d).relay.relayed_no_route, + ); + } + cJSON_AddNumberToObject( + root, + b"budget_exhausted_cameras\0".as_ptr() as *const ::core::ffi::c_char, + (*d).budget_exhausted_cameras as ::core::ffi::c_double, + ); + let mut cameras: *mut cJSON = + cJSON_AddArrayToObject(root, b"cameras\0".as_ptr() as *const ::core::ffi::c_char); + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while !cameras.is_null() && i < (*d).camera_count { + let mut c: *const observation_retention_camera_status_t = (*d).cameras.offset(i as isize); + let mut item: *mut cJSON = cJSON_CreateObject(); + if item.is_null() { + break; + } + cJSON_AddStringToObject( + item, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*c).camera_uuid as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + item, + b"retained_bytes\0".as_ptr() as *const ::core::ffi::c_char, + (*c).retained_bytes as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + item, + b"max_bytes\0".as_ptr() as *const ::core::ffi::c_char, + (*c).max_bytes as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + item, + b"max_age_days\0".as_ptr() as *const ::core::ffi::c_char, + (*c).max_age_days as ::core::ffi::c_double, + ); + cJSON_AddBoolToObject( + item, + b"budget_exhausted\0".as_ptr() as *const ::core::ffi::c_char, + (*c).budget_exhausted as cJSON_bool, + ); + cJSON_AddItemToArray(cameras, item); + i += 1; + } + return root; +} +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); diff --git a/rust/generated/src__core__observation_evidence.rs b/rust/generated/src__core__observation_evidence.rs new file mode 100644 index 000000000..a5f47ca26 --- /dev/null +++ b/rust/generated/src__core__observation_evidence.rs @@ -0,0 +1,3083 @@ +unsafe extern "C" { + pub type jpeg_encoder_cache; + unsafe fn db_observation_insert_batch( + batch: *const observation_batch_input_t, + asset: *const observation_asset_input_t, + result: *mut observation_insert_result_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_asset_transition( + asset_uuid: *const ::core::ffi::c_char, + from: observation_asset_state_t, + to: observation_asset_state_t, + reason: *const ::core::ffi::c_char, + media: *const observation_asset_media_t, + result: *mut observation_transition_result_t, + ) -> ::core::ffi::c_int; + unsafe fn __errno_location() -> *mut ::core::ffi::c_int; + unsafe fn open( + __file: *const ::core::ffi::c_char, + __oflag: ::core::ffi::c_int, + ... + ) -> ::core::ffi::c_int; + unsafe fn gmtime_r(__timer: *const time_t, __tp: *mut tm) -> *mut tm; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn pthread_create( + __newthread: *mut pthread_t, + __attr: *const pthread_attr_t, + __start_routine: Option< + unsafe extern "C" fn(*mut ::core::ffi::c_void) -> *mut ::core::ffi::c_void, + >, + __arg: *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int; + unsafe fn pthread_join( + __th: pthread_t, + __thread_return: *mut *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_trylock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_lock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_unlock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn sem_init( + __sem: *mut sem_t, + __pshared: ::core::ffi::c_int, + __value: ::core::ffi::c_uint, + ) -> ::core::ffi::c_int; + unsafe fn sem_destroy(__sem: *mut sem_t) -> ::core::ffi::c_int; + unsafe fn sem_timedwait(__sem: *mut sem_t, __abstime: *const timespec) -> ::core::ffi::c_int; + unsafe fn sem_post(__sem: *mut sem_t) -> ::core::ffi::c_int; + unsafe fn rename( + __old: *const ::core::ffi::c_char, + __new: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn mkdir(__path: *const ::core::ffi::c_char, __mode: __mode_t) -> ::core::ffi::c_int; + unsafe fn close(__fd: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn write( + __fd: ::core::ffi::c_int, + __buf: *const ::core::ffi::c_void, + __n: size_t, + ) -> ssize_t; + unsafe fn usleep(__useconds: __useconds_t) -> ::core::ffi::c_int; + unsafe fn unlink(__name: *const ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn fsync(__fd: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn register_component( + name: *const ::core::ffi::c_char, + r#type: component_type_t, + context: *mut ::core::ffi::c_void, + priority: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn update_component_state(component_id: ::core::ffi::c_int, state: component_state_t); + unsafe fn get_stream_config_by_uuid( + camera_uuid: *const ::core::ffi::c_char, + stream: *mut stream_config_t, + ) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn lightnvr_uuid_generate_v4(output: *mut ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn jpeg_encoder_cache_create( + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + channels: ::core::ffi::c_int, + quality: ::core::ffi::c_int, + ) -> *mut jpeg_encoder_cache_t; + unsafe fn jpeg_encoder_cache_encode_to_memory( + encoder: *mut jpeg_encoder_cache_t, + frame_data: *const ::core::ffi::c_uchar, + out_data: *mut *mut ::core::ffi::c_uchar, + out_size: *mut size_t, + ) -> ::core::ffi::c_int; + unsafe fn jpeg_encoder_cache_destroy(encoder: *mut jpeg_encoder_cache_t); + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strrchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; + unsafe fn strerror(__errnum: ::core::ffi::c_int) -> *mut ::core::ffi::c_char; + unsafe fn malloc(__size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __mode_t = ::core::ffi::c_uint; +pub type __time_t = ::core::ffi::c_long; +pub type __useconds_t = ::core::ffi::c_uint; +pub type __clockid_t = ::core::ffi::c_int; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type ssize_t = isize; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +pub type __pthread_list_t = __pthread_internal_list; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_mutex_s { + pub __lock: ::core::ffi::c_int, + pub __count: ::core::ffi::c_uint, + pub __owner: ::core::ffi::c_int, + pub __nusers: ::core::ffi::c_uint, + pub __kind: ::core::ffi::c_int, + pub __spins: ::core::ffi::c_short, + pub __glibc_reserved: ::core::ffi::c_short, + pub __list: __pthread_list_t, +} +pub type pthread_t = ::core::ffi::c_ulong; +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_attr_t { + pub __size: [::core::ffi::c_char; 56], + pub __align: ::core::ffi::c_long, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::core::ffi::c_char; 40], + pub __align: ::core::ffi::c_long, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct tm { + pub tm_sec: ::core::ffi::c_int, + pub tm_min: ::core::ffi::c_int, + pub tm_hour: ::core::ffi::c_int, + pub tm_mday: ::core::ffi::c_int, + pub tm_mon: ::core::ffi::c_int, + pub tm_year: ::core::ffi::c_int, + pub tm_wday: ::core::ffi::c_int, + pub tm_yday: ::core::ffi::c_int, + pub tm_isdst: ::core::ffi::c_int, + pub tm_gmtoff: ::core::ffi::c_long, + pub tm_zone: *const ::core::ffi::c_char, +} +pub type uint8_t = u8; +pub type uint32_t = u32; +pub type uint64_t = u64; +pub type intptr_t = isize; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_protocol_t(pub ::core::ffi::c_uint); +impl stream_protocol_t { + pub const STREAM_PROTOCOL_TCP: Self = Self(0); + pub const STREAM_PROTOCOL_UDP: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stream_config_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub location_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub url: [::core::ffi::c_char; 512], + pub enabled: bool, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub fps: ::core::ffi::c_int, + pub codec: [::core::ffi::c_char; 16], + pub priority: ::core::ffi::c_int, + pub record: bool, + pub segment_duration: ::core::ffi::c_int, + pub detection_based_recording: bool, + pub detection_model: [::core::ffi::c_char; 512], + pub detection_interval: ::core::ffi::c_int, + pub detection_threshold: ::core::ffi::c_float, + pub pre_detection_buffer: ::core::ffi::c_int, + pub post_detection_buffer: ::core::ffi::c_int, + pub detection_api_url: [::core::ffi::c_char; 512], + pub detection_object_filter: [::core::ffi::c_char; 16], + pub detection_object_filter_list: [::core::ffi::c_char; 256], + pub streaming_enabled: bool, + pub protocol: stream_protocol_t, + pub record_audio: bool, + pub audio_voice_enhancement: bool, + pub onvif_username: [::core::ffi::c_char; 64], + pub onvif_password: [::core::ffi::c_char; 64], + pub onvif_profile: [::core::ffi::c_char; 64], + pub onvif_port: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub is_onvif: bool, + pub backchannel_enabled: bool, + pub retention_days: ::core::ffi::c_int, + pub detection_retention_days: ::core::ffi::c_int, + pub max_storage_mb: ::core::ffi::c_int, + pub tier_critical_multiplier: ::core::ffi::c_double, + pub tier_important_multiplier: ::core::ffi::c_double, + pub tier_ephemeral_multiplier: ::core::ffi::c_double, + pub storage_priority: ::core::ffi::c_int, + pub ptz_enabled: bool, + pub ptz_max_x: ::core::ffi::c_int, + pub ptz_max_y: ::core::ffi::c_int, + pub ptz_max_z: ::core::ffi::c_int, + pub ptz_has_home: bool, + pub record_on_schedule: bool, + pub recording_schedule: [uint8_t; 168], + pub detection_record_on_schedule: bool, + pub detection_recording_schedule: [uint8_t; 168], + pub tags: [::core::ffi::c_char; 256], + pub admin_url: [::core::ffi::c_char; 512], + pub privacy_mode: bool, + pub motion_trigger_source: [::core::ffi::c_char; 256], + pub go2rtc_source_override: [::core::ffi::c_char; 8192], + pub sub_stream_url: [::core::ffi::c_char; 512], + pub detection_url: [::core::ffi::c_char; 512], + pub publish_url: [::core::ffi::c_char; 512], + pub playback_transport: [::core::ffi::c_char; 24], + pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_object_input_t { + pub object_index: ::core::ffi::c_int, + pub label: [::core::ffi::c_char; 32], + pub confidence: ::core::ffi::c_double, + pub has_box: bool, + pub box_x: ::core::ffi::c_double, + pub box_y: ::core::ffi::c_double, + pub box_width: ::core::ffi::c_double, + pub box_height: ::core::ffi::c_double, + pub has_track: bool, + pub track_id: [::core::ffi::c_char; 65], + pub track_namespace: [::core::ffi::c_char; 65], + pub track_session: [::core::ffi::c_char; 37], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_batch_input_t { + pub batch_id: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub stream_name: [::core::ffi::c_char; 64], + pub observed_at_ms: int64_t, + pub engine_key: [::core::ffi::c_char; 65], + pub engine_type: [::core::ffi::c_char; 16], + pub model: [::core::ffi::c_char; 129], + pub model_version: [::core::ffi::c_char; 65], + pub recording_id: int64_t, + pub object_count: ::core::ffi::c_int, + pub objects: [observation_object_input_t; 20], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_input_t { + pub asset_uuid: [::core::ffi::c_char; 37], + pub state: observation_asset_state_t, + pub reason: [::core::ffi::c_char; 33], + pub origin: [::core::ffi::c_char; 24], + pub media_type: [::core::ffi::c_char; 24], + pub captured_at_ms: int64_t, + pub transform_json: [::core::ffi::c_char; 1024], + pub expires_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_insert_result_t { + pub asset_uuid: [::core::ffi::c_char; 37], + pub object_count: ::core::ffi::c_int, + pub observation_uuids: [[::core::ffi::c_char; 37]; 20], + pub last_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_media_t { + pub path: *const ::core::ffi::c_char, + pub bytes: int64_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub expires_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_transition_result_t { + pub observations_bumped: ::core::ffi::c_int, + pub last_seq: int64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_evidence_result_t(pub ::core::ffi::c_int); +impl observation_evidence_result_t { + pub const OBS_EVIDENCE_ACCEPTED: Self = Self(0); + pub const OBS_EVIDENCE_DROPPED_QUEUE_FULL: Self = Self(1); + pub const OBS_EVIDENCE_DROPPED_BUDGET: Self = Self(2); + pub const OBS_EVIDENCE_DISABLED: Self = Self(3); + pub const OBS_EVIDENCE_INVALID: Self = Self(-1); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_transform_mode_t(pub ::core::ffi::c_uint); +impl observation_transform_mode_t { + pub const OBS_TRANSFORM_NONE: Self = Self(0); + pub const OBS_TRANSFORM_RESIZE: Self = Self(1); + pub const OBS_TRANSFORM_LETTERBOX: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_transform_t { + pub mode: observation_transform_mode_t, + pub source_width: ::core::ffi::c_int, + pub source_height: ::core::ffi::c_int, + pub inference_width: ::core::ffi::c_int, + pub inference_height: ::core::ffi::c_int, + pub scale_x: ::core::ffi::c_double, + pub scale_y: ::core::ffi::c_double, + pub pad_x: ::core::ffi::c_double, + pub pad_y: ::core::ffi::c_double, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_config_t { + pub storage_path: *const ::core::ffi::c_char, + pub global_max_bytes: int64_t, + pub global_max_items: ::core::ffi::c_int, + pub per_camera_max_items: ::core::ffi::c_int, + pub per_camera_max_bytes: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed_0 { + pub lifecycle: atomic_int, + pub inflight: atomic_int, + pub storage_path: [::core::ffi::c_char; 512], + pub global_max_bytes: int64_t, + pub global_max_items: ::core::ffi::c_int, + pub per_camera_max_items: ::core::ffi::c_int, + pub per_camera_max_bytes: int64_t, + pub start_lock: pthread_mutex_t, + pub thread: pthread_t, + pub thread_started: atomic_int, + pub worker_running: atomic_int, + pub component_id: ::core::ffi::c_int, + pub head: *mut evidence_item_t, + pub ring: *mut drop_ring_t, + pub drop_pending: atomic_int, + pub wake: sem_t, + pub stop: atomic_int, + pub drain_deadline_ns: int64_t, + pub items: atomic_int, + pub bytes: atomic_llong, + pub high_water_items: atomic_uint, + pub high_water_bytes: atomic_ullong, + pub camera_high_water_items: atomic_uint, + pub discard_max_bytes: int64_t, + pub discard_bytes: atomic_llong, + pub discard_deferred: atomic_ullong, + pub discard_inline: atomic_ullong, + pub submit_latency_count: atomic_ullong, + pub submit_latency_sum_ns: atomic_ullong, + pub submit_latency_max_ns: atomic_ullong, + pub submit_latency_bucket: [atomic_ullong; 10], + pub accepted: atomic_ullong, + pub dropped_queue_full: atomic_ullong, + pub dropped_budget: atomic_ullong, + pub dropped_disabled: atomic_ullong, + pub dropped_untracked: atomic_ullong, + pub unavailable_submitted: atomic_ullong, + pub ready: atomic_ullong, + pub failed_encode: atomic_ullong, + pub failed_write: atomic_ullong, + pub failed_db: atomic_ullong, + pub disabled_at_encode: atomic_ullong, + pub interrupted: atomic_ullong, + pub bytes_written: atomic_ullong, + pub encoder: *mut jpeg_encoder_cache_t, + pub encoder_w: ::core::ffi::c_int, + pub encoder_h: ::core::ffi::c_int, +} +pub type jpeg_encoder_cache_t = jpeg_encoder_cache; +pub type atomic_ullong = ::core::ffi::c_ulonglong; +pub type atomic_llong = ::core::ffi::c_longlong; +pub type atomic_uint = ::core::ffi::c_uint; +pub type atomic_int = ::core::ffi::c_int; +#[derive(Copy, Clone)] +#[repr(C)] +pub union sem_t { + pub __size: [::core::ffi::c_char; 32], + pub __align: ::core::ffi::c_long, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct drop_ring_t { + pub cells: [drop_cell_t; 256], + pub enqueue_pos: atomic_size_t, + pub dequeue_pos: size_t, +} +pub type atomic_size_t = size_t; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct drop_cell_t { + pub seq: atomic_size_t, + pub record: drop_record_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct drop_record_t { + pub batch: observation_batch_input_t, + pub asset_uuid: [::core::ffi::c_char; 37], + pub reason: [::core::ffi::c_char; 33], + pub discard: *mut uint8_t, + pub discard_charge: size_t, +} +pub type evidence_item_t = evidence_item; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct evidence_item { + pub next: *mut evidence_item, + pub is_jpeg: bool, + pub slot: ::core::ffi::c_int, + pub charge: size_t, + pub data: *mut uint8_t, + pub data_size: size_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub batch: observation_batch_input_t, + pub asset: observation_asset_input_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct camera_slot_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub items: atomic_int, + pub bytes: atomic_llong, + pub budget_exhausted: atomic_int, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct component_state_t(pub ::core::ffi::c_uint); +impl component_state_t { + pub const COMPONENT_RUNNING: Self = Self(0); + pub const COMPONENT_STOPPING: Self = Self(1); + pub const COMPONENT_STOPPED: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_evidence_stage_t(pub ::core::ffi::c_uint); +impl observation_evidence_stage_t { + pub const OBS_EVIDENCE_STAGE_ITEM_BEGIN: Self = Self(0); + pub const OBS_EVIDENCE_STAGE_BEFORE_RENAME: Self = Self(1); + pub const OBS_EVIDENCE_STAGE_ITEM_DONE: Self = Self(2); + pub const OBS_EVIDENCE_STAGE_AFTER_INSERT: Self = Self(3); + pub const OBS_EVIDENCE_STAGE_BEFORE_COMMIT: Self = Self(4); +} +pub type observation_evidence_stage_fn = Option< + unsafe extern "C" fn( + observation_evidence_stage_t, + *const ::core::ffi::c_char, + *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int, +>; +pub type observation_evidence_enabled_fn = + Option bool>; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct component_type_t(pub ::core::ffi::c_uint); +impl component_type_t { + pub const COMPONENT_DETECTION_THREAD: Self = Self(0); + pub const COMPONENT_SERVER_THREAD: Self = Self(1); + pub const COMPONENT_HLS_WRITER: Self = Self(2); + pub const COMPONENT_MP4_WRITER: Self = Self(3); + pub const COMPONENT_OTHER: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_stats_t { + pub initialized: bool, + pub worker_running: bool, + pub allocated: bool, + pub accepted: uint64_t, + pub dropped_queue_full: uint64_t, + pub dropped_budget: uint64_t, + pub dropped_disabled: uint64_t, + pub dropped_untracked: uint64_t, + pub unavailable_submitted: uint64_t, + pub ready: uint64_t, + pub failed_encode: uint64_t, + pub failed_write: uint64_t, + pub failed_db: uint64_t, + pub disabled_at_encode: uint64_t, + pub interrupted: uint64_t, + pub bytes_written: uint64_t, + pub backlog_items: uint32_t, + pub backlog_bytes: uint64_t, + pub drop_ring_depth: uint32_t, + pub high_water_items: uint32_t, + pub high_water_bytes: uint64_t, + pub budget_exhausted_cameras: uint32_t, + pub limit_items: uint32_t, + pub limit_camera_items: uint32_t, + pub limit_bytes: uint64_t, + pub limit_camera_bytes: uint64_t, + pub camera_high_water_items: uint32_t, + pub submit_latency_count: uint64_t, + pub submit_latency_sum_ns: uint64_t, + pub submit_latency_max_ns: uint64_t, + pub submit_latency_bucket: [uint64_t; 10], + pub discard_deferred: uint64_t, + pub discard_inline: uint64_t, + pub discard_pending_bytes: uint64_t, + pub limit_discard_bytes: uint64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_1(pub ::core::ffi::c_uint); +impl C2Rust_Unnamed_1 { + pub const PTHREAD_MUTEX_TIMED_NP: Self = Self(0); + pub const PTHREAD_MUTEX_RECURSIVE_NP: Self = Self(1); + pub const PTHREAD_MUTEX_ERRORCHECK_NP: Self = Self(2); + pub const PTHREAD_MUTEX_ADAPTIVE_NP: Self = Self(3); + pub const PTHREAD_MUTEX_NORMAL: Self = Self(0); + pub const PTHREAD_MUTEX_RECURSIVE: Self = Self(1); + pub const PTHREAD_MUTEX_ERRORCHECK: Self = Self(2); + pub const PTHREAD_MUTEX_DEFAULT: Self = Self(0); + pub const PTHREAD_MUTEX_FAST_NP: Self = Self(0); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct memory_order(pub ::core::ffi::c_uint); +impl memory_order { + pub const memory_order_relaxed: Self = Self(0); + pub const memory_order_consume: Self = Self(1); + pub const memory_order_acquire: Self = Self(2); + pub const memory_order_release: Self = Self(3); + pub const memory_order_acq_rel: Self = Self(4); + pub const memory_order_seq_cst: Self = Self(5); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_2(pub ::core::ffi::c_uint); +impl C2Rust_Unnamed_2 { + pub const LC_OFF: Self = Self(0); + pub const LC_READY: Self = Self(1); + pub const LC_RUNNING: Self = Self(2); + pub const LC_STOPPING: Self = Self(3); +} +pub const MAX_STREAMS: ::core::ffi::c_int = LIGHTNVR_MAX_STREAMS; +pub const OBS_UUID_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; +pub const OBS_MAX_BATCH: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const OBS_EVIDENCE_PER_CAMERA_DEPTH: ::core::ffi::c_int = 8 as ::core::ffi::c_int; +pub const OBS_EVIDENCE_GLOBAL_DEPTH: ::core::ffi::c_int = 64 as ::core::ffi::c_int; +pub const OBS_EVIDENCE_DROP_RING_SIZE: ::core::ffi::c_int = 256 as ::core::ffi::c_int; +pub const OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES: ::core::ffi::c_longlong = 32 + as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong; +pub const OBS_EVIDENCE_JPEG_QUALITY: ::core::ffi::c_int = 85 as ::core::ffi::c_int; +pub const OBS_EVIDENCE_DIR: [::core::ffi::c_char; 13] = + unsafe { ::core::mem::transmute::<[u8; 13], [::core::ffi::c_char; 13]>(*b"observations\0") }; +pub const OBS_EVIDENCE_DEFAULT_DRAIN_MS: ::core::ffi::c_int = 2000 as ::core::ffi::c_int; +pub const OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS: ::core::ffi::c_int = 10 as ::core::ffi::c_int; +pub const O_RDONLY: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const O_WRONLY: ::core::ffi::c_int = 0o1 as ::core::ffi::c_int; +pub const O_CREAT: ::core::ffi::c_int = 0o100 as ::core::ffi::c_int; +pub const O_TRUNC: ::core::ffi::c_int = 0o1000 as ::core::ffi::c_int; +pub const __O_DIRECTORY: ::core::ffi::c_int = 0o200000 as ::core::ffi::c_int; +pub const __O_NOFOLLOW: ::core::ffi::c_int = 0o400000 as ::core::ffi::c_int; +pub const O_DIRECTORY: ::core::ffi::c_int = __O_DIRECTORY; +pub const O_NOFOLLOW: ::core::ffi::c_int = __O_NOFOLLOW; +pub const O_CLOEXEC: ::core::ffi::c_int = __O_CLOEXEC; +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const CLOCK_MONOTONIC: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const RGB24_SWS_TAIL_PADDING: ::core::ffi::c_int = 128 as ::core::ffi::c_int; +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const EINTR: ::core::ffi::c_int = 4 as ::core::ffi::c_int; +pub const EEXIST: ::core::ffi::c_int = 17 as ::core::ffi::c_int; +pub const __O_CLOEXEC: ::core::ffi::c_int = 0o2000000 as ::core::ffi::c_int; +pub const EVIDENCE_MAX_CAMERAS: ::core::ffi::c_int = MAX_STREAMS; +pub const EVIDENCE_WAIT_MS: ::core::ffi::c_int = 200 as ::core::ffi::c_int; +static mut g_slots: [camera_slot_t; 1024] = [camera_slot_t { + camera_uuid: [0; 37], + items: 0, + bytes: 0, + budget_exhausted: 0, +}; 1024]; +static mut g_slot_count: atomic_int = 0; +static mut g_slot_lock: pthread_mutex_t = pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_1::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, +}; +static mut g_hook_lock: pthread_mutex_t = pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_1::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, +}; +static mut g_stage_hook: observation_evidence_stage_fn = None; +static mut g_stage_ctx: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +static mut g_enabled_probe: observation_evidence_enabled_fn = None; +static mut g_enabled_ctx: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +static mut g: C2Rust_Unnamed_0 = C2Rust_Unnamed_0 { + lifecycle: 0, + inflight: 0, + storage_path: [0; 512], + global_max_bytes: 0, + global_max_items: 0, + per_camera_max_items: 0, + per_camera_max_bytes: 0, + start_lock: pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_1::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, + }, + thread: 0, + thread_started: 0, + worker_running: 0, + component_id: -1 as ::core::ffi::c_int, + head: ::core::ptr::null_mut::(), + ring: ::core::ptr::null_mut::(), + drop_pending: 0, + wake: sem_t { __size: [0; 32] }, + stop: 0, + drain_deadline_ns: 0, + items: 0, + bytes: 0, + high_water_items: 0, + high_water_bytes: 0, + camera_high_water_items: 0, + discard_max_bytes: 0, + discard_bytes: 0, + discard_deferred: 0, + discard_inline: 0, + submit_latency_count: 0, + submit_latency_sum_ns: 0, + submit_latency_max_ns: 0, + submit_latency_bucket: [0; 10], + accepted: 0, + dropped_queue_full: 0, + dropped_budget: 0, + dropped_disabled: 0, + dropped_untracked: 0, + unavailable_submitted: 0, + ready: 0, + failed_encode: 0, + failed_write: 0, + failed_db: 0, + disabled_at_encode: 0, + interrupted: 0, + bytes_written: 0, + encoder: ::core::ptr::null_mut::(), + encoder_w: 0, + encoder_h: 0, +}; +unsafe extern "C" fn mono_ns() -> int64_t { + let mut ts: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_MONOTONIC, &raw mut ts); + return (ts.tv_sec as int64_t as ::core::ffi::c_longlong * 1000000000 as ::core::ffi::c_longlong + + ts.tv_nsec as ::core::ffi::c_longlong) as int64_t; +} +unsafe extern "C" fn bump_max_uint(mut target: *mut atomic_uint, mut value: ::core::ffi::c_uint) { + let mut current: ::core::ffi::c_uint = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(target); + while value > current && { + let c2rust_result = ::core::intrinsics::atomic_cxchgweak::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(target, *&raw mut current, value); + *&raw mut current = c2rust_result.0; + !c2rust_result.1 + } {} +} +unsafe extern "C" fn bump_max_ull( + mut target: *mut atomic_ullong, + mut value: ::core::ffi::c_ulonglong, +) { + let mut current: ::core::ffi::c_ulonglong = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(target); + while value > current && { + let c2rust_result = ::core::intrinsics::atomic_cxchgweak::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(target, *&raw mut current, value); + *&raw mut current = c2rust_result.0; + !c2rust_result.1 + } {} +} +#[unsafe(no_mangle)] +pub static mut observation_evidence_submit_latency_bounds_ns: [uint64_t; 9] = [ + 1000 as uint64_t, + 5000 as uint64_t, + 10000 as uint64_t, + 50000 as uint64_t, + 100000 as uint64_t, + 250000 as uint64_t, + 500000 as uint64_t, + 1000000 as uint64_t, + 10000000 as uint64_t, +]; +unsafe extern "C" fn record_submit_latency(mut start_ns: int64_t) { + let mut elapsed: int64_t = mono_ns() - start_ns; + let mut ns: ::core::ffi::c_ulonglong = if elapsed > 0 as int64_t { + elapsed as ::core::ffi::c_ulonglong + } else { + 0 as ::core::ffi::c_ulonglong + }; + let mut bucket: ::core::ffi::c_int = + OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1 as ::core::ffi::c_int { + if ns + <= observation_evidence_submit_latency_bounds_ns[i as usize] as ::core::ffi::c_ulonglong + { + bucket = i; + break; + } else { + i += 1; + } + } + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::Relaxed }>( + (&raw mut g.submit_latency_bucket as *mut atomic_ullong).offset(bucket as isize), + 1 as ::core::ffi::c_ulonglong, + ); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::Relaxed }>( + &raw mut g.submit_latency_sum_ns, + ns, + ); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::Relaxed }>( + &raw mut g.submit_latency_count, + 1 as ::core::ffi::c_ulonglong, + ); + bump_max_ull(&raw mut g.submit_latency_max_ns, ns); +} +unsafe extern "C" fn stage_hook( + mut stage: observation_evidence_stage_t, + mut path: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + pthread_mutex_lock(&raw mut g_hook_lock); + let mut r#fn: observation_evidence_stage_fn = g_stage_hook; + let mut ctx: *mut ::core::ffi::c_void = g_stage_ctx; + pthread_mutex_unlock(&raw mut g_hook_lock); + return if r#fn.is_some() { + r#fn.expect("non-null function pointer")(stage, path, ctx) + } else { + 0 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn camera_enabled(mut camera_uuid: *const ::core::ffi::c_char) -> bool { + pthread_mutex_lock(&raw mut g_hook_lock); + let mut r#fn: observation_evidence_enabled_fn = g_enabled_probe; + let mut ctx: *mut ::core::ffi::c_void = g_enabled_ctx; + pthread_mutex_unlock(&raw mut g_hook_lock); + return if r#fn.is_some() { + r#fn.expect("non-null function pointer")(camera_uuid, ctx) as ::core::ffi::c_int + } else { + r#true + } != 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_set_stage_hook( + mut r#fn: observation_evidence_stage_fn, + mut ctx: *mut ::core::ffi::c_void, +) { + pthread_mutex_lock(&raw mut g_hook_lock); + g_stage_hook = r#fn; + g_stage_ctx = ctx; + pthread_mutex_unlock(&raw mut g_hook_lock); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_set_enabled_probe( + mut r#fn: observation_evidence_enabled_fn, + mut ctx: *mut ::core::ffi::c_void, +) { + pthread_mutex_lock(&raw mut g_hook_lock); + g_enabled_probe = r#fn; + g_enabled_ctx = ctx; + pthread_mutex_unlock(&raw mut g_hook_lock); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_stream_enabled_probe( + mut camera_uuid: *const ::core::ffi::c_char, + mut ctx: *mut ::core::ffi::c_void, +) -> bool { + let mut stream: *mut stream_config_t = + calloc(1 as size_t, ::core::mem::size_of::()) as *mut stream_config_t; + if stream.is_null() { + return r#true != 0; + } + let mut enabled: bool = get_stream_config_by_uuid(camera_uuid, stream) + == 0 as ::core::ffi::c_int + && (*stream).observation_evidence_enabled as ::core::ffi::c_int != 0; + free(stream as *mut ::core::ffi::c_void); + return enabled; +} +unsafe extern "C" fn slot_find(mut camera_uuid: *const ::core::ffi::c_char) -> ::core::ffi::c_int { + let mut count: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Acquire }, + >(&raw mut g_slot_count); + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if strcmp( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(i as isize)).camera_uuid + as *mut ::core::ffi::c_char, + camera_uuid, + ) == 0 as ::core::ffi::c_int + { + return i; + } + i += 1; + } + return -1 as ::core::ffi::c_int; +} +unsafe extern "C" fn slot_get(mut camera_uuid: *const ::core::ffi::c_char) -> ::core::ffi::c_int { + let mut slot: ::core::ffi::c_int = slot_find(camera_uuid); + if slot >= 0 as ::core::ffi::c_int { + return slot; + } + pthread_mutex_lock(&raw mut g_slot_lock); + slot = slot_find(camera_uuid); + if slot < 0 as ::core::ffi::c_int { + let mut count: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Relaxed }, + >(&raw mut g_slot_count); + if count < EVIDENCE_MAX_CAMERAS { + let mut s: *mut camera_slot_t = + (&raw mut g_slots as *mut camera_slot_t).offset(count as isize); + safe_strcpy( + &raw mut (*s).camera_uuid as *mut ::core::ffi::c_char, + camera_uuid, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*s).items, + 0 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*s).bytes, + 0 as ::core::ffi::c_longlong, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*s).budget_exhausted, + 0 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::Release }>( + &raw mut g_slot_count, + count + 1 as ::core::ffi::c_int, + ); + slot = count; + } + } + pthread_mutex_unlock(&raw mut g_slot_lock); + return slot; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_set_budget_exhausted( + mut camera_uuid: *const ::core::ffi::c_char, + mut exhausted: bool, +) { + if !lightnvr_uuid_is_valid(camera_uuid) { + return; + } + let mut slot: ::core::ffi::c_int = if exhausted as ::core::ffi::c_int != 0 { + slot_get(camera_uuid) + } else { + slot_find(camera_uuid) + }; + if slot < 0 as ::core::ffi::c_int { + if exhausted { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: camera table full; budget flag for %s not stored\0".as_ptr() + as *const ::core::ffi::c_char, + camera_uuid, + ); + } + return; + } + let mut previous: bool = + ::core::intrinsics::atomic_xchg::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(slot as isize)) + .budget_exhausted, + if exhausted as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }, + ) != 0 as ::core::ffi::c_int; + if previous as ::core::ffi::c_int != exhausted as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: budget_exhausted %s for camera %s\0".as_ptr() as *const ::core::ffi::c_char, + if exhausted as ::core::ffi::c_int != 0 { + b"set\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"cleared\0".as_ptr() as *const ::core::ffi::c_char + }, + camera_uuid, + ); + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_is_budget_exhausted( + mut camera_uuid: *const ::core::ffi::c_char, +) -> bool { + if camera_uuid.is_null() { + return r#false != 0; + } + let mut slot: ::core::ffi::c_int = slot_find(camera_uuid); + return slot >= 0 as ::core::ffi::c_int + && ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(slot as isize)) + .budget_exhausted, + ) != 0; +} +unsafe extern "C" fn format_number( + mut value: ::core::ffi::c_double, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) -> bool { + if value.is_finite() as ::core::ffi::c_int == 0 { + return r#false != 0; + } + let mut n: ::core::ffi::c_int = snprintf( + out, + size, + b"%.6g\0".as_ptr() as *const ::core::ffi::c_char, + value, + ); + return n > 0 as ::core::ffi::c_int && (n as size_t) < size; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_transform_json( + mut transform: *const observation_evidence_transform_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, +) -> ::core::ffi::c_int { + if out.is_null() || out_size == 0 as size_t { + return -1 as ::core::ffi::c_int; + } + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + let mut t: observation_evidence_transform_t = observation_evidence_transform_t { + mode: observation_transform_mode_t::OBS_TRANSFORM_NONE, + source_width: 0, + source_height: 0, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + }; + if !transform.is_null() { + t = *transform; + } else { + if width <= 0 as ::core::ffi::c_int || height <= 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + memset( + &raw mut t as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + t.mode = observation_transform_mode_t::OBS_TRANSFORM_NONE; + t.inference_width = width; + t.source_width = t.inference_width; + t.inference_height = height; + t.source_height = t.inference_height; + t.scale_y = 1.0f64; + t.scale_x = t.scale_y; + } + static mut modes: [*const ::core::ffi::c_char; 3] = [ + b"none\0".as_ptr() as *const ::core::ffi::c_char, + b"resize\0".as_ptr() as *const ::core::ffi::c_char, + b"letterbox\0".as_ptr() as *const ::core::ffi::c_char, + ]; + if (t.mode.0 as ::core::ffi::c_int) < 0 as ::core::ffi::c_int + || t.mode.0 as ::core::ffi::c_int + > observation_transform_mode_t::OBS_TRANSFORM_LETTERBOX.0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if t.source_width <= 0 as ::core::ffi::c_int + || t.source_height <= 0 as ::core::ffi::c_int + || t.inference_width <= 0 as ::core::ffi::c_int + || t.inference_height <= 0 as ::core::ffi::c_int + || !(t.scale_x > 0 as ::core::ffi::c_int as ::core::ffi::c_double) + || !(t.scale_y > 0 as ::core::ffi::c_int as ::core::ffi::c_double) + || !(t.pad_x >= 0 as ::core::ffi::c_int as ::core::ffi::c_double) + || !(t.pad_y >= 0 as ::core::ffi::c_int as ::core::ffi::c_double) + { + return -1 as ::core::ffi::c_int; + } + let mut sx: [::core::ffi::c_char; 32] = [0; 32]; + let mut sy: [::core::ffi::c_char; 32] = [0; 32]; + let mut px: [::core::ffi::c_char; 32] = [0; 32]; + let mut py: [::core::ffi::c_char; 32] = [0; 32]; + if !format_number( + t.scale_x, + &raw mut sx as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ) || !format_number( + t.scale_y, + &raw mut sy as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ) || !format_number( + t.pad_x, + &raw mut px as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ) || !format_number( + t.pad_y, + &raw mut py as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ) { + return -1 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = snprintf( + out, + out_size, + b"{\"mode\":\"%s\",\"source_width\":%d,\"source_height\":%d,\"inference_width\":%d,\"inference_height\":%d,\"scale_x\":%s,\"scale_y\":%s,\"pad_x\":%s,\"pad_y\":%s}\0" + .as_ptr() as *const ::core::ffi::c_char, + modes[t.mode.0 as usize], + t.source_width, + t.source_height, + t.inference_width, + t.inference_height, + &raw mut sx as *mut ::core::ffi::c_char, + &raw mut sy as *mut ::core::ffi::c_char, + &raw mut px as *mut ::core::ffi::c_char, + &raw mut py as *mut ::core::ffi::c_char, + ); + if n < 0 as ::core::ffi::c_int || n as size_t >= out_size { + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_relative_path( + mut camera_uuid: *const ::core::ffi::c_char, + mut asset_uuid: *const ::core::ffi::c_char, + mut observed_at_ms: int64_t, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, +) -> ::core::ffi::c_int { + if out.is_null() || out_size == 0 as size_t { + return -1 as ::core::ffi::c_int; + } + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + if !lightnvr_uuid_is_valid(camera_uuid) + || !lightnvr_uuid_is_valid(asset_uuid) + || observed_at_ms < 0 as int64_t + { + return -1 as ::core::ffi::c_int; + } + let mut seconds: time_t = (observed_at_ms / 1000 as int64_t) as time_t; + let mut tm: tm = tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: ::core::ptr::null::<::core::ffi::c_char>(), + }; + if gmtime_r(&raw mut seconds, &raw mut tm).is_null() { + return -1 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = snprintf( + out, + out_size, + b"%s/%s/%04d-%02d-%02d/%s.jpg\0".as_ptr() as *const ::core::ffi::c_char, + OBS_EVIDENCE_DIR.as_ptr(), + camera_uuid, + tm.tm_year + 1900 as ::core::ffi::c_int, + tm.tm_mon + 1 as ::core::ffi::c_int, + tm.tm_mday, + asset_uuid, + ); + if n < 0 as ::core::ffi::c_int || n as size_t >= out_size { + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn ring_create() -> *mut drop_ring_t { + let mut ring: *mut drop_ring_t = + calloc(1 as size_t, ::core::mem::size_of::()) as *mut drop_ring_t; + if ring.is_null() { + return ::core::ptr::null_mut::(); + } + let mut i: size_t = 0 as size_t; + while i < OBS_EVIDENCE_DROP_RING_SIZE as size_t { + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::Relaxed }>( + &raw mut (*(&raw mut (*ring).cells as *mut drop_cell_t).offset(i as isize)).seq, + i, + ); + i = i.wrapping_add(1); + } + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*ring).enqueue_pos, + 0 as size_t, + ); + return ring; +} +unsafe extern "C" fn ring_push( + mut ring: *mut drop_ring_t, + mut batch: *const observation_batch_input_t, + mut asset_uuid: *const ::core::ffi::c_char, + mut reason: *const ::core::ffi::c_char, + mut discard: *mut uint8_t, + mut discard_charge: size_t, +) -> bool { + if ring.is_null() { + return r#false != 0; + } + let mut pos: size_t = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Relaxed }, + >(&raw mut (*ring).enqueue_pos); + let mut cell: *mut drop_cell_t = ::core::ptr::null_mut::(); + loop { + cell = (&raw mut (*ring).cells as *mut drop_cell_t).offset( + (pos & (OBS_EVIDENCE_DROP_RING_SIZE - 1 as ::core::ffi::c_int) as size_t) as isize, + ); + let mut seq: size_t = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Acquire }, + >(&raw mut (*cell).seq); + let mut diff: intptr_t = seq as intptr_t - pos as intptr_t; + if diff == 0 as intptr_t { + let c2rust_result = ::core::intrinsics::atomic_cxchgweak::< + _, + { ::core::intrinsics::AtomicOrdering::Relaxed }, + { ::core::intrinsics::AtomicOrdering::Relaxed }, + >( + &raw mut (*ring).enqueue_pos, + *&raw mut pos, + pos.wrapping_add(1 as size_t), + ); + *&raw mut pos = c2rust_result.0; + if c2rust_result.1 { + break; + } + } else if diff < 0 as intptr_t { + return r#false != 0; + } else { + pos = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Relaxed }, + >(&raw mut (*ring).enqueue_pos); + } + } + (*cell).record.batch = *batch; + safe_strcpy( + &raw mut (*cell).record.asset_uuid as *mut ::core::ffi::c_char, + asset_uuid, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + safe_strcpy( + &raw mut (*cell).record.reason as *mut ::core::ffi::c_char, + reason, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + 0 as size_t, + ); + (*cell).record.discard = discard; + (*cell).record.discard_charge = discard_charge; + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.drop_pending, + 1 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::Release }>( + &raw mut (*cell).seq, + pos.wrapping_add(1 as size_t), + ); + return r#true != 0; +} +unsafe extern "C" fn ring_pop(mut ring: *mut drop_ring_t, mut out: *mut drop_record_t) -> bool { + if ring.is_null() { + return r#false != 0; + } + let mut cell: *mut drop_cell_t = (&raw mut (*ring).cells as *mut drop_cell_t).offset( + ((*ring).dequeue_pos & (OBS_EVIDENCE_DROP_RING_SIZE - 1 as ::core::ffi::c_int) as size_t) + as isize, + ); + let mut seq: size_t = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Acquire }, + >(&raw mut (*cell).seq); + if (seq as intptr_t - (*ring).dequeue_pos.wrapping_add(1 as size_t) as intptr_t) < 0 as intptr_t + { + return r#false != 0; + } + *out = (*cell).record; + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::Release }>( + &raw mut (*cell).seq, + (*ring) + .dequeue_pos + .wrapping_add(OBS_EVIDENCE_DROP_RING_SIZE as size_t), + ); + (*ring).dequeue_pos = (*ring).dequeue_pos.wrapping_add(1); + return r#true != 0; +} +unsafe extern "C" fn release_item(mut item: *mut evidence_item_t) { + if (*item).slot >= 0 as ::core::ffi::c_int { + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset((*item).slot as isize)) + .items, + 1 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset((*item).slot as isize)) + .bytes, + (*item).charge as ::core::ffi::c_longlong, + ); + } + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.items, + 1 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.bytes, + (*item).charge as ::core::ffi::c_longlong, + ); + free((*item).data as *mut ::core::ffi::c_void); + free(item as *mut ::core::ffi::c_void); +} +unsafe extern "C" fn insert_unavailable( + mut batch: *const observation_batch_input_t, + mut asset_uuid: *const ::core::ffi::c_char, + mut reason: *const ::core::ffi::c_char, +) { + let mut asset: observation_asset_input_t = observation_asset_input_t { + asset_uuid: [0; 37], + state: observation_asset_state_t::OBS_ASSET_PENDING, + reason: [0; 33], + origin: [0; 24], + media_type: [0; 24], + captured_at_ms: 0, + transform_json: [0; 1024], + expires_at_ms: 0, + }; + memset( + &raw mut asset as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + safe_strcpy( + &raw mut asset.asset_uuid as *mut ::core::ffi::c_char, + asset_uuid, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + asset.state = observation_asset_state_t::OBS_ASSET_UNAVAILABLE; + safe_strcpy( + &raw mut asset.reason as *mut ::core::ffi::c_char, + reason, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + 0 as size_t, + ); + let mut result: observation_insert_result_t = observation_insert_result_t { + asset_uuid: [0; 37], + object_count: 0, + observation_uuids: [[0; 37]; 20], + last_seq: 0, + }; + let mut rc: ::core::ffi::c_int = + db_observation_insert_batch(batch, &raw mut asset, &raw mut result); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 && rc != C2Rust_Unnamed::DB_OBS_DUPLICATE.0 { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_db, + 1 as ::core::ffi::c_ulonglong, + ); + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: recording batch %s as unavailable(%s) failed: %d\0".as_ptr() + as *const ::core::ffi::c_char, + &raw const (*batch).batch_id as *const ::core::ffi::c_char, + reason, + rc, + ); + } +} +unsafe extern "C" fn process_drops() { + let mut ring: *mut drop_ring_t = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.ring); + let mut record: *mut drop_record_t = ::core::ptr::null_mut::(); + while !ring.is_null() { + if record.is_null() { + record = malloc(::core::mem::size_of::()) as *mut drop_record_t; + if record.is_null() { + return; + } + } + if !ring_pop(ring, record) { + break; + } + if !(*record).discard.is_null() { + free((*record).discard as *mut ::core::ffi::c_void); + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.discard_bytes, + (*record).discard_charge as ::core::ffi::c_longlong, + ); + } + insert_unavailable( + &raw mut (*record).batch, + &raw mut (*record).asset_uuid as *mut ::core::ffi::c_char, + &raw mut (*record).reason as *mut ::core::ffi::c_char, + ); + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.drop_pending, + 1 as ::core::ffi::c_int, + ); + } + free(record as *mut ::core::ffi::c_void); +} +unsafe extern "C" fn transition_failed( + mut item: *mut evidence_item_t, + mut reason: *const ::core::ffi::c_char, +) { + let mut result: observation_transition_result_t = observation_transition_result_t { + observations_bumped: 0, + last_seq: 0, + }; + let mut rc: ::core::ffi::c_int = db_observation_asset_transition( + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + observation_asset_state_t::OBS_ASSET_PENDING, + observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + reason, + ::core::ptr::null::(), + &raw mut result, + ); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 && rc != C2Rust_Unnamed::DB_OBS_CAS_LOST.0 { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_db, + 1 as ::core::ffi::c_ulonglong, + ); + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: asset %s pending -> unavailable(%s) failed: %d\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + reason, + rc, + ); + } +} +unsafe extern "C" fn mkdir_parents(mut path: *const ::core::ffi::c_char) -> ::core::ffi::c_int { + let mut buf: [::core::ffi::c_char; 512] = [0; 512]; + if safe_strcpy( + &raw mut buf as *mut ::core::ffi::c_char, + path, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut p: *mut ::core::ffi::c_char = + (&raw mut buf as *mut ::core::ffi::c_char).offset(1 as ::core::ffi::c_int as isize); + while *p != 0 { + if *p as ::core::ffi::c_int == '/' as ::core::ffi::c_int { + *p = '\0' as ::core::ffi::c_char; + if mkdir(&raw mut buf as *mut ::core::ffi::c_char, 0o750 as __mode_t) + != 0 as ::core::ffi::c_int + && *__errno_location() != EEXIST + { + return -1 as ::core::ffi::c_int; + } + *p = '/' as ::core::ffi::c_char; + } + p = p.offset(1); + } + if mkdir(&raw mut buf as *mut ::core::ffi::c_char, 0o750 as __mode_t) != 0 as ::core::ffi::c_int + && *__errno_location() != EEXIST + { + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn fsync_dir(mut dir: *const ::core::ffi::c_char) -> ::core::ffi::c_int { + let mut fd: ::core::ffi::c_int = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if fd < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut rc: ::core::ffi::c_int = fsync(fd); + close(fd); + return rc; +} +unsafe extern "C" fn write_atomic( + mut dir: *const ::core::ffi::c_char, + mut final_path: *const ::core::ffi::c_char, + mut data: *const uint8_t, + mut size: size_t, +) -> ::core::ffi::c_int { + let mut tmp_path: [::core::ffi::c_char; 520] = [0; 520]; + let mut n: ::core::ffi::c_int = snprintf( + &raw mut tmp_path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 520]>(), + b"%s.tmp\0".as_ptr() as *const ::core::ffi::c_char, + final_path, + ); + if n < 0 as ::core::ffi::c_int + || n as size_t >= ::core::mem::size_of::<[::core::ffi::c_char; 520]>() + { + return -1 as ::core::ffi::c_int; + } + if mkdir_parents(dir) != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: cannot create %s: %s\0".as_ptr() as *const ::core::ffi::c_char, + dir, + strerror(*__errno_location()), + ); + return -1 as ::core::ffi::c_int; + } + let mut fd: ::core::ffi::c_int = open( + &raw mut tmp_path as *mut ::core::ffi::c_char, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, + 0o640 as ::core::ffi::c_int, + ); + if fd < 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: cannot create %s: %s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut tmp_path as *mut ::core::ffi::c_char, + strerror(*__errno_location()), + ); + return -1 as ::core::ffi::c_int; + } + let mut written: size_t = 0 as size_t; + while written < size { + let mut w: ssize_t = write( + fd, + data.offset(written as isize) as *const ::core::ffi::c_void, + size.wrapping_sub(written), + ); + if w < 0 as ssize_t && *__errno_location() == EINTR { + continue; + } + if w <= 0 as ssize_t { + break; + } + written = written.wrapping_add(w as size_t); + } + let mut ok: bool = written == size && fsync(fd) == 0 as ::core::ffi::c_int; + if close(fd) != 0 as ::core::ffi::c_int { + ok = r#false != 0; + } + if ok as ::core::ffi::c_int != 0 + && stage_hook( + observation_evidence_stage_t::OBS_EVIDENCE_STAGE_BEFORE_RENAME, + final_path, + ) != 0 as ::core::ffi::c_int + { + ok = r#false != 0; + } + if !ok { + unlink(&raw mut tmp_path as *mut ::core::ffi::c_char); + return -1 as ::core::ffi::c_int; + } + if rename(&raw mut tmp_path as *mut ::core::ffi::c_char, final_path) != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: rename %s failed: %s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut tmp_path as *mut ::core::ffi::c_char, + strerror(*__errno_location()), + ); + unlink(&raw mut tmp_path as *mut ::core::ffi::c_char); + return -1 as ::core::ffi::c_int; + } + if fsync_dir(dir) != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: fsync of %s failed: %s\0".as_ptr() as *const ::core::ffi::c_char, + dir, + strerror(*__errno_location()), + ); + unlink(final_path); + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn encode_rgb( + mut item: *mut evidence_item_t, + mut jpeg: *mut *mut ::core::ffi::c_uchar, + mut jpeg_size: *mut size_t, +) -> ::core::ffi::c_int { + if g.encoder.is_null() || g.encoder_w != (*item).width || g.encoder_h != (*item).height { + if !g.encoder.is_null() { + jpeg_encoder_cache_destroy(g.encoder); + } + g.encoder = jpeg_encoder_cache_create( + (*item).width, + (*item).height, + 3 as ::core::ffi::c_int, + OBS_EVIDENCE_JPEG_QUALITY, + ); + g.encoder_w = if !g.encoder.is_null() { + (*item).width + } else { + 0 as ::core::ffi::c_int + }; + g.encoder_h = if !g.encoder.is_null() { + (*item).height + } else { + 0 as ::core::ffi::c_int + }; + if g.encoder.is_null() { + return -1 as ::core::ffi::c_int; + } + } + return jpeg_encoder_cache_encode_to_memory( + g.encoder, + (*item).data as *const ::core::ffi::c_uchar, + jpeg, + jpeg_size, + ); +} +unsafe extern "C" fn clear_media_fields(mut asset: *mut observation_asset_input_t) { + (*asset).origin[0usize] = '\0' as ::core::ffi::c_char; + (*asset).media_type[0usize] = '\0' as ::core::ffi::c_char; + (*asset).transform_json[0usize] = '\0' as ::core::ffi::c_char; +} +unsafe extern "C" fn abandon_item(mut item: *mut evidence_item_t) { + clear_media_fields(&raw mut (*item).asset); + insert_unavailable( + &raw mut (*item).batch, + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + b"interrupted\0".as_ptr() as *const ::core::ffi::c_char, + ); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.interrupted, + 1 as ::core::ffi::c_ulonglong, + ); +} +unsafe extern "C" fn process_item(mut item: *mut evidence_item_t) { + let mut camera: *const ::core::ffi::c_char = + &raw mut (*item).batch.camera_uuid as *mut ::core::ffi::c_char; + if !camera_enabled(camera) { + clear_media_fields(&raw mut (*item).asset); + insert_unavailable( + &raw mut (*item).batch, + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + ); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.disabled_at_encode, + 1 as ::core::ffi::c_ulonglong, + ); + return; + } + let mut inserted: observation_insert_result_t = observation_insert_result_t { + asset_uuid: [0; 37], + object_count: 0, + observation_uuids: [[0; 37]; 20], + last_seq: 0, + }; + let mut rc: ::core::ffi::c_int = db_observation_insert_batch( + &raw mut (*item).batch, + &raw mut (*item).asset, + &raw mut inserted, + ); + if rc == C2Rust_Unnamed::DB_OBS_DUPLICATE.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: batch %s already recorded; dropping its frame\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*item).batch.batch_id as *mut ::core::ffi::c_char, + ); + return; + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_db, + 1 as ::core::ffi::c_ulonglong, + ); + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: inserting batch %s failed: %d\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut (*item).batch.batch_id as *mut ::core::ffi::c_char, + rc, + ); + return; + } + stage_hook( + observation_evidence_stage_t::OBS_EVIDENCE_STAGE_AFTER_INSERT, + ::core::ptr::null::<::core::ffi::c_char>(), + ); + let mut relative: [::core::ffi::c_char; 512] = [0; 512]; + let mut final_path: [::core::ffi::c_char; 512] = [0; 512]; + let mut dir: [::core::ffi::c_char; 512] = [0; 512]; + if observation_evidence_relative_path( + camera, + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + (*item).batch.observed_at_ms, + &raw mut relative as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ) != 0 as ::core::ffi::c_int + || snprintf( + &raw mut final_path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + b"%s/%s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut g.storage_path as *mut ::core::ffi::c_char, + &raw mut relative as *mut ::core::ffi::c_char, + ) >= ::core::mem::size_of::<[::core::ffi::c_char; 512]>() as ::core::ffi::c_int + { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_write, + 1 as ::core::ffi::c_ulonglong, + ); + transition_failed( + item, + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + safe_strcpy( + &raw mut dir as *mut ::core::ffi::c_char, + &raw mut final_path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + let mut slash: *mut ::core::ffi::c_char = strrchr( + &raw mut dir as *mut ::core::ffi::c_char, + '/' as ::core::ffi::c_int, + ); + if !slash.is_null() { + *slash = '\0' as ::core::ffi::c_char; + } + let mut jpeg: *mut ::core::ffi::c_uchar = ::core::ptr::null_mut::<::core::ffi::c_uchar>(); + let mut jpeg_size: size_t = 0 as size_t; + if (*item).is_jpeg { + jpeg = (*item).data as *mut ::core::ffi::c_uchar; + jpeg_size = (*item).data_size; + } else if encode_rgb(item, &raw mut jpeg, &raw mut jpeg_size) != 0 as ::core::ffi::c_int + || jpeg.is_null() + || jpeg_size == 0 as size_t + { + free(jpeg as *mut ::core::ffi::c_void); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_encode, + 1 as ::core::ffi::c_ulonglong, + ); + transition_failed( + item, + b"encode_failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } else { + free((*item).data as *mut ::core::ffi::c_void); + (*item).data = ::core::ptr::null_mut::(); + } + let mut write_rc: ::core::ffi::c_int = write_atomic( + &raw mut dir as *mut ::core::ffi::c_char, + &raw mut final_path as *mut ::core::ffi::c_char, + jpeg as *const uint8_t, + jpeg_size, + ); + if !(*item).is_jpeg { + free(jpeg as *mut ::core::ffi::c_void); + } + if write_rc != 0 as ::core::ffi::c_int { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_write, + 1 as ::core::ffi::c_ulonglong, + ); + transition_failed( + item, + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut media: observation_asset_media_t = observation_asset_media_t { + path: &raw mut relative as *mut ::core::ffi::c_char, + bytes: jpeg_size as int64_t, + width: (*item).width, + height: (*item).height, + expires_at_ms: 0 as int64_t, + }; + let mut result: observation_transition_result_t = observation_transition_result_t { + observations_bumped: 0, + last_seq: 0, + }; + stage_hook( + observation_evidence_stage_t::OBS_EVIDENCE_STAGE_BEFORE_COMMIT, + &raw mut final_path as *mut ::core::ffi::c_char, + ); + rc = db_observation_asset_transition( + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + observation_asset_state_t::OBS_ASSET_PENDING, + observation_asset_state_t::OBS_ASSET_READY, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut media, + &raw mut result, + ); + if rc == C2Rust_Unnamed::DB_OBS_ERROR.0 { + rc = db_observation_asset_transition( + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + observation_asset_state_t::OBS_ASSET_PENDING, + observation_asset_state_t::OBS_ASSET_READY, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut media, + &raw mut result, + ); + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + unlink(&raw mut final_path as *mut ::core::ffi::c_char); + fsync_dir(&raw mut dir as *mut ::core::ffi::c_char); + if rc != C2Rust_Unnamed::DB_OBS_CAS_LOST.0 { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.failed_db, + 1 as ::core::ffi::c_ulonglong, + ); + transition_failed( + item, + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: asset %s pending -> ready failed: %d\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + rc, + ); + return; + } + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.ready, + 1 as ::core::ffi::c_ulonglong, + ); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.bytes_written, + jpeg_size as ::core::ffi::c_ulonglong, + ); +} +unsafe extern "C" fn take_all_fifo() -> *mut evidence_item_t { + let mut list: *mut evidence_item_t = + ::core::intrinsics::atomic_xchg::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.head, + ::core::ptr::null_mut::(), + ); + let mut fifo: *mut evidence_item_t = ::core::ptr::null_mut::(); + while !list.is_null() { + let mut next: *mut evidence_item_t = (*list).next; + (*list).next = fifo as *mut evidence_item; + fifo = list; + list = next; + } + return fifo; +} +unsafe extern "C" fn drain_expired() -> bool { + return ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.stop, + ) != 0 + && mono_ns() + >= ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.drain_deadline_ns, + ); +} +unsafe extern "C" fn run_items(mut list: *mut evidence_item_t) { + while !list.is_null() { + let mut item: *mut evidence_item_t = list; + list = (*list).next as *mut evidence_item_t; + stage_hook( + observation_evidence_stage_t::OBS_EVIDENCE_STAGE_ITEM_BEGIN, + ::core::ptr::null::<::core::ffi::c_char>(), + ); + if drain_expired() { + abandon_item(item); + } else { + process_item(item); + } + stage_hook( + observation_evidence_stage_t::OBS_EVIDENCE_STAGE_ITEM_DONE, + ::core::ptr::null::<::core::ffi::c_char>(), + ); + release_item(item); + process_drops(); + } +} +unsafe extern "C" fn evidence_worker( + mut arg: *mut ::core::ffi::c_void, +) -> *mut ::core::ffi::c_void { + g.component_id = register_component( + b"observation_evidence\0".as_ptr() as *const ::core::ffi::c_char, + component_type_t::COMPONENT_OTHER, + NULL, + 20 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.worker_running, + 1 as ::core::ffi::c_int, + ); + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation evidence worker started\0".as_ptr() as *const ::core::ffi::c_char, + ); + loop { + process_drops(); + let mut list: *mut evidence_item_t = take_all_fifo(); + if !list.is_null() { + run_items(list); + } else { + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.stop, + ) != 0 + { + break; + } + let mut deadline: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_REALTIME, &raw mut deadline); + deadline.tv_nsec += + EVIDENCE_WAIT_MS as ::core::ffi::c_long * 1000000 as ::core::ffi::c_long; + if deadline.tv_nsec >= 1000000000 as ::core::ffi::c_long { + deadline.tv_sec += 1 as __time_t; + deadline.tv_nsec -= 1000000000 as ::core::ffi::c_long; + } + while sem_timedwait(&raw mut g.wake, &raw mut deadline) != 0 as ::core::ffi::c_int + && *__errno_location() == EINTR + {} + } + } + if !g.encoder.is_null() { + jpeg_encoder_cache_destroy(g.encoder); + g.encoder = ::core::ptr::null_mut::(); + g.encoder_h = 0 as ::core::ffi::c_int; + g.encoder_w = g.encoder_h; + } + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.worker_running, + 0 as ::core::ffi::c_int, + ); + if g.component_id >= 0 as ::core::ffi::c_int { + update_component_state(g.component_id, component_state_t::COMPONENT_STOPPED); + } + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation evidence worker stopped\0".as_ptr() as *const ::core::ffi::c_char, + ); + return NULL; +} +unsafe extern "C" fn ensure_started() { + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.lifecycle, + ) == C2Rust_Unnamed_2::LC_RUNNING.0 as ::core::ffi::c_int + { + return; + } + if pthread_mutex_trylock(&raw mut g.start_lock) != 0 as ::core::ffi::c_int { + return; + } + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.lifecycle, + ) == C2Rust_Unnamed_2::LC_READY.0 as ::core::ffi::c_int + && ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.thread_started, + ) == 0 + { + let mut ring: *mut drop_ring_t = ring_create(); + if !ring.is_null() { + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.ring, + ring, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.stop, + 0 as ::core::ffi::c_int, + ); + if pthread_create( + &raw mut g.thread, + ::core::ptr::null::(), + Some( + evidence_worker + as unsafe extern "C" fn( + *mut ::core::ffi::c_void, + ) + -> *mut ::core::ffi::c_void, + ), + NULL, + ) == 0 as ::core::ffi::c_int + { + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.thread_started, + 1 as ::core::ffi::c_int, + ); + let mut expected: ::core::ffi::c_int = + C2Rust_Unnamed_2::LC_READY.0 as ::core::ffi::c_int; + let c2rust_result = ::core::intrinsics::atomic_cxchg::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >( + &raw mut g.lifecycle, + *&raw mut expected, + C2Rust_Unnamed_2::LC_RUNNING.0 as ::core::ffi::c_int, + ); + *&raw mut expected = c2rust_result.0; + c2rust_result.1; + } else { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Evidence: cannot start worker thread\0".as_ptr() + as *const ::core::ffi::c_char, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.ring, + ::core::ptr::null_mut::(), + ); + free(ring as *mut ::core::ffi::c_void); + } + } + } + pthread_mutex_unlock(&raw mut g.start_lock); +} +unsafe extern "C" fn enter() -> bool { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.inflight, + 1 as ::core::ffi::c_int, + ); + let mut lc: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.lifecycle); + if lc == C2Rust_Unnamed_2::LC_READY.0 as ::core::ffi::c_int + || lc == C2Rust_Unnamed_2::LC_RUNNING.0 as ::core::ffi::c_int + { + return r#true != 0; + } + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.inflight, + 1 as ::core::ffi::c_int, + ); + return r#false != 0; +} +unsafe extern "C" fn leave() { + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.inflight, + 1 as ::core::ffi::c_int, + ); +} +unsafe extern "C" fn reserve_counter( + mut counter: *mut atomic_int, + mut limit: ::core::ffi::c_int, +) -> bool { + if ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + counter, + 1 as ::core::ffi::c_int, + ) + 1 as ::core::ffi::c_int + > limit + { + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + counter, + 1 as ::core::ffi::c_int, + ); + return r#false != 0; + } + return r#true != 0; +} +unsafe extern "C" fn reserve_bytes( + mut counter: *mut atomic_llong, + mut charge: ::core::ffi::c_longlong, + mut limit: ::core::ffi::c_longlong, +) -> bool { + if ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + counter, charge, + ) + charge + > limit + { + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + counter, charge, + ); + return r#false != 0; + } + return r#true != 0; +} +unsafe extern "C" fn reserve(mut slot: ::core::ffi::c_int, mut charge: size_t) -> bool { + let mut c: ::core::ffi::c_longlong = charge as ::core::ffi::c_longlong; + if !reserve_counter(&raw mut g.items, g.global_max_items) { + return r#false != 0; + } + '_undo_items: { + if reserve_bytes( + &raw mut g.bytes, + c, + g.global_max_bytes as ::core::ffi::c_longlong, + ) { + 's_39: { + if slot >= 0 as ::core::ffi::c_int { + if reserve_counter( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(slot as isize)) + .items, + g.per_camera_max_items, + ) { + if !reserve_bytes( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t) + .offset(slot as isize)) + .bytes, + c, + g.per_camera_max_bytes as ::core::ffi::c_longlong, + ) { + ::core::intrinsics::atomic_xsub::< + _, + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t) + .offset(slot as isize)) + .items, + 1 as ::core::ffi::c_int, + ); + } else { + bump_max_uint( + &raw mut g.camera_high_water_items, + ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t) + .offset(slot as isize)) + .items, + ) as ::core::ffi::c_uint, + ); + break 's_39; + } + } + ::core::intrinsics::atomic_xsub::< + _, + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.bytes, c); + break '_undo_items; + } + } + return r#true != 0; + } + } + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.items, + 1 as ::core::ffi::c_int, + ); + return r#false != 0; +} +unsafe extern "C" fn wake_worker() { + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.thread_started, + ) != 0 + { + sem_post(&raw mut g.wake); + } +} +unsafe extern "C" fn record_drop( + mut batch: *const observation_batch_input_t, + mut asset_uuid: *const ::core::ffi::c_char, + mut reason: *const ::core::ffi::c_char, + mut discard: *mut uint8_t, + mut discard_size: size_t, +) -> bool { + let mut charge: size_t = 0 as size_t; + if !discard.is_null() { + if reserve_bytes( + &raw mut g.discard_bytes, + discard_size as ::core::ffi::c_longlong, + g.discard_max_bytes as ::core::ffi::c_longlong, + ) { + charge = discard_size; + } else { + free(discard as *mut ::core::ffi::c_void); + discard = ::core::ptr::null_mut::(); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.discard_inline, + 1 as ::core::ffi::c_ulonglong, + ); + } + } + if ring_push( + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.ring, + ), + batch, + asset_uuid, + reason, + discard, + charge, + ) { + if !discard.is_null() { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.discard_deferred, + 1 as ::core::ffi::c_ulonglong, + ); + } + wake_worker(); + return r#true != 0; + } + if !discard.is_null() { + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.discard_bytes, + charge as ::core::ffi::c_longlong, + ); + free(discard as *mut ::core::ffi::c_void); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.discard_inline, + 1 as ::core::ffi::c_ulonglong, + ); + } + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.dropped_untracked, + 1 as ::core::ffi::c_ulonglong, + ); + return r#false != 0; +} +unsafe extern "C" fn batch_valid(mut batch: *const observation_batch_input_t) -> bool { + return !batch.is_null() + && lightnvr_uuid_is_valid(&raw const (*batch).camera_uuid as *const ::core::ffi::c_char) + as ::core::ffi::c_int + != 0 + && lightnvr_uuid_is_valid(&raw const (*batch).batch_id as *const ::core::ffi::c_char) + as ::core::ffi::c_int + != 0 + && (*batch).object_count >= 1 as ::core::ffi::c_int + && (*batch).object_count <= OBS_MAX_BATCH + && (*batch).observed_at_ms >= 0 as int64_t; +} +unsafe extern "C" fn admit( + mut batch: *const observation_batch_input_t, + mut data: *mut uint8_t, + mut data_size: size_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut is_jpeg: bool, + mut transform: *const observation_evidence_transform_t, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> observation_evidence_result_t { + if !asset_uuid_out.is_null() { + *asset_uuid_out.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if !enter() { + free(data as *mut ::core::ffi::c_void); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.dropped_disabled, + 1 as ::core::ffi::c_ulonglong, + ); + return observation_evidence_result_t::OBS_EVIDENCE_DISABLED; + } + let mut result: observation_evidence_result_t = + observation_evidence_result_t::OBS_EVIDENCE_INVALID; + let mut transform_json: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut asset_uuid: [::core::ffi::c_char; 37] = [0; 37]; + if !batch_valid(batch) + || data.is_null() + || data_size == 0 as size_t + || width <= 0 as ::core::ffi::c_int + || height <= 0 as ::core::ffi::c_int + || observation_evidence_transform_json( + transform, + width, + height, + &raw mut transform_json as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + || lightnvr_uuid_generate_v4(&raw mut asset_uuid as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + free(data as *mut ::core::ffi::c_void); + leave(); + return observation_evidence_result_t::OBS_EVIDENCE_INVALID; + } + ensure_started(); + let mut slot: ::core::ffi::c_int = + slot_get(&raw const (*batch).camera_uuid as *const ::core::ffi::c_char); + let mut charge: size_t = data_size + .wrapping_add( + (if is_jpeg as ::core::ffi::c_int != 0 { + 0 as ::core::ffi::c_int + } else { + RGB24_SWS_TAIL_PADDING + }) as size_t, + ) + .wrapping_add(::core::mem::size_of::()); + let mut item: *mut evidence_item_t = ::core::ptr::null_mut::(); + if slot >= 0 as ::core::ffi::c_int + && ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(slot as isize)) + .budget_exhausted, + ) != 0 + { + result = observation_evidence_result_t::OBS_EVIDENCE_DROPPED_BUDGET; + } else if !reserve(slot, charge) { + result = observation_evidence_result_t::OBS_EVIDENCE_DROPPED_QUEUE_FULL; + } else { + item = malloc(::core::mem::size_of::()) as *mut evidence_item_t; + if item.is_null() { + if slot >= 0 as ::core::ffi::c_int { + ::core::intrinsics::atomic_xsub::< + _, + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(slot as isize)) + .items, + 1 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_xsub::< + _, + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(slot as isize)) + .bytes, + charge as ::core::ffi::c_longlong, + ); + } + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.items, + 1 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_xsub::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.bytes, + charge as ::core::ffi::c_longlong, + ); + result = observation_evidence_result_t::OBS_EVIDENCE_DROPPED_QUEUE_FULL; + } else { + result = observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED; + } + } + if result.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 { + let mut budget: bool = + result.0 == observation_evidence_result_t::OBS_EVIDENCE_DROPPED_BUDGET.0; + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + if budget as ::core::ffi::c_int != 0 { + &raw mut g.dropped_budget + } else { + &raw mut g.dropped_queue_full + }, + 1 as ::core::ffi::c_ulonglong, + ); + if record_drop( + batch, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + if budget as ::core::ffi::c_int != 0 { + b"budget_exhausted\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"queue_full\0".as_ptr() as *const ::core::ffi::c_char + }, + data, + data_size, + ) as ::core::ffi::c_int + != 0 + && !asset_uuid_out.is_null() + { + safe_strcpy( + asset_uuid_out, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + } + leave(); + return result; + } + memset( + item as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + 48 as size_t, + ); + (*item).is_jpeg = is_jpeg; + (*item).slot = slot; + (*item).charge = charge; + (*item).data = data; + (*item).data_size = data_size; + (*item).width = width; + (*item).height = height; + (*item).batch = *batch; + memset( + &raw mut (*item).asset as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + safe_strcpy( + &raw mut (*item).asset.asset_uuid as *mut ::core::ffi::c_char, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + (*item).asset.state = observation_asset_state_t::OBS_ASSET_PENDING; + safe_strcpy( + &raw mut (*item).asset.origin as *mut ::core::ffi::c_char, + if is_jpeg as ::core::ffi::c_int != 0 { + b"submitted_image\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"inference_input\0".as_ptr() as *const ::core::ffi::c_char + }, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + 0 as size_t, + ); + safe_strcpy( + &raw mut (*item).asset.media_type as *mut ::core::ffi::c_char, + b"image/jpeg\0".as_ptr() as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + 0 as size_t, + ); + (*item).asset.captured_at_ms = (*batch).observed_at_ms; + safe_strcpy( + &raw mut (*item).asset.transform_json as *mut ::core::ffi::c_char, + &raw mut transform_json as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + 0 as size_t, + ); + let mut head: *mut evidence_item_t = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.head); + loop { + (*item).next = head as *mut evidence_item; + let c2rust_result = ::core::intrinsics::atomic_cxchgweak::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.head, *&raw mut head, item); + *&raw mut head = c2rust_result.0; + if c2rust_result.1 { + break; + } + } + bump_max_uint( + &raw mut g.high_water_items, + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.items, + ) as ::core::ffi::c_uint, + ); + bump_max_ull( + &raw mut g.high_water_bytes, + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.bytes, + ) as ::core::ffi::c_ulonglong, + ); + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.accepted, + 1 as ::core::ffi::c_ulonglong, + ); + if !asset_uuid_out.is_null() { + safe_strcpy( + asset_uuid_out, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + } + wake_worker(); + leave(); + return observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_submit( + mut batch: *const observation_batch_input_t, + mut rgb24: *mut uint8_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut transform: *const observation_evidence_transform_t, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> observation_evidence_result_t { + let mut start: int64_t = mono_ns(); + let mut size: size_t = if width > 0 as ::core::ffi::c_int && height > 0 as ::core::ffi::c_int { + (width as size_t) + .wrapping_mul(height as size_t) + .wrapping_mul(3 as size_t) + } else { + 0 as size_t + }; + let mut rc: observation_evidence_result_t = admit( + batch, + rgb24, + size, + width, + height, + r#false != 0, + transform, + asset_uuid_out, + ); + if rc.0 != observation_evidence_result_t::OBS_EVIDENCE_DISABLED.0 { + record_submit_latency(start); + } + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_submit_jpeg( + mut batch: *const observation_batch_input_t, + mut jpeg: *mut uint8_t, + mut jpeg_size: size_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut transform: *const observation_evidence_transform_t, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> observation_evidence_result_t { + let mut start: int64_t = mono_ns(); + let mut rc: observation_evidence_result_t = admit( + batch, + jpeg, + jpeg_size, + width, + height, + r#true != 0, + transform, + asset_uuid_out, + ); + if rc.0 != observation_evidence_result_t::OBS_EVIDENCE_DISABLED.0 { + record_submit_latency(start); + } + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_submit_unavailable( + mut batch: *const observation_batch_input_t, + mut reason: *const ::core::ffi::c_char, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> observation_evidence_result_t { + let mut start: int64_t = mono_ns(); + if !asset_uuid_out.is_null() { + *asset_uuid_out.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if !enter() { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.dropped_disabled, + 1 as ::core::ffi::c_ulonglong, + ); + return observation_evidence_result_t::OBS_EVIDENCE_DISABLED; + } + let mut asset_uuid: [::core::ffi::c_char; 37] = [0; 37]; + if !batch_valid(batch) + || reason.is_null() + || strcmp( + reason, + b"no_matching_media\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + || lightnvr_uuid_generate_v4(&raw mut asset_uuid as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + leave(); + record_submit_latency(start); + return observation_evidence_result_t::OBS_EVIDENCE_INVALID; + } + ensure_started(); + let mut result: observation_evidence_result_t = + observation_evidence_result_t::OBS_EVIDENCE_DROPPED_QUEUE_FULL; + if record_drop( + batch, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + reason, + ::core::ptr::null_mut::(), + 0 as size_t, + ) { + ::core::intrinsics::atomic_xadd::<_, _, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.unavailable_submitted, + 1 as ::core::ffi::c_ulonglong, + ); + if !asset_uuid_out.is_null() { + safe_strcpy( + asset_uuid_out, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + } + result = observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED; + } + leave(); + record_submit_latency(start); + return result; +} +unsafe extern "C" fn reset_counters() { + let mut counters: [*mut atomic_ullong; 19] = [ + &raw mut g.accepted, + &raw mut g.dropped_queue_full, + &raw mut g.dropped_budget, + &raw mut g.dropped_disabled, + &raw mut g.dropped_untracked, + &raw mut g.unavailable_submitted, + &raw mut g.ready, + &raw mut g.failed_encode, + &raw mut g.failed_write, + &raw mut g.failed_db, + &raw mut g.disabled_at_encode, + &raw mut g.interrupted, + &raw mut g.bytes_written, + &raw mut g.high_water_bytes, + &raw mut g.submit_latency_count, + &raw mut g.submit_latency_sum_ns, + &raw mut g.submit_latency_max_ns, + &raw mut g.discard_deferred, + &raw mut g.discard_inline, + ]; + let mut i: size_t = 0 as size_t; + while i < ::core::mem::size_of::<[*mut atomic_ullong; 19]>() + .wrapping_div(::core::mem::size_of::<*mut atomic_ullong>()) + { + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + counters[i], + 0 as ::core::ffi::c_ulonglong, + ); + i = i.wrapping_add(1); + } + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS { + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + (&raw mut g.submit_latency_bucket as *mut atomic_ullong).offset(i_0 as isize), + 0 as ::core::ffi::c_ulonglong, + ); + i_0 += 1; + } + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.high_water_items, + 0 as ::core::ffi::c_uint, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.camera_high_water_items, + 0 as ::core::ffi::c_uint, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_init( + mut config: *const observation_evidence_config_t, +) -> ::core::ffi::c_int { + if config.is_null() + || (*config).storage_path.is_null() + || *(*config).storage_path.offset(0isize) == 0 + || (*config).global_max_bytes < 0 as int64_t + || (*config).global_max_items < 0 as ::core::ffi::c_int + || (*config).per_camera_max_items < 0 as ::core::ffi::c_int + || (*config).per_camera_max_bytes < 0 as int64_t + { + return -1 as ::core::ffi::c_int; + } + if strlen((*config).storage_path).wrapping_add(128 as size_t) + >= ::core::mem::size_of::<[::core::ffi::c_char; 512]>() + { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut g.start_lock); + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.lifecycle, + ) != C2Rust_Unnamed_2::LC_OFF.0 as ::core::ffi::c_int + { + pthread_mutex_unlock(&raw mut g.start_lock); + return -1 as ::core::ffi::c_int; + } + safe_strcpy( + &raw mut g.storage_path as *mut ::core::ffi::c_char, + (*config).storage_path, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + let mut len: size_t = strlen(&raw mut g.storage_path as *mut ::core::ffi::c_char); + while len > 1 as size_t + && g.storage_path[len.wrapping_sub(1 as size_t)] as ::core::ffi::c_int + == '/' as ::core::ffi::c_int + { + len = len.wrapping_sub(1); + g.storage_path[len] = '\0' as ::core::ffi::c_char; + } + g.global_max_bytes = (if (*config).global_max_bytes > 0 as int64_t { + (*config).global_max_bytes as ::core::ffi::c_longlong + } else { + OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES + }) as int64_t; + g.global_max_items = if (*config).global_max_items > 0 as ::core::ffi::c_int { + (*config).global_max_items + } else { + OBS_EVIDENCE_GLOBAL_DEPTH + }; + g.per_camera_max_items = if (*config).per_camera_max_items > 0 as ::core::ffi::c_int { + (*config).per_camera_max_items + } else { + OBS_EVIDENCE_PER_CAMERA_DEPTH + }; + g.per_camera_max_bytes = if (*config).per_camera_max_bytes > 0 as int64_t { + (*config).per_camera_max_bytes + } else { + g.global_max_bytes + }; + g.discard_max_bytes = + (if g.global_max_bytes as ::core::ffi::c_longlong > OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES { + g.global_max_bytes as ::core::ffi::c_longlong + } else { + OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES + }) as int64_t; + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.discard_bytes, + 0 as ::core::ffi::c_longlong, + ); + if sem_init( + &raw mut g.wake, + 0 as ::core::ffi::c_int, + 0 as ::core::ffi::c_uint, + ) != 0 as ::core::ffi::c_int + { + pthread_mutex_unlock(&raw mut g.start_lock); + return -1 as ::core::ffi::c_int; + } + reset_counters(); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.items, + 0 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.bytes, + 0 as ::core::ffi::c_longlong, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.drop_pending, + 0 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.stop, + 0 as ::core::ffi::c_int, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.lifecycle, + C2Rust_Unnamed_2::LC_READY.0 as ::core::ffi::c_int, + ); + pthread_mutex_unlock(&raw mut g.start_lock); + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation evidence initialized (worker starts on first submission; queue %d items / %lld bytes, %d per camera)\0" + .as_ptr() as *const ::core::ffi::c_char, + g.global_max_items, + g.global_max_bytes as ::core::ffi::c_longlong, + g.per_camera_max_items, + ); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_shutdown(mut drain_timeout_ms: ::core::ffi::c_int) { + let mut lc: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.lifecycle); + if lc != C2Rust_Unnamed_2::LC_READY.0 as ::core::ffi::c_int + && lc != C2Rust_Unnamed_2::LC_RUNNING.0 as ::core::ffi::c_int + || { + let c2rust_result = ::core::intrinsics::atomic_cxchg::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >( + &raw mut g.lifecycle, + *&raw mut lc, + C2Rust_Unnamed_2::LC_STOPPING.0 as ::core::ffi::c_int, + ); + *&raw mut lc = c2rust_result.0; + !c2rust_result.1 + } + { + return; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.inflight, + ) > 0 as ::core::ffi::c_int + && i < 50000 as ::core::ffi::c_int + { + usleep(100 as __useconds_t); + i += 1; + } + if drain_timeout_ms < 0 as ::core::ffi::c_int { + drain_timeout_ms = OBS_EVIDENCE_DEFAULT_DRAIN_MS; + } + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.drain_deadline_ns, + (mono_ns() as ::core::ffi::c_longlong + + drain_timeout_ms as int64_t as ::core::ffi::c_longlong + * 1000000 as ::core::ffi::c_longlong) as int64_t, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.stop, + 1 as ::core::ffi::c_int, + ); + pthread_mutex_lock(&raw mut g.start_lock); + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.thread_started, + ) != 0 + { + sem_post(&raw mut g.wake); + pthread_join( + g.thread, + ::core::ptr::null_mut::<*mut ::core::ffi::c_void>(), + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.thread_started, + 0 as ::core::ffi::c_int, + ); + } + pthread_mutex_unlock(&raw mut g.start_lock); + let mut list: *mut evidence_item_t = take_all_fifo(); + while !list.is_null() { + let mut item: *mut evidence_item_t = list; + list = (*list).next as *mut evidence_item_t; + abandon_item(item); + release_item(item); + } + process_drops(); + if !g.encoder.is_null() { + jpeg_encoder_cache_destroy(g.encoder); + g.encoder = ::core::ptr::null_mut::(); + } + free( + ::core::intrinsics::atomic_xchg::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.ring, + ::core::ptr::null_mut::(), + ) as *mut ::core::ffi::c_void, + ); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.drop_pending, + 0 as ::core::ffi::c_int, + ); + sem_destroy(&raw mut g.wake); + g.component_id = -1 as ::core::ffi::c_int; + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.lifecycle, + C2Rust_Unnamed_2::LC_OFF.0 as ::core::ffi::c_int, + ); + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation evidence shut down (ready=%llu interrupted=%llu dropped_untracked=%llu)\0" + .as_ptr() as *const ::core::ffi::c_char, + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.ready, + ), + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.interrupted, + ), + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.dropped_untracked, + ), + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_evidence_get_stats( + mut out: *mut observation_evidence_stats_t, +) { + if out.is_null() { + return; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut lc: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.lifecycle); + (*out).initialized = lc == C2Rust_Unnamed_2::LC_READY.0 as ::core::ffi::c_int + || lc == C2Rust_Unnamed_2::LC_RUNNING.0 as ::core::ffi::c_int; + (*out).worker_running = + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.worker_running, + ) != 0; + (*out).allocated = !::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.ring) + .is_null() + || ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g.thread_started, + ) != 0; + (*out).accepted = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.accepted) as uint64_t; + (*out).dropped_queue_full = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.dropped_queue_full) as uint64_t; + (*out).dropped_budget = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.dropped_budget) as uint64_t; + (*out).dropped_disabled = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.dropped_disabled) as uint64_t; + (*out).dropped_untracked = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.dropped_untracked) as uint64_t; + (*out).unavailable_submitted = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.unavailable_submitted) as uint64_t; + (*out).ready = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.ready) as uint64_t; + (*out).failed_encode = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.failed_encode) as uint64_t; + (*out).failed_write = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.failed_write) as uint64_t; + (*out).failed_db = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.failed_db) as uint64_t; + (*out).disabled_at_encode = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.disabled_at_encode) as uint64_t; + (*out).interrupted = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.interrupted) as uint64_t; + (*out).bytes_written = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.bytes_written) as uint64_t; + let mut items: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.items); + let mut bytes: ::core::ffi::c_longlong = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.bytes); + let mut drops: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.drop_pending); + (*out).backlog_items = if items > 0 as ::core::ffi::c_int { + items as uint32_t + } else { + 0 as uint32_t + }; + (*out).backlog_bytes = if bytes > 0 as ::core::ffi::c_longlong { + bytes as uint64_t + } else { + 0 as uint64_t + }; + (*out).drop_ring_depth = if drops > 0 as ::core::ffi::c_int { + drops as uint32_t + } else { + 0 as uint32_t + }; + (*out).high_water_items = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.high_water_items) as uint32_t; + (*out).high_water_bytes = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.high_water_bytes) as uint64_t; + (*out).camera_high_water_items = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.camera_high_water_items) as uint32_t; + if (*out).initialized { + (*out).limit_items = g.global_max_items as uint32_t; + (*out).limit_camera_items = g.per_camera_max_items as uint32_t; + (*out).limit_bytes = g.global_max_bytes as uint64_t; + (*out).limit_camera_bytes = g.per_camera_max_bytes as uint64_t; + (*out).limit_discard_bytes = g.discard_max_bytes as uint64_t; + } + (*out).discard_deferred = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.discard_deferred) as uint64_t; + (*out).discard_inline = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.discard_inline) as uint64_t; + let mut pending_discard: ::core::ffi::c_longlong = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.discard_bytes); + (*out).discard_pending_bytes = if pending_discard > 0 as ::core::ffi::c_longlong { + pending_discard as uint64_t + } else { + 0 as uint64_t + }; + (*out).submit_latency_count = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.submit_latency_count) as uint64_t; + (*out).submit_latency_sum_ns = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.submit_latency_sum_ns) as uint64_t; + (*out).submit_latency_max_ns = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::SeqCst }, + >(&raw mut g.submit_latency_max_ns) as uint64_t; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS { + (*out).submit_latency_bucket[i as usize] = + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + (&raw mut g.submit_latency_bucket as *mut atomic_ullong).offset(i as isize), + ) as uint64_t; + i += 1; + } + let mut count: ::core::ffi::c_int = ::core::intrinsics::atomic_load::< + _, + { ::core::intrinsics::AtomicOrdering::Acquire }, + >(&raw mut g_slot_count); + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < count { + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*(&raw mut g_slots as *mut camera_slot_t).offset(i_0 as isize)) + .budget_exhausted, + ) != 0 + { + (*out).budget_exhausted_cameras = (*out).budget_exhausted_cameras.wrapping_add(1); + } + i_0 += 1; + } +} +pub const LIGHTNVR_MAX_STREAMS: ::core::ffi::c_int = 1024 as ::core::ffi::c_int; +pub const __ATOMIC_SEQ_CST: ::core::ffi::c_int = 5 as ::core::ffi::c_int; diff --git a/rust/generated/src__core__observation_relay.rs b/rust/generated/src__core__observation_relay.rs new file mode 100644 index 000000000..24244bac3 --- /dev/null +++ b/rust/generated/src__core__observation_relay.rs @@ -0,0 +1,1843 @@ +unsafe extern "C" { + unsafe fn strftime( + __s: *mut ::core::ffi::c_char, + __maxsize: size_t, + __format: *const ::core::ffi::c_char, + __tp: *const tm, + ) -> size_t; + unsafe fn gmtime_r(__timer: *const time_t, __tp: *mut tm) -> *mut tm; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn cJSON_Delete(item: *mut cJSON); + unsafe fn cJSON_CreateObject() -> *mut cJSON; + unsafe fn cJSON_AddNullToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_AddNumberToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + number: ::core::ffi::c_double, + ) -> *mut cJSON; + unsafe fn cJSON_AddStringToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn event_registry_find( + r#type: *const ::core::ffi::c_char, + ) -> *const event_type_definition_t; + unsafe fn event_envelope_create_with_severity_and_id( + event: *mut event_envelope_t, + r#type: *const ::core::ffi::c_char, + source: *const ::core::ffi::c_char, + subject: *const ::core::ffi::c_char, + occurred_at: time_t, + data: *const cJSON, + severity: event_severity_t, + event_id: *const ::core::ffi::c_char, + error: *mut ::core::ffi::c_char, + error_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn event_envelope_clear(event: *mut event_envelope_t); + static mut g_config: config_t; + unsafe fn observation_asset_state_name( + state: observation_asset_state_t, + ) -> *const ::core::ffi::c_char; + unsafe fn observation_change_cause_name( + cause: observation_change_cause_t, + ) -> *const ::core::ffi::c_char; + unsafe fn db_observation_meta_get(out: *mut observation_meta_t) -> ::core::ffi::c_int; + unsafe fn db_observation_pending_publications( + out: *mut observation_publication_t, + max: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_publication_mark( + change_seq: int64_t, + state: observation_publication_state_t, + last_error: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn db_observations_set_publication_hook(hook: db_observation_publication_hook_t); + unsafe fn observation_cursor_head_write( + storage_path: *const ::core::ffi::c_char, + epoch_uuid: *const ::core::ffi::c_char, + head_seq: int64_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_cursor_head_reconcile( + storage_path: *const ::core::ffi::c_char, + regenerated: *mut bool, + ) -> ::core::ffi::c_int; + unsafe fn pthread_create( + __newthread: *mut pthread_t, + __attr: *const pthread_attr_t, + __start_routine: Option< + unsafe extern "C" fn(*mut ::core::ffi::c_void) -> *mut ::core::ffi::c_void, + >, + __arg: *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int; + unsafe fn pthread_join( + __th: pthread_t, + __thread_return: *mut *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_lock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_unlock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_cond_init( + __cond: *mut pthread_cond_t, + __cond_attr: *const pthread_condattr_t, + ) -> ::core::ffi::c_int; + unsafe fn pthread_cond_signal(__cond: *mut pthread_cond_t) -> ::core::ffi::c_int; + unsafe fn pthread_cond_broadcast(__cond: *mut pthread_cond_t) -> ::core::ffi::c_int; + unsafe fn pthread_cond_timedwait( + __cond: *mut pthread_cond_t, + __mutex: *mut pthread_mutex_t, + __abstime: *const timespec, + ) -> ::core::ffi::c_int; + unsafe fn pthread_condattr_init(__attr: *mut pthread_condattr_t) -> ::core::ffi::c_int; + unsafe fn pthread_condattr_destroy(__attr: *mut pthread_condattr_t) -> ::core::ffi::c_int; + unsafe fn pthread_condattr_setclock( + __attr: *mut pthread_condattr_t, + __clock_id: __clockid_t, + ) -> ::core::ffi::c_int; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn event_bus_publish( + event: *const event_envelope_t, + error: *mut ::core::ffi::c_char, + error_size: size_t, + ) -> event_bus_result_t; + unsafe fn event_identity_get_source( + output: *mut ::core::ffi::c_char, + output_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn event_producer_enqueue_durable( + event: *const event_envelope_t, + error: *mut ::core::ffi::c_char, + error_size: size_t, + ) -> event_producer_enqueue_result_t; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn db_event_outbox_count_undelivered_type( + event_type: *const ::core::ffi::c_char, + count: *mut int64_t, + ) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strerror(__errnum: ::core::ffi::c_int) -> *mut ::core::ffi::c_char; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __time_t = ::core::ffi::c_long; +pub type __clockid_t = ::core::ffi::c_int; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union __atomic_wide_counter { + pub __value64: ::core::ffi::c_ulonglong, + pub __value32: C2Rust_Unnamed, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed { + pub __low: ::core::ffi::c_uint, + pub __high: ::core::ffi::c_uint, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +pub type __pthread_list_t = __pthread_internal_list; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_mutex_s { + pub __lock: ::core::ffi::c_int, + pub __count: ::core::ffi::c_uint, + pub __owner: ::core::ffi::c_int, + pub __nusers: ::core::ffi::c_uint, + pub __kind: ::core::ffi::c_int, + pub __spins: ::core::ffi::c_short, + pub __glibc_reserved: ::core::ffi::c_short, + pub __list: __pthread_list_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_cond_s { + pub __wseq: __atomic_wide_counter, + pub __g1_start: __atomic_wide_counter, + pub __g_size: [::core::ffi::c_uint; 2], + pub __g1_orig_size: ::core::ffi::c_uint, + pub __wrefs: ::core::ffi::c_uint, + pub __g_signals: [::core::ffi::c_uint; 2], + pub __unused_initialized_1: ::core::ffi::c_uint, + pub __unused_initialized_2: ::core::ffi::c_uint, +} +pub type pthread_t = ::core::ffi::c_ulong; +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_condattr_t { + pub __size: [::core::ffi::c_char; 4], + pub __align: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_attr_t { + pub __size: [::core::ffi::c_char; 56], + pub __align: ::core::ffi::c_long, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::core::ffi::c_char; 40], + pub __align: ::core::ffi::c_long, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_cond_t { + pub __data: __pthread_cond_s, + pub __size: [::core::ffi::c_char; 48], + pub __align: ::core::ffi::c_longlong, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct tm { + pub tm_sec: ::core::ffi::c_int, + pub tm_min: ::core::ffi::c_int, + pub tm_hour: ::core::ffi::c_int, + pub tm_mday: ::core::ffi::c_int, + pub tm_mon: ::core::ffi::c_int, + pub tm_year: ::core::ffi::c_int, + pub tm_wday: ::core::ffi::c_int, + pub tm_yday: ::core::ffi::c_int, + pub tm_isdst: ::core::ffi::c_int, + pub tm_gmtoff: ::core::ffi::c_long, + pub tm_zone: *const ::core::ffi::c_char, +} +pub type uint8_t = u8; +pub type uint32_t = u32; +pub type uint64_t = u64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_severity_t(pub ::core::ffi::c_uint); +impl event_severity_t { + pub const EVENT_SEVERITY_INFO: Self = Self(0); + pub const EVENT_SEVERITY_WARNING: Self = Self(1); + pub const EVENT_SEVERITY_ERROR: Self = Self(2); + pub const EVENT_SEVERITY_CRITICAL: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_sensitivity_t(pub ::core::ffi::c_uint); +impl event_sensitivity_t { + pub const EVENT_SENSITIVITY_OPERATIONAL: Self = Self(0); + pub const EVENT_SENSITIVITY_INTERNAL: Self = Self(1); + pub const EVENT_SENSITIVITY_RESTRICTED: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_media_policy_t(pub ::core::ffi::c_uint); +impl event_media_policy_t { + pub const EVENT_MEDIA_FORBIDDEN: Self = Self(0); + pub const EVENT_MEDIA_REFERENCE_ALLOWED: Self = Self(1); + pub const EVENT_MEDIA_BINARY_OPT_IN: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_expected_rate_t(pub ::core::ffi::c_uint); +impl event_expected_rate_t { + pub const EVENT_RATE_LOW: Self = Self(0); + pub const EVENT_RATE_MEDIUM: Self = Self(1); + pub const EVENT_RATE_HIGH: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_subject_kind_t(pub ::core::ffi::c_uint); +impl event_subject_kind_t { + pub const EVENT_SUBJECT_CAMERA: Self = Self(0); + pub const EVENT_SUBJECT_STORAGE: Self = Self(1); + pub const EVENT_SUBJECT_SYSTEM: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct event_type_definition_t { + pub r#type: *const ::core::ffi::c_char, + pub family: *const ::core::ffi::c_char, + pub description: *const ::core::ffi::c_char, + pub severity: event_severity_t, + pub dynamic_severity: bool, + pub minimum_severity: event_severity_t, + pub maximum_severity: event_severity_t, + pub sensitivity: event_sensitivity_t, + pub media_policy: event_media_policy_t, + pub expected_rate: event_expected_rate_t, + pub subject_kind: event_subject_kind_t, + pub default_expiry_seconds: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct event_envelope_t { + pub specversion: [::core::ffi::c_char; 4], + pub id: [::core::ffi::c_char; 37], + pub r#type: [::core::ffi::c_char; 96], + pub source: [::core::ffi::c_char; 128], + pub subject: [::core::ffi::c_char; 96], + pub time: [::core::ffi::c_char; 32], + pub datacontenttype: [::core::ffi::c_char; 32], + pub occurred_at: time_t, + pub expires_at: time_t, + pub severity: event_severity_t, + pub data: *mut cJSON, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct system_health_policy_settings_t { + pub enabled: bool, + pub profile: [::core::ffi::c_char; 16], + pub fast_interval_seconds: uint32_t, + pub normal_interval_seconds: uint32_t, + pub slow_interval_seconds: uint32_t, + pub device_interval_seconds: uint32_t, + pub write_probe_enabled: bool, + pub hardware_provider: [::core::ffi::c_char; 16], + pub presence_interval_seconds: uint32_t, + pub incident_retention_days: uint32_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_protocol_t(pub ::core::ffi::c_uint); +impl stream_protocol_t { + pub const STREAM_PROTOCOL_TCP: Self = Self(0); + pub const STREAM_PROTOCOL_UDP: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stream_config_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub location_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub url: [::core::ffi::c_char; 512], + pub enabled: bool, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub fps: ::core::ffi::c_int, + pub codec: [::core::ffi::c_char; 16], + pub priority: ::core::ffi::c_int, + pub record: bool, + pub segment_duration: ::core::ffi::c_int, + pub detection_based_recording: bool, + pub detection_model: [::core::ffi::c_char; 512], + pub detection_interval: ::core::ffi::c_int, + pub detection_threshold: ::core::ffi::c_float, + pub pre_detection_buffer: ::core::ffi::c_int, + pub post_detection_buffer: ::core::ffi::c_int, + pub detection_api_url: [::core::ffi::c_char; 512], + pub detection_object_filter: [::core::ffi::c_char; 16], + pub detection_object_filter_list: [::core::ffi::c_char; 256], + pub streaming_enabled: bool, + pub protocol: stream_protocol_t, + pub record_audio: bool, + pub audio_voice_enhancement: bool, + pub onvif_username: [::core::ffi::c_char; 64], + pub onvif_password: [::core::ffi::c_char; 64], + pub onvif_profile: [::core::ffi::c_char; 64], + pub onvif_port: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub is_onvif: bool, + pub backchannel_enabled: bool, + pub retention_days: ::core::ffi::c_int, + pub detection_retention_days: ::core::ffi::c_int, + pub max_storage_mb: ::core::ffi::c_int, + pub tier_critical_multiplier: ::core::ffi::c_double, + pub tier_important_multiplier: ::core::ffi::c_double, + pub tier_ephemeral_multiplier: ::core::ffi::c_double, + pub storage_priority: ::core::ffi::c_int, + pub ptz_enabled: bool, + pub ptz_max_x: ::core::ffi::c_int, + pub ptz_max_y: ::core::ffi::c_int, + pub ptz_max_z: ::core::ffi::c_int, + pub ptz_has_home: bool, + pub record_on_schedule: bool, + pub recording_schedule: [uint8_t; 168], + pub detection_record_on_schedule: bool, + pub detection_recording_schedule: [uint8_t; 168], + pub tags: [::core::ffi::c_char; 256], + pub admin_url: [::core::ffi::c_char; 512], + pub privacy_mode: bool, + pub motion_trigger_source: [::core::ffi::c_char; 256], + pub go2rtc_source_override: [::core::ffi::c_char; 8192], + pub sub_stream_url: [::core::ffi::c_char; 512], + pub detection_url: [::core::ffi::c_char; 512], + pub publish_url: [::core::ffi::c_char; 512], + pub playback_transport: [::core::ffi::c_char; 24], + pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_engine_config_t { + pub enabled: bool, + pub num_threads: ::core::ffi::c_int, + pub delegate: [::core::ffi::c_char; 16], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct config_t { + pub pid_file: [::core::ffi::c_char; 512], + pub log_file: [::core::ffi::c_char; 512], + pub log_level: ::core::ffi::c_int, + pub syslog_enabled: bool, + pub syslog_ident: [::core::ffi::c_char; 64], + pub syslog_facility: ::core::ffi::c_int, + pub storage_path: [::core::ffi::c_char; 512], + pub storage_path_hls: [::core::ffi::c_char; 512], + pub max_storage_size: uint64_t, + pub retention_days: ::core::ffi::c_int, + pub auto_delete_oldest: bool, + pub mp4_faststart: bool, + pub storage_min_free_pct: ::core::ffi::c_int, + pub storage_pressure_warning_pct: ::core::ffi::c_double, + pub storage_pressure_critical_pct: ::core::ffi::c_double, + pub storage_pressure_emergency_pct: ::core::ffi::c_double, + pub generate_thumbnails: bool, + pub thumbnails_per_recording: ::core::ffi::c_int, + pub record_mp4_directly: bool, + pub mp4_storage_path: [::core::ffi::c_char; 512], + pub mp4_directory_format: [::core::ffi::c_char; 32], + pub mp4_segment_duration: ::core::ffi::c_int, + pub mp4_retention_days: ::core::ffi::c_int, + pub models_path: [::core::ffi::c_char; 512], + pub api_detection_url: [::core::ffi::c_char; 512], + pub api_detection_backend: [::core::ffi::c_char; 32], + pub api_detection_format: [::core::ffi::c_char; 32], + pub api_detection_detector_name: [::core::ffi::c_char; 64], + pub default_detection_threshold: ::core::ffi::c_int, + pub default_pre_detection_buffer: ::core::ffi::c_int, + pub default_post_detection_buffer: ::core::ffi::c_int, + pub default_playback_transport: [::core::ffi::c_char; 24], + pub detection_grace_period: ::core::ffi::c_int, + pub db_path: [::core::ffi::c_char; 512], + pub db_backup_interval_minutes: ::core::ffi::c_int, + pub db_backup_retention_count: ::core::ffi::c_int, + pub db_post_backup_script: [::core::ffi::c_char; 512], + pub db_startup_check: ::core::ffi::c_int, + pub db_backup_verify: ::core::ffi::c_int, + pub web_thread_pool_size: ::core::ffi::c_int, + pub web_port: ::core::ffi::c_int, + pub web_bind_ip: [::core::ffi::c_char; 32], + pub web_root: [::core::ffi::c_char; 512], + pub web_ssl_enabled: bool, + pub web_ssl_cert_path: [::core::ffi::c_char; 512], + pub web_ssl_key_path: [::core::ffi::c_char; 512], + pub web_auth_enabled: bool, + pub web_username: [::core::ffi::c_char; 32], + pub web_password: [::core::ffi::c_char; 32], + pub audio_disabled: bool, + pub auto_disabled: bool, + pub webrtc_disabled: bool, + pub hls_disabled: bool, + pub mse_disabled: bool, + pub auth_timeout_hours: ::core::ffi::c_int, + pub auth_absolute_timeout_hours: ::core::ffi::c_int, + pub trusted_device_days: ::core::ffi::c_int, + pub trusted_proxy_cidrs: [::core::ffi::c_char; 1024], + pub demo_mode: bool, + pub force_mfa_on_login: bool, + pub login_rate_limit_enabled: bool, + pub login_rate_limit_max_attempts: ::core::ffi::c_int, + pub login_rate_limit_window_seconds: ::core::ffi::c_int, + pub web_compression_enabled: bool, + pub web_use_minified_assets: bool, + pub web_cache_max_age_html: ::core::ffi::c_int, + pub web_cache_max_age_css: ::core::ffi::c_int, + pub web_cache_max_age_js: ::core::ffi::c_int, + pub web_cache_max_age_images: ::core::ffi::c_int, + pub web_cache_max_age_fonts: ::core::ffi::c_int, + pub web_cache_max_age_default: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub onvif_discovery_interval: ::core::ffi::c_int, + pub onvif_discovery_network: [::core::ffi::c_char; 64], + pub max_streams: ::core::ffi::c_int, + pub streams: *mut stream_config_t, + pub buffer_size: ::core::ffi::c_int, + pub use_swap: bool, + pub swap_file: [::core::ffi::c_char; 512], + pub swap_size: uint64_t, + pub memory_constrained: bool, + pub hw_accel_enabled: bool, + pub hw_accel_device: [::core::ffi::c_char; 32], + pub go2rtc_enabled: bool, + pub go2rtc_binary_path: [::core::ffi::c_char; 512], + pub go2rtc_config_dir: [::core::ffi::c_char; 512], + pub go2rtc_api_port: ::core::ffi::c_int, + pub go2rtc_rtsp_port: ::core::ffi::c_int, + pub go2rtc_force_native_hls: bool, + pub go2rtc_proxy_max_inflight: ::core::ffi::c_int, + pub go2rtc_webrtc_enabled: bool, + pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, + pub go2rtc_stun_enabled: bool, + pub go2rtc_stun_server: [::core::ffi::c_char; 256], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], + pub go2rtc_ice_servers: [::core::ffi::c_char; 512], + pub webrtc_connection_timeout_ms: ::core::ffi::c_int, + pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, + pub turn_enabled: bool, + pub turn_server_url: [::core::ffi::c_char; 256], + pub turn_username: [::core::ffi::c_char; 64], + pub turn_password: [::core::ffi::c_char; 64], + pub mqtt_enabled: bool, + pub mqtt_broker_host: [::core::ffi::c_char; 256], + pub mqtt_broker_port: ::core::ffi::c_int, + pub mqtt_username: [::core::ffi::c_char; 64], + pub mqtt_password: [::core::ffi::c_char; 128], + pub mqtt_client_id: [::core::ffi::c_char; 64], + pub mqtt_topic_prefix: [::core::ffi::c_char; 128], + pub mqtt_tls_enabled: bool, + pub mqtt_keepalive: ::core::ffi::c_int, + pub mqtt_qos: ::core::ffi::c_int, + pub mqtt_retain: bool, + pub mqtt_ha_discovery: bool, + pub mqtt_ha_discovery_prefix: [::core::ffi::c_char; 128], + pub mqtt_ha_snapshot_interval: ::core::ffi::c_int, + pub health: system_health_policy_settings_t, + pub detection_engine: detection_engine_config_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_0(pub ::core::ffi::c_int); +impl C2Rust_Unnamed_0 { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_change_cause_t(pub ::core::ffi::c_uint); +impl observation_change_cause_t { + pub const OBS_CAUSE_CREATED: Self = Self(0); + pub const OBS_CAUSE_EVIDENCE: Self = Self(1); + pub const OBS_CAUSE_CLASSIFICATION: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_publication_state_t(pub ::core::ffi::c_uint); +impl observation_publication_state_t { + pub const OBS_PUBLICATION_PENDING: Self = Self(0); + pub const OBS_PUBLICATION_RELAYED: Self = Self(1); + pub const OBS_PUBLICATION_RELAYED_NO_ROUTE: Self = Self(2); + pub const OBS_PUBLICATION_EXPIRED: Self = Self(3); + pub const OBS_PUBLICATION_FAILED: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_publication_t { + pub change_seq: int64_t, + pub message_id: [::core::ffi::c_char; 37], + pub attempts: ::core::ffi::c_int, + pub observation_uuid: [::core::ffi::c_char; 37], + pub revision: ::core::ffi::c_int, + pub cause: observation_change_cause_t, + pub evidence_state: observation_asset_state_t, + pub evidence_reason: [::core::ffi::c_char; 33], + pub change_created_at_ms: int64_t, + pub camera_uuid: [::core::ffi::c_char; 37], + pub label: [::core::ffi::c_char; 32], + pub observed_at_ms: int64_t, + pub asset_uuid: [::core::ffi::c_char; 37], +} +pub type db_observation_publication_hook_t = Option ()>; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_options_t { + pub batch_size: ::core::ffi::c_int, + pub poll_interval_ms: ::core::ffi::c_int, + pub outbox_cap: ::core::ffi::c_int, + pub startup_drain_ms: ::core::ffi::c_int, + pub storage_path: *const ::core::ffi::c_char, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_stats_t { + pub running: bool, + pub passes: uint64_t, + pub relayed: uint64_t, + pub duplicates: uint64_t, + pub relayed_no_route: uint64_t, + pub expired: uint64_t, + pub failed: uint64_t, + pub enqueue_full: uint64_t, + pub enqueue_errors: uint64_t, + pub mark_errors: uint64_t, + pub read_errors: uint64_t, + pub cap_pauses: uint64_t, + pub backoffs: uint64_t, + pub sidecar_writes: uint64_t, + pub sidecar_errors: uint64_t, + pub epoch_regenerations: uint64_t, + pub last_relayed_seq: int64_t, + pub oldest_pending_created_at_ms: int64_t, + pub last_pass_pending: ::core::ffi::c_int, + pub outbox_type_pending: int64_t, + pub consecutive_failures: ::core::ffi::c_int, + pub backoff_until_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_pass_t { + pub read: ::core::ffi::c_int, + pub processed: ::core::ffi::c_int, + pub backoff: bool, + pub cap_reached: bool, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct relay_settings_t { + pub batch_size: ::core::ffi::c_int, + pub poll_interval_ms: ::core::ffi::c_int, + pub outbox_cap: ::core::ffi::c_int, + pub startup_drain_ms: ::core::ffi::c_int, + pub storage_path: [::core::ffi::c_char; 512], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed_1 { + pub mutex: pthread_mutex_t, + pub wake: pthread_cond_t, + pub cond_ready: bool, + pub run_mutex: pthread_mutex_t, + pub thread: pthread_t, + pub running: bool, + pub stop_requested: bool, + pub notified: bool, + pub settings: relay_settings_t, + pub stats: observation_relay_stats_t, + pub sidecar_dirty: bool, + pub sidecar_last_write_ms: int64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_bus_result_t(pub ::core::ffi::c_int); +impl event_bus_result_t { + pub const EVENT_BUS_OK: Self = Self(0); + pub const EVENT_BUS_NOT_RUNNING: Self = Self(-1); + pub const EVENT_BUS_INVALID_EVENT: Self = Self(-2); + pub const EVENT_BUS_QUEUE_FULL: Self = Self(-3); + pub const EVENT_BUS_ALLOCATION_FAILED: Self = Self(-4); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct event_producer_enqueue_result_t(pub ::core::ffi::c_int); +impl event_producer_enqueue_result_t { + pub const EVENT_PRODUCER_ENQUEUE_FAILED: Self = Self(-1); + pub const EVENT_PRODUCER_ENQUEUE_ENQUEUED: Self = Self(0); + pub const EVENT_PRODUCER_ENQUEUE_DUPLICATE: Self = Self(1); + pub const EVENT_PRODUCER_ENQUEUE_NO_ROUTE: Self = Self(2); + pub const EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED: Self = Self(3); + pub const EVENT_PRODUCER_ENQUEUE_FULL: Self = Self(4); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_relay_stage_t(pub ::core::ffi::c_uint); +impl observation_relay_stage_t { + pub const OBS_RELAY_STAGE_BEFORE_ENQUEUE: Self = Self(0); + pub const OBS_RELAY_STAGE_AFTER_ENQUEUE: Self = Self(1); + pub const OBS_RELAY_STAGE_AFTER_MARK: Self = Self(2); +} +pub type observation_relay_stage_fn = Option< + unsafe extern "C" fn( + observation_relay_stage_t, + *const observation_publication_t, + *mut ::core::ffi::c_void, + ) -> (), +>; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_2(pub ::core::ffi::c_uint); +impl C2Rust_Unnamed_2 { + pub const PTHREAD_MUTEX_TIMED_NP: Self = Self(0); + pub const PTHREAD_MUTEX_RECURSIVE_NP: Self = Self(1); + pub const PTHREAD_MUTEX_ERRORCHECK_NP: Self = Self(2); + pub const PTHREAD_MUTEX_ADAPTIVE_NP: Self = Self(3); + pub const PTHREAD_MUTEX_NORMAL: Self = Self(0); + pub const PTHREAD_MUTEX_RECURSIVE: Self = Self(1); + pub const PTHREAD_MUTEX_ERRORCHECK: Self = Self(2); + pub const PTHREAD_MUTEX_DEFAULT: Self = Self(0); + pub const PTHREAD_MUTEX_FAST_NP: Self = Self(0); +} +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const CLOCK_MONOTONIC: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const OBSERVATION_CHANGED_EVENT_TYPE: [::core::ffi::c_char; 45] = unsafe { + ::core::mem::transmute::<[u8; 45], [::core::ffi::c_char; 45]>( + *b"io.lightnvr.detection.observation_changed.v1\0", + ) +}; +pub const OBSERVATION_RELAY_OUTBOX_CAP: ::core::ffi::c_int = 2000 as ::core::ffi::c_int; +pub const OBSERVATION_RELAY_DEFAULT_BATCH: ::core::ffi::c_int = 64 as ::core::ffi::c_int; +pub const OBSERVATION_RELAY_MAX_BATCH: ::core::ffi::c_int = 256 as ::core::ffi::c_int; +pub const OBSERVATION_RELAY_DEFAULT_POLL_MS: ::core::ffi::c_int = 1000 as ::core::ffi::c_int; +pub const OBSERVATION_RELAY_MAX_BACKOFF_MS: ::core::ffi::c_int = 30000 as ::core::ffi::c_int; +pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const RELAY_DEFAULT_STARTUP_DRAIN_MS: ::core::ffi::c_int = 5000 as ::core::ffi::c_int; +static mut RELAY: C2Rust_Unnamed_1 = C2Rust_Unnamed_1 { + mutex: pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_2::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, + }, + wake: pthread_cond_t { + __data: __pthread_cond_s { + __wseq: __atomic_wide_counter { __value64: 0 }, + __g1_start: __atomic_wide_counter { __value64: 0 }, + __g_size: [0; 2], + __g1_orig_size: 0, + __wrefs: 0, + __g_signals: [0; 2], + __unused_initialized_1: 0, + __unused_initialized_2: 0, + }, + }, + cond_ready: false, + run_mutex: pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_2::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, + }, + thread: 0, + running: false, + stop_requested: false, + notified: false, + settings: relay_settings_t { + batch_size: 0, + poll_interval_ms: 0, + outbox_cap: 0, + startup_drain_ms: 0, + storage_path: [0; 512], + }, + stats: observation_relay_stats_t { + running: false, + passes: 0, + relayed: 0, + duplicates: 0, + relayed_no_route: 0, + expired: 0, + failed: 0, + enqueue_full: 0, + enqueue_errors: 0, + mark_errors: 0, + read_errors: 0, + cap_pauses: 0, + backoffs: 0, + sidecar_writes: 0, + sidecar_errors: 0, + epoch_regenerations: 0, + last_relayed_seq: 0, + oldest_pending_created_at_ms: 0, + last_pass_pending: 0, + outbox_type_pending: 0, + consecutive_failures: 0, + backoff_until_ms: 0, + }, + sidecar_dirty: r#true != 0, + sidecar_last_write_ms: 0, +}; +unsafe extern "C" fn realtime_ms() -> int64_t { + let mut ts: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_REALTIME, &raw mut ts); + return ts.tv_sec as int64_t * 1000 as int64_t + ts.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn monotonic_ms() -> int64_t { + let mut ts: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_MONOTONIC, &raw mut ts); + return ts.tv_sec as int64_t * 1000 as int64_t + ts.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn set_error( + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, + mut message: *const ::core::ffi::c_char, +) { + if error.is_null() || error_size == 0 as size_t { + return; + } + snprintf( + error, + error_size, + b"%s\0".as_ptr() as *const ::core::ffi::c_char, + if !message.is_null() { + message + } else { + b"observation relay error\0".as_ptr() as *const ::core::ffi::c_char + }, + ); +} +unsafe extern "C" fn ensure_cond_locked() { + if RELAY.cond_ready { + return; + } + let mut attr: pthread_condattr_t = pthread_condattr_t { __size: [0; 4] }; + pthread_condattr_init(&raw mut attr); + pthread_condattr_setclock(&raw mut attr, CLOCK_MONOTONIC); + pthread_cond_init(&raw mut RELAY.wake, &raw mut attr); + pthread_condattr_destroy(&raw mut attr); + RELAY.cond_ready = r#true != 0; +} +unsafe extern "C" fn resolve_settings( + mut options: *const observation_relay_options_t, +) -> relay_settings_t { + let mut settings: relay_settings_t = relay_settings_t { + batch_size: 0, + poll_interval_ms: 0, + outbox_cap: 0, + startup_drain_ms: 0, + storage_path: [0; 512], + }; + memset( + &raw mut settings as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + settings.batch_size = OBSERVATION_RELAY_DEFAULT_BATCH; + settings.poll_interval_ms = OBSERVATION_RELAY_DEFAULT_POLL_MS; + settings.outbox_cap = OBSERVATION_RELAY_OUTBOX_CAP; + settings.startup_drain_ms = RELAY_DEFAULT_STARTUP_DRAIN_MS; + let mut storage: *const ::core::ffi::c_char = + &raw mut g_config.storage_path as *mut ::core::ffi::c_char; + if !options.is_null() { + if (*options).batch_size > 0 as ::core::ffi::c_int { + settings.batch_size = (*options).batch_size; + } + if (*options).poll_interval_ms > 0 as ::core::ffi::c_int { + settings.poll_interval_ms = (*options).poll_interval_ms; + } + if (*options).outbox_cap > 0 as ::core::ffi::c_int { + settings.outbox_cap = (*options).outbox_cap; + } + if (*options).startup_drain_ms > 0 as ::core::ffi::c_int { + settings.startup_drain_ms = (*options).startup_drain_ms; + } + if !(*options).storage_path.is_null() { + storage = (*options).storage_path; + } + } + if settings.batch_size > OBSERVATION_RELAY_MAX_BATCH { + settings.batch_size = OBSERVATION_RELAY_MAX_BATCH; + } + safe_strcpy( + &raw mut settings.storage_path as *mut ::core::ffi::c_char, + if !storage.is_null() { + storage + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + return settings; +} +unsafe extern "C" fn format_ms_utc( + mut ms: int64_t, + mut out: *mut ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if ms <= 0 as int64_t { + return -1 as ::core::ffi::c_int; + } + let mut seconds: time_t = (ms / 1000 as int64_t) as time_t; + let mut utc: tm = tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: ::core::ptr::null::<::core::ffi::c_char>(), + }; + if gmtime_r(&raw mut seconds, &raw mut utc).is_null() { + return -1 as ::core::ffi::c_int; + } + let mut base: [::core::ffi::c_char; 24] = [0; 24]; + if strftime( + &raw mut base as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + b"%Y-%m-%dT%H:%M:%S\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut utc, + ) == 0 as size_t + { + return -1 as ::core::ffi::c_int; + } + let mut written: ::core::ffi::c_int = snprintf( + out, + 32 as size_t, + b"%s.%03dZ\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut base as *mut ::core::ffi::c_char, + (ms % 1000 as int64_t) as ::core::ffi::c_int, + ); + return if written > 0 as ::core::ffi::c_int && written < 32 as ::core::ffi::c_int { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_build_envelope( + mut publication: *const observation_publication_t, + mut source: *const ::core::ffi::c_char, + mut event: *mut event_envelope_t, + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, +) -> ::core::ffi::c_int { + if !error.is_null() && error_size > 0 as size_t { + *error.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if publication.is_null() || source.is_null() || event.is_null() { + set_error( + error, + error_size, + b"publication, source and event are required\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + memset( + event as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut state: *const ::core::ffi::c_char = + observation_asset_state_name((*publication).evidence_state); + let mut cause: *const ::core::ffi::c_char = observation_change_cause_name((*publication).cause); + let mut observed_at: [::core::ffi::c_char; 32] = [0; 32]; + if state.is_null() + || cause.is_null() + || format_ms_utc( + (*publication).observed_at_ms, + &raw mut observed_at as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + || (*publication).change_created_at_ms < 1000 as int64_t + { + set_error( + error, + error_size, + b"publication state, cause or timestamps are invalid\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut subject: [::core::ffi::c_char; 96] = [0; 96]; + let mut written: ::core::ffi::c_int = snprintf( + &raw mut subject as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 96]>(), + b"camera/%s\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).camera_uuid as *const ::core::ffi::c_char, + ); + if written < 0 as ::core::ffi::c_int + || written as size_t >= ::core::mem::size_of::<[::core::ffi::c_char; 96]>() + { + set_error( + error, + error_size, + b"publication camera identity is invalid\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut data: *mut cJSON = cJSON_CreateObject(); + let mut ok: bool = !data.is_null() + && !cJSON_AddStringToObject( + data, + b"observation_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).observation_uuid as *const ::core::ffi::c_char, + ) + .is_null() + && !cJSON_AddNumberToObject( + data, + b"revision\0".as_ptr() as *const ::core::ffi::c_char, + (*publication).revision as ::core::ffi::c_double, + ) + .is_null() + && !cJSON_AddStringToObject( + data, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).camera_uuid as *const ::core::ffi::c_char, + ) + .is_null() + && !cJSON_AddStringToObject( + data, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).label as *const ::core::ffi::c_char, + ) + .is_null() + && !cJSON_AddStringToObject( + data, + b"observed_at\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut observed_at as *mut ::core::ffi::c_char, + ) + .is_null() + && !cJSON_AddStringToObject( + data, + b"change_cause\0".as_ptr() as *const ::core::ffi::c_char, + cause, + ) + .is_null() + && !cJSON_AddStringToObject( + data, + b"evidence_state\0".as_ptr() as *const ::core::ffi::c_char, + state, + ) + .is_null() + && if (*publication).evidence_reason[0usize] as ::core::ffi::c_int != 0 { + !cJSON_AddStringToObject( + data, + b"evidence_reason\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).evidence_reason as *const ::core::ffi::c_char, + ) + .is_null() as ::core::ffi::c_int + } else { + !cJSON_AddNullToObject( + data, + b"evidence_reason\0".as_ptr() as *const ::core::ffi::c_char, + ) + .is_null() as ::core::ffi::c_int + } != 0 + && !cJSON_AddStringToObject( + data, + b"asset_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).asset_uuid as *const ::core::ffi::c_char, + ) + .is_null(); + if ok as ::core::ffi::c_int != 0 + && (*publication).evidence_state.0 == observation_asset_state_t::OBS_ASSET_READY.0 + { + let mut url: [::core::ffi::c_char; 96] = [0; 96]; + snprintf( + &raw mut url as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 96]>(), + b"/api/observations/%s/snapshot\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*publication).observation_uuid as *const ::core::ffi::c_char, + ); + ok = !cJSON_AddStringToObject( + data, + b"snapshot_url\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut url as *mut ::core::ffi::c_char, + ) + .is_null(); + } + if !ok { + cJSON_Delete(data); + set_error( + error, + error_size, + b"observation event allocation failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut result: ::core::ffi::c_int = event_envelope_create_with_severity_and_id( + event, + OBSERVATION_CHANGED_EVENT_TYPE.as_ptr(), + source, + &raw mut subject as *mut ::core::ffi::c_char, + ((*publication).change_created_at_ms / 1000 as int64_t) as time_t, + data, + event_severity_t::EVENT_SEVERITY_INFO, + &raw const (*publication).message_id as *const ::core::ffi::c_char, + error, + error_size, + ); + cJSON_Delete(data); + return result; +} +unsafe extern "C" fn write_sidecar_now( + mut storage_path: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + let mut meta: observation_meta_t = observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }; + if db_observation_meta_get(&raw mut meta) != C2Rust_Unnamed_0::DB_OBS_OK.0 + || observation_cursor_head_write( + storage_path, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + meta.head_seq, + ) != 0 as ::core::ffi::c_int + { + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.sidecar_errors = RELAY.stats.sidecar_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.sidecar_writes = RELAY.stats.sidecar_writes.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_write_cursor_head( + mut storage_path: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + let mut path: *const ::core::ffi::c_char = if !storage_path.is_null() { + storage_path + } else { + &raw mut g_config.storage_path as *mut ::core::ffi::c_char + }; + if path.is_null() || *path.offset(0isize) == 0 { + return -1 as ::core::ffi::c_int; + } + return write_sidecar_now(path); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_check_cursor_head( + mut storage_path: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + let mut path: *const ::core::ffi::c_char = if !storage_path.is_null() { + storage_path + } else { + &raw mut g_config.storage_path as *mut ::core::ffi::c_char + }; + if path.is_null() || *path.offset(0isize) == 0 { + return 0 as ::core::ffi::c_int; + } + let mut regenerated: bool = r#false != 0; + if observation_cursor_head_reconcile(path, &raw mut regenerated) != 0 as ::core::ffi::c_int { + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.sidecar_errors = RELAY.stats.sidecar_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.sidecar_writes = RELAY.stats.sidecar_writes.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + if !regenerated { + return 0 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.epoch_regenerations = RELAY.stats.epoch_regenerations.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return 1 as ::core::ffi::c_int; +} +unsafe extern "C" fn maybe_write_sidecar_locked(mut settings: *const relay_settings_t) { + if !RELAY.sidecar_dirty || (*settings).storage_path[0usize] == 0 { + return; + } + let mut now: int64_t = monotonic_ms(); + if RELAY.sidecar_last_write_ms != 0 as int64_t + && now - RELAY.sidecar_last_write_ms < 1000 as int64_t + { + return; + } + if write_sidecar_now(&raw const (*settings).storage_path as *const ::core::ffi::c_char) + == 0 as ::core::ffi::c_int + { + RELAY.sidecar_dirty = r#false != 0; + RELAY.sidecar_last_write_ms = now; + } +} +static mut g_stage_lock: pthread_mutex_t = pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_2::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, +}; +static mut g_stage_hook: observation_relay_stage_fn = None; +static mut g_stage_ctx: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_set_stage_hook( + mut r#fn: observation_relay_stage_fn, + mut ctx: *mut ::core::ffi::c_void, +) { + pthread_mutex_lock(&raw mut g_stage_lock); + g_stage_hook = r#fn; + g_stage_ctx = ctx; + pthread_mutex_unlock(&raw mut g_stage_lock); +} +unsafe extern "C" fn stage_hook( + mut stage: observation_relay_stage_t, + mut row: *const observation_publication_t, +) { + pthread_mutex_lock(&raw mut g_stage_lock); + let mut r#fn: observation_relay_stage_fn = g_stage_hook; + let mut ctx: *mut ::core::ffi::c_void = g_stage_ctx; + pthread_mutex_unlock(&raw mut g_stage_lock); + if r#fn.is_some() { + r#fn.expect("non-null function pointer")(stage, row, ctx); + } +} +unsafe extern "C" fn mark( + mut seq: int64_t, + mut state: observation_publication_state_t, + mut error: *const ::core::ffi::c_char, + mut cas_lost: *mut bool, +) -> bool { + let mut rc: ::core::ffi::c_int = db_observation_publication_mark(seq, state, error); + *cas_lost = + rc == C2Rust_Unnamed_0::DB_OBS_CAS_LOST.0 || rc == C2Rust_Unnamed_0::DB_OBS_NOT_FOUND.0; + if rc == C2Rust_Unnamed_0::DB_OBS_OK.0 || *cas_lost as ::core::ffi::c_int != 0 { + return r#true != 0; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.mark_errors = RELAY.stats.mark_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Observation relay could not mark publication %lld (%d)\0".as_ptr() + as *const ::core::ffi::c_char, + seq as ::core::ffi::c_longlong, + rc, + ); + return r#false != 0; +} +unsafe extern "C" fn run_pass_locked( + mut settings: *const relay_settings_t, + mut pass: *mut observation_relay_pass_t, +) -> ::core::ffi::c_int { + let mut rows: *mut observation_publication_t = calloc( + (*settings).batch_size as size_t, + ::core::mem::size_of::(), + ) as *mut observation_publication_t; + if rows.is_null() { + (*pass).backoff = r#true != 0; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.read_errors = RELAY.stats.read_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return -1 as ::core::ffi::c_int; + } + let mut count: ::core::ffi::c_int = + db_observation_pending_publications(rows, (*settings).batch_size); + if count < 0 as ::core::ffi::c_int { + free(rows as *mut ::core::ffi::c_void); + (*pass).backoff = r#true != 0; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.read_errors = RELAY.stats.read_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return -1 as ::core::ffi::c_int; + } + (*pass).read = count; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.passes = RELAY.stats.passes.wrapping_add(1); + RELAY.stats.last_pass_pending = count; + RELAY.stats.oldest_pending_created_at_ms = if count > 0 as ::core::ffi::c_int { + (*rows.offset(0isize)).change_created_at_ms + } else { + 0 as int64_t + }; + pthread_mutex_unlock(&raw mut RELAY.mutex); + if count == 0 as ::core::ffi::c_int { + free(rows as *mut ::core::ffi::c_void); + maybe_write_sidecar_locked(settings); + return 0 as ::core::ffi::c_int; + } + RELAY.sidecar_dirty = r#true != 0; + let mut source: [::core::ffi::c_char; 128] = [0; 128]; + let mut outbox_pending: int64_t = 0 as int64_t; + if event_identity_get_source( + &raw mut source as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + ) != 0 as ::core::ffi::c_int + || db_event_outbox_count_undelivered_type( + OBSERVATION_CHANGED_EVENT_TYPE.as_ptr(), + &raw mut outbox_pending, + ) != 0 as ::core::ffi::c_int + { + free(rows as *mut ::core::ffi::c_void); + (*pass).backoff = r#true != 0; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.read_errors = RELAY.stats.read_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.outbox_type_pending = outbox_pending; + pthread_mutex_unlock(&raw mut RELAY.mutex); + let mut room: int64_t = (*settings).outbox_cap as int64_t - outbox_pending; + let mut definition: *const event_type_definition_t = + event_registry_find(OBSERVATION_CHANGED_EVENT_TYPE.as_ptr()); + let mut expiry_ms: int64_t = (if !definition.is_null() { + (*definition).default_expiry_seconds + } else { + 3600 as ::core::ffi::c_int + }) as int64_t + * 1000 as int64_t; + let mut index: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while index < count { + let mut row: *const observation_publication_t = rows.offset(index as isize); + let mut cas_lost: bool = r#false != 0; + if realtime_ms() >= (*row).change_created_at_ms + expiry_ms { + if !mark( + (*row).change_seq, + observation_publication_state_t::OBS_PUBLICATION_EXPIRED, + b"delivery expiry elapsed\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut cas_lost, + ) { + (*pass).backoff = r#true != 0; + break; + } else if !cas_lost { + (*pass).processed += 1; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.expired = RELAY.stats.expired.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + } + } else { + let mut event: event_envelope_t = event_envelope_t { + specversion: [0; 4], + id: [0; 37], + r#type: [0; 96], + source: [0; 128], + subject: [0; 96], + time: [0; 32], + datacontenttype: [0; 32], + occurred_at: 0, + expires_at: 0, + severity: event_severity_t::EVENT_SEVERITY_INFO, + data: ::core::ptr::null_mut::(), + }; + let mut error: [::core::ffi::c_char; 256] = ::core::mem::transmute::< + [u8; 256], + [::core::ffi::c_char; 256], + >( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + if observation_relay_build_envelope( + row, + &raw mut source as *mut ::core::ffi::c_char, + &raw mut event, + &raw mut error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + ) != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Observation publication %lld is not a valid event: %s\0".as_ptr() + as *const ::core::ffi::c_char, + (*row).change_seq as ::core::ffi::c_longlong, + &raw mut error as *mut ::core::ffi::c_char, + ); + if !mark( + (*row).change_seq, + observation_publication_state_t::OBS_PUBLICATION_FAILED, + &raw mut error as *mut ::core::ffi::c_char, + &raw mut cas_lost, + ) { + (*pass).backoff = r#true != 0; + break; + } else if !cas_lost { + (*pass).processed += 1; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.failed = RELAY.stats.failed.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + } + } else if room <= 0 as int64_t { + event_envelope_clear(&raw mut event); + (*pass).cap_reached = r#true != 0; + (*pass).backoff = r#true != 0; + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.cap_pauses = RELAY.stats.cap_pauses.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + break; + } else { + stage_hook( + observation_relay_stage_t::OBS_RELAY_STAGE_BEFORE_ENQUEUE, + row, + ); + let mut result: event_producer_enqueue_result_t = event_producer_enqueue_durable( + &raw mut event, + &raw mut error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + ); + if result.0 == event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_ENQUEUED.0 + || result.0 + == event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_DUPLICATE.0 + { + stage_hook( + observation_relay_stage_t::OBS_RELAY_STAGE_AFTER_ENQUEUE, + row, + ); + } + let mut state: observation_publication_state_t = + observation_publication_state_t::OBS_PUBLICATION_PENDING; + let mut retry: bool = r#false != 0; + match result { + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_ENQUEUED => { + room -= 1; + state = observation_publication_state_t::OBS_PUBLICATION_RELAYED; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_DUPLICATE => { + state = observation_publication_state_t::OBS_PUBLICATION_RELAYED; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_NO_ROUTE + | event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED => { + state = observation_publication_state_t::OBS_PUBLICATION_RELAYED_NO_ROUTE; + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FULL => { + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.enqueue_full = + RELAY.stats.enqueue_full.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + state = observation_publication_state_t::OBS_PUBLICATION_PENDING; + retry = r#true != 0; + set_error( + &raw mut error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + b"outbox full\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_FAILED | _ => { + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.stats.enqueue_errors = + RELAY.stats.enqueue_errors.wrapping_add(1 as uint64_t); + pthread_mutex_unlock(&raw mut RELAY.mutex); + state = observation_publication_state_t::OBS_PUBLICATION_PENDING; + retry = r#true != 0; + if error[0usize] == 0 { + set_error( + &raw mut error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + b"durable enqueue failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + } + } + let mut marked: bool = mark( + (*row).change_seq, + state, + if retry as ::core::ffi::c_int != 0 { + &raw mut error as *mut ::core::ffi::c_char + } else { + ::core::ptr::null_mut::<::core::ffi::c_char>() + }, + &raw mut cas_lost, + ); + if retry as ::core::ffi::c_int != 0 || !marked { + event_envelope_clear(&raw mut event); + (*pass).backoff = r#true != 0; + break; + } else { + if !cas_lost { + stage_hook(observation_relay_stage_t::OBS_RELAY_STAGE_AFTER_MARK, row); + (*pass).processed += 1; + pthread_mutex_lock(&raw mut RELAY.mutex); + if state.0 == observation_publication_state_t::OBS_PUBLICATION_RELAYED.0 { + RELAY.stats.relayed = RELAY.stats.relayed.wrapping_add(1); + if result.0 + == event_producer_enqueue_result_t::EVENT_PRODUCER_ENQUEUE_DUPLICATE + .0 + { + RELAY.stats.duplicates = RELAY.stats.duplicates.wrapping_add(1); + } + } else { + RELAY.stats.relayed_no_route = + RELAY.stats.relayed_no_route.wrapping_add(1); + } + RELAY.stats.last_relayed_seq = (*row).change_seq; + pthread_mutex_unlock(&raw mut RELAY.mutex); + event_bus_publish( + &raw mut event, + ::core::ptr::null_mut::<::core::ffi::c_char>(), + 0 as size_t, + ); + } + event_envelope_clear(&raw mut event); + } + } + } + index += 1; + } + free(rows as *mut ::core::ffi::c_void); + maybe_write_sidecar_locked(settings); + return (*pass).processed; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_run_once( + mut options: *const observation_relay_options_t, + mut pass: *mut observation_relay_pass_t, +) -> ::core::ffi::c_int { + let mut local: observation_relay_pass_t = observation_relay_pass_t { + read: 0, + processed: 0, + backoff: false, + cap_reached: false, + }; + if pass.is_null() { + pass = &raw mut local; + } + memset( + pass as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut settings: relay_settings_t = relay_settings_t { + batch_size: 0, + poll_interval_ms: 0, + outbox_cap: 0, + startup_drain_ms: 0, + storage_path: [0; 512], + }; + if !options.is_null() { + settings = resolve_settings(options); + } else { + pthread_mutex_lock(&raw mut RELAY.mutex); + let mut running: bool = RELAY.running; + settings = RELAY.settings; + pthread_mutex_unlock(&raw mut RELAY.mutex); + if !running { + settings = resolve_settings(::core::ptr::null::()); + } + } + pthread_mutex_lock(&raw mut RELAY.run_mutex); + let mut result: ::core::ffi::c_int = run_pass_locked(&raw mut settings, pass); + pthread_mutex_unlock(&raw mut RELAY.run_mutex); + return result; +} +unsafe extern "C" fn relay_main( + mut argument: *mut ::core::ffi::c_void, +) -> *mut ::core::ffi::c_void { + let mut backoff_ms: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + loop { + pthread_mutex_lock(&raw mut RELAY.mutex); + let mut stop: bool = RELAY.stop_requested; + let mut settings: relay_settings_t = RELAY.settings; + RELAY.notified = r#false != 0; + pthread_mutex_unlock(&raw mut RELAY.mutex); + if stop { + break; + } + let mut pass: observation_relay_pass_t = observation_relay_pass_t { + read: 0, + processed: 0, + backoff: false, + cap_reached: false, + }; + memset( + &raw mut pass as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + pthread_mutex_lock(&raw mut RELAY.run_mutex); + run_pass_locked(&raw mut settings, &raw mut pass); + pthread_mutex_unlock(&raw mut RELAY.run_mutex); + let mut wait_ms: ::core::ffi::c_int = 0; + let mut in_backoff: bool = pass.backoff; + if pass.backoff { + backoff_ms = if backoff_ms <= 0 as ::core::ffi::c_int { + 1000 as ::core::ffi::c_int + } else { + backoff_ms * 2 as ::core::ffi::c_int + }; + if backoff_ms > OBSERVATION_RELAY_MAX_BACKOFF_MS { + backoff_ms = OBSERVATION_RELAY_MAX_BACKOFF_MS; + } + wait_ms = backoff_ms; + } else { + backoff_ms = 0 as ::core::ffi::c_int; + wait_ms = if pass.read >= settings.batch_size { + 0 as ::core::ffi::c_int + } else { + settings.poll_interval_ms + }; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + if in_backoff { + RELAY.stats.backoffs = RELAY.stats.backoffs.wrapping_add(1); + RELAY.stats.consecutive_failures += 1; + RELAY.stats.backoff_until_ms = realtime_ms() + wait_ms as int64_t; + } else { + RELAY.stats.consecutive_failures = 0 as ::core::ffi::c_int; + RELAY.stats.backoff_until_ms = 0 as int64_t; + } + let mut deadline: int64_t = monotonic_ms() + wait_ms as int64_t; + while !RELAY.stop_requested && (in_backoff as ::core::ffi::c_int != 0 || !RELAY.notified) { + let mut remaining: int64_t = deadline - monotonic_ms(); + if remaining <= 0 as int64_t { + break; + } + let mut until: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_MONOTONIC, &raw mut until); + until.tv_sec += (remaining / 1000 as int64_t) as ::core::ffi::c_long; + until.tv_nsec += + (remaining % 1000 as int64_t * 1000000 as int64_t) as ::core::ffi::c_long; + if until.tv_nsec >= 1000000000 as ::core::ffi::c_long { + until.tv_sec += 1; + until.tv_nsec -= 1000000000 as ::core::ffi::c_long; + } + pthread_cond_timedwait(&raw mut RELAY.wake, &raw mut RELAY.mutex, &raw mut until); + } + pthread_mutex_unlock(&raw mut RELAY.mutex); + } + return NULL; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_start( + mut options: *const observation_relay_options_t, +) -> ::core::ffi::c_int { + let mut settings: relay_settings_t = resolve_settings(options); + pthread_mutex_lock(&raw mut RELAY.mutex); + ensure_cond_locked(); + if RELAY.running { + pthread_mutex_unlock(&raw mut RELAY.mutex); + return 0 as ::core::ffi::c_int; + } + RELAY.settings = settings; + RELAY.stop_requested = r#false != 0; + RELAY.notified = r#false != 0; + pthread_mutex_unlock(&raw mut RELAY.mutex); + db_observations_set_publication_hook(Some( + observation_relay_notify as unsafe extern "C" fn() -> (), + )); + if settings.storage_path[0usize] as ::core::ffi::c_int != 0 + && observation_relay_check_cursor_head( + &raw mut settings.storage_path as *mut ::core::ffi::c_char, + ) < 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation relay could not verify the cursor-head sidecar\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + let mut drain_deadline: int64_t = monotonic_ms() + settings.startup_drain_ms as int64_t; + let mut drained: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + loop { + let mut pass: observation_relay_pass_t = observation_relay_pass_t { + read: 0, + processed: 0, + backoff: false, + cap_reached: false, + }; + memset( + &raw mut pass as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + pthread_mutex_lock(&raw mut RELAY.run_mutex); + run_pass_locked(&raw mut settings, &raw mut pass); + pthread_mutex_unlock(&raw mut RELAY.run_mutex); + drained += pass.processed; + if pass.backoff as ::core::ffi::c_int != 0 + || pass.read < settings.batch_size + || monotonic_ms() >= drain_deadline + { + break; + } + } + if drained > 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation relay drained %d staged publication(s)\0".as_ptr() + as *const ::core::ffi::c_char, + drained, + ); + } + pthread_mutex_lock(&raw mut RELAY.mutex); + let mut rc: ::core::ffi::c_int = pthread_create( + &raw mut RELAY.thread, + ::core::ptr::null::(), + Some( + relay_main + as unsafe extern "C" fn(*mut ::core::ffi::c_void) -> *mut ::core::ffi::c_void, + ), + NULL, + ); + if rc == 0 as ::core::ffi::c_int { + RELAY.running = r#true != 0; + RELAY.stats.running = r#true != 0; + } + pthread_mutex_unlock(&raw mut RELAY.mutex); + if rc != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to start observation relay thread: %s\0".as_ptr() + as *const ::core::ffi::c_char, + strerror(rc), + ); + return -1 as ::core::ffi::c_int; + } + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation publication relay started\0".as_ptr() as *const ::core::ffi::c_char, + ); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_stop() { + pthread_mutex_lock(&raw mut RELAY.mutex); + if !RELAY.running { + pthread_mutex_unlock(&raw mut RELAY.mutex); + return; + } + RELAY.stop_requested = r#true != 0; + pthread_cond_broadcast(&raw mut RELAY.wake); + let mut thread: pthread_t = RELAY.thread; + pthread_mutex_unlock(&raw mut RELAY.mutex); + pthread_join(thread, ::core::ptr::null_mut::<*mut ::core::ffi::c_void>()); + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.running = r#false != 0; + RELAY.stats.running = r#false != 0; + RELAY.stop_requested = r#false != 0; + pthread_mutex_unlock(&raw mut RELAY.mutex); + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation publication relay stopped\0".as_ptr() as *const ::core::ffi::c_char, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_notify() { + pthread_mutex_lock(&raw mut RELAY.mutex); + RELAY.notified = r#true != 0; + if RELAY.cond_ready { + pthread_cond_signal(&raw mut RELAY.wake); + } + pthread_mutex_unlock(&raw mut RELAY.mutex); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_get_stats(mut stats: *mut observation_relay_stats_t) { + if stats.is_null() { + return; + } + pthread_mutex_lock(&raw mut RELAY.mutex); + *stats = RELAY.stats; + pthread_mutex_unlock(&raw mut RELAY.mutex); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_relay_reset_stats() { + pthread_mutex_lock(&raw mut RELAY.run_mutex); + RELAY.sidecar_dirty = r#true != 0; + RELAY.sidecar_last_write_ms = 0 as int64_t; + pthread_mutex_unlock(&raw mut RELAY.run_mutex); + pthread_mutex_lock(&raw mut RELAY.mutex); + let mut running: bool = RELAY.stats.running; + memset( + &raw mut RELAY.stats as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + RELAY.stats.running = running; + pthread_mutex_unlock(&raw mut RELAY.mutex); +} +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; diff --git a/rust/generated/src__core__observation_retention.rs b/rust/generated/src__core__observation_retention.rs new file mode 100644 index 000000000..80403ea8d --- /dev/null +++ b/rust/generated/src__core__observation_retention.rs @@ -0,0 +1,2577 @@ +use ::c2rust_bitfields; +use ::libc; +unsafe extern "C" { + pub type _IO_wide_data; + pub type _IO_codecvt; + pub type _IO_marker; + pub type sqlite3; + pub type sqlite3_mutex; + pub type sqlite3_stmt; + pub type __dirstream; + unsafe fn sqlite3_changes(_: *mut sqlite3) -> ::core::ffi::c_int; + unsafe fn sqlite3_prepare_v2( + db: *mut sqlite3, + zSql: *const ::core::ffi::c_char, + nByte: ::core::ffi::c_int, + ppStmt: *mut *mut sqlite3_stmt, + pzTail: *mut *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_int( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_int64( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: sqlite3_int64, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_text( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: *const ::core::ffi::c_char, + _: ::core::ffi::c_int, + _: Option ()>, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_step(_: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_column_int( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_column_int64(_: *mut sqlite3_stmt, iCol: ::core::ffi::c_int) + -> sqlite3_int64; + unsafe fn sqlite3_column_text( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> *const ::core::ffi::c_uchar; + unsafe fn sqlite3_finalize(pStmt: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_reset(pStmt: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_mutex_enter(_: *mut sqlite3_mutex); + unsafe fn sqlite3_mutex_leave(_: *mut sqlite3_mutex); + unsafe fn sqlite3_db_mutex(_: *mut sqlite3) -> *mut sqlite3_mutex; + static mut g_config: config_t; + unsafe fn observation_asset_state_name( + state: observation_asset_state_t, + ) -> *const ::core::ffi::c_char; + unsafe fn db_observation_asset_transition( + asset_uuid: *const ::core::ffi::c_char, + from: observation_asset_state_t, + to: observation_asset_state_t, + reason: *const ::core::ffi::c_char, + media: *const observation_asset_media_t, + result: *mut observation_transition_result_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_meta_get(out: *mut observation_meta_t) -> ::core::ffi::c_int; + unsafe fn db_observation_meta_regenerate_epoch() -> ::core::ffi::c_int; + unsafe fn db_observations_prune( + before_ms: int64_t, + batch: ::core::ffi::c_int, + result: *mut observation_prune_result_t, + ) -> ::core::ffi::c_int; + unsafe fn cJSON_Parse(value: *const ::core::ffi::c_char) -> *mut cJSON; + unsafe fn cJSON_Delete(item: *mut cJSON); + unsafe fn cJSON_GetObjectItemCaseSensitive( + object: *const cJSON, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_IsNumber(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsString(item: *const cJSON) -> cJSON_bool; + unsafe fn closedir(__dirp: *mut DIR) -> ::core::ffi::c_int; + unsafe fn fdopendir(__fd: ::core::ffi::c_int) -> *mut DIR; + unsafe fn readdir(__dirp: *mut DIR) -> *mut dirent; + unsafe fn dirfd(__dirp: *mut DIR) -> ::core::ffi::c_int; + unsafe fn __errno_location() -> *mut ::core::ffi::c_int; + unsafe fn open( + __file: *const ::core::ffi::c_char, + __oflag: ::core::ffi::c_int, + ... + ) -> ::core::ffi::c_int; + unsafe fn openat( + __fd: ::core::ffi::c_int, + __file: *const ::core::ffi::c_char, + __oflag: ::core::ffi::c_int, + ... + ) -> ::core::ffi::c_int; + unsafe fn time(__timer: *mut time_t) -> time_t; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_trylock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_lock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_unlock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn rename( + __old: *const ::core::ffi::c_char, + __new: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn fclose(__stream: *mut FILE) -> ::core::ffi::c_int; + unsafe fn fopen( + __filename: *const ::core::ffi::c_char, + __modes: *const ::core::ffi::c_char, + ) -> *mut FILE; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn fread( + __ptr: *mut ::core::ffi::c_void, + __size: size_t, + __n: size_t, + __stream: *mut FILE, + ) -> ::core::ffi::c_ulong; + unsafe fn fstatat( + __fd: ::core::ffi::c_int, + __file: *const ::core::ffi::c_char, + __buf: *mut stat, + __flag: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn lstat(__file: *const ::core::ffi::c_char, __buf: *mut stat) -> ::core::ffi::c_int; + unsafe fn close(__fd: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn write( + __fd: ::core::ffi::c_int, + __buf: *const ::core::ffi::c_void, + __n: size_t, + ) -> ssize_t; + unsafe fn unlink(__name: *const ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn unlinkat( + __fd: ::core::ffi::c_int, + __name: *const ::core::ffi::c_char, + __flag: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn fsync(__fd: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn mkdir_recursive(path: *const ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn get_db_handle() -> *mut sqlite3; + unsafe fn get_db_mutex() -> *mut pthread_mutex_t; + unsafe fn db_open_readonly_connection(connection: *mut *mut sqlite3) -> ::core::ffi::c_int; + unsafe fn db_close_readonly_connection(connection: *mut sqlite3); + unsafe fn db_get_system_setting( + key: *const ::core::ffi::c_char, + out: *mut ::core::ffi::c_char, + out_len: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn observation_evidence_set_budget_exhausted( + camera_uuid: *const ::core::ffi::c_char, + exhausted: bool, + ); + unsafe fn memcpy( + __dest: *mut ::core::ffi::c_void, + __src: *const ::core::ffi::c_void, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strncmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + __n: size_t, + ) -> ::core::ffi::c_int; + unsafe fn strchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; + unsafe fn strerror(__errnum: ::core::ffi::c_int) -> *mut ::core::ffi::c_char; + unsafe fn strtol( + __nptr: *const ::core::ffi::c_char, + __endptr: *mut *mut ::core::ffi::c_char, + __base: ::core::ffi::c_int, + ) -> ::core::ffi::c_long; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn realloc(__ptr: *mut ::core::ffi::c_void, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __uint64_t = u64; +pub type __dev_t = ::core::ffi::c_ulong; +pub type __uid_t = ::core::ffi::c_uint; +pub type __gid_t = ::core::ffi::c_uint; +pub type __ino_t = ::core::ffi::c_ulong; +pub type __mode_t = ::core::ffi::c_uint; +pub type __nlink_t = ::core::ffi::c_ulong; +pub type __off_t = ::core::ffi::c_long; +pub type __off64_t = ::core::ffi::c_long; +pub type __time_t = ::core::ffi::c_long; +pub type __clockid_t = ::core::ffi::c_int; +pub type __blksize_t = ::core::ffi::c_long; +pub type __blkcnt_t = ::core::ffi::c_long; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type ssize_t = isize; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +pub type __pthread_list_t = __pthread_internal_list; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_mutex_s { + pub __lock: ::core::ffi::c_int, + pub __count: ::core::ffi::c_uint, + pub __owner: ::core::ffi::c_int, + pub __nusers: ::core::ffi::c_uint, + pub __kind: ::core::ffi::c_int, + pub __spins: ::core::ffi::c_short, + pub __glibc_reserved: ::core::ffi::c_short, + pub __list: __pthread_list_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::core::ffi::c_char; 40], + pub __align: ::core::ffi::c_long, +} +#[derive(Copy, Clone, ::c2rust_bitfields::BitfieldStruct)] +#[repr(C)] +pub struct _IO_FILE { + pub _flags: ::core::ffi::c_int, + pub _IO_read_ptr: *mut ::core::ffi::c_char, + pub _IO_read_end: *mut ::core::ffi::c_char, + pub _IO_read_base: *mut ::core::ffi::c_char, + pub _IO_write_base: *mut ::core::ffi::c_char, + pub _IO_write_ptr: *mut ::core::ffi::c_char, + pub _IO_write_end: *mut ::core::ffi::c_char, + pub _IO_buf_base: *mut ::core::ffi::c_char, + pub _IO_buf_end: *mut ::core::ffi::c_char, + pub _IO_save_base: *mut ::core::ffi::c_char, + pub _IO_backup_base: *mut ::core::ffi::c_char, + pub _IO_save_end: *mut ::core::ffi::c_char, + pub _markers: *mut _IO_marker, + pub _chain: *mut _IO_FILE, + pub _fileno: ::core::ffi::c_int, + #[bitfield(name = "_flags2", ty = "::core::ffi::c_int", bits = "0..=23")] + pub _flags2: [u8; 3], + pub _short_backupbuf: [::core::ffi::c_char; 1], + pub _old_offset: __off_t, + pub _cur_column: ::core::ffi::c_ushort, + pub _vtable_offset: ::core::ffi::c_schar, + pub _shortbuf: [::core::ffi::c_char; 1], + pub _lock: *mut ::core::ffi::c_void, + pub _offset: __off64_t, + pub _codecvt: *mut _IO_codecvt, + pub _wide_data: *mut _IO_wide_data, + pub _freeres_list: *mut _IO_FILE, + pub _freeres_buf: *mut ::core::ffi::c_void, + pub _prevchain: *mut *mut _IO_FILE, + pub _mode: ::core::ffi::c_int, + pub _unused3: ::core::ffi::c_int, + pub _total_written: __uint64_t, + pub _unused2: [::core::ffi::c_char; 8], +} +pub type _IO_lock_t = (); +pub type FILE = _IO_FILE; +pub type uint8_t = u8; +pub type uint32_t = u32; +pub type uint64_t = u64; +pub type sqlite_int64 = ::core::ffi::c_longlong; +pub type sqlite3_int64 = sqlite_int64; +pub type sqlite3_destructor_type = Option ()>; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct system_health_policy_settings_t { + pub enabled: bool, + pub profile: [::core::ffi::c_char; 16], + pub fast_interval_seconds: uint32_t, + pub normal_interval_seconds: uint32_t, + pub slow_interval_seconds: uint32_t, + pub device_interval_seconds: uint32_t, + pub write_probe_enabled: bool, + pub hardware_provider: [::core::ffi::c_char; 16], + pub presence_interval_seconds: uint32_t, + pub incident_retention_days: uint32_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_protocol_t(pub ::core::ffi::c_uint); +impl stream_protocol_t { + pub const STREAM_PROTOCOL_TCP: Self = Self(0); + pub const STREAM_PROTOCOL_UDP: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stream_config_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub location_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub url: [::core::ffi::c_char; 512], + pub enabled: bool, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub fps: ::core::ffi::c_int, + pub codec: [::core::ffi::c_char; 16], + pub priority: ::core::ffi::c_int, + pub record: bool, + pub segment_duration: ::core::ffi::c_int, + pub detection_based_recording: bool, + pub detection_model: [::core::ffi::c_char; 512], + pub detection_interval: ::core::ffi::c_int, + pub detection_threshold: ::core::ffi::c_float, + pub pre_detection_buffer: ::core::ffi::c_int, + pub post_detection_buffer: ::core::ffi::c_int, + pub detection_api_url: [::core::ffi::c_char; 512], + pub detection_object_filter: [::core::ffi::c_char; 16], + pub detection_object_filter_list: [::core::ffi::c_char; 256], + pub streaming_enabled: bool, + pub protocol: stream_protocol_t, + pub record_audio: bool, + pub audio_voice_enhancement: bool, + pub onvif_username: [::core::ffi::c_char; 64], + pub onvif_password: [::core::ffi::c_char; 64], + pub onvif_profile: [::core::ffi::c_char; 64], + pub onvif_port: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub is_onvif: bool, + pub backchannel_enabled: bool, + pub retention_days: ::core::ffi::c_int, + pub detection_retention_days: ::core::ffi::c_int, + pub max_storage_mb: ::core::ffi::c_int, + pub tier_critical_multiplier: ::core::ffi::c_double, + pub tier_important_multiplier: ::core::ffi::c_double, + pub tier_ephemeral_multiplier: ::core::ffi::c_double, + pub storage_priority: ::core::ffi::c_int, + pub ptz_enabled: bool, + pub ptz_max_x: ::core::ffi::c_int, + pub ptz_max_y: ::core::ffi::c_int, + pub ptz_max_z: ::core::ffi::c_int, + pub ptz_has_home: bool, + pub record_on_schedule: bool, + pub recording_schedule: [uint8_t; 168], + pub detection_record_on_schedule: bool, + pub detection_recording_schedule: [uint8_t; 168], + pub tags: [::core::ffi::c_char; 256], + pub admin_url: [::core::ffi::c_char; 512], + pub privacy_mode: bool, + pub motion_trigger_source: [::core::ffi::c_char; 256], + pub go2rtc_source_override: [::core::ffi::c_char; 8192], + pub sub_stream_url: [::core::ffi::c_char; 512], + pub detection_url: [::core::ffi::c_char; 512], + pub publish_url: [::core::ffi::c_char; 512], + pub playback_transport: [::core::ffi::c_char; 24], + pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_engine_config_t { + pub enabled: bool, + pub num_threads: ::core::ffi::c_int, + pub delegate: [::core::ffi::c_char; 16], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct config_t { + pub pid_file: [::core::ffi::c_char; 512], + pub log_file: [::core::ffi::c_char; 512], + pub log_level: ::core::ffi::c_int, + pub syslog_enabled: bool, + pub syslog_ident: [::core::ffi::c_char; 64], + pub syslog_facility: ::core::ffi::c_int, + pub storage_path: [::core::ffi::c_char; 512], + pub storage_path_hls: [::core::ffi::c_char; 512], + pub max_storage_size: uint64_t, + pub retention_days: ::core::ffi::c_int, + pub auto_delete_oldest: bool, + pub mp4_faststart: bool, + pub storage_min_free_pct: ::core::ffi::c_int, + pub storage_pressure_warning_pct: ::core::ffi::c_double, + pub storage_pressure_critical_pct: ::core::ffi::c_double, + pub storage_pressure_emergency_pct: ::core::ffi::c_double, + pub generate_thumbnails: bool, + pub thumbnails_per_recording: ::core::ffi::c_int, + pub record_mp4_directly: bool, + pub mp4_storage_path: [::core::ffi::c_char; 512], + pub mp4_directory_format: [::core::ffi::c_char; 32], + pub mp4_segment_duration: ::core::ffi::c_int, + pub mp4_retention_days: ::core::ffi::c_int, + pub models_path: [::core::ffi::c_char; 512], + pub api_detection_url: [::core::ffi::c_char; 512], + pub api_detection_backend: [::core::ffi::c_char; 32], + pub api_detection_format: [::core::ffi::c_char; 32], + pub api_detection_detector_name: [::core::ffi::c_char; 64], + pub default_detection_threshold: ::core::ffi::c_int, + pub default_pre_detection_buffer: ::core::ffi::c_int, + pub default_post_detection_buffer: ::core::ffi::c_int, + pub default_playback_transport: [::core::ffi::c_char; 24], + pub detection_grace_period: ::core::ffi::c_int, + pub db_path: [::core::ffi::c_char; 512], + pub db_backup_interval_minutes: ::core::ffi::c_int, + pub db_backup_retention_count: ::core::ffi::c_int, + pub db_post_backup_script: [::core::ffi::c_char; 512], + pub db_startup_check: ::core::ffi::c_int, + pub db_backup_verify: ::core::ffi::c_int, + pub web_thread_pool_size: ::core::ffi::c_int, + pub web_port: ::core::ffi::c_int, + pub web_bind_ip: [::core::ffi::c_char; 32], + pub web_root: [::core::ffi::c_char; 512], + pub web_ssl_enabled: bool, + pub web_ssl_cert_path: [::core::ffi::c_char; 512], + pub web_ssl_key_path: [::core::ffi::c_char; 512], + pub web_auth_enabled: bool, + pub web_username: [::core::ffi::c_char; 32], + pub web_password: [::core::ffi::c_char; 32], + pub audio_disabled: bool, + pub auto_disabled: bool, + pub webrtc_disabled: bool, + pub hls_disabled: bool, + pub mse_disabled: bool, + pub auth_timeout_hours: ::core::ffi::c_int, + pub auth_absolute_timeout_hours: ::core::ffi::c_int, + pub trusted_device_days: ::core::ffi::c_int, + pub trusted_proxy_cidrs: [::core::ffi::c_char; 1024], + pub demo_mode: bool, + pub force_mfa_on_login: bool, + pub login_rate_limit_enabled: bool, + pub login_rate_limit_max_attempts: ::core::ffi::c_int, + pub login_rate_limit_window_seconds: ::core::ffi::c_int, + pub web_compression_enabled: bool, + pub web_use_minified_assets: bool, + pub web_cache_max_age_html: ::core::ffi::c_int, + pub web_cache_max_age_css: ::core::ffi::c_int, + pub web_cache_max_age_js: ::core::ffi::c_int, + pub web_cache_max_age_images: ::core::ffi::c_int, + pub web_cache_max_age_fonts: ::core::ffi::c_int, + pub web_cache_max_age_default: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub onvif_discovery_interval: ::core::ffi::c_int, + pub onvif_discovery_network: [::core::ffi::c_char; 64], + pub max_streams: ::core::ffi::c_int, + pub streams: *mut stream_config_t, + pub buffer_size: ::core::ffi::c_int, + pub use_swap: bool, + pub swap_file: [::core::ffi::c_char; 512], + pub swap_size: uint64_t, + pub memory_constrained: bool, + pub hw_accel_enabled: bool, + pub hw_accel_device: [::core::ffi::c_char; 32], + pub go2rtc_enabled: bool, + pub go2rtc_binary_path: [::core::ffi::c_char; 512], + pub go2rtc_config_dir: [::core::ffi::c_char; 512], + pub go2rtc_api_port: ::core::ffi::c_int, + pub go2rtc_rtsp_port: ::core::ffi::c_int, + pub go2rtc_force_native_hls: bool, + pub go2rtc_proxy_max_inflight: ::core::ffi::c_int, + pub go2rtc_webrtc_enabled: bool, + pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, + pub go2rtc_stun_enabled: bool, + pub go2rtc_stun_server: [::core::ffi::c_char; 256], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], + pub go2rtc_ice_servers: [::core::ffi::c_char; 512], + pub webrtc_connection_timeout_ms: ::core::ffi::c_int, + pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, + pub turn_enabled: bool, + pub turn_server_url: [::core::ffi::c_char; 256], + pub turn_username: [::core::ffi::c_char; 64], + pub turn_password: [::core::ffi::c_char; 64], + pub mqtt_enabled: bool, + pub mqtt_broker_host: [::core::ffi::c_char; 256], + pub mqtt_broker_port: ::core::ffi::c_int, + pub mqtt_username: [::core::ffi::c_char; 64], + pub mqtt_password: [::core::ffi::c_char; 128], + pub mqtt_client_id: [::core::ffi::c_char; 64], + pub mqtt_topic_prefix: [::core::ffi::c_char; 128], + pub mqtt_tls_enabled: bool, + pub mqtt_keepalive: ::core::ffi::c_int, + pub mqtt_qos: ::core::ffi::c_int, + pub mqtt_retain: bool, + pub mqtt_ha_discovery: bool, + pub mqtt_ha_discovery_prefix: [::core::ffi::c_char; 128], + pub mqtt_ha_snapshot_interval: ::core::ffi::c_int, + pub health: system_health_policy_settings_t, + pub detection_engine: detection_engine_config_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_media_t { + pub path: *const ::core::ffi::c_char, + pub bytes: int64_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub expires_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_transition_result_t { + pub observations_bumped: ::core::ffi::c_int, + pub last_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_prune_result_t { + pub changes_deleted: ::core::ffi::c_int, + pub observations_deleted: ::core::ffi::c_int, + pub assets_deleted: ::core::ffi::c_int, + pub oldest_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_reconcile_result_t { + pub tmp_files_deleted: ::core::ffi::c_int, + pub stale_files_deleted: ::core::ffi::c_int, + pub pending_interrupted: ::core::ffi::c_int, + pub ready_missing: ::core::ffi::c_int, + pub epoch_regenerated: bool, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +pub type atomic_int = ::core::ffi::c_int; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct asset_row_t { + pub uuid: [::core::ffi::c_char; 37], + pub path: [::core::ffi::c_char; 512], + pub bytes: int64_t, + pub ref_ms: int64_t, + pub state: [::core::ffi::c_char; 16], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stat { + pub st_dev: __dev_t, + pub st_ino: __ino_t, + pub st_nlink: __nlink_t, + pub st_mode: __mode_t, + pub st_uid: __uid_t, + pub st_gid: __gid_t, + pub __pad0: ::core::ffi::c_int, + pub st_rdev: __dev_t, + pub st_size: __off_t, + pub st_blksize: __blksize_t, + pub st_blocks: __blkcnt_t, + pub st_atim: timespec, + pub st_mtim: timespec, + pub st_ctim: timespec, + pub __glibc_reserved: [__syscall_slong_t; 3], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct sweep_t { + pub db: *mut sqlite3, + pub lookup: *mut sqlite3_stmt, + pub root: *const ::core::ffi::c_char, + pub result: *mut observation_reconcile_result_t, + pub errors: ::core::ffi::c_int, +} +pub type DIR = __dirstream; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct dirent { + pub d_ino: __ino_t, + pub d_off: __off_t, + pub d_reclen: ::core::ffi::c_ushort, + pub d_type: ::core::ffi::c_uchar, + pub d_name: [::core::ffi::c_char; 256], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +pub type cJSON_bool = ::core::ffi::c_int; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_retention_result_t { + pub assets_expired: ::core::ffi::c_int, + pub unreferenced_deleted: ::core::ffi::c_int, + pub files_deleted: ::core::ffi::c_int, + pub changes_pruned: ::core::ffi::c_int, + pub observations_pruned: ::core::ffi::c_int, + pub cameras_budget_exhausted: ::core::ffi::c_int, + pub more_work: bool, + pub oldest_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_retention_camera_status_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub retained_bytes: int64_t, + pub max_bytes: int64_t, + pub max_age_days: ::core::ffi::c_int, + pub budget_exhausted: bool, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_0(pub ::core::ffi::c_uint); +impl C2Rust_Unnamed_0 { + pub const PTHREAD_MUTEX_TIMED_NP: Self = Self(0); + pub const PTHREAD_MUTEX_RECURSIVE_NP: Self = Self(1); + pub const PTHREAD_MUTEX_ERRORCHECK_NP: Self = Self(2); + pub const PTHREAD_MUTEX_ADAPTIVE_NP: Self = Self(3); + pub const PTHREAD_MUTEX_NORMAL: Self = Self(0); + pub const PTHREAD_MUTEX_RECURSIVE: Self = Self(1); + pub const PTHREAD_MUTEX_ERRORCHECK: Self = Self(2); + pub const PTHREAD_MUTEX_DEFAULT: Self = Self(0); + pub const PTHREAD_MUTEX_FAST_NP: Self = Self(0); +} +pub const __O_CLOEXEC: ::core::ffi::c_int = 0o2000000 as ::core::ffi::c_int; +pub const INT64_MAX: ::core::ffi::c_long = 9223372036854775807 as ::core::ffi::c_long; +pub const SQLITE_OK: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const SQLITE_ROW: ::core::ffi::c_int = 100 as ::core::ffi::c_int; +pub const SQLITE_DONE: ::core::ffi::c_int = 101 as ::core::ffi::c_int; +pub const SQLITE_STATIC: sqlite3_destructor_type = None; +pub const OBS_UUID_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; +pub const OBS_RETENTION_DIR: [::core::ffi::c_char; 13] = + unsafe { ::core::mem::transmute::<[u8; 13], [::core::ffi::c_char; 13]>(*b"observations\0") }; +pub const OBS_RETENTION_INTERVAL_SEC: ::core::ffi::c_int = 300 as ::core::ffi::c_int; +pub const OBS_RETENTION_MAX_EVICTIONS: ::core::ffi::c_int = 256 as ::core::ffi::c_int; +pub const OBS_RETENTION_MAX_PRUNED_CHANGES: ::core::ffi::c_int = 5000 as ::core::ffi::c_int; +pub const OBS_RETENTION_PRUNE_BATCH: ::core::ffi::c_int = 500 as ::core::ffi::c_int; +pub const OBS_RETENTION_DEFAULT_HISTORY_DAYS: ::core::ffi::c_int = 30 as ::core::ffi::c_int; +pub const OBS_RETENTION_DEFAULT_MAX_AGE_DAYS: ::core::ffi::c_int = 7 as ::core::ffi::c_int; +pub const OBS_RETENTION_DEFAULT_MAX_BYTES: ::core::ffi::c_longlong = + 2147483648 as ::core::ffi::c_longlong; +pub const __S_IFMT: ::core::ffi::c_int = 0o170000 as ::core::ffi::c_int; +pub const O_RDONLY: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const O_WRONLY: ::core::ffi::c_int = 0o1 as ::core::ffi::c_int; +pub const O_CREAT: ::core::ffi::c_int = 0o100 as ::core::ffi::c_int; +pub const O_TRUNC: ::core::ffi::c_int = 0o1000 as ::core::ffi::c_int; +pub const __O_DIRECTORY: ::core::ffi::c_int = 0o200000 as ::core::ffi::c_int; +pub const __O_NOFOLLOW: ::core::ffi::c_int = 0o400000 as ::core::ffi::c_int; +pub const O_DIRECTORY: ::core::ffi::c_int = __O_DIRECTORY; +pub const O_NOFOLLOW: ::core::ffi::c_int = __O_NOFOLLOW; +pub const O_CLOEXEC: ::core::ffi::c_int = __O_CLOEXEC; +pub const AT_SYMLINK_NOFOLLOW: ::core::ffi::c_int = 0x100 as ::core::ffi::c_int; +pub const AT_REMOVEDIR: ::core::ffi::c_int = 0x200 as ::core::ffi::c_int; +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const DAY_MS: ::core::ffi::c_longlong = 86400000 as ::core::ffi::c_longlong; +pub const OBS_WALK_MAX_DEPTH: ::core::ffi::c_int = 4 as ::core::ffi::c_int; +pub const OBS_PAGE: ::core::ffi::c_int = 256 as ::core::ffi::c_int; +static mut g_run_mutex: pthread_mutex_t = pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_0::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, +}; +static mut g_startup_done: atomic_int = 0 as ::core::ffi::c_int; +static mut g_status_mutex: pthread_mutex_t = pthread_mutex_t { + __data: __pthread_mutex_s { + __lock: 0 as ::core::ffi::c_int, + __count: 0 as ::core::ffi::c_uint, + __owner: 0 as ::core::ffi::c_int, + __nusers: 0 as ::core::ffi::c_uint, + __kind: C2Rust_Unnamed_0::PTHREAD_MUTEX_TIMED_NP.0 as ::core::ffi::c_int, + __spins: 0 as ::core::ffi::c_short, + __glibc_reserved: 0 as ::core::ffi::c_short, + __list: __pthread_internal_list { + __prev: ::core::ptr::null_mut::<__pthread_internal_list>(), + __next: ::core::ptr::null_mut::<__pthread_internal_list>(), + }, + }, +}; +static mut g_status: *mut observation_retention_camera_status_t = + ::core::ptr::null_mut::(); +static mut g_status_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +static mut g_exhausted_count: atomic_int = 0 as ::core::ffi::c_int; +static mut g_last_tick: time_t = 0 as time_t; +static mut g_more_work: bool = r#false != 0; +unsafe extern "C" fn root_dir( + mut storage_path: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) -> ::core::ffi::c_int { + if storage_path.is_null() || *storage_path == 0 { + return -1 as ::core::ffi::c_int; + } + let mut len: size_t = strlen(storage_path); + while len > 1 as size_t + && *storage_path.offset(len.wrapping_sub(1 as size_t) as isize) as ::core::ffi::c_int + == '/' as ::core::ffi::c_int + { + len = len.wrapping_sub(1); + } + let mut n: ::core::ffi::c_int = snprintf( + out, + size, + b"%.*s/observations\0".as_ptr() as *const ::core::ffi::c_char, + len as ::core::ffi::c_int, + storage_path, + ); + return if n > 0 as ::core::ffi::c_int && (n as size_t) < size { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn has_suffix( + mut value: *const ::core::ffi::c_char, + mut suffix: *const ::core::ffi::c_char, +) -> bool { + let mut a: size_t = strlen(value); + let mut b: size_t = strlen(suffix); + return a >= b + && strcmp(value.offset(a as isize).offset(-(b as isize)), suffix) + == 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn safe_relative(mut rel: *const ::core::ffi::c_char) -> bool { + if *rel == 0 || *rel as ::core::ffi::c_int == '/' as ::core::ffi::c_int { + return r#false != 0; + } + let mut segment: *const ::core::ffi::c_char = rel; + loop { + let mut end: *const ::core::ffi::c_char = strchr(segment, '/' as ::core::ffi::c_int); + let mut len: size_t = if !end.is_null() { + end.offset_from(segment) as size_t + } else { + strlen(segment) + }; + if len == 0 as size_t + || len == 1 as size_t + && *segment.offset(0isize) as ::core::ffi::c_int == '.' as ::core::ffi::c_int + || len == 2 as size_t + && *segment.offset(0isize) as ::core::ffi::c_int == '.' as ::core::ffi::c_int + && *segment.offset(1isize) as ::core::ffi::c_int == '.' as ::core::ffi::c_int + { + return r#false != 0; + } + if end.is_null() { + return r#true != 0; + } + segment = end.offset(1 as ::core::ffi::c_int as isize); + } +} +unsafe extern "C" fn resolve_asset_path( + mut root: *const ::core::ffi::c_char, + mut stored: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) -> ::core::ffi::c_int { + if stored.is_null() + || *stored == 0 + || !has_suffix(stored, b".jpg\0".as_ptr() as *const ::core::ffi::c_char) + { + return -1 as ::core::ffi::c_int; + } + let mut rel: *const ::core::ffi::c_char = stored; + let mut root_len: size_t = strlen(root); + if *stored.offset(0isize) as ::core::ffi::c_int == '/' as ::core::ffi::c_int { + if strncmp(stored, root, root_len) != 0 as ::core::ffi::c_int + || *stored.offset(root_len as isize) as ::core::ffi::c_int != '/' as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + rel = stored + .offset(root_len as isize) + .offset(1 as ::core::ffi::c_int as isize); + } else if strncmp( + stored, + b"observations/\0".as_ptr() as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 13]>(), + ) == 0 as ::core::ffi::c_int + { + rel = stored.offset(::core::mem::size_of::<[::core::ffi::c_char; 13]>() as isize); + } + if !safe_relative(rel) { + return -1 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = snprintf( + out, + size, + b"%s/%s\0".as_ptr() as *const ::core::ffi::c_char, + root, + rel, + ); + return if n > 0 as ::core::ffi::c_int && (n as size_t) < size { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn remove_file( + mut path: *const ::core::ffi::c_char, + mut deleted: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if unlink(path) == 0 as ::core::ffi::c_int { + if !deleted.is_null() { + *deleted += 1; + } + return 0 as ::core::ffi::c_int; + } + if *__errno_location() == ENOENT || *__errno_location() == ENOTDIR { + return 0 as ::core::ffi::c_int; + } + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Failed to delete observation evidence file %s: %s\0".as_ptr() + as *const ::core::ffi::c_char, + path, + strerror(*__errno_location()), + ); + return -1 as ::core::ffi::c_int; +} +unsafe extern "C" fn remove_asset_file( + mut root: *const ::core::ffi::c_char, + mut stored: *const ::core::ffi::c_char, + mut deleted: *mut ::core::ffi::c_int, +) { + let mut path: [::core::ffi::c_char; 1024] = [0; 1024]; + if resolve_asset_path( + root, + stored, + &raw mut path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + { + if !stored.is_null() && *stored as ::core::ffi::c_int != 0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation asset path is outside the evidence tree; not deleting\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + return; + } + remove_file(&raw mut path as *mut ::core::ffi::c_char, deleted); +} +unsafe extern "C" fn fsync_dir(mut dir: *const ::core::ffi::c_char) -> ::core::ffi::c_int { + let mut fd: ::core::ffi::c_int = open(dir, O_RDONLY | O_DIRECTORY); + if fd < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut rc: ::core::ffi::c_int = fsync(fd); + close(fd); + return rc; +} +unsafe extern "C" fn wall_ms() -> int64_t { + let mut ts: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_REALTIME, &raw mut ts); + return ts.tv_sec as int64_t * 1000 as int64_t + ts.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn copy_text( + mut stmt: *mut sqlite3_stmt, + mut column: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) { + let mut text: *const ::core::ffi::c_uchar = sqlite3_column_text(stmt, column); + safe_strcpy( + out, + if !text.is_null() { + text as *const ::core::ffi::c_char + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + size, + 0 as size_t, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_cursor_head_write( + mut storage_path: *const ::core::ffi::c_char, + mut epoch_uuid: *const ::core::ffi::c_char, + mut head_seq: int64_t, +) -> ::core::ffi::c_int { + let mut root: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut path: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut tmp: [::core::ffi::c_char; 1024] = [0; 1024]; + if !lightnvr_uuid_is_valid(epoch_uuid) + || head_seq < 0 as int64_t + || root_dir( + storage_path, + &raw mut root as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if snprintf( + &raw mut path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + b"%s/.cursor_head.json\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut root as *mut ::core::ffi::c_char, + ) >= ::core::mem::size_of::<[::core::ffi::c_char; 1024]>() as ::core::ffi::c_int + || snprintf( + &raw mut tmp as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + b"%s/.cursor_head.json.tmp\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut root as *mut ::core::ffi::c_char, + ) >= ::core::mem::size_of::<[::core::ffi::c_char; 1024]>() as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if mkdir_recursive(&raw mut root as *mut ::core::ffi::c_char) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut body: [::core::ffi::c_char; 160] = [0; 160]; + let mut len: ::core::ffi::c_int = snprintf( + &raw mut body as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 160]>(), + b"{\"epoch\":\"%s\",\"head_seq\":%lld}\n\0".as_ptr() as *const ::core::ffi::c_char, + epoch_uuid, + head_seq as ::core::ffi::c_longlong, + ); + let mut fd: ::core::ffi::c_int = open( + &raw mut tmp as *mut ::core::ffi::c_char, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + 0o640 as ::core::ffi::c_int, + ); + if fd < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut rc: ::core::ffi::c_int = if write( + fd, + &raw mut body as *mut ::core::ffi::c_char as *const ::core::ffi::c_void, + len as size_t, + ) == len as ssize_t + && fsync(fd) == 0 as ::core::ffi::c_int + { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; + if close(fd) != 0 as ::core::ffi::c_int { + rc = -1 as ::core::ffi::c_int; + } + if rc == 0 as ::core::ffi::c_int + && rename( + &raw mut tmp as *mut ::core::ffi::c_char, + &raw mut path as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + rc = -1 as ::core::ffi::c_int; + } + if rc != 0 as ::core::ffi::c_int { + unlink(&raw mut tmp as *mut ::core::ffi::c_char); + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Failed to write observation cursor head sidecar\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + fsync_dir(&raw mut root as *mut ::core::ffi::c_char); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_cursor_head_read( + mut storage_path: *const ::core::ffi::c_char, + mut epoch_uuid: *mut ::core::ffi::c_char, + mut head_seq: *mut int64_t, +) -> ::core::ffi::c_int { + let mut root: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut path: [::core::ffi::c_char; 1024] = [0; 1024]; + if epoch_uuid.is_null() + || head_seq.is_null() + || root_dir( + storage_path, + &raw mut root as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if snprintf( + &raw mut path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + b"%s/.cursor_head.json\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut root as *mut ::core::ffi::c_char, + ) >= ::core::mem::size_of::<[::core::ffi::c_char; 1024]>() as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut f: *mut FILE = fopen( + &raw mut path as *mut ::core::ffi::c_char, + b"rb\0".as_ptr() as *const ::core::ffi::c_char, + ); + if f.is_null() { + return if *__errno_location() == ENOENT { + 1 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; + } + let mut body: [::core::ffi::c_char; 512] = [0; 512]; + let mut n: size_t = fread( + &raw mut body as *mut ::core::ffi::c_char as *mut ::core::ffi::c_void, + 1 as size_t, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>().wrapping_sub(1 as size_t), + f, + ) as size_t; + fclose(f); + body[n] = '\0' as ::core::ffi::c_char; + let mut json: *mut cJSON = cJSON_Parse(&raw mut body as *mut ::core::ffi::c_char); + let mut rc: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if !json.is_null() { + let mut epoch: *const cJSON = cJSON_GetObjectItemCaseSensitive( + json, + b"epoch\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut head: *const cJSON = cJSON_GetObjectItemCaseSensitive( + json, + b"head_seq\0".as_ptr() as *const ::core::ffi::c_char, + ); + if cJSON_IsString(epoch) != 0 + && lightnvr_uuid_is_valid((*epoch).valuestring) as ::core::ffi::c_int != 0 + && cJSON_IsNumber(head) != 0 + && (*head).valuedouble >= 0 as ::core::ffi::c_int as ::core::ffi::c_double + && (*head).valuedouble <= 9007199254740991.0f64 + && (*head).valuedouble == (*head).valuedouble as int64_t as ::core::ffi::c_double + { + safe_strcpy( + epoch_uuid, + (*epoch).valuestring, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + *head_seq = (*head).valuedouble as int64_t; + rc = 0 as ::core::ffi::c_int; + } + cJSON_Delete(json); + } + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_cursor_head_reconcile( + mut storage_path: *const ::core::ffi::c_char, + mut regenerated: *mut bool, +) -> ::core::ffi::c_int { + if !regenerated.is_null() { + *regenerated = r#false != 0; + } + let mut meta: observation_meta_t = observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }; + if db_observation_meta_get(&raw mut meta) != C2Rust_Unnamed::DB_OBS_OK.0 { + return -1 as ::core::ffi::c_int; + } + let mut epoch: [::core::ffi::c_char; 37] = [0; 37]; + let mut head: int64_t = 0 as int64_t; + let mut rc: ::core::ffi::c_int = observation_cursor_head_read( + storage_path, + &raw mut epoch as *mut ::core::ffi::c_char, + &raw mut head, + ); + if rc < 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation cursor head sidecar is malformed; recreating it\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + if rc == 0 as ::core::ffi::c_int + && strcmp( + &raw mut epoch as *mut ::core::ffi::c_char, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + && meta.head_seq < head + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation history rewound (DB head %lld < recorded head %lld): database was restored; regenerating cursor epoch\0" + .as_ptr() as *const ::core::ffi::c_char, + meta.head_seq as ::core::ffi::c_longlong, + head as ::core::ffi::c_longlong, + ); + if db_observation_meta_regenerate_epoch() != C2Rust_Unnamed::DB_OBS_OK.0 + || db_observation_meta_get(&raw mut meta) != C2Rust_Unnamed::DB_OBS_OK.0 + { + return -1 as ::core::ffi::c_int; + } + if !regenerated.is_null() { + *regenerated = r#true != 0; + } + } + return observation_cursor_head_write( + storage_path, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + meta.head_seq, + ); +} +unsafe extern "C" fn remove_at( + mut dir_fd: ::core::ffi::c_int, + mut name: *const ::core::ffi::c_char, + mut path: *const ::core::ffi::c_char, + mut deleted: *mut ::core::ffi::c_int, +) { + if unlinkat(dir_fd, name, 0 as ::core::ffi::c_int) == 0 as ::core::ffi::c_int { + if !deleted.is_null() { + *deleted += 1; + } + return; + } + if *__errno_location() == ENOENT || *__errno_location() == ENOTDIR { + return; + } + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Failed to delete observation evidence file %s: %s\0".as_ptr() + as *const ::core::ffi::c_char, + path, + strerror(*__errno_location()), + ); +} +unsafe extern "C" fn sweep_final( + mut s: *mut sweep_t, + mut dir_fd: ::core::ffi::c_int, + mut path: *const ::core::ffi::c_char, + mut name: *const ::core::ffi::c_char, +) { + let mut uuid: [::core::ffi::c_char; 37] = [0; 37]; + let mut len: size_t = strlen(name).wrapping_sub(4 as size_t); + if len != (OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as size_t { + return; + } + memcpy( + &raw mut uuid as *mut ::core::ffi::c_char as *mut ::core::ffi::c_void, + name as *const ::core::ffi::c_void, + len, + ); + uuid[len] = '\0' as ::core::ffi::c_char; + if !lightnvr_uuid_is_valid(&raw mut uuid as *mut ::core::ffi::c_char) { + return; + } + sqlite3_reset((*s).lookup); + sqlite3_bind_text( + (*s).lookup, + 1 as ::core::ffi::c_int, + &raw mut uuid as *mut ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step((*s).lookup); + if step == SQLITE_ROW { + let mut state: [::core::ffi::c_char; 16] = [0; 16]; + let mut stored: [::core::ffi::c_char; 512] = [0; 512]; + let mut expected: [::core::ffi::c_char; 1024] = [0; 1024]; + copy_text( + (*s).lookup, + 0 as ::core::ffi::c_int, + &raw mut state as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + copy_text( + (*s).lookup, + 1 as ::core::ffi::c_int, + &raw mut stored as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ); + if strcmp( + &raw mut state as *mut ::core::ffi::c_char, + b"ready\0".as_ptr() as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + && resolve_asset_path( + (*s).root, + &raw mut stored as *mut ::core::ffi::c_char, + &raw mut expected as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) == 0 as ::core::ffi::c_int + && strcmp(&raw mut expected as *mut ::core::ffi::c_char, path) + == 0 as ::core::ffi::c_int + { + return; + } + } else if step != SQLITE_DONE { + (*s).errors += 1; + return; + } + remove_at( + dir_fd, + name, + path, + &raw mut (*(*s).result).stale_files_deleted, + ); +} +unsafe extern "C" fn sweep_dir_fd( + mut s: *mut sweep_t, + mut dir_fd: ::core::ffi::c_int, + mut dir: *const ::core::ffi::c_char, + mut depth: ::core::ffi::c_int, +) { + let mut d: *mut DIR = fdopendir(dir_fd); + if d.is_null() { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Cannot scan evidence directory %s: %s\0".as_ptr() as *const ::core::ffi::c_char, + dir, + strerror(*__errno_location()), + ); + close(dir_fd); + return; + } + let mut fd: ::core::ffi::c_int = dirfd(d); + let mut entry: *mut dirent = ::core::ptr::null_mut::(); + loop { + entry = readdir(d); + if entry.is_null() { + break; + } + let mut name: *const ::core::ffi::c_char = + &raw mut (*entry).d_name as *mut ::core::ffi::c_char; + if strcmp(name, b".\0".as_ptr() as *const ::core::ffi::c_char) == 0 as ::core::ffi::c_int + || strcmp(name, b"..\0".as_ptr() as *const ::core::ffi::c_char) + == 0 as ::core::ffi::c_int + { + continue; + } + let mut path: [::core::ffi::c_char; 1024] = [0; 1024]; + if snprintf( + &raw mut path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + b"%s/%s\0".as_ptr() as *const ::core::ffi::c_char, + dir, + name, + ) >= ::core::mem::size_of::<[::core::ffi::c_char; 1024]>() as ::core::ffi::c_int + { + continue; + } + let mut st: stat = stat { + st_dev: 0, + st_ino: 0, + st_nlink: 0, + st_mode: 0, + st_uid: 0, + st_gid: 0, + __pad0: 0, + st_rdev: 0, + st_size: 0, + st_blksize: 0, + st_blocks: 0, + st_atim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + st_mtim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + st_ctim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + __glibc_reserved: [0; 3], + }; + if fstatat(fd, name, &raw mut st, AT_SYMLINK_NOFOLLOW) != 0 as ::core::ffi::c_int { + continue; + } + if st.st_mode & __S_IFMT as __mode_t == 0o40000 as __mode_t { + if depth >= OBS_WALK_MAX_DEPTH { + continue; + } + let mut child: ::core::ffi::c_int = + openat(fd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if child < 0 as ::core::ffi::c_int { + continue; + } + sweep_dir_fd( + s, + child, + &raw mut path as *mut ::core::ffi::c_char, + depth + 1 as ::core::ffi::c_int, + ); + if depth > 1 as ::core::ffi::c_int { + unlinkat(fd, name, AT_REMOVEDIR); + } + } else { + if !(st.st_mode & __S_IFMT as __mode_t == 0o100000 as __mode_t) { + continue; + } + if has_suffix(name, b".jpg.tmp\0".as_ptr() as *const ::core::ffi::c_char) { + remove_at( + fd, + name, + &raw mut path as *mut ::core::ffi::c_char, + &raw mut (*(*s).result).tmp_files_deleted, + ); + } else if has_suffix(name, b".jpg\0".as_ptr() as *const ::core::ffi::c_char) { + sweep_final(s, fd, &raw mut path as *mut ::core::ffi::c_char, name); + } + } + } + closedir(d); +} +unsafe extern "C" fn sweep_dir( + mut s: *mut sweep_t, + mut dir: *const ::core::ffi::c_char, + mut depth: ::core::ffi::c_int, +) { + let mut fd: ::core::ffi::c_int = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if fd < 0 as ::core::ffi::c_int { + if *__errno_location() != ENOENT { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Cannot scan evidence directory %s: %s\0".as_ptr() as *const ::core::ffi::c_char, + dir, + strerror(*__errno_location()), + ); + } + return; + } + sweep_dir_fd(s, fd, dir, depth); +} +unsafe extern "C" fn sweep_files( + mut root: *const ::core::ffi::c_char, + mut result: *mut observation_reconcile_result_t, +) -> ::core::ffi::c_int { + let mut s: sweep_t = sweep_t { + db: ::core::ptr::null_mut::(), + lookup: ::core::ptr::null_mut::(), + root: root, + result: result, + errors: 0, + }; + if db_open_readonly_connection(&raw mut s.db) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + if sqlite3_prepare_v2( + s.db, + b"SELECT state, path FROM observation_assets WHERE uuid = ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut s.lookup, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + db_close_readonly_connection(s.db); + return -1 as ::core::ffi::c_int; + } + sweep_dir(&raw mut s, root, 1 as ::core::ffi::c_int); + sqlite3_finalize(s.lookup); + db_close_readonly_connection(s.db); + return if s.errors != 0 { + -1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn page_by_state( + mut state: *const ::core::ffi::c_char, + mut after: *const ::core::ffi::c_char, + mut rows: *mut asset_row_t, + mut max: ::core::ffi::c_int, +) -> ::core::ffi::c_int { + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut count: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + b"SELECT uuid, COALESCE(path, '') FROM observation_assets WHERE state = ?1 AND uuid > ?2 ORDER BY uuid LIMIT ?3;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + state, + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + sqlite3_bind_text( + stmt, + 2 as ::core::ffi::c_int, + after, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int(stmt, 3 as ::core::ffi::c_int, max); + count = 0 as ::core::ffi::c_int; + let mut step: ::core::ffi::c_int = 0; + while count < max + && { + step = sqlite3_step(stmt); + step == SQLITE_ROW + } + { + memset( + rows.offset(count as isize) as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + copy_text( + stmt, + 0 as ::core::ffi::c_int, + &raw mut (*rows.offset(count as isize)).uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + copy_text( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*rows.offset(count as isize)).path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ); + count += 1; + } + if count < max && step != SQLITE_DONE { + count = -1 as ::core::ffi::c_int; + } + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return count; +} +unsafe extern "C" fn reconcile_state( + mut root: *const ::core::ffi::c_char, + mut from: observation_asset_state_t, + mut result: *mut observation_reconcile_result_t, +) -> ::core::ffi::c_int { + let mut rows: *mut asset_row_t = + calloc(OBS_PAGE as size_t, ::core::mem::size_of::()) as *mut asset_row_t; + if rows.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut after: [::core::ffi::c_char; 37] = + ::core::mem::transmute::<[u8; 37], [::core::ffi::c_char; 37]>( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + let mut errors: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + loop { + let mut n: ::core::ffi::c_int = page_by_state( + observation_asset_state_name(from), + &raw mut after as *mut ::core::ffi::c_char, + rows, + OBS_PAGE, + ); + if n < 0 as ::core::ffi::c_int { + errors += 1; + break; + } else { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < n { + let mut reason: *const ::core::ffi::c_char = + ::core::ptr::null::<::core::ffi::c_char>(); + if from.0 == observation_asset_state_t::OBS_ASSET_PENDING.0 { + reason = b"interrupted\0".as_ptr() as *const ::core::ffi::c_char; + } else { + let mut path: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut st: stat = stat { + st_dev: 0, + st_ino: 0, + st_nlink: 0, + st_mode: 0, + st_uid: 0, + st_gid: 0, + __pad0: 0, + st_rdev: 0, + st_size: 0, + st_blksize: 0, + st_blocks: 0, + st_atim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + st_mtim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + st_ctim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + __glibc_reserved: [0; 3], + }; + if resolve_asset_path( + root, + &raw mut (*rows.offset(i as isize)).path as *mut ::core::ffi::c_char, + &raw mut path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + || lstat(&raw mut path as *mut ::core::ffi::c_char, &raw mut st) + != 0 as ::core::ffi::c_int + || !(st.st_mode & __S_IFMT as __mode_t == 0o100000 as __mode_t) + { + reason = b"write_failed\0".as_ptr() as *const ::core::ffi::c_char; + } + } + if !reason.is_null() { + let mut rc: ::core::ffi::c_int = db_observation_asset_transition( + &raw mut (*rows.offset(i as isize)).uuid as *mut ::core::ffi::c_char, + from, + observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + reason, + ::core::ptr::null::(), + ::core::ptr::null_mut::(), + ); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + if from.0 == observation_asset_state_t::OBS_ASSET_PENDING.0 { + (*result).pending_interrupted += 1; + } else { + (*result).ready_missing += 1; + } + } else if rc != C2Rust_Unnamed::DB_OBS_CAS_LOST.0 + && rc != C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 + { + errors += 1; + } + } + i += 1; + } + if n < OBS_PAGE { + break; + } + safe_strcpy( + &raw mut after as *mut ::core::ffi::c_char, + &raw mut (*rows.offset((n - 1 as ::core::ffi::c_int) as isize)).uuid + as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + } + } + free(rows as *mut ::core::ffi::c_void); + return if errors != 0 { + -1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_startup( + mut storage_path: *const ::core::ffi::c_char, + mut result: *mut observation_reconcile_result_t, +) -> ::core::ffi::c_int { + let mut local: observation_reconcile_result_t = observation_reconcile_result_t { + tmp_files_deleted: 0, + stale_files_deleted: 0, + pending_interrupted: 0, + ready_missing: 0, + epoch_regenerated: false, + }; + if result.is_null() { + result = &raw mut local; + } + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut root: [::core::ffi::c_char; 1024] = [0; 1024]; + if root_dir( + storage_path, + &raw mut root as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut g_run_mutex); + let mut rc: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if observation_cursor_head_reconcile(storage_path, &raw mut (*result).epoch_regenerated) + != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation cursor head reconciliation failed\0".as_ptr() + as *const ::core::ffi::c_char, + ); + rc = -1 as ::core::ffi::c_int; + } + if sweep_files(&raw mut root as *mut ::core::ffi::c_char, result) != 0 as ::core::ffi::c_int { + rc = -1 as ::core::ffi::c_int; + } + if reconcile_state( + &raw mut root as *mut ::core::ffi::c_char, + observation_asset_state_t::OBS_ASSET_PENDING, + result, + ) != 0 as ::core::ffi::c_int + { + rc = -1 as ::core::ffi::c_int; + } + if reconcile_state( + &raw mut root as *mut ::core::ffi::c_char, + observation_asset_state_t::OBS_ASSET_READY, + result, + ) != 0 as ::core::ffi::c_int + { + rc = -1 as ::core::ffi::c_int; + } + if rc == 0 as ::core::ffi::c_int { + let mut meta: observation_meta_t = observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }; + if db_observation_meta_get(&raw mut meta) == C2Rust_Unnamed::DB_OBS_OK.0 { + observation_cursor_head_write( + storage_path, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + meta.head_seq, + ); + } + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_startup_done, + 1 as ::core::ffi::c_int, + ); + } + pthread_mutex_unlock(&raw mut g_run_mutex); + if (*result).tmp_files_deleted != 0 + || (*result).stale_files_deleted != 0 + || (*result).pending_interrupted != 0 + || (*result).ready_missing != 0 + || (*result).epoch_regenerated as ::core::ffi::c_int != 0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation reconciliation: %d temp and %d stale files deleted, %d pending interrupted, %d ready missing%s\0" + .as_ptr() as *const ::core::ffi::c_char, + (*result).tmp_files_deleted, + (*result).stale_files_deleted, + (*result).pending_interrupted, + (*result).ready_missing, + if (*result).epoch_regenerated as ::core::ffi::c_int != 0 { + b", epoch regenerated\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + } + if rc != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Observation startup reconciliation incomplete; eviction disabled\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_startup_done() -> bool { + return ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_startup_done, + ) != 0; +} +unsafe extern "C" fn history_days() -> ::core::ffi::c_int { + let mut value: [::core::ffi::c_char; 32] = [0; 32]; + if db_get_system_setting( + b"observation_history_retention_days\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>() as ::core::ffi::c_int, + ) != 0 as ::core::ffi::c_int + { + return OBS_RETENTION_DEFAULT_HISTORY_DAYS; + } + let mut end: *mut ::core::ffi::c_char = ::core::ptr::null_mut::<::core::ffi::c_char>(); + let mut days: ::core::ffi::c_long = strtol( + &raw mut value as *mut ::core::ffi::c_char, + &raw mut end, + 10 as ::core::ffi::c_int, + ); + if end == &raw mut value as *mut ::core::ffi::c_char + || days < 1 as ::core::ffi::c_long + || days > 365 as ::core::ffi::c_long + { + return OBS_RETENTION_DEFAULT_HISTORY_DAYS; + } + return days as ::core::ffi::c_int; +} +unsafe extern "C" fn prune_history( + mut now_ms: int64_t, + mut result: *mut observation_retention_result_t, +) -> ::core::ffi::c_int { + let mut before_ms: int64_t = (now_ms as ::core::ffi::c_longlong + - history_days() as int64_t as ::core::ffi::c_longlong * DAY_MS) + as int64_t; + while (*result).changes_pruned < OBS_RETENTION_MAX_PRUNED_CHANGES { + let mut prune: observation_prune_result_t = observation_prune_result_t { + changes_deleted: 0, + observations_deleted: 0, + assets_deleted: 0, + oldest_seq: 0, + }; + if db_observations_prune(before_ms, OBS_RETENTION_PRUNE_BATCH, &raw mut prune) + != C2Rust_Unnamed::DB_OBS_OK.0 + { + return -1 as ::core::ffi::c_int; + } + (*result).changes_pruned += prune.changes_deleted; + (*result).observations_pruned += prune.observations_deleted; + (*result).unreferenced_deleted += prune.assets_deleted; + if prune.changes_deleted < OBS_RETENTION_PRUNE_BATCH { + return 0 as ::core::ffi::c_int; + } + } + (*result).more_work = r#true != 0; + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn delete_asset_row(mut uuid: *const ::core::ffi::c_char) -> ::core::ffi::c_int { + let mut mutex: *mut pthread_mutex_t = get_db_mutex(); + if mutex.is_null() { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(mutex); + let mut db: *mut sqlite3 = get_db_handle(); + let mut rc: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if !db.is_null() { + let mut connection: *mut sqlite3_mutex = sqlite3_db_mutex(db); + sqlite3_mutex_enter(connection); + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + b"DELETE FROM observation_assets WHERE uuid = ?1 AND protected = 0 AND state IN ('unavailable', 'expired') AND NOT EXISTS (SELECT 1 FROM object_observations WHERE asset_uuid = ?1);\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = sqlite3_changes(db); + } + } + sqlite3_finalize(stmt); + sqlite3_mutex_leave(connection); + } + pthread_mutex_unlock(mutex); + return rc; +} +unsafe extern "C" fn expire_asset( + mut root: *const ::core::ffi::c_char, + mut row: *const asset_row_t, + mut result: *mut observation_retention_result_t, +) -> ::core::ffi::c_int { + let mut rc: ::core::ffi::c_int = db_observation_asset_transition( + &raw const (*row).uuid as *const ::core::ffi::c_char, + observation_asset_state_t::OBS_ASSET_READY, + observation_asset_state_t::OBS_ASSET_EXPIRED, + b"retention_expired\0".as_ptr() as *const ::core::ffi::c_char, + ::core::ptr::null::(), + ::core::ptr::null_mut::(), + ); + if rc == C2Rust_Unnamed::DB_OBS_CAS_LOST.0 || rc == C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 { + return 0 as ::core::ffi::c_int; + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + return -1 as ::core::ffi::c_int; + } + (*result).assets_expired += 1; + remove_asset_file( + root, + &raw const (*row).path as *const ::core::ffi::c_char, + &raw mut (*result).files_deleted, + ); + return 1 as ::core::ffi::c_int; +} +unsafe extern "C" fn query_rows( + mut sql: *const ::core::ffi::c_char, + mut camera: *const ::core::ffi::c_char, + mut bound: int64_t, + mut max: ::core::ffi::c_int, + mut rows: *mut asset_row_t, +) -> ::core::ffi::c_int { + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut count: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + sql, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + let mut index: ::core::ffi::c_int = 1 as ::core::ffi::c_int; + if !camera.is_null() { + let c2rust_fresh0 = index; + index += 1; + sqlite3_bind_text( + stmt, + c2rust_fresh0, + camera, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + } + let c2rust_fresh1 = index; + index += 1; + sqlite3_bind_int64(stmt, c2rust_fresh1, bound as sqlite3_int64); + sqlite3_bind_int(stmt, index, max); + count = 0 as ::core::ffi::c_int; + let mut step: ::core::ffi::c_int = SQLITE_DONE; + while count < max && { + step = sqlite3_step(stmt); + step == SQLITE_ROW + } { + let c2rust_fresh2 = count; + count += 1; + let mut row: *mut asset_row_t = rows.offset(c2rust_fresh2 as isize); + memset( + row as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + copy_text( + stmt, + 0 as ::core::ffi::c_int, + &raw mut (*row).uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + copy_text( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*row).path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ); + (*row).bytes = sqlite3_column_int64(stmt, 2 as ::core::ffi::c_int) as int64_t; + (*row).ref_ms = sqlite3_column_int64(stmt, 3 as ::core::ffi::c_int) as int64_t; + copy_text( + stmt, + 4 as ::core::ffi::c_int, + &raw mut (*row).state as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + } + if count < max && step != SQLITE_DONE { + count = -1 as ::core::ffi::c_int; + } + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return count; +} +unsafe extern "C" fn reclaim_unreferenced( + mut root: *const ::core::ffi::c_char, + mut budget: *mut ::core::ffi::c_int, + mut result: *mut observation_retention_result_t, + mut rows: *mut asset_row_t, +) -> ::core::ffi::c_int { + if *budget <= 0 as ::core::ffi::c_int { + return 0 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = query_rows( + b"SELECT a.uuid, COALESCE(a.path, ''), COALESCE(a.bytes, 0), 0, a.state FROM observation_assets a WHERE a.state <> 'pending' AND a.protected = 0 AND ?1 > 0 AND NOT EXISTS (SELECT 1 FROM object_observations o WHERE o.asset_uuid = a.uuid) ORDER BY a.uuid LIMIT ?2;\0" + .as_ptr() as *const ::core::ffi::c_char, + ::core::ptr::null::<::core::ffi::c_char>(), + 1 as int64_t, + *budget, + rows, + ); + if n < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < n { + *budget -= 1; + if strcmp( + &raw mut (*rows.offset(i as isize)).state as *mut ::core::ffi::c_char, + b"ready\0".as_ptr() as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + { + if expire_asset(root, rows.offset(i as isize), result) < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + } else { + remove_asset_file( + root, + &raw mut (*rows.offset(i as isize)).path as *mut ::core::ffi::c_char, + &raw mut (*result).files_deleted, + ); + } + let mut deleted: ::core::ffi::c_int = + delete_asset_row(&raw mut (*rows.offset(i as isize)).uuid as *mut ::core::ffi::c_char); + if deleted < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + (*result).unreferenced_deleted += deleted; + i += 1; + } + if *budget <= 0 as ::core::ffi::c_int { + (*result).more_work = r#true != 0; + } + return 0 as ::core::ffi::c_int; +} +pub const EVICTION_CANDIDATES: [::core::ffi::c_char; 417] = unsafe { + ::core::mem::transmute::< + [u8; 417], + [::core::ffi::c_char; 417], + >( + *b"SELECT uuid, path, bytes, ref_ms, 'ready' FROM (SELECT a.uuid AS uuid, COALESCE(a.path, '') AS path, COALESCE(a.bytes, 0) AS bytes, COALESCE((SELECT MAX(o.observed_at_ms) FROM object_observations o WHERE o.asset_uuid = a.uuid), a.captured_at_ms, a.created_at_ms) AS ref_ms FROM observation_assets a WHERE a.camera_uuid = ?1 AND a.state = 'ready' AND a.protected = 0) WHERE ref_ms < ?2 ORDER BY ref_ms, uuid LIMIT ?3;\0", + ) +}; +unsafe extern "C" fn count_unprotected_ready( + mut camera: *const ::core::ffi::c_char, + mut count: *mut int64_t, +) -> ::core::ffi::c_int { + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + b"SELECT count(*) FROM observation_assets WHERE camera_uuid = ? AND state = 'ready' AND protected = 0;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + camera, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) == SQLITE_ROW { + *count = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + rc = 0 as ::core::ffi::c_int; + } + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return rc; +} +unsafe extern "C" fn enforce_camera( + mut root: *const ::core::ffi::c_char, + mut now_ms: int64_t, + mut camera: *mut observation_retention_camera_status_t, + mut budget: *mut ::core::ffi::c_int, + mut result: *mut observation_retention_result_t, + mut rows: *mut asset_row_t, +) -> ::core::ffi::c_int { + let mut cutoff: int64_t = (now_ms as ::core::ffi::c_longlong + - (*camera).max_age_days as int64_t as ::core::ffi::c_longlong * DAY_MS) + as int64_t; + if *budget > 0 as ::core::ffi::c_int { + let mut n: ::core::ffi::c_int = query_rows( + EVICTION_CANDIDATES.as_ptr(), + &raw mut (*camera).camera_uuid as *mut ::core::ffi::c_char, + cutoff, + if *budget < OBS_PAGE { + *budget + } else { + OBS_PAGE + }, + rows, + ); + if n < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < n { + *budget -= 1; + let mut rc: ::core::ffi::c_int = expire_asset(root, rows.offset(i as isize), result); + if rc < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + if rc > 0 as ::core::ffi::c_int { + (*camera).retained_bytes -= (*rows.offset(i as isize)).bytes; + } + i += 1; + } + if n == OBS_PAGE { + (*result).more_work = r#true != 0; + } + } + while (*camera).retained_bytes > (*camera).max_bytes && *budget > 0 as ::core::ffi::c_int { + let mut want: ::core::ffi::c_int = if *budget < OBS_PAGE { + *budget + } else { + OBS_PAGE + }; + let mut n_0: ::core::ffi::c_int = query_rows( + EVICTION_CANDIDATES.as_ptr(), + &raw mut (*camera).camera_uuid as *mut ::core::ffi::c_char, + INT64_MAX as int64_t, + want, + rows, + ); + if n_0 < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + if n_0 == 0 as ::core::ffi::c_int { + break; + } + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < n_0 && (*camera).retained_bytes > (*camera).max_bytes { + *budget -= 1; + let mut rc_0: ::core::ffi::c_int = + expire_asset(root, rows.offset(i_0 as isize), result); + if rc_0 < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + if rc_0 > 0 as ::core::ffi::c_int { + (*camera).retained_bytes -= (*rows.offset(i_0 as isize)).bytes; + } + i_0 += 1; + } + if n_0 < want { + break; + } + } + (*camera).budget_exhausted = r#false != 0; + if (*camera).retained_bytes > (*camera).max_bytes { + let mut evictable: int64_t = 0 as int64_t; + if count_unprotected_ready( + &raw mut (*camera).camera_uuid as *mut ::core::ffi::c_char, + &raw mut evictable, + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if evictable == 0 as int64_t { + (*camera).budget_exhausted = r#true != 0; + (*result).cameras_budget_exhausted += 1; + } else { + (*result).more_work = r#true != 0; + } + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn load_cameras( + mut out: *mut *mut observation_retention_camera_status_t, + mut count: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + *out = ::core::ptr::null_mut::(); + *count = 0 as ::core::ffi::c_int; + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + let mut capacity: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + b"SELECT c.camera_uuid, s.observation_max_age_days, s.observation_max_bytes, (SELECT COALESCE(SUM(x.bytes), 0) FROM observation_assets x WHERE x.camera_uuid = c.camera_uuid AND x.state = 'ready') FROM (SELECT DISTINCT camera_uuid FROM observation_assets WHERE state = 'ready') c LEFT JOIN streams s ON s.camera_uuid = c.camera_uuid ORDER BY c.camera_uuid;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + rc = 0 as ::core::ffi::c_int; + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if step != SQLITE_ROW { + break; + } + if *count == capacity { + let mut next: ::core::ffi::c_int = if capacity != 0 { + capacity * 2 as ::core::ffi::c_int + } else { + 16 as ::core::ffi::c_int + }; + let mut grown: *mut ::core::ffi::c_void = realloc( + *out as *mut ::core::ffi::c_void, + (next as size_t) + .wrapping_mul( + ::core::mem::size_of::< + observation_retention_camera_status_t, + >(), + ), + ); + if grown.is_null() { + rc = -1 as ::core::ffi::c_int; + break; + } else { + *out = grown as *mut observation_retention_camera_status_t; + capacity = next; + } + } + let c2rust_fresh3 = *count; + *count += 1; + let mut camera: *mut observation_retention_camera_status_t = (*out) + .offset(c2rust_fresh3 as isize); + memset( + camera as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + copy_text( + stmt, + 0 as ::core::ffi::c_int, + &raw mut (*camera).camera_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*camera).max_age_days = sqlite3_column_int(stmt, 1 as ::core::ffi::c_int); + (*camera).max_bytes = sqlite3_column_int64(stmt, 2 as ::core::ffi::c_int) + as int64_t; + (*camera).retained_bytes = sqlite3_column_int64( + stmt, + 3 as ::core::ffi::c_int, + ) as int64_t; + if (*camera).max_age_days <= 0 as ::core::ffi::c_int { + (*camera).max_age_days = OBS_RETENTION_DEFAULT_MAX_AGE_DAYS; + } + if (*camera).max_bytes <= 0 as int64_t { + (*camera).max_bytes = OBS_RETENTION_DEFAULT_MAX_BYTES as int64_t; + } + } + if rc == 0 as ::core::ffi::c_int && step != SQLITE_DONE { + rc = -1 as ::core::ffi::c_int; + } + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + if rc != 0 as ::core::ffi::c_int { + free(*out as *mut ::core::ffi::c_void); + *out = ::core::ptr::null_mut::(); + *count = 0 as ::core::ffi::c_int; + } + return rc; +} +unsafe extern "C" fn find_status( + mut cameras: *const observation_retention_camera_status_t, + mut count: ::core::ffi::c_int, + mut uuid: *const ::core::ffi::c_char, +) -> *const observation_retention_camera_status_t { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if strcmp( + &raw const (*cameras.offset(i as isize)).camera_uuid as *const ::core::ffi::c_char, + uuid, + ) == 0 as ::core::ffi::c_int + { + return cameras.offset(i as isize); + } + i += 1; + } + return ::core::ptr::null::(); +} +unsafe extern "C" fn publish_status( + mut cameras: *mut observation_retention_camera_status_t, + mut count: ::core::ffi::c_int, +) { + let mut exhausted: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if (*cameras.offset(i as isize)).budget_exhausted { + exhausted += 1; + } + i += 1; + } + pthread_mutex_lock(&raw mut g_status_mutex); + let mut old: *mut observation_retention_camera_status_t = g_status; + let mut old_count: ::core::ffi::c_int = g_status_count; + g_status = cameras; + g_status_count = count; + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_exhausted_count, + exhausted, + ); + pthread_mutex_unlock(&raw mut g_status_mutex); + if Some( + observation_evidence_set_budget_exhausted + as unsafe extern "C" fn(*const ::core::ffi::c_char, bool) -> (), + ) + .is_some() + { + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < count { + let mut before: *const observation_retention_camera_status_t = find_status( + old, + old_count, + &raw mut (*cameras.offset(i_0 as isize)).camera_uuid as *mut ::core::ffi::c_char, + ); + if (*cameras.offset(i_0 as isize)).budget_exhausted as ::core::ffi::c_int != 0 + || !before.is_null() && (*before).budget_exhausted as ::core::ffi::c_int != 0 + { + observation_evidence_set_budget_exhausted( + &raw mut (*cameras.offset(i_0 as isize)).camera_uuid + as *mut ::core::ffi::c_char, + (*cameras.offset(i_0 as isize)).budget_exhausted, + ); + } + i_0 += 1; + } + let mut i_1: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_1 < old_count { + if (*old.offset(i_1 as isize)).budget_exhausted as ::core::ffi::c_int != 0 + && find_status( + cameras, + count, + &raw mut (*old.offset(i_1 as isize)).camera_uuid as *mut ::core::ffi::c_char, + ) + .is_null() + { + observation_evidence_set_budget_exhausted( + &raw mut (*old.offset(i_1 as isize)).camera_uuid as *mut ::core::ffi::c_char, + r#false != 0, + ); + } + i_1 += 1; + } + } + free(old as *mut ::core::ffi::c_void); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_run( + mut storage_path: *const ::core::ffi::c_char, + mut now_ms: int64_t, + mut result: *mut observation_retention_result_t, +) -> ::core::ffi::c_int { + let mut local: observation_retention_result_t = observation_retention_result_t { + assets_expired: 0, + unreferenced_deleted: 0, + files_deleted: 0, + changes_pruned: 0, + observations_pruned: 0, + cameras_budget_exhausted: 0, + more_work: false, + oldest_seq: 0, + }; + if result.is_null() { + result = &raw mut local; + } + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut root: [::core::ffi::c_char; 1024] = [0; 1024]; + if root_dir( + storage_path, + &raw mut root as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_startup_done, + ) == 0 + { + return 1 as ::core::ffi::c_int; + } + let mut rows: *mut asset_row_t = + calloc(OBS_PAGE as size_t, ::core::mem::size_of::()) as *mut asset_row_t; + if rows.is_null() { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut g_run_mutex); + let mut budget: ::core::ffi::c_int = OBS_RETENTION_MAX_EVICTIONS; + let mut rc: ::core::ffi::c_int = prune_history(now_ms, result); + if rc == 0 as ::core::ffi::c_int { + rc = reclaim_unreferenced( + &raw mut root as *mut ::core::ffi::c_char, + &raw mut budget, + result, + rows, + ); + } + let mut cameras: *mut observation_retention_camera_status_t = + ::core::ptr::null_mut::(); + let mut camera_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if rc == 0 as ::core::ffi::c_int { + rc = load_cameras(&raw mut cameras, &raw mut camera_count); + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while rc == 0 as ::core::ffi::c_int && i < camera_count { + rc = enforce_camera( + &raw mut root as *mut ::core::ffi::c_char, + now_ms, + cameras.offset(i as isize), + &raw mut budget, + result, + rows, + ); + i += 1; + } + if rc == 0 as ::core::ffi::c_int { + publish_status(cameras, camera_count); + cameras = ::core::ptr::null_mut::(); + } + free(cameras as *mut ::core::ffi::c_void); + pthread_mutex_unlock(&raw mut g_run_mutex); + free(rows as *mut ::core::ffi::c_void); + let mut meta: observation_meta_t = observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }; + if db_observation_meta_get(&raw mut meta) == C2Rust_Unnamed::DB_OBS_OK.0 { + (*result).oldest_seq = meta.oldest_seq; + } + if (*result).assets_expired != 0 + || (*result).unreferenced_deleted != 0 + || (*result).changes_pruned != 0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Observation retention: %d media expired, %d files deleted, %d unreferenced assets reclaimed, %d changes and %d observations pruned (oldest seq %lld)\0" + .as_ptr() as *const ::core::ffi::c_char, + (*result).assets_expired, + (*result).files_deleted, + (*result).unreferenced_deleted, + (*result).changes_pruned, + (*result).observations_pruned, + (*result).oldest_seq as ::core::ffi::c_longlong, + ); + } + if (*result).cameras_budget_exhausted != 0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Observation evidence budget exhausted on %d camera(s): only protected media remains\0" + .as_ptr() as *const ::core::ffi::c_char, + (*result).cameras_budget_exhausted, + ); + } + if rc != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Observation retention pass failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + return if rc == 0 as ::core::ffi::c_int { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_maintenance_tick() { + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_startup_done, + ) == 0 + { + return; + } + let mut now: time_t = time(::core::ptr::null_mut::()); + if !g_more_work + && g_last_tick != 0 as time_t + && now - g_last_tick < OBS_RETENTION_INTERVAL_SEC as time_t + { + return; + } + g_last_tick = now; + let mut result: observation_retention_result_t = observation_retention_result_t { + assets_expired: 0, + unreferenced_deleted: 0, + files_deleted: 0, + changes_pruned: 0, + observations_pruned: 0, + cameras_budget_exhausted: 0, + more_work: false, + oldest_seq: 0, + }; + let mut rc: ::core::ffi::c_int = observation_retention_run( + &raw mut g_config.storage_path as *mut ::core::ffi::c_char, + wall_ms(), + &raw mut result, + ); + g_more_work = rc == 0 as ::core::ffi::c_int && result.more_work as ::core::ffi::c_int != 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_budget_exhausted( + mut camera_uuid: *const ::core::ffi::c_char, +) -> bool { + if camera_uuid.is_null() + || ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_exhausted_count, + ) == 0 as ::core::ffi::c_int + { + return r#false != 0; + } + if pthread_mutex_trylock(&raw mut g_status_mutex) != 0 as ::core::ffi::c_int { + return r#false != 0; + } + let mut exhausted: bool = r#false != 0; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < g_status_count { + if strcmp( + &raw mut (*g_status.offset(i as isize)).camera_uuid as *mut ::core::ffi::c_char, + camera_uuid, + ) == 0 as ::core::ffi::c_int + { + exhausted = (*g_status.offset(i as isize)).budget_exhausted; + break; + } else { + i += 1; + } + } + pthread_mutex_unlock(&raw mut g_status_mutex); + return exhausted; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_budget_exhausted_count() -> ::core::ffi::c_int { + return ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_exhausted_count, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_camera_status( + mut out: *mut observation_retention_camera_status_t, + mut max: ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if out.is_null() || max <= 0 as ::core::ffi::c_int { + return 0 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut g_status_mutex); + let mut n: ::core::ffi::c_int = if g_status_count < max { + g_status_count + } else { + max + }; + if n > 0 as ::core::ffi::c_int { + memcpy( + out as *mut ::core::ffi::c_void, + g_status as *const ::core::ffi::c_void, + (n as size_t) + .wrapping_mul(::core::mem::size_of::()), + ); + } + pthread_mutex_unlock(&raw mut g_status_mutex); + return n; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_retention_reset_for_testing() { + pthread_mutex_lock(&raw mut g_run_mutex); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut g_startup_done, + 0 as ::core::ffi::c_int, + ); + publish_status( + ::core::ptr::null_mut::(), + 0 as ::core::ffi::c_int, + ); + g_last_tick = 0 as time_t; + g_more_work = r#false != 0; + pthread_mutex_unlock(&raw mut g_run_mutex); +} +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const ENOENT: ::core::ffi::c_int = 2 as ::core::ffi::c_int; +pub const ENOTDIR: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const __ATOMIC_SEQ_CST: ::core::ffi::c_int = 5 as ::core::ffi::c_int; diff --git a/rust/generated/src__database__db_api_tokens.rs b/rust/generated/src__database__db_api_tokens.rs index a1364ee04..cba969758 100644 --- a/rust/generated/src__database__db_api_tokens.rs +++ b/rust/generated/src__database__db_api_tokens.rs @@ -295,7 +295,8 @@ impl C2Rust_Unnamed_0 { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__database__db_audit.rs b/rust/generated/src__database__db_audit.rs index 673caaaec..115de8080 100644 --- a/rust/generated/src__database__db_audit.rs +++ b/rust/generated/src__database__db_audit.rs @@ -232,7 +232,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__database__db_auth.rs b/rust/generated/src__database__db_auth.rs index 0f43a032f..735031198 100644 --- a/rust/generated/src__database__db_auth.rs +++ b/rust/generated/src__database__db_auth.rs @@ -405,7 +405,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -512,6 +512,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__database__db_authorization.rs b/rust/generated/src__database__db_authorization.rs index e527ba108..e348377dc 100644 --- a/rust/generated/src__database__db_authorization.rs +++ b/rust/generated/src__database__db_authorization.rs @@ -210,7 +210,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__database__db_authorization_snapshot.rs b/rust/generated/src__database__db_authorization_snapshot.rs index 3c4cf40ed..cc565079a 100644 --- a/rust/generated/src__database__db_authorization_snapshot.rs +++ b/rust/generated/src__database__db_authorization_snapshot.rs @@ -223,7 +223,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__database__db_backup.rs b/rust/generated/src__database__db_backup.rs index dc4236022..bdd7d53c2 100644 --- a/rust/generated/src__database__db_backup.rs +++ b/rust/generated/src__database__db_backup.rs @@ -141,6 +141,7 @@ pub type __syscall_slong_t = ::core::ffi::c_long; pub type off_t = __off_t; pub type ssize_t = isize; pub type clockid_t = __clockid_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct timespec { @@ -329,7 +330,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -436,6 +437,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__database__db_core.rs b/rust/generated/src__database__db_core.rs index 61c745312..173eaa588 100644 --- a/rust/generated/src__database__db_core.rs +++ b/rust/generated/src__database__db_core.rs @@ -1,4 +1,5 @@ use ::c2rust_bitfields; +use ::libc; unsafe extern "C" { pub type _IO_wide_data; pub type _IO_codecvt; @@ -75,6 +76,13 @@ unsafe extern "C" { ppStmt: *mut *mut sqlite3_stmt, pzTail: *mut *const ::core::ffi::c_char, ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_text( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: *const ::core::ffi::c_char, + _: ::core::ffi::c_int, + _: Option ()>, + ) -> ::core::ffi::c_int; unsafe fn sqlite3_step(_: *mut sqlite3_stmt) -> ::core::ffi::c_int; unsafe fn sqlite3_column_text( _: *mut sqlite3_stmt, @@ -267,6 +275,7 @@ pub type pid_t = __pid_t; pub type ssize_t = isize; pub type clockid_t = __clockid_t; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct timespec { @@ -416,6 +425,7 @@ pub struct dirent { pub type DIR = __dirstream; pub type sqlite_int64 = ::core::ffi::c_longlong; pub type sqlite3_int64 = sqlite_int64; +pub type sqlite3_destructor_type = Option ()>; #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] pub struct C2Rust_Unnamed_0(pub ::core::ffi::c_uint); @@ -569,7 +579,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -676,6 +686,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -10747,6 +10761,51 @@ unsafe extern "C" fn private_existing_database_files( } return 0 as ::core::ffi::c_int; } +unsafe extern "C" fn regenerate_observation_epoch_after_restore() -> ::core::ffi::c_int { + let mut epoch: [::core::ffi::c_char; 37] = [0; 37]; + if db.is_null() + || lightnvr_uuid_generate_v4(&raw mut epoch as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + b"UPDATE observation_meta SET epoch_uuid = ? WHERE id = 1;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + &raw mut epoch as *mut ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = 0 as ::core::ffi::c_int; + } + } + sqlite3_finalize(stmt); + if rc == 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Regenerated observation cursor epoch after database restore\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + return rc; +} #[unsafe(no_mangle)] pub unsafe extern "C" fn init_database( mut db_path: *const ::core::ffi::c_char, @@ -10761,6 +10820,7 @@ pub unsafe extern "C" fn init_database_ex( let mut rc: ::core::ffi::c_int = 0; let mut err_msg: *mut ::core::ffi::c_char = ::core::ptr::null_mut::<::core::ffi::c_char>(); let mut is_new_database: bool = r#false != 0; + let mut restored_from_backup: bool = r#false != 0; let mut test_db: *mut sqlite3 = ::core::ptr::null_mut::(); let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); if !db.is_null() { @@ -11059,6 +11119,7 @@ pub unsafe extern "C" fn init_database_ex( b"Successfully restored database from backup\0".as_ptr() as *const ::core::ffi::c_char, ); + restored_from_backup = r#true != 0; } } else { _log_message_ctx( @@ -11174,6 +11235,7 @@ pub unsafe extern "C" fn init_database_ex( b"Successfully restored database from backup\0".as_ptr() as *const ::core::ffi::c_char, ); + restored_from_backup = r#true != 0; } } else { _log_message_ctx( @@ -11615,6 +11677,18 @@ pub unsafe extern "C" fn init_database_ex( db = ::core::ptr::null_mut::(); return -1 as ::core::ffi::c_int; } + if restored_from_backup as ::core::ffi::c_int != 0 + && db_init_flags & C2Rust_Unnamed_1::DB_INIT_NO_MIGRATE.0 == 0 + && regenerate_observation_epoch_after_restore() != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to regenerate the observation cursor epoch after restore\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } if db_camera_tags_backfill_legacy_once() != 0 as ::core::ffi::c_int { _log_message_ctx( log_level_t::LOG_LEVEL_ERROR, diff --git a/rust/generated/src__database__db_event_outbox.rs b/rust/generated/src__database__db_event_outbox.rs index 14e12f9c7..874ef4424 100644 --- a/rust/generated/src__database__db_event_outbox.rs +++ b/rust/generated/src__database__db_event_outbox.rs @@ -293,6 +293,7 @@ impl C2Rust_Unnamed { pub const _ISpunct: Self = Self(4); pub const _ISalnum: Self = Self(8); } +pub const EVENT_TYPE_MAX: ::core::ffi::c_int = 96 as ::core::ffi::c_int; pub const EVENT_ENVELOPE_MAX_BYTES: ::core::ffi::c_uint = (96 as ::core::ffi::c_uint).wrapping_mul(1024 as ::core::ffi::c_uint); pub const EVENT_OUTBOX_DESTINATION_MAX: ::core::ffi::c_int = 64 as ::core::ffi::c_int; @@ -1527,6 +1528,56 @@ pub unsafe extern "C" fn db_event_outbox_prune_terminal( }; } #[unsafe(no_mangle)] +pub unsafe extern "C" fn db_event_outbox_count_undelivered_type( + mut event_type: *const ::core::ffi::c_char, + mut count: *mut int64_t, +) -> ::core::ffi::c_int { + if count.is_null() || !valid_text(event_type, EVENT_TYPE_MAX as size_t, r#true != 0) { + return -1 as ::core::ffi::c_int; + } + *count = 0 as int64_t; + let mut db: *mut sqlite3 = get_db_handle(); + let mut mutex: *mut pthread_mutex_t = get_db_mutex(); + if db.is_null() || mutex.is_null() { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(mutex); + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = sqlite3_prepare_v2( + db, + b"SELECT count(*) FROM event_outbox WHERE event_type=? AND state IN ('pending','delivering');\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ); + if rc == SQLITE_OK { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + event_type, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + rc = sqlite3_step(stmt); + if rc == SQLITE_ROW { + *count = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + } + } + if !stmt.is_null() { + sqlite3_finalize(stmt); + } + pthread_mutex_unlock(mutex); + return if rc == SQLITE_ROW { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +#[unsafe(no_mangle)] pub unsafe extern "C" fn db_event_outbox_get_stats( mut destination: *const ::core::ffi::c_char, mut now: int64_t, diff --git a/rust/generated/src__database__db_migrations.rs b/rust/generated/src__database__db_migrations.rs index 98bf5a6af..5de19afe7 100644 --- a/rust/generated/src__database__db_migrations.rs +++ b/rust/generated/src__database__db_migrations.rs @@ -1388,7 +1388,39 @@ static mut migration_0094_down: [::core::ffi::c_char; 323] = unsafe { *b"-- Pending challenges must not survive removal of replay protection.\nDELETE FROM sessions WHERE mfa_pending = 1;\nALTER TABLE users DROP COLUMN totp_setup_expires_at;\nALTER TABLE users DROP COLUMN totp_window_started_at;\nALTER TABLE users DROP COLUMN totp_failed_attempts;\nALTER TABLE users DROP COLUMN totp_last_used_step;\0", ) }; -static mut embedded_migrations_data: [migration_t; 94] = unsafe { +static mut migration_0095_up: [::core::ffi::c_char; 6453] = unsafe { + ::core::mem::transmute::< + [u8; 6453], + [::core::ffi::c_char; 6453], + >( + *b"CREATE TABLE observation_assets (\n uuid TEXT PRIMARY KEY,\n camera_uuid TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN ('pending', 'ready', 'unavailable', 'expired')),\n reason TEXT CHECK (reason IS NULL OR length(reason) BETWEEN 1 AND 32),\n origin TEXT CHECK (origin IS NULL OR origin IN ('inference_input', 'submitted_image')),\n media_type TEXT,\n path TEXT,\n bytes INTEGER CHECK (bytes IS NULL OR bytes >= 0),\n width INTEGER CHECK (width IS NULL OR width > 0),\n height INTEGER CHECK (height IS NULL OR height > 0),\n captured_at_ms INTEGER,\n transform_json TEXT,\n protected INTEGER NOT NULL DEFAULT 0 CHECK (protected IN (0, 1)),\n expires_at_ms INTEGER,\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n CHECK ((state IN ('pending', 'ready')) = (reason IS NULL))\n);\n\n-- Reconciliation scans by state; retention walks one camera's ready assets\n-- oldest first.\nCREATE INDEX idx_observation_assets_state_camera\nON observation_assets(state, camera_uuid, created_at_ms);\n\nCREATE TABLE object_observations (\n uuid TEXT PRIMARY KEY,\n camera_uuid TEXT NOT NULL,\n stream_name_at_capture TEXT NOT NULL,\n batch_id TEXT NOT NULL,\n object_index INTEGER NOT NULL CHECK (object_index BETWEEN 0 AND 19),\n observed_at_ms INTEGER NOT NULL,\n created_at_ms INTEGER NOT NULL,\n engine_key TEXT NOT NULL,\n engine_type TEXT NOT NULL CHECK (engine_type IN ('object', 'api', 'onvif', 'external')),\n model TEXT,\n model_version TEXT,\n label TEXT NOT NULL CHECK (length(label) BETWEEN 1 AND 31),\n confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0),\n box_x REAL,\n box_y REAL,\n box_width REAL,\n box_height REAL,\n track_id TEXT,\n track_namespace TEXT,\n track_session TEXT,\n recording_id INTEGER REFERENCES recordings(id) ON DELETE SET NULL,\n asset_uuid TEXT REFERENCES observation_assets(uuid),\n revision INTEGER NOT NULL DEFAULT 1 CHECK (revision >= 1),\n UNIQUE(batch_id, object_index),\n -- Normalized xywh relative to the stored evidence image; all or nothing.\n -- The 1e-6 slack absorbs rounding to 6 decimals. IS NOT NULL is explicit\n -- because a CHECK that evaluates to NULL passes.\n CHECK ((box_x IS NULL AND box_y IS NULL AND box_width IS NULL AND box_height IS NULL)\n OR (box_x IS NOT NULL AND box_y IS NOT NULL\n AND box_width IS NOT NULL AND box_height IS NOT NULL\n AND box_x >= 0.0 AND box_y >= 0.0\n AND box_width > 0.0 AND box_height > 0.0\n AND box_x + box_width <= 1.000001 AND box_y + box_height <= 1.000001)),\n CHECK ((track_id IS NULL AND track_namespace IS NULL AND track_session IS NULL)\n OR (length(track_id) BETWEEN 1 AND 64\n AND track_namespace IS NOT NULL AND track_session IS NOT NULL))\n);\n\nCREATE INDEX idx_object_observations_camera_time\nON object_observations(camera_uuid, observed_at_ms);\n\nCREATE INDEX idx_object_observations_label_time\nON object_observations(label, observed_at_ms);\n\nCREATE INDEX idx_object_observations_created\nON object_observations(created_at_ms);\n\n-- Asset transitions bump every referencing observation; refcount is derived.\nCREATE INDEX idx_object_observations_asset\nON object_observations(asset_uuid);\n\n-- Keeps the ON DELETE SET NULL action cheap on every recording delete.\nCREATE INDEX idx_object_observations_recording\nON object_observations(recording_id);\n\n-- seq is the history cursor: monotonic persistence order, never reused.\nCREATE TABLE observation_changes (\n seq INTEGER PRIMARY KEY AUTOINCREMENT,\n observation_uuid TEXT NOT NULL REFERENCES object_observations(uuid) ON DELETE CASCADE,\n revision INTEGER NOT NULL CHECK (revision >= 1),\n cause TEXT NOT NULL CHECK (cause IN ('created', 'evidence', 'classification')),\n evidence_state TEXT NOT NULL CHECK (evidence_state IN ('pending', 'ready', 'unavailable', 'expired')),\n evidence_reason TEXT CHECK (evidence_reason IS NULL OR length(evidence_reason) BETWEEN 1 AND 32),\n created_at_ms INTEGER NOT NULL,\n UNIQUE(observation_uuid, revision),\n CHECK ((evidence_state IN ('pending', 'ready')) = (evidence_reason IS NULL)),\n CHECK ((cause = 'created') = (revision = 1))\n);\n\nCREATE TABLE observation_publications (\n change_seq INTEGER PRIMARY KEY REFERENCES observation_changes(seq) ON DELETE CASCADE,\n message_id TEXT NOT NULL UNIQUE,\n state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN (\n 'pending', 'relayed', 'relayed_no_route', 'expired', 'failed'\n )),\n attempts INTEGER NOT NULL DEFAULT 0 CHECK (attempts >= 0),\n last_error TEXT,\n updated_at_ms INTEGER NOT NULL\n);\n\n-- The relay reads pending rows in seq order.\nCREATE INDEX idx_observation_publications_pending\nON observation_publications(change_seq) WHERE state = 'pending';\n\n-- One row. epoch_uuid identifies this DB generation for cursors (contract\n-- D9); oldest_seq is the lowest seq that may still be retained (prune\n-- boundary, initially 1). Kept out of system_settings so it can be written\n-- inside observation transactions.\nCREATE TABLE observation_meta (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n epoch_uuid TEXT NOT NULL CHECK (length(epoch_uuid) = 36),\n oldest_seq INTEGER NOT NULL DEFAULT 1 CHECK (oldest_seq >= 1)\n);\n\nINSERT INTO observation_meta (id, epoch_uuid, oldest_seq)\nVALUES (\n 1,\n lower(\n hex(randomblob(4)) || '-' ||\n hex(randomblob(2)) || '-4' ||\n substr(hex(randomblob(2)), 2) || '-' ||\n substr('89ab', (abs(random()) % 4) + 1, 1) ||\n substr(hex(randomblob(2)), 2) || '-' ||\n hex(randomblob(6))\n ),\n 1\n);\n\n-- Per-camera evidence settings (contract section 13). Evidence is off by\n-- default; an empty label list means every object label.\nALTER TABLE streams ADD COLUMN observation_evidence_enabled INTEGER NOT NULL DEFAULT 0\n CHECK (observation_evidence_enabled IN (0, 1));\nALTER TABLE streams ADD COLUMN observation_labels TEXT NOT NULL DEFAULT '';\nALTER TABLE streams ADD COLUMN observation_max_age_days INTEGER NOT NULL DEFAULT 7\n CHECK (observation_max_age_days > 0);\nALTER TABLE streams ADD COLUMN observation_max_bytes INTEGER NOT NULL DEFAULT 2147483648\n CHECK (observation_max_bytes > 0);\n\nINSERT OR IGNORE INTO system_settings (key, value)\nVALUES ('observation_history_retention_days', '30');\n\nINSERT OR IGNORE INTO system_settings (key, value)\nVALUES ('observation_global_queue_bytes', '33554432');\0", + ) +}; +static mut migration_0095_down: [::core::ffi::c_char; 1100] = unsafe { + ::core::mem::transmute::< + [u8; 1100], + [::core::ffi::c_char; 1100], + >( + *b"-- Drops all observation history and evidence metadata; take a backup first.\n-- Evidence files under /observations are left on disk.\nDELETE FROM system_settings WHERE key IN (\n 'observation_history_retention_days', 'observation_global_queue_bytes'\n);\nALTER TABLE streams DROP COLUMN observation_max_bytes;\nALTER TABLE streams DROP COLUMN observation_max_age_days;\nALTER TABLE streams DROP COLUMN observation_labels;\nALTER TABLE streams DROP COLUMN observation_evidence_enabled;\nDROP TABLE IF EXISTS observation_meta;\nDROP INDEX IF EXISTS idx_observation_publications_pending;\nDROP TABLE IF EXISTS observation_publications;\nDROP TABLE IF EXISTS observation_changes;\nDROP INDEX IF EXISTS idx_object_observations_recording;\nDROP INDEX IF EXISTS idx_object_observations_asset;\nDROP INDEX IF EXISTS idx_object_observations_created;\nDROP INDEX IF EXISTS idx_object_observations_label_time;\nDROP INDEX IF EXISTS idx_object_observations_camera_time;\nDROP TABLE IF EXISTS object_observations;\nDROP INDEX IF EXISTS idx_observation_assets_state_camera;\nDROP TABLE IF EXISTS observation_assets;\0", + ) +}; +static mut migration_0096_up: [::core::ffi::c_char; 1971] = unsafe { + ::core::mem::transmute::< + [u8; 1971], + [::core::ffi::c_char; 1971], + >( + *b"CREATE TABLE observation_classifications (\n observation_uuid TEXT NOT NULL REFERENCES object_observations(uuid) ON DELETE CASCADE,\n key TEXT NOT NULL CHECK (length(key) BETWEEN 3 AND 65),\n writer_namespace TEXT NOT NULL CHECK (length(writer_namespace) BETWEEN 1 AND 32),\n writer_user_id INTEGER NOT NULL CHECK (writer_user_id > 0),\n classifier TEXT NOT NULL CHECK (length(classifier) BETWEEN 1 AND 64),\n model_version TEXT CHECK (model_version IS NULL OR length(model_version) BETWEEN 1 AND 64),\n label TEXT NOT NULL CHECK (length(CAST(label AS BLOB)) BETWEEN 1 AND 128),\n taxonomy_id TEXT CHECK (taxonomy_id IS NULL OR length(taxonomy_id) BETWEEN 1 AND 64),\n confidence REAL NOT NULL CHECK (confidence >= 0.0 AND confidence <= 1.0),\n classified_at_ms INTEGER NOT NULL CHECK (classified_at_ms >= 0),\n input_asset_uuid TEXT NOT NULL CHECK (length(input_asset_uuid) = 36),\n input_revision INTEGER NOT NULL CHECK (input_revision >= 1),\n kind TEXT NOT NULL CHECK (kind IN ('automated', 'owner_confirmed')),\n annotation_revision INTEGER NOT NULL DEFAULT 1 CHECK (annotation_revision >= 1),\n created_at_ms INTEGER NOT NULL,\n updated_at_ms INTEGER NOT NULL,\n PRIMARY KEY (observation_uuid, key),\n -- The key's namespace segment is the writer's namespace.\n CHECK (substr(key, 1, length(writer_namespace) + 1) = writer_namespace || '/')\n);\n\n-- New persisted action-mask positions are appended; existing positions never\n-- move. Built-in roles fail closed: only Administrator receives the action.\nINSERT INTO authz_actions\n (action_key, category, description, camera_scoped, destructive, sort_order, bit_index)\nVALUES\n ('observations.annotate', 'Integrations',\n 'Annotate detection observations with classifier results', 1, 0, 200, 19);\n\nINSERT INTO authz_role_actions (role_uuid, action_key)\nSELECT '00000000-0000-4000-8000-000000000001', action_key\nFROM authz_actions\nWHERE action_key = 'observations.annotate';\0", + ) +}; +static mut migration_0096_down: [::core::ffi::c_char; 257] = unsafe { + ::core::mem::transmute::< + [u8; 257], + [::core::ffi::c_char; 257], + >( + *b"-- Drops all classification annotations; take a backup first.\nDELETE FROM authz_role_actions WHERE action_key = 'observations.annotate';\nDELETE FROM authz_actions WHERE action_key = 'observations.annotate';\nDROP TABLE IF EXISTS observation_classifications;\0", + ) +}; +static mut embedded_migrations_data: [migration_t; 96] = unsafe { [ migration_t { version: ::core::mem::transmute::< @@ -2988,9 +3020,43 @@ static mut embedded_migrations_data: [migration_t; 94] = unsafe { status: migrate_status_t::MIGRATE_STATUS_PENDING, is_embedded: r#true != 0, }, + migration_t { + version: ::core::mem::transmute::< + [u8; 32], + [::core::ffi::c_char; 32], + >(*b"0095\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"), + description: ::core::mem::transmute::< + [u8; 256], + [::core::ffi::c_char; 256], + >( + *b"add_object_observations\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ), + filepath: [0; 512], + sql_up: &raw const migration_0095_up as *const ::core::ffi::c_char, + sql_down: &raw const migration_0095_down as *const ::core::ffi::c_char, + status: migrate_status_t::MIGRATE_STATUS_PENDING, + is_embedded: r#true != 0, + }, + migration_t { + version: ::core::mem::transmute::< + [u8; 32], + [::core::ffi::c_char; 32], + >(*b"0096\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"), + description: ::core::mem::transmute::< + [u8; 256], + [::core::ffi::c_char; 256], + >( + *b"add_observation_classifications\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ), + filepath: [0; 512], + sql_up: &raw const migration_0096_up as *const ::core::ffi::c_char, + sql_down: &raw const migration_0096_down as *const ::core::ffi::c_char, + status: migrate_status_t::MIGRATE_STATUS_PENDING, + is_embedded: r#true != 0, + }, ] }; -pub const EMBEDDED_MIGRATIONS_COUNT: ::core::ffi::c_int = 94 as ::core::ffi::c_int; +pub const EMBEDDED_MIGRATIONS_COUNT: ::core::ffi::c_int = 96 as ::core::ffi::c_int; pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const DEFAULT_MIGRATIONS_DIR: [::core::ffi::c_char; 31] = unsafe { diff --git a/rust/generated/src__database__db_observation_classifications.rs b/rust/generated/src__database__db_observation_classifications.rs new file mode 100644 index 000000000..4c4d83765 --- /dev/null +++ b/rust/generated/src__database__db_observation_classifications.rs @@ -0,0 +1,1637 @@ +use ::libc; +unsafe extern "C" { + pub type sqlite3; + pub type sqlite3_mutex; + pub type sqlite3_stmt; + unsafe fn sqlite3_exec( + _: *mut sqlite3, + sql: *const ::core::ffi::c_char, + callback: Option< + unsafe extern "C" fn( + *mut ::core::ffi::c_void, + ::core::ffi::c_int, + *mut *mut ::core::ffi::c_char, + *mut *mut ::core::ffi::c_char, + ) -> ::core::ffi::c_int, + >, + _: *mut ::core::ffi::c_void, + errmsg: *mut *mut ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_changes(_: *mut sqlite3) -> ::core::ffi::c_int; + unsafe fn sqlite3_errmsg(_: *mut sqlite3) -> *const ::core::ffi::c_char; + unsafe fn sqlite3_prepare_v2( + db: *mut sqlite3, + zSql: *const ::core::ffi::c_char, + nByte: ::core::ffi::c_int, + ppStmt: *mut *mut sqlite3_stmt, + pzTail: *mut *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_double( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: ::core::ffi::c_double, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_int( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_int64( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: sqlite3_int64, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_null(_: *mut sqlite3_stmt, _: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_text( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: *const ::core::ffi::c_char, + _: ::core::ffi::c_int, + _: Option ()>, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_clear_bindings(_: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_step(_: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_column_double( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> ::core::ffi::c_double; + unsafe fn sqlite3_column_int( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_column_int64(_: *mut sqlite3_stmt, iCol: ::core::ffi::c_int) + -> sqlite3_int64; + unsafe fn sqlite3_column_text( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> *const ::core::ffi::c_uchar; + unsafe fn sqlite3_finalize(pStmt: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_reset(pStmt: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_mutex_enter(_: *mut sqlite3_mutex); + unsafe fn sqlite3_mutex_leave(_: *mut sqlite3_mutex); + unsafe fn sqlite3_db_mutex(_: *mut sqlite3) -> *mut sqlite3_mutex; + unsafe fn observation_asset_state_parse( + name: *const ::core::ffi::c_char, + state: *mut observation_asset_state_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_bump_revision_locked( + db: *mut sqlite3, + source: *const ::core::ffi::c_char, + observation_uuid: *const ::core::ffi::c_char, + cause: observation_change_cause_t, + now_ms: int64_t, + seq_out: *mut int64_t, + revision_out: *mut ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn fabs(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; + unsafe fn time(__timer: *mut time_t) -> time_t; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_lock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_unlock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn event_identity_get_source( + output: *mut ::core::ffi::c_char, + output_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn observation_relay_notify(); + unsafe fn get_db_handle() -> *mut sqlite3; + unsafe fn get_db_mutex() -> *mut pthread_mutex_t; + unsafe fn db_open_readonly_connection(connection: *mut *mut sqlite3) -> ::core::ffi::c_int; + unsafe fn db_close_readonly_connection(connection: *mut sqlite3); + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strnlen(__string: *const ::core::ffi::c_char, __maxlen: size_t) -> size_t; + unsafe fn strcasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn malloc(__size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __time_t = ::core::ffi::c_long; +pub type __clockid_t = ::core::ffi::c_int; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +pub type __pthread_list_t = __pthread_internal_list; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_mutex_s { + pub __lock: ::core::ffi::c_int, + pub __count: ::core::ffi::c_uint, + pub __owner: ::core::ffi::c_int, + pub __nusers: ::core::ffi::c_uint, + pub __kind: ::core::ffi::c_int, + pub __spins: ::core::ffi::c_short, + pub __glibc_reserved: ::core::ffi::c_short, + pub __list: __pthread_list_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::core::ffi::c_char; 40], + pub __align: ::core::ffi::c_long, +} +pub type sqlite_int64 = ::core::ffi::c_longlong; +pub type sqlite3_int64 = sqlite_int64; +pub type sqlite3_destructor_type = Option ()>; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_change_cause_t(pub ::core::ffi::c_uint); +impl observation_change_cause_t { + pub const OBS_CAUSE_CREATED: Self = Self(0); + pub const OBS_CAUSE_EVIDENCE: Self = Self(1); + pub const OBS_CAUSE_CLASSIFICATION: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_classification_kind_t(pub ::core::ffi::c_uint); +impl observation_classification_kind_t { + pub const OBS_CLS_KIND_AUTOMATED: Self = Self(0); + pub const OBS_CLS_KIND_OWNER_CONFIRMED: Self = Self(1); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_0(pub ::core::ffi::c_int); +impl C2Rust_Unnamed_0 { + pub const DB_OBS_CLS_CREATED: Self = Self(0); + pub const DB_OBS_CLS_UPDATED: Self = Self(1); + pub const DB_OBS_CLS_NOOP: Self = Self(2); + pub const DB_OBS_CLS_REVISION_CONFLICT: Self = Self(3); + pub const DB_OBS_CLS_OWNER_LOCKED: Self = Self(4); + pub const DB_OBS_CLS_EVIDENCE_MISMATCH: Self = Self(5); + pub const DB_OBS_CLS_WRITER_MISMATCH: Self = Self(6); + pub const DB_OBS_CLS_LIMIT: Self = Self(7); + pub const DB_OBS_CLS_ERROR: Self = Self(-1); + pub const DB_OBS_CLS_INVALID: Self = Self(-2); + pub const DB_OBS_CLS_NOT_FOUND: Self = Self(-3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_t { + pub key: [::core::ffi::c_char; 66], + pub writer_namespace: [::core::ffi::c_char; 33], + pub writer_user_id: int64_t, + pub classifier: [::core::ffi::c_char; 65], + pub model_version: [::core::ffi::c_char; 65], + pub label: [::core::ffi::c_char; 129], + pub taxonomy_id: [::core::ffi::c_char; 65], + pub confidence: ::core::ffi::c_double, + pub classified_at_ms: int64_t, + pub input_asset_uuid: [::core::ffi::c_char; 37], + pub input_revision: ::core::ffi::c_int, + pub kind: observation_classification_kind_t, + pub annotation_revision: ::core::ffi::c_int, + pub created_at_ms: int64_t, + pub updated_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_input_t { + pub writer_namespace: *const ::core::ffi::c_char, + pub writer_user_id: int64_t, + pub name: *const ::core::ffi::c_char, + pub classifier: *const ::core::ffi::c_char, + pub model_version: *const ::core::ffi::c_char, + pub label: *const ::core::ffi::c_char, + pub taxonomy_id: *const ::core::ffi::c_char, + pub confidence: ::core::ffi::c_double, + pub classified_at_ms: int64_t, + pub input_asset_uuid: *const ::core::ffi::c_char, + pub input_revision: ::core::ffi::c_int, + pub kind: observation_classification_kind_t, + pub expected_revision: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_result_t { + pub has_annotation: bool, + pub annotation: observation_classification_t, + pub observation_revision: ::core::ffi::c_int, + pub change_seq: int64_t, + pub observation_asset_uuid: [::core::ffi::c_char; 37], + pub evidence_state: observation_asset_state_t, + pub limit: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cls_tx_t { + pub mutex: *mut pthread_mutex_t, + pub db: *mut sqlite3, + pub connection_mutex: *mut sqlite3_mutex, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_state_t { + pub revision: ::core::ffi::c_int, + pub asset_uuid: [::core::ffi::c_char; 37], + pub state: observation_asset_state_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_set_t { + pub automated_count: ::core::ffi::c_int, + pub automated: [observation_classification_t; 8], + pub owner_confirmed_count: ::core::ffi::c_int, + pub owner_confirmed: [observation_classification_t; 8], +} +pub type observation_classification_visit_fn = Option< + unsafe extern "C" fn( + size_t, + *const observation_classification_set_t, + *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int, +>; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_detection_ref_t { + pub label: *const ::core::ffi::c_char, + pub timestamp: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_detection_link_t { + pub linked: bool, + pub observation_uuid: [::core::ffi::c_char; 37], + pub has_classification: bool, + pub owner_confirmed: bool, + pub classification: observation_classification_t, +} +pub const INT64_MAX: ::core::ffi::c_long = 9223372036854775807 as ::core::ffi::c_long; +pub const SQLITE_OK: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const SQLITE_ROW: ::core::ffi::c_int = 100 as ::core::ffi::c_int; +pub const SQLITE_DONE: ::core::ffi::c_int = 101 as ::core::ffi::c_int; +pub const SQLITE_STATIC: sqlite3_destructor_type = None; +pub const OBS_CLS_SEGMENT_MAX: ::core::ffi::c_int = 32 as ::core::ffi::c_int; +pub const OBS_CLS_CLASSIFIER_SIZE: ::core::ffi::c_int = 65 as ::core::ffi::c_int; +pub const OBS_CLS_MODEL_VERSION_SIZE: ::core::ffi::c_int = 65 as ::core::ffi::c_int; +pub const OBS_CLS_LABEL_SIZE: ::core::ffi::c_int = 129 as ::core::ffi::c_int; +pub const OBS_CLS_TAXONOMY_SIZE: ::core::ffi::c_int = 65 as ::core::ffi::c_int; +pub const OBS_CLS_MAX_PER_OBSERVATION: ::core::ffi::c_int = 8 as ::core::ffi::c_int; +pub const OBS_CLS_CONFIDENCE_EPSILON: ::core::ffi::c_double = 1e-6f64; +pub const OBS_DETECTION_LINK_WINDOW_SECONDS: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +static mut KIND_NAMES: [*const ::core::ffi::c_char; 2] = [ + b"automated\0".as_ptr() as *const ::core::ffi::c_char, + b"owner_confirmed\0".as_ptr() as *const ::core::ffi::c_char, +]; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_classification_kind_name( + mut kind: observation_classification_kind_t, +) -> *const ::core::ffi::c_char { + return if kind.0 == observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED.0 + || kind.0 == observation_classification_kind_t::OBS_CLS_KIND_OWNER_CONFIRMED.0 + { + KIND_NAMES[kind.0 as usize] + } else { + b"automated\0".as_ptr() as *const ::core::ffi::c_char + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_classification_kind_parse( + mut name: *const ::core::ffi::c_char, + mut kind: *mut observation_classification_kind_t, +) -> ::core::ffi::c_int { + if name.is_null() || kind.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < 2 as ::core::ffi::c_int { + if strcmp(name, KIND_NAMES[i as usize]) == 0 as ::core::ffi::c_int { + *kind = observation_classification_kind_t(i as ::core::ffi::c_uint); + return 0 as ::core::ffi::c_int; + } + i += 1; + } + return -1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_classification_segment_is_valid( + mut segment: *const ::core::ffi::c_char, +) -> bool { + if segment.is_null() { + return r#false != 0; + } + let mut length: size_t = strnlen( + segment, + (OBS_CLS_SEGMENT_MAX + 1 as ::core::ffi::c_int) as size_t, + ); + if length == 0 as size_t || length > OBS_CLS_SEGMENT_MAX as size_t { + return r#false != 0; + } + let mut i: size_t = 0 as size_t; + while i < length { + let mut c: ::core::ffi::c_uchar = *segment.offset(i as isize) as ::core::ffi::c_uchar; + let mut alnum: bool = c as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'z' as ::core::ffi::c_int + || c as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= '9' as ::core::ffi::c_int; + if if i == 0 as size_t { + !alnum as ::core::ffi::c_int + } else { + !(alnum as ::core::ffi::c_int != 0 + || c as ::core::ffi::c_int == '_' as ::core::ffi::c_int + || c as ::core::ffi::c_int == '.' as ::core::ffi::c_int + || c as ::core::ffi::c_int == '-' as ::core::ffi::c_int) + as ::core::ffi::c_int + } != 0 + { + return r#false != 0; + } + i = i.wrapping_add(1); + } + return r#true != 0; +} +unsafe extern "C" fn empty_text(mut value: *const ::core::ffi::c_char) -> bool { + return value.is_null() || *value.offset(0isize) == 0; +} +unsafe extern "C" fn optional_len_ok( + mut value: *const ::core::ffi::c_char, + mut max: size_t, +) -> bool { + return empty_text(value) as ::core::ffi::c_int != 0 + || strnlen(value, max.wrapping_add(1 as size_t)) <= max; +} +unsafe extern "C" fn required_len_ok( + mut value: *const ::core::ffi::c_char, + mut max: size_t, +) -> bool { + if empty_text(value) { + return r#false != 0; + } + return strnlen(value, max.wrapping_add(1 as size_t)) <= max; +} +unsafe extern "C" fn valid_input(mut r#in: *const observation_classification_input_t) -> bool { + return !r#in.is_null() + && observation_classification_segment_is_valid((*r#in).writer_namespace) + as ::core::ffi::c_int + != 0 + && observation_classification_segment_is_valid((*r#in).name) as ::core::ffi::c_int != 0 + && (*r#in).writer_user_id > 0 as int64_t + && required_len_ok( + (*r#in).classifier, + (OBS_CLS_CLASSIFIER_SIZE - 1 as ::core::ffi::c_int) as size_t, + ) as ::core::ffi::c_int + != 0 + && optional_len_ok( + (*r#in).model_version, + (OBS_CLS_MODEL_VERSION_SIZE - 1 as ::core::ffi::c_int) as size_t, + ) as ::core::ffi::c_int + != 0 + && required_len_ok( + (*r#in).label, + (OBS_CLS_LABEL_SIZE - 1 as ::core::ffi::c_int) as size_t, + ) as ::core::ffi::c_int + != 0 + && optional_len_ok( + (*r#in).taxonomy_id, + (OBS_CLS_TAXONOMY_SIZE - 1 as ::core::ffi::c_int) as size_t, + ) as ::core::ffi::c_int + != 0 + && (*r#in).confidence.is_finite() as ::core::ffi::c_int != 0 + && (*r#in).confidence >= 0.0f64 + && (*r#in).confidence <= 1.0f64 + && (*r#in).classified_at_ms >= 0 as int64_t + && lightnvr_uuid_is_valid((*r#in).input_asset_uuid) as ::core::ffi::c_int != 0 + && (*r#in).input_revision >= 1 as ::core::ffi::c_int + && ((*r#in).kind.0 == observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED.0 + || (*r#in).kind.0 + == observation_classification_kind_t::OBS_CLS_KIND_OWNER_CONFIRMED.0) + && (*r#in).expected_revision >= 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn current_time_ms() -> int64_t { + let mut now: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + if clock_gettime(CLOCK_REALTIME, &raw mut now) != 0 as ::core::ffi::c_int { + return time(::core::ptr::null_mut::()) as int64_t * 1000 as int64_t; + } + return now.tv_sec as int64_t * 1000 as int64_t + now.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn copy_column( + mut stmt: *mut sqlite3_stmt, + mut column: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) { + let mut value: *const ::core::ffi::c_uchar = sqlite3_column_text(stmt, column); + safe_strcpy( + out, + if !value.is_null() { + value as *const ::core::ffi::c_char + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + size, + 0 as size_t, + ); +} +unsafe extern "C" fn bind_text_or_null( + mut stmt: *mut sqlite3_stmt, + mut index: ::core::ffi::c_int, + mut value: *const ::core::ffi::c_char, +) { + if !empty_text(value) { + sqlite3_bind_text( + stmt, + index, + value, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + } else { + sqlite3_bind_null(stmt, index); + }; +} +unsafe extern "C" fn same_optional( + mut stored: *const ::core::ffi::c_char, + mut requested: *const ::core::ffi::c_char, +) -> bool { + return strcmp( + stored, + if !requested.is_null() { + requested + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + ) == 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn read_annotation( + mut stmt: *mut sqlite3_stmt, + mut out: *mut observation_classification_t, +) { + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + copy_column( + stmt, + 0 as ::core::ffi::c_int, + &raw mut (*out).key as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 66]>(), + ); + copy_column( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*out).writer_namespace as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + ); + (*out).writer_user_id = sqlite3_column_int64(stmt, 2 as ::core::ffi::c_int) as int64_t; + copy_column( + stmt, + 3 as ::core::ffi::c_int, + &raw mut (*out).classifier as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + copy_column( + stmt, + 4 as ::core::ffi::c_int, + &raw mut (*out).model_version as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + copy_column( + stmt, + 5 as ::core::ffi::c_int, + &raw mut (*out).label as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 129]>(), + ); + copy_column( + stmt, + 6 as ::core::ffi::c_int, + &raw mut (*out).taxonomy_id as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + (*out).confidence = sqlite3_column_double(stmt, 7 as ::core::ffi::c_int); + (*out).classified_at_ms = sqlite3_column_int64(stmt, 8 as ::core::ffi::c_int) as int64_t; + copy_column( + stmt, + 9 as ::core::ffi::c_int, + &raw mut (*out).input_asset_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*out).input_revision = sqlite3_column_int(stmt, 10 as ::core::ffi::c_int); + let mut kind: [::core::ffi::c_char; 24] = [0; 24]; + copy_column( + stmt, + 11 as ::core::ffi::c_int, + &raw mut kind as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + ); + if observation_classification_kind_parse( + &raw mut kind as *mut ::core::ffi::c_char, + &raw mut (*out).kind, + ) != 0 as ::core::ffi::c_int + { + (*out).kind = observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED; + } + (*out).annotation_revision = sqlite3_column_int(stmt, 12 as ::core::ffi::c_int); + (*out).created_at_ms = sqlite3_column_int64(stmt, 13 as ::core::ffi::c_int) as int64_t; + (*out).updated_at_ms = sqlite3_column_int64(stmt, 14 as ::core::ffi::c_int) as int64_t; +} +unsafe extern "C" fn load_annotation( + mut db: *mut sqlite3, + mut observation_uuid: *const ::core::ffi::c_char, + mut key: *const ::core::ffi::c_char, + mut out: *mut observation_classification_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut found: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + b"SELECT key, writer_namespace, writer_user_id, classifier, model_version, label, taxonomy_id, confidence, classified_at_ms, input_asset_uuid, input_revision, kind, annotation_revision, created_at_ms, updated_at_ms FROM observation_classifications WHERE observation_uuid = ? AND key = ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 2 as ::core::ffi::c_int, + key, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + read_annotation(stmt, out); + found = 1 as ::core::ffi::c_int; + } else if step == SQLITE_DONE { + found = 0 as ::core::ffi::c_int; + } + } + if found < 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to read classification: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + return found; +} +unsafe extern "C" fn same_payload( + mut stored: *const observation_classification_t, + mut r#in: *const observation_classification_input_t, +) -> bool { + return strcmp( + &raw const (*stored).classifier as *const ::core::ffi::c_char, + (*r#in).classifier, + ) == 0 as ::core::ffi::c_int + && same_optional( + &raw const (*stored).model_version as *const ::core::ffi::c_char, + (*r#in).model_version, + ) as ::core::ffi::c_int + != 0 + && strcmp( + &raw const (*stored).label as *const ::core::ffi::c_char, + (*r#in).label, + ) == 0 as ::core::ffi::c_int + && same_optional( + &raw const (*stored).taxonomy_id as *const ::core::ffi::c_char, + (*r#in).taxonomy_id, + ) as ::core::ffi::c_int + != 0 + && fabs((*stored).confidence - (*r#in).confidence) <= OBS_CLS_CONFIDENCE_EPSILON + && (*stored).classified_at_ms == (*r#in).classified_at_ms + && strcasecmp( + &raw const (*stored).input_asset_uuid as *const ::core::ffi::c_char, + (*r#in).input_asset_uuid, + ) == 0 as ::core::ffi::c_int + && (*stored).input_revision == (*r#in).input_revision + && (*stored).kind.0 == (*r#in).kind.0; +} +unsafe extern "C" fn load_observation( + mut db: *mut sqlite3, + mut observation_uuid: *const ::core::ffi::c_char, + mut out: *mut observation_state_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT o.revision, a.uuid, a.state FROM object_observations o JOIN observation_assets a ON a.uuid = o.asset_uuid WHERE o.uuid = ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + (*out).revision = sqlite3_column_int(stmt, 0 as ::core::ffi::c_int); + copy_column( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*out).asset_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + let mut state: [::core::ffi::c_char; 16] = [0; 16]; + copy_column( + stmt, + 2 as ::core::ffi::c_int, + &raw mut state as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + if observation_asset_state_parse( + &raw mut state as *mut ::core::ffi::c_char, + &raw mut (*out).state, + ) != 0 as ::core::ffi::c_int + { + (*out).state = observation_asset_state_t::OBS_ASSET_UNAVAILABLE; + } + rc = C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0; + } else if step == SQLITE_DONE { + rc = C2Rust_Unnamed_0::DB_OBS_CLS_NOT_FOUND.0; + } + } + if rc == C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to read observation: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn count_keys( + mut db: *mut sqlite3, + mut observation_uuid: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut count: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + if sqlite3_prepare_v2( + db, + b"SELECT count(*) FROM observation_classifications WHERE observation_uuid = ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) == SQLITE_ROW { + count = sqlite3_column_int(stmt, 0 as ::core::ffi::c_int); + } + } + sqlite3_finalize(stmt); + return count; +} +unsafe extern "C" fn store_annotation( + mut db: *mut sqlite3, + mut observation_uuid: *const ::core::ffi::c_char, + mut key: *const ::core::ffi::c_char, + mut r#in: *const observation_classification_input_t, + mut asset_uuid: *const ::core::ffi::c_char, + mut existing: *const observation_classification_t, + mut now_ms: int64_t, +) -> ::core::ffi::c_int { + static mut INSERT_SQL: *const ::core::ffi::c_char = b"INSERT INTO observation_classifications (classifier, model_version, label, taxonomy_id, confidence, classified_at_ms, input_asset_uuid, input_revision, kind, updated_at_ms, observation_uuid, key, writer_namespace, writer_user_id, annotation_revision, created_at_ms) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, 1, ?10);\0" + .as_ptr() as *const ::core::ffi::c_char; + static mut UPDATE_SQL: *const ::core::ffi::c_char = b"UPDATE observation_classifications SET classifier = ?1, model_version = ?2, label = ?3, taxonomy_id = ?4, confidence = ?5, classified_at_ms = ?6, input_asset_uuid = ?7, input_revision = ?8, kind = ?9, updated_at_ms = ?10, annotation_revision = annotation_revision + 1 WHERE observation_uuid = ?11 AND key = ?12 AND writer_user_id = ?14 AND annotation_revision = ?15;\0" + .as_ptr() as *const ::core::ffi::c_char; + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + if sqlite3_prepare_v2( + db, + if !existing.is_null() { + UPDATE_SQL + } else { + INSERT_SQL + }, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + (*r#in).classifier, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + bind_text_or_null(stmt, 2 as ::core::ffi::c_int, (*r#in).model_version); + sqlite3_bind_text( + stmt, + 3 as ::core::ffi::c_int, + (*r#in).label, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + bind_text_or_null(stmt, 4 as ::core::ffi::c_int, (*r#in).taxonomy_id); + sqlite3_bind_double(stmt, 5 as ::core::ffi::c_int, (*r#in).confidence); + sqlite3_bind_int64( + stmt, + 6 as ::core::ffi::c_int, + (*r#in).classified_at_ms as sqlite3_int64, + ); + sqlite3_bind_text( + stmt, + 7 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int(stmt, 8 as ::core::ffi::c_int, (*r#in).input_revision); + sqlite3_bind_text( + stmt, + 9 as ::core::ffi::c_int, + observation_classification_kind_name((*r#in).kind), + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + sqlite3_bind_int64(stmt, 10 as ::core::ffi::c_int, now_ms as sqlite3_int64); + sqlite3_bind_text( + stmt, + 11 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 12 as ::core::ffi::c_int, + key, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if !existing.is_null() { + sqlite3_bind_int64( + stmt, + 14 as ::core::ffi::c_int, + (*existing).writer_user_id as sqlite3_int64, + ); + sqlite3_bind_int( + stmt, + 15 as ::core::ffi::c_int, + (*existing).annotation_revision, + ); + } else { + sqlite3_bind_text( + stmt, + 13 as ::core::ffi::c_int, + (*r#in).writer_namespace, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int64( + stmt, + 14 as ::core::ffi::c_int, + (*r#in).writer_user_id as sqlite3_int64, + ); + } + if sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(db) == 1 as ::core::ffi::c_int { + rc = if !existing.is_null() { + C2Rust_Unnamed_0::DB_OBS_CLS_UPDATED.0 + } else { + C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 + }; + } + } + if rc == C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to store classification: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn tx_begin(mut tx: *mut cls_tx_t) -> ::core::ffi::c_int { + (*tx).mutex = get_db_mutex(); + if (*tx).mutex.is_null() { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock((*tx).mutex); + (*tx).db = get_db_handle(); + if (*tx).db.is_null() { + pthread_mutex_unlock((*tx).mutex); + return -1 as ::core::ffi::c_int; + } + (*tx).connection_mutex = sqlite3_db_mutex((*tx).db); + sqlite3_mutex_enter((*tx).connection_mutex); + if sqlite3_exec( + (*tx).db, + b"BEGIN IMMEDIATE;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ) != SQLITE_OK + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to begin classification transaction: %s\0".as_ptr() + as *const ::core::ffi::c_char, + sqlite3_errmsg((*tx).db), + ); + sqlite3_mutex_leave((*tx).connection_mutex); + pthread_mutex_unlock((*tx).mutex); + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn tx_end(mut tx: *mut cls_tx_t, mut commit: bool) -> bool { + let mut ok: bool = r#true != 0; + if commit as ::core::ffi::c_int != 0 + && sqlite3_exec( + (*tx).db, + b"COMMIT;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ) != SQLITE_OK + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to commit classification transaction: %s\0".as_ptr() + as *const ::core::ffi::c_char, + sqlite3_errmsg((*tx).db), + ); + ok = r#false != 0; + } + if !commit || !ok { + sqlite3_exec( + (*tx).db, + b"ROLLBACK;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ); + } + sqlite3_mutex_leave((*tx).connection_mutex); + pthread_mutex_unlock((*tx).mutex); + return ok; +} +unsafe extern "C" fn put_locked( + mut db: *mut sqlite3, + mut source: *const ::core::ffi::c_char, + mut observation_uuid: *const ::core::ffi::c_char, + mut key: *const ::core::ffi::c_char, + mut r#in: *const observation_classification_input_t, + mut result: *mut observation_classification_result_t, +) -> ::core::ffi::c_int { + let mut obs: observation_state_t = observation_state_t { + revision: 0, + asset_uuid: [0; 37], + state: observation_asset_state_t::OBS_ASSET_PENDING, + }; + let mut rc: ::core::ffi::c_int = load_observation(db, observation_uuid, &raw mut obs); + if rc != C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 { + return rc; + } + (*result).observation_revision = obs.revision; + safe_strcpy( + &raw mut (*result).observation_asset_uuid as *mut ::core::ffi::c_char, + &raw mut obs.asset_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + (*result).evidence_state = obs.state; + let mut existing: observation_classification_t = observation_classification_t { + key: [0; 66], + writer_namespace: [0; 33], + writer_user_id: 0, + classifier: [0; 65], + model_version: [0; 65], + label: [0; 129], + taxonomy_id: [0; 65], + confidence: 0., + classified_at_ms: 0, + input_asset_uuid: [0; 37], + input_revision: 0, + kind: observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED, + annotation_revision: 0, + created_at_ms: 0, + updated_at_ms: 0, + }; + let mut found: ::core::ffi::c_int = + load_annotation(db, observation_uuid, key, &raw mut existing); + if found < 0 as ::core::ffi::c_int { + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + if found != 0 + && (existing.writer_user_id != (*r#in).writer_user_id + || strcmp( + &raw mut existing.writer_namespace as *mut ::core::ffi::c_char, + (*r#in).writer_namespace, + ) != 0 as ::core::ffi::c_int) + { + return C2Rust_Unnamed_0::DB_OBS_CLS_WRITER_MISMATCH.0; + } + if strcasecmp( + (*r#in).input_asset_uuid, + &raw mut obs.asset_uuid as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + || (*r#in).input_revision > obs.revision + || obs.state.0 != observation_asset_state_t::OBS_ASSET_READY.0 + && obs.state.0 != observation_asset_state_t::OBS_ASSET_EXPIRED.0 + { + return C2Rust_Unnamed_0::DB_OBS_CLS_EVIDENCE_MISMATCH.0; + } + if found != 0 { + (*result).has_annotation = r#true != 0; + (*result).annotation = existing; + if same_payload(&raw mut existing, r#in) { + return C2Rust_Unnamed_0::DB_OBS_CLS_NOOP.0; + } + if existing.kind.0 == observation_classification_kind_t::OBS_CLS_KIND_OWNER_CONFIRMED.0 + && (*r#in).kind.0 == observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED.0 + { + return C2Rust_Unnamed_0::DB_OBS_CLS_OWNER_LOCKED.0; + } + if (*r#in).expected_revision != existing.annotation_revision { + return C2Rust_Unnamed_0::DB_OBS_CLS_REVISION_CONFLICT.0; + } + } else { + if (*r#in).expected_revision != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed_0::DB_OBS_CLS_REVISION_CONFLICT.0; + } + let mut keys: ::core::ffi::c_int = count_keys(db, observation_uuid); + if keys < 0 as ::core::ffi::c_int { + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + if keys >= OBS_CLS_MAX_PER_OBSERVATION { + return C2Rust_Unnamed_0::DB_OBS_CLS_LIMIT.0; + } + } + let mut now_ms: int64_t = current_time_ms(); + let mut stored: ::core::ffi::c_int = store_annotation( + db, + observation_uuid, + key, + r#in, + &raw mut obs.asset_uuid as *mut ::core::ffi::c_char, + if found != 0 { + &raw mut existing + } else { + ::core::ptr::null_mut::() + }, + now_ms, + ); + if stored != C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 + && stored != C2Rust_Unnamed_0::DB_OBS_CLS_UPDATED.0 + { + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + let mut seq: int64_t = 0 as int64_t; + let mut revision: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if db_observation_bump_revision_locked( + db, + source, + observation_uuid, + observation_change_cause_t::OBS_CAUSE_CLASSIFICATION, + now_ms, + &raw mut seq, + &raw mut revision, + ) != C2Rust_Unnamed::DB_OBS_OK.0 + { + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + let mut written: observation_classification_t = observation_classification_t { + key: [0; 66], + writer_namespace: [0; 33], + writer_user_id: 0, + classifier: [0; 65], + model_version: [0; 65], + label: [0; 129], + taxonomy_id: [0; 65], + confidence: 0., + classified_at_ms: 0, + input_asset_uuid: [0; 37], + input_revision: 0, + kind: observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED, + annotation_revision: 0, + created_at_ms: 0, + updated_at_ms: 0, + }; + if load_annotation(db, observation_uuid, key, &raw mut written) != 1 as ::core::ffi::c_int { + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + (*result).has_annotation = r#true != 0; + (*result).annotation = written; + (*result).observation_revision = revision; + (*result).change_seq = seq; + return stored; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_classification_put( + mut observation_uuid: *const ::core::ffi::c_char, + mut input: *const observation_classification_input_t, + mut result: *mut observation_classification_result_t, +) -> ::core::ffi::c_int { + if result.is_null() { + return C2Rust_Unnamed_0::DB_OBS_CLS_INVALID.0; + } + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*result).limit = OBS_CLS_MAX_PER_OBSERVATION; + if !lightnvr_uuid_is_valid(observation_uuid) || !valid_input(input) { + return C2Rust_Unnamed_0::DB_OBS_CLS_INVALID.0; + } + let mut key: [::core::ffi::c_char; 66] = [0; 66]; + let mut written: ::core::ffi::c_int = snprintf( + &raw mut key as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 66]>(), + b"%s/%s\0".as_ptr() as *const ::core::ffi::c_char, + (*input).writer_namespace, + (*input).name, + ); + if written < 0 as ::core::ffi::c_int + || written as size_t >= ::core::mem::size_of::<[::core::ffi::c_char; 66]>() + { + return C2Rust_Unnamed_0::DB_OBS_CLS_INVALID.0; + } + let mut source: [::core::ffi::c_char; 160] = [0; 160]; + if event_identity_get_source( + &raw mut source as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 160]>(), + ) != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Classification write needs the event installation identity\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + let mut tx: cls_tx_t = cls_tx_t { + mutex: ::core::ptr::null_mut::(), + db: ::core::ptr::null_mut::(), + connection_mutex: ::core::ptr::null_mut::(), + }; + if tx_begin(&raw mut tx) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + let mut rc: ::core::ffi::c_int = put_locked( + tx.db, + &raw mut source as *mut ::core::ffi::c_char, + observation_uuid, + &raw mut key as *mut ::core::ffi::c_char, + input, + result, + ); + let mut changed: bool = rc == C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 + || rc == C2Rust_Unnamed_0::DB_OBS_CLS_UPDATED.0; + if !tx_end(&raw mut tx, changed) { + rc = C2Rust_Unnamed_0::DB_OBS_CLS_ERROR.0; + } + if rc == C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 || rc == C2Rust_Unnamed_0::DB_OBS_CLS_UPDATED.0 + { + observation_relay_notify(); + } else if rc < 0 as ::core::ffi::c_int && rc != C2Rust_Unnamed_0::DB_OBS_CLS_NOT_FOUND.0 { + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*result).limit = OBS_CLS_MAX_PER_OBSERVATION; + } else if rc == C2Rust_Unnamed_0::DB_OBS_CLS_WRITER_MISMATCH.0 { + (*result).has_annotation = r#false != 0; + memset( + &raw mut (*result).annotation as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + } + return rc; +} +pub const CLS_LIST_SQL: [::core::ffi::c_char; 311] = unsafe { + ::core::mem::transmute::< + [u8; 311], + [::core::ffi::c_char; 311], + >( + *b"SELECT key, writer_namespace, writer_user_id, classifier, model_version, label, taxonomy_id, confidence, classified_at_ms, input_asset_uuid, input_revision, kind, annotation_revision, created_at_ms, updated_at_ms FROM observation_classifications WHERE observation_uuid = ? ORDER BY updated_at_ms DESC, key ASC;\0", + ) +}; +unsafe extern "C" fn list_with( + mut stmt: *mut sqlite3_stmt, + mut observation_uuid: *const ::core::ffi::c_char, + mut out: *mut observation_classification_set_t, +) -> ::core::ffi::c_int { + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if step != SQLITE_ROW { + break; + } + let mut row: observation_classification_t = observation_classification_t { + key: [0; 66], + writer_namespace: [0; 33], + writer_user_id: 0, + classifier: [0; 65], + model_version: [0; 65], + label: [0; 129], + taxonomy_id: [0; 65], + confidence: 0., + classified_at_ms: 0, + input_asset_uuid: [0; 37], + input_revision: 0, + kind: observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED, + annotation_revision: 0, + created_at_ms: 0, + updated_at_ms: 0, + }; + read_annotation(stmt, &raw mut row); + if row.kind.0 == observation_classification_kind_t::OBS_CLS_KIND_OWNER_CONFIRMED.0 { + if (*out).owner_confirmed_count < OBS_CLS_MAX_PER_OBSERVATION { + let c2rust_fresh0 = (*out).owner_confirmed_count; + (*out).owner_confirmed_count += 1; + (*out).owner_confirmed[c2rust_fresh0 as usize] = row; + } + } else if (*out).automated_count < OBS_CLS_MAX_PER_OBSERVATION { + let c2rust_fresh1 = (*out).automated_count; + (*out).automated_count += 1; + (*out).automated[c2rust_fresh1 as usize] = row; + } + } + if step == SQLITE_DONE { + return C2Rust_Unnamed::DB_OBS_OK.0; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + return C2Rust_Unnamed::DB_OBS_ERROR.0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_classifications_list( + mut observation_uuid: *const ::core::ffi::c_char, + mut out: *mut observation_classification_set_t, +) -> ::core::ffi::c_int { + if out.is_null() || !lightnvr_uuid_is_valid(observation_uuid) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + CLS_LIST_SQL.as_ptr(), + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + rc = list_with(stmt, observation_uuid, out); + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to list classifications: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_classifications_list_many( + mut observation_uuids: *const *const ::core::ffi::c_char, + mut count: size_t, + mut visit: observation_classification_visit_fn, + mut context: *mut ::core::ffi::c_void, +) -> ::core::ffi::c_int { + if visit.is_none() || count > 0 as size_t && observation_uuids.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut i: size_t = 0 as size_t; + while i < count { + if !lightnvr_uuid_is_valid(*observation_uuids.offset(i as isize)) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + i = i.wrapping_add(1); + } + if count == 0 as size_t { + return C2Rust_Unnamed::DB_OBS_OK.0; + } + let mut set: *mut observation_classification_set_t = + malloc(::core::mem::size_of::()) + as *mut observation_classification_set_t; + if set.is_null() { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + free(set as *mut ::core::ffi::c_void); + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + CLS_LIST_SQL.as_ptr(), + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + let mut i_0: size_t = 0 as size_t; + while rc == C2Rust_Unnamed::DB_OBS_OK.0 && i_0 < count { + if list_with(stmt, *observation_uuids.offset(i_0 as isize), set) + != C2Rust_Unnamed::DB_OBS_OK.0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to list classifications: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + break; + } else { + let mut visited: ::core::ffi::c_int = + visit.expect("non-null function pointer")(i_0, set, context); + if visited != 0 as ::core::ffi::c_int { + rc = visited; + } + i_0 = i_0.wrapping_add(1); + } + } + } else { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to list classifications: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + free(set as *mut ::core::ffi::c_void); + return rc; +} +pub const LINK_MATCH_SQL: [::core::ffi::c_char; 201] = unsafe { + ::core::mem::transmute::< + [u8; 201], + [::core::ffi::c_char; 201], + >( + *b"SELECT uuid FROM object_observations WHERE camera_uuid = ?1 AND observed_at_ms BETWEEN ?2 AND ?3 AND label = ?4 COLLATE NOCASE ORDER BY abs((observed_at_ms / 1000) - ?5), observed_at_ms, uuid LIMIT 1;\0", + ) +}; +unsafe extern "C" fn link_one( + mut r#match: *mut sqlite3_stmt, + mut list: *mut sqlite3_stmt, + mut camera_uuid: *const ::core::ffi::c_char, + mut r#ref: *const observation_detection_ref_t, + mut set: *mut observation_classification_set_t, + mut out: *mut observation_detection_link_t, +) -> ::core::ffi::c_int { + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + if (*r#ref).label.is_null() || *(*r#ref).label.offset(0isize) == 0 { + return C2Rust_Unnamed::DB_OBS_OK.0; + } + if (*r#ref).timestamp < 0 as int64_t + || (*r#ref).timestamp > INT64_MAX as int64_t / 2000 as int64_t + { + return C2Rust_Unnamed::DB_OBS_OK.0; + } + let mut window: int64_t = OBS_DETECTION_LINK_WINDOW_SECONDS as int64_t; + sqlite3_reset(r#match); + sqlite3_clear_bindings(r#match); + sqlite3_bind_text( + r#match, + 1 as ::core::ffi::c_int, + camera_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int64( + r#match, + 2 as ::core::ffi::c_int, + (((*r#ref).timestamp - window) * 1000 as int64_t) as sqlite3_int64, + ); + sqlite3_bind_int64( + r#match, + 3 as ::core::ffi::c_int, + (((*r#ref).timestamp + window) * 1000 as int64_t + 999 as int64_t) as sqlite3_int64, + ); + sqlite3_bind_text( + r#match, + 4 as ::core::ffi::c_int, + (*r#ref).label, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int64( + r#match, + 5 as ::core::ffi::c_int, + (*r#ref).timestamp as sqlite3_int64, + ); + let mut step: ::core::ffi::c_int = sqlite3_step(r#match); + if step == SQLITE_DONE { + return C2Rust_Unnamed::DB_OBS_OK.0; + } + if step != SQLITE_ROW { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + copy_column( + r#match, + 0 as ::core::ffi::c_int, + &raw mut (*out).observation_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + if !lightnvr_uuid_is_valid(&raw mut (*out).observation_uuid as *mut ::core::ffi::c_char) { + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + return C2Rust_Unnamed::DB_OBS_OK.0; + } + (*out).linked = r#true != 0; + if list_with( + list, + &raw mut (*out).observation_uuid as *mut ::core::ffi::c_char, + set, + ) != C2Rust_Unnamed::DB_OBS_OK.0 + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + if (*set).owner_confirmed_count > 0 as ::core::ffi::c_int { + (*out).owner_confirmed = r#true != 0; + (*out).has_classification = r#true != 0; + (*out).classification = (*set).owner_confirmed[0usize]; + } else if (*set).automated_count > 0 as ::core::ffi::c_int { + (*out).has_classification = r#true != 0; + (*out).classification = (*set).automated[0usize]; + } + return C2Rust_Unnamed::DB_OBS_OK.0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_link_detections( + mut camera_uuid: *const ::core::ffi::c_char, + mut refs: *const observation_detection_ref_t, + mut count: size_t, + mut out: *mut observation_detection_link_t, +) -> ::core::ffi::c_int { + if count == 0 as size_t { + return C2Rust_Unnamed::DB_OBS_OK.0; + } + if refs.is_null() || out.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + count.wrapping_mul(::core::mem::size_of::()), + ); + if !lightnvr_uuid_is_valid(camera_uuid) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut set: *mut observation_classification_set_t = + malloc(::core::mem::size_of::()) + as *mut observation_classification_set_t; + if set.is_null() { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if db_open_readonly_connection(&raw mut db) != 0 as ::core::ffi::c_int { + free(set as *mut ::core::ffi::c_void); + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut r#match: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut list: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + LINK_MATCH_SQL.as_ptr(), + -1 as ::core::ffi::c_int, + &raw mut r#match, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + && sqlite3_prepare_v2( + db, + CLS_LIST_SQL.as_ptr(), + -1 as ::core::ffi::c_int, + &raw mut list, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + let mut i: size_t = 0 as size_t; + while rc == C2Rust_Unnamed::DB_OBS_OK.0 && i < count { + rc = link_one( + r#match, + list, + camera_uuid, + refs.offset(i as isize), + set, + out.offset(i as isize), + ); + i = i.wrapping_add(1); + } + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to link detections to observations: %s\0".as_ptr() + as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + count.wrapping_mul(::core::mem::size_of::()), + ); + } + sqlite3_finalize(r#match); + sqlite3_finalize(list); + db_close_readonly_connection(db); + free(set as *mut ::core::ffi::c_void); + return rc; +} diff --git a/rust/generated/src__database__db_observations.rs b/rust/generated/src__database__db_observations.rs new file mode 100644 index 000000000..4bcbc1343 --- /dev/null +++ b/rust/generated/src__database__db_observations.rs @@ -0,0 +1,3855 @@ +use ::libc; +unsafe extern "C" { + pub type sqlite3; + pub type sqlite3_mutex; + pub type sqlite3_stmt; + unsafe fn sqlite3_exec( + _: *mut sqlite3, + sql: *const ::core::ffi::c_char, + callback: Option< + unsafe extern "C" fn( + *mut ::core::ffi::c_void, + ::core::ffi::c_int, + *mut *mut ::core::ffi::c_char, + *mut *mut ::core::ffi::c_char, + ) -> ::core::ffi::c_int, + >, + _: *mut ::core::ffi::c_void, + errmsg: *mut *mut ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_last_insert_rowid(_: *mut sqlite3) -> sqlite3_int64; + unsafe fn sqlite3_changes(_: *mut sqlite3) -> ::core::ffi::c_int; + unsafe fn sqlite3_errmsg(_: *mut sqlite3) -> *const ::core::ffi::c_char; + unsafe fn sqlite3_prepare_v2( + db: *mut sqlite3, + zSql: *const ::core::ffi::c_char, + nByte: ::core::ffi::c_int, + ppStmt: *mut *mut sqlite3_stmt, + pzTail: *mut *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_double( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: ::core::ffi::c_double, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_int( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_int64( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: sqlite3_int64, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_null(_: *mut sqlite3_stmt, _: ::core::ffi::c_int) -> ::core::ffi::c_int; + unsafe fn sqlite3_bind_text( + _: *mut sqlite3_stmt, + _: ::core::ffi::c_int, + _: *const ::core::ffi::c_char, + _: ::core::ffi::c_int, + _: Option ()>, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_clear_bindings(_: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_step(_: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_column_double( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> ::core::ffi::c_double; + unsafe fn sqlite3_column_int( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_column_int64(_: *mut sqlite3_stmt, iCol: ::core::ffi::c_int) + -> sqlite3_int64; + unsafe fn sqlite3_column_text( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> *const ::core::ffi::c_uchar; + unsafe fn sqlite3_column_type( + _: *mut sqlite3_stmt, + iCol: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn sqlite3_finalize(pStmt: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_reset(pStmt: *mut sqlite3_stmt) -> ::core::ffi::c_int; + unsafe fn sqlite3_mutex_enter(_: *mut sqlite3_mutex); + unsafe fn sqlite3_mutex_leave(_: *mut sqlite3_mutex); + unsafe fn sqlite3_db_mutex(_: *mut sqlite3) -> *mut sqlite3_mutex; + unsafe fn cJSON_ParseWithLength( + value: *const ::core::ffi::c_char, + buffer_length: size_t, + ) -> *mut cJSON; + unsafe fn cJSON_Delete(item: *mut cJSON); + unsafe fn cJSON_GetObjectItemCaseSensitive( + object: *const cJSON, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_IsNumber(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsString(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsObject(item: *const cJSON) -> cJSON_bool; + unsafe fn floor(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; + unsafe fn round(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; + unsafe fn time(__timer: *mut time_t) -> time_t; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_lock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn pthread_mutex_unlock(__mutex: *mut pthread_mutex_t) -> ::core::ffi::c_int; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn event_identity_get_source( + output: *mut ::core::ffi::c_char, + output_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn get_db_handle() -> *mut sqlite3; + unsafe fn get_db_mutex() -> *mut pthread_mutex_t; + unsafe fn db_open_readonly_connection(connection: *mut *mut sqlite3) -> ::core::ffi::c_int; + unsafe fn db_close_readonly_connection(connection: *mut sqlite3); + unsafe fn base64url_encode( + input: *const ::core::ffi::c_uchar, + input_len: size_t, + dst: *mut ::core::ffi::c_char, + dst_size: size_t, + out_len: *mut size_t, + ) -> ::core::ffi::c_int; + unsafe fn base64url_decode( + src: *const ::core::ffi::c_char, + src_len: size_t, + dst: *mut ::core::ffi::c_uchar, + dst_size: size_t, + out_len: *mut size_t, + ) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn lightnvr_uuid_generate_v4(output: *mut ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_generate_v5( + namespace_uuid: *const ::core::ffi::c_char, + name: *const ::core::ffi::c_char, + name_len: size_t, + output: *mut ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn memchr( + __s: *const ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; + unsafe fn strnlen(__string: *const ::core::ffi::c_char, __maxlen: size_t) -> size_t; + unsafe fn strcasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn realloc(__ptr: *mut ::core::ffi::c_void, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __time_t = ::core::ffi::c_long; +pub type __clockid_t = ::core::ffi::c_int; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_internal_list { + pub __prev: *mut __pthread_internal_list, + pub __next: *mut __pthread_internal_list, +} +pub type __pthread_list_t = __pthread_internal_list; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct __pthread_mutex_s { + pub __lock: ::core::ffi::c_int, + pub __count: ::core::ffi::c_uint, + pub __owner: ::core::ffi::c_int, + pub __nusers: ::core::ffi::c_uint, + pub __kind: ::core::ffi::c_int, + pub __spins: ::core::ffi::c_short, + pub __glibc_reserved: ::core::ffi::c_short, + pub __list: __pthread_list_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union pthread_mutex_t { + pub __data: __pthread_mutex_s, + pub __size: [::core::ffi::c_char; 40], + pub __align: ::core::ffi::c_long, +} +pub type sqlite_int64 = ::core::ffi::c_longlong; +pub type sqlite3_int64 = sqlite_int64; +pub type sqlite3_destructor_type = Option ()>; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_change_cause_t(pub ::core::ffi::c_uint); +impl observation_change_cause_t { + pub const OBS_CAUSE_CREATED: Self = Self(0); + pub const OBS_CAUSE_EVIDENCE: Self = Self(1); + pub const OBS_CAUSE_CLASSIFICATION: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_publication_state_t(pub ::core::ffi::c_uint); +impl observation_publication_state_t { + pub const OBS_PUBLICATION_PENDING: Self = Self(0); + pub const OBS_PUBLICATION_RELAYED: Self = Self(1); + pub const OBS_PUBLICATION_RELAYED_NO_ROUTE: Self = Self(2); + pub const OBS_PUBLICATION_EXPIRED: Self = Self(3); + pub const OBS_PUBLICATION_FAILED: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed_0 { + pub reason: *const ::core::ffi::c_char, + pub state: observation_asset_state_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +pub type cJSON_bool = ::core::ffi::c_int; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_object_input_t { + pub object_index: ::core::ffi::c_int, + pub label: [::core::ffi::c_char; 32], + pub confidence: ::core::ffi::c_double, + pub has_box: bool, + pub box_x: ::core::ffi::c_double, + pub box_y: ::core::ffi::c_double, + pub box_width: ::core::ffi::c_double, + pub box_height: ::core::ffi::c_double, + pub has_track: bool, + pub track_id: [::core::ffi::c_char; 65], + pub track_namespace: [::core::ffi::c_char; 65], + pub track_session: [::core::ffi::c_char; 37], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_batch_input_t { + pub batch_id: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub stream_name: [::core::ffi::c_char; 64], + pub observed_at_ms: int64_t, + pub engine_key: [::core::ffi::c_char; 65], + pub engine_type: [::core::ffi::c_char; 16], + pub model: [::core::ffi::c_char; 129], + pub model_version: [::core::ffi::c_char; 65], + pub recording_id: int64_t, + pub object_count: ::core::ffi::c_int, + pub objects: [observation_object_input_t; 20], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_input_t { + pub asset_uuid: [::core::ffi::c_char; 37], + pub state: observation_asset_state_t, + pub reason: [::core::ffi::c_char; 33], + pub origin: [::core::ffi::c_char; 24], + pub media_type: [::core::ffi::c_char; 24], + pub captured_at_ms: int64_t, + pub transform_json: [::core::ffi::c_char; 1024], + pub expires_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_insert_result_t { + pub asset_uuid: [::core::ffi::c_char; 37], + pub object_count: ::core::ffi::c_int, + pub observation_uuids: [[::core::ffi::c_char; 37]; 20], + pub last_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct obs_tx_t { + pub mutex: *mut pthread_mutex_t, + pub db: *mut sqlite3, + pub connection_mutex: *mut sqlite3_mutex, +} +pub type db_observation_publication_hook_t = Option ()>; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_media_t { + pub path: *const ::core::ffi::c_char, + pub bytes: int64_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub expires_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_transition_result_t { + pub observations_bumped: ::core::ffi::c_int, + pub last_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_record_t { + pub uuid: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub state: observation_asset_state_t, + pub reason: [::core::ffi::c_char; 33], + pub origin: [::core::ffi::c_char; 24], + pub media_type: [::core::ffi::c_char; 24], + pub path: [::core::ffi::c_char; 512], + pub has_bytes: bool, + pub bytes: int64_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub captured_at_ms: int64_t, + pub transform_json: [::core::ffi::c_char; 1024], + pub is_protected: bool, + pub expires_at_ms: int64_t, + pub created_at_ms: int64_t, + pub updated_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_record_t { + pub uuid: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub stream_name_at_capture: [::core::ffi::c_char; 64], + pub batch_id: [::core::ffi::c_char; 37], + pub object_index: ::core::ffi::c_int, + pub observed_at_ms: int64_t, + pub created_at_ms: int64_t, + pub engine_key: [::core::ffi::c_char; 65], + pub engine_type: [::core::ffi::c_char; 16], + pub model: [::core::ffi::c_char; 129], + pub model_version: [::core::ffi::c_char; 65], + pub label: [::core::ffi::c_char; 32], + pub confidence: ::core::ffi::c_double, + pub has_box: bool, + pub box_x: ::core::ffi::c_double, + pub box_y: ::core::ffi::c_double, + pub box_width: ::core::ffi::c_double, + pub box_height: ::core::ffi::c_double, + pub has_track: bool, + pub track_id: [::core::ffi::c_char; 65], + pub track_namespace: [::core::ffi::c_char; 65], + pub track_session: [::core::ffi::c_char; 37], + pub recording_id: int64_t, + pub revision: ::core::ffi::c_int, + pub asset: observation_asset_record_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_list_query_t { + pub camera_uuids: *const [::core::ffi::c_char; 37], + pub camera_count: size_t, + pub all_cameras: bool, + pub labels: [[::core::ffi::c_char; 32]; 8], + pub label_count: size_t, + pub has_since: bool, + pub since_ms: int64_t, + pub has_until: bool, + pub until_ms: int64_t, + pub limit: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_page_t { + pub items: *mut observation_record_t, + pub count: size_t, + pub next_cursor: [::core::ffi::c_char; 257], + pub has_more: bool, + pub oldest_cursor: [::core::ffi::c_char; 257], + pub has_oldest_observed_at: bool, + pub oldest_observed_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_db_diagnostics_t { + pub meta: observation_meta_t, + pub has_oldest_observed_at: bool, + pub oldest_observed_at_ms: int64_t, + pub observations: int64_t, + pub publications_pending: int64_t, + pub publications_failed: int64_t, + pub publications_expired: int64_t, + pub oldest_pending_created_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_publication_t { + pub change_seq: int64_t, + pub message_id: [::core::ffi::c_char; 37], + pub attempts: ::core::ffi::c_int, + pub observation_uuid: [::core::ffi::c_char; 37], + pub revision: ::core::ffi::c_int, + pub cause: observation_change_cause_t, + pub evidence_state: observation_asset_state_t, + pub evidence_reason: [::core::ffi::c_char; 33], + pub change_created_at_ms: int64_t, + pub camera_uuid: [::core::ffi::c_char; 37], + pub label: [::core::ffi::c_char; 32], + pub observed_at_ms: int64_t, + pub asset_uuid: [::core::ffi::c_char; 37], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_prune_result_t { + pub changes_deleted: ::core::ffi::c_int, + pub observations_deleted: ::core::ffi::c_int, + pub assets_deleted: ::core::ffi::c_int, + pub oldest_seq: int64_t, +} +pub const SQLITE_OK: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const SQLITE_ROW: ::core::ffi::c_int = 100 as ::core::ffi::c_int; +pub const SQLITE_DONE: ::core::ffi::c_int = 101 as ::core::ffi::c_int; +pub const SQLITE_NULL: ::core::ffi::c_int = 5 as ::core::ffi::c_int; +pub const SQLITE_STATIC: sqlite3_destructor_type = None; +pub const MAX_PATH_LENGTH: ::core::ffi::c_int = 512 as ::core::ffi::c_int; +pub const OBS_UUID_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; +pub const OBS_MAX_BATCH: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const OBS_LABEL_SIZE: ::core::ffi::c_int = 32 as ::core::ffi::c_int; +pub const OBS_CURSOR_SIZE: ::core::ffi::c_int = 257 as ::core::ffi::c_int; +pub const OBS_LIST_DEFAULT_LIMIT: ::core::ffi::c_int = 100 as ::core::ffi::c_int; +pub const OBS_LIST_MAX_LIMIT: ::core::ffi::c_int = 500 as ::core::ffi::c_int; +pub const OBS_LIST_SCAN_CAP: ::core::ffi::c_int = 5000 as ::core::ffi::c_int; +pub const OBS_QUERY_MAX_LABELS: ::core::ffi::c_int = 8 as ::core::ffi::c_int; +pub const OBS_MESSAGE_ID_NAMESPACE: [::core::ffi::c_char; 37] = unsafe { + ::core::mem::transmute::<[u8; 37], [::core::ffi::c_char; 37]>( + *b"0bc01005-a431-522b-9124-a114f67d187a\0", + ) +}; +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const OBS_SOURCE_SIZE: ::core::ffi::c_int = 160 as ::core::ffi::c_int; +pub const OBS_PRUNE_MAX_BATCH: ::core::ffi::c_int = 10000 as ::core::ffi::c_int; +pub const OBS_MAX_SAFE_SEQ: ::core::ffi::c_longlong = 9007199254740991 as ::core::ffi::c_longlong; +static mut STATE_NAMES: [*const ::core::ffi::c_char; 4] = [ + b"pending\0".as_ptr() as *const ::core::ffi::c_char, + b"ready\0".as_ptr() as *const ::core::ffi::c_char, + b"unavailable\0".as_ptr() as *const ::core::ffi::c_char, + b"expired\0".as_ptr() as *const ::core::ffi::c_char, +]; +static mut CAUSE_NAMES: [*const ::core::ffi::c_char; 3] = [ + b"created\0".as_ptr() as *const ::core::ffi::c_char, + b"evidence\0".as_ptr() as *const ::core::ffi::c_char, + b"classification\0".as_ptr() as *const ::core::ffi::c_char, +]; +static mut PUBLICATION_NAMES: [*const ::core::ffi::c_char; 5] = [ + b"pending\0".as_ptr() as *const ::core::ffi::c_char, + b"relayed\0".as_ptr() as *const ::core::ffi::c_char, + b"relayed_no_route\0".as_ptr() as *const ::core::ffi::c_char, + b"expired\0".as_ptr() as *const ::core::ffi::c_char, + b"failed\0".as_ptr() as *const ::core::ffi::c_char, +]; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_asset_state_name( + mut state: observation_asset_state_t, +) -> *const ::core::ffi::c_char { + return if state.0 < 4 as ::core::ffi::c_uint { + STATE_NAMES[state.0 as usize] + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_asset_state_parse( + mut name: *const ::core::ffi::c_char, + mut state: *mut observation_asset_state_t, +) -> ::core::ffi::c_int { + if name.is_null() || state.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < 4 as ::core::ffi::c_int { + if strcmp(name, STATE_NAMES[i as usize]) == 0 as ::core::ffi::c_int { + *state = observation_asset_state_t(i as ::core::ffi::c_uint); + return 0 as ::core::ffi::c_int; + } + i += 1; + } + return -1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_change_cause_name( + mut cause: observation_change_cause_t, +) -> *const ::core::ffi::c_char { + return if cause.0 < 3 as ::core::ffi::c_uint { + CAUSE_NAMES[cause.0 as usize] + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }; +} +unsafe extern "C" fn cause_parse( + mut name: *const ::core::ffi::c_char, + mut cause: *mut observation_change_cause_t, +) -> ::core::ffi::c_int { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while !name.is_null() && i < 3 as ::core::ffi::c_int { + if strcmp(name, CAUSE_NAMES[i as usize]) == 0 as ::core::ffi::c_int { + *cause = observation_change_cause_t(i as ::core::ffi::c_uint); + return 0 as ::core::ffi::c_int; + } + i += 1; + } + return -1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_publication_state_name( + mut state: observation_publication_state_t, +) -> *const ::core::ffi::c_char { + return if state.0 < 5 as ::core::ffi::c_uint { + PUBLICATION_NAMES[state.0 as usize] + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }; +} +static mut REASONS: [C2Rust_Unnamed_0; 9] = [ + C2Rust_Unnamed_0 { + reason: b"queue_full\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"budget_exhausted\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"encode_failed\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"interrupted\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"no_matching_media\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_UNAVAILABLE, + }, + C2Rust_Unnamed_0 { + reason: b"retention_expired\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_EXPIRED, + }, + C2Rust_Unnamed_0 { + reason: b"deleted\0".as_ptr() as *const ::core::ffi::c_char, + state: observation_asset_state_t::OBS_ASSET_EXPIRED, + }, +]; +unsafe extern "C" fn empty_text(mut value: *const ::core::ffi::c_char) -> bool { + return value.is_null() || *value.offset(0isize) == 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_reason_is_valid( + mut state: observation_asset_state_t, + mut reason: *const ::core::ffi::c_char, +) -> bool { + if state.0 == observation_asset_state_t::OBS_ASSET_PENDING.0 + || state.0 == observation_asset_state_t::OBS_ASSET_READY.0 + { + return empty_text(reason); + } + if state.0 != observation_asset_state_t::OBS_ASSET_UNAVAILABLE.0 + && state.0 != observation_asset_state_t::OBS_ASSET_EXPIRED.0 + { + return r#false != 0; + } + if empty_text(reason) { + return r#false != 0; + } + let mut i: size_t = 0 as size_t; + while i < ::core::mem::size_of::<[C2Rust_Unnamed_0; 9]>() + .wrapping_div(::core::mem::size_of::()) + { + if strcmp(reason, REASONS[i].reason) == 0 as ::core::ffi::c_int { + return REASONS[i].state.0 == state.0; + } + i = i.wrapping_add(1); + } + return r#false != 0; +} +unsafe extern "C" fn reason_in( + mut reason: *const ::core::ffi::c_char, + mut allowed: *const *const ::core::ffi::c_char, + mut count: size_t, +) -> bool { + let mut i: size_t = 0 as size_t; + while !reason.is_null() && i < count { + if strcmp(reason, *allowed.offset(i as isize)) == 0 as ::core::ffi::c_int { + return r#true != 0; + } + i = i.wrapping_add(1); + } + return r#false != 0; +} +unsafe extern "C" fn transition_allowed( + mut from: observation_asset_state_t, + mut to: observation_asset_state_t, + mut reason: *const ::core::ffi::c_char, +) -> bool { + static mut pending_unavailable: [*const ::core::ffi::c_char; 4] = [ + b"encode_failed\0".as_ptr() as *const ::core::ffi::c_char, + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + b"interrupted\0".as_ptr() as *const ::core::ffi::c_char, + b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + ]; + static mut ready_unavailable: [*const ::core::ffi::c_char; 1] = + [b"write_failed\0".as_ptr() as *const ::core::ffi::c_char]; + static mut ready_expired: [*const ::core::ffi::c_char; 2] = [ + b"retention_expired\0".as_ptr() as *const ::core::ffi::c_char, + b"deleted\0".as_ptr() as *const ::core::ffi::c_char, + ]; + if from.0 == observation_asset_state_t::OBS_ASSET_PENDING.0 + && to.0 == observation_asset_state_t::OBS_ASSET_READY.0 + { + return empty_text(reason); + } + if from.0 == observation_asset_state_t::OBS_ASSET_PENDING.0 + && to.0 == observation_asset_state_t::OBS_ASSET_UNAVAILABLE.0 + { + return reason_in( + reason, + &raw const pending_unavailable as *const *const ::core::ffi::c_char, + 4 as size_t, + ); + } + if from.0 == observation_asset_state_t::OBS_ASSET_READY.0 + && to.0 == observation_asset_state_t::OBS_ASSET_UNAVAILABLE.0 + { + return reason_in( + reason, + &raw const ready_unavailable as *const *const ::core::ffi::c_char, + 1 as size_t, + ); + } + if from.0 == observation_asset_state_t::OBS_ASSET_READY.0 + && to.0 == observation_asset_state_t::OBS_ASSET_EXPIRED.0 + { + return reason_in( + reason, + &raw const ready_expired as *const *const ::core::ffi::c_char, + 2 as size_t, + ); + } + return r#false != 0; +} +unsafe extern "C" fn terminated(mut value: *const ::core::ffi::c_char, mut size: size_t) -> bool { + return !value.is_null() + && !memchr( + value as *const ::core::ffi::c_void, + '\0' as ::core::ffi::c_int, + size, + ) + .is_null(); +} +unsafe extern "C" fn text_len_between( + mut value: *const ::core::ffi::c_char, + mut size: size_t, + mut min: size_t, + mut max: size_t, +) -> bool { + if !terminated(value, size) { + return r#false != 0; + } + let mut length: size_t = strlen(value); + return length >= min && length <= max; +} +unsafe extern "C" fn current_time_ms() -> int64_t { + let mut now: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + if clock_gettime(CLOCK_REALTIME, &raw mut now) != 0 as ::core::ffi::c_int { + return time(::core::ptr::null_mut::()) as int64_t * 1000 as int64_t; + } + return now.tv_sec as int64_t * 1000 as int64_t + now.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn copy_column( + mut stmt: *mut sqlite3_stmt, + mut column: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) { + let mut value: *const ::core::ffi::c_uchar = sqlite3_column_text(stmt, column); + safe_strcpy( + out, + if !value.is_null() { + value as *const ::core::ffi::c_char + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + size, + 0 as size_t, + ); +} +unsafe extern "C" fn bind_text_or_null( + mut stmt: *mut sqlite3_stmt, + mut index: ::core::ffi::c_int, + mut value: *const ::core::ffi::c_char, +) { + if !value.is_null() && *value.offset(0isize) as ::core::ffi::c_int != 0 { + sqlite3_bind_text( + stmt, + index, + value, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + } else { + sqlite3_bind_null(stmt, index); + }; +} +unsafe extern "C" fn bind_positive_or_null( + mut stmt: *mut sqlite3_stmt, + mut index: ::core::ffi::c_int, + mut value: int64_t, +) { + if value > 0 as int64_t { + sqlite3_bind_int64(stmt, index, value as sqlite3_int64); + } else { + sqlite3_bind_null(stmt, index); + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_message_id( + mut source: *const ::core::ffi::c_char, + mut observation_uuid: *const ::core::ffi::c_char, + mut revision: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if out.is_null() + || empty_text(source) as ::core::ffi::c_int != 0 + || !lightnvr_uuid_is_valid(observation_uuid) + || revision < 1 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut name: [::core::ffi::c_char; 221] = [0; 221]; + let mut written: ::core::ffi::c_int = snprintf( + &raw mut name as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 221]>(), + b"%s|%s|%d\0".as_ptr() as *const ::core::ffi::c_char, + source, + observation_uuid, + revision, + ); + if written < 0 as ::core::ffi::c_int + || written as size_t >= ::core::mem::size_of::<[::core::ffi::c_char; 221]>() + { + return -1 as ::core::ffi::c_int; + } + return lightnvr_uuid_generate_v5( + OBS_MESSAGE_ID_NAMESPACE.as_ptr(), + &raw mut name as *mut ::core::ffi::c_char, + written as size_t, + out, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_cursor_encode( + mut seq: int64_t, + mut epoch_uuid: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, +) -> ::core::ffi::c_int { + if out.is_null() || out_size == 0 as size_t { + return -1 as ::core::ffi::c_int; + } + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + if seq < 0 as int64_t + || seq as ::core::ffi::c_longlong > OBS_MAX_SAFE_SEQ + || !lightnvr_uuid_is_valid(epoch_uuid) + { + return -1 as ::core::ffi::c_int; + } + let mut json: [::core::ffi::c_char; 128] = [0; 128]; + let mut written: ::core::ffi::c_int = snprintf( + &raw mut json as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + b"{\"v\":1,\"seq\":%lld,\"epoch\":\"%s\"}\0".as_ptr() as *const ::core::ffi::c_char, + seq as ::core::ffi::c_longlong, + epoch_uuid, + ); + if written < 0 as ::core::ffi::c_int + || written as size_t >= ::core::mem::size_of::<[::core::ffi::c_char; 128]>() + { + return -1 as ::core::ffi::c_int; + } + return base64url_encode( + &raw mut json as *mut ::core::ffi::c_char as *const ::core::ffi::c_uchar, + written as size_t, + out, + out_size, + ::core::ptr::null_mut::(), + ); +} +unsafe extern "C" fn json_integer( + mut item: *const cJSON, + mut min: ::core::ffi::c_double, + mut max: ::core::ffi::c_double, + mut value: *mut int64_t, +) -> bool { + if cJSON_IsNumber(item) == 0 { + return r#false != 0; + } + let mut number: ::core::ffi::c_double = (*item).valuedouble; + if number.is_finite() as ::core::ffi::c_int == 0 + || number < min + || number > max + || floor(number) != number + { + return r#false != 0; + } + *value = number as int64_t; + return r#true != 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_cursor_decode( + mut cursor: *const ::core::ffi::c_char, + mut seq: *mut int64_t, + mut epoch_uuid: *mut ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if cursor.is_null() || seq.is_null() || epoch_uuid.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID_CURSOR.0; + } + let mut length: size_t = strnlen(cursor, OBS_CURSOR_SIZE as size_t); + if length == 0 as size_t || length > (OBS_CURSOR_SIZE - 1 as ::core::ffi::c_int) as size_t { + return C2Rust_Unnamed::DB_OBS_INVALID_CURSOR.0; + } + let mut json: [::core::ffi::c_uchar; 257] = [0; 257]; + let mut json_length: size_t = 0 as size_t; + if base64url_decode( + cursor, + length, + &raw mut json as *mut ::core::ffi::c_uchar, + ::core::mem::size_of::<[::core::ffi::c_uchar; 257]>().wrapping_sub(1 as size_t), + &raw mut json_length, + ) != 0 as ::core::ffi::c_int + || !memchr( + &raw mut json as *mut ::core::ffi::c_uchar as *const ::core::ffi::c_void, + '\0' as ::core::ffi::c_int, + json_length, + ) + .is_null() + { + return C2Rust_Unnamed::DB_OBS_INVALID_CURSOR.0; + } + json[json_length] = '\0' as ::core::ffi::c_uchar; + let mut root: *mut cJSON = cJSON_ParseWithLength( + &raw mut json as *mut ::core::ffi::c_uchar as *const ::core::ffi::c_char, + json_length, + ); + let mut result: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_INVALID_CURSOR.0; + let mut version: int64_t = 0 as int64_t; + let mut value: int64_t = 0 as int64_t; + let mut epoch: *const cJSON = if !root.is_null() { + cJSON_GetObjectItemCaseSensitive(root, b"epoch\0".as_ptr() as *const ::core::ffi::c_char) + } else { + ::core::ptr::null_mut::() + }; + if cJSON_IsObject(root) != 0 + && json_integer( + cJSON_GetObjectItemCaseSensitive(root, b"v\0".as_ptr() as *const ::core::ffi::c_char), + 1 as ::core::ffi::c_int as ::core::ffi::c_double, + 1 as ::core::ffi::c_int as ::core::ffi::c_double, + &raw mut version, + ) as ::core::ffi::c_int + != 0 + && json_integer( + cJSON_GetObjectItemCaseSensitive(root, b"seq\0".as_ptr() as *const ::core::ffi::c_char), + 0 as ::core::ffi::c_int as ::core::ffi::c_double, + OBS_MAX_SAFE_SEQ as ::core::ffi::c_double, + &raw mut value, + ) as ::core::ffi::c_int + != 0 + && cJSON_IsString(epoch) != 0 + && lightnvr_uuid_is_valid((*epoch).valuestring) as ::core::ffi::c_int != 0 + { + *seq = value; + safe_strcpy( + epoch_uuid, + (*epoch).valuestring, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + result = C2Rust_Unnamed::DB_OBS_OK.0; + } + cJSON_Delete(root); + return result; +} +static mut publication_hook: db_observation_publication_hook_t = None; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observations_set_publication_hook( + mut hook: db_observation_publication_hook_t, +) { + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::Release }>( + &raw mut publication_hook as *mut *mut (), + ::core::mem::transmute::(hook), + ); +} +unsafe extern "C" fn notify_publications_committed() { + let mut hook: db_observation_publication_hook_t = + ::core::mem::transmute::<*mut (), db_observation_publication_hook_t>( + ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::Acquire }>( + &raw mut publication_hook as *mut *mut (), + ), + ); + if hook.is_some() { + hook.expect("non-null function pointer")(); + } +} +unsafe extern "C" fn tx_begin(mut tx: *mut obs_tx_t) -> ::core::ffi::c_int { + (*tx).mutex = get_db_mutex(); + if (*tx).mutex.is_null() { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock((*tx).mutex); + (*tx).db = get_db_handle(); + if (*tx).db.is_null() { + pthread_mutex_unlock((*tx).mutex); + return -1 as ::core::ffi::c_int; + } + (*tx).connection_mutex = sqlite3_db_mutex((*tx).db); + sqlite3_mutex_enter((*tx).connection_mutex); + if sqlite3_exec( + (*tx).db, + b"BEGIN IMMEDIATE;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ) != SQLITE_OK + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to begin observation transaction: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg((*tx).db), + ); + sqlite3_mutex_leave((*tx).connection_mutex); + pthread_mutex_unlock((*tx).mutex); + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn tx_end( + mut tx: *mut obs_tx_t, + mut rc: ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if rc == C2Rust_Unnamed::DB_OBS_OK.0 + && sqlite3_exec( + (*tx).db, + b"COMMIT;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ) != SQLITE_OK + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to commit observation transaction: %s\0".as_ptr() + as *const ::core::ffi::c_char, + sqlite3_errmsg((*tx).db), + ); + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + sqlite3_exec( + (*tx).db, + b"ROLLBACK;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ); + } + sqlite3_mutex_leave((*tx).connection_mutex); + pthread_mutex_unlock((*tx).mutex); + return rc; +} +unsafe extern "C" fn get_source(mut source: *mut ::core::ffi::c_char) -> ::core::ffi::c_int { + if event_identity_get_source(source, OBS_SOURCE_SIZE as size_t) != 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Observation write needs the event installation identity\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn write_change_locked( + mut db: *mut sqlite3, + mut source: *const ::core::ffi::c_char, + mut observation_uuid: *const ::core::ffi::c_char, + mut revision: ::core::ffi::c_int, + mut cause: observation_change_cause_t, + mut evidence_state: *const ::core::ffi::c_char, + mut evidence_reason: *const ::core::ffi::c_char, + mut now_ms: int64_t, + mut seq_out: *mut int64_t, +) -> ::core::ffi::c_int { + let mut message_id: [::core::ffi::c_char; 37] = [0; 37]; + if observation_message_id( + source, + observation_uuid, + revision, + &raw mut message_id as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"INSERT INTO observation_changes (observation_uuid, revision, cause, evidence_state, evidence_reason, created_at_ms) VALUES (?, ?, ?, ?, ?, ?);\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int(stmt, 2 as ::core::ffi::c_int, revision); + sqlite3_bind_text( + stmt, + 3 as ::core::ffi::c_int, + observation_change_cause_name(cause), + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + sqlite3_bind_text( + stmt, + 4 as ::core::ffi::c_int, + evidence_state, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + bind_text_or_null(stmt, 5 as ::core::ffi::c_int, evidence_reason); + sqlite3_bind_int64(stmt, 6 as ::core::ffi::c_int, now_ms as sqlite3_int64); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to insert observation change: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + return rc; + } + let mut seq: int64_t = sqlite3_last_insert_rowid(db) as int64_t; + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + b"INSERT INTO observation_publications (change_seq, message_id, state, attempts, updated_at_ms) VALUES (?, ?, 'pending', 0, ?);\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int64(stmt, 1 as ::core::ffi::c_int, seq as sqlite3_int64); + sqlite3_bind_text( + stmt, + 2 as ::core::ffi::c_int, + &raw mut message_id as *mut ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int64(stmt, 3 as ::core::ffi::c_int, now_ms as sqlite3_int64); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to insert observation publication: %s\0".as_ptr() + as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + return rc; + } + if !seq_out.is_null() { + *seq_out = seq; + } + return C2Rust_Unnamed::DB_OBS_OK.0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_bump_revision_locked( + mut db: *mut sqlite3, + mut source: *const ::core::ffi::c_char, + mut observation_uuid: *const ::core::ffi::c_char, + mut cause: observation_change_cause_t, + mut now_ms: int64_t, + mut seq_out: *mut int64_t, + mut revision_out: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if db.is_null() + || empty_text(source) as ::core::ffi::c_int != 0 + || !lightnvr_uuid_is_valid(observation_uuid) + || cause.0 != observation_change_cause_t::OBS_CAUSE_EVIDENCE.0 + && cause.0 != observation_change_cause_t::OBS_CAUSE_CLASSIFICATION.0 + { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut revision: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut state: [::core::ffi::c_char; 16] = ::core::mem::transmute::< + [u8; 16], + [::core::ffi::c_char; 16], + >(*b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); + let mut reason: [::core::ffi::c_char; 33] = + ::core::mem::transmute::<[u8; 33], [::core::ffi::c_char; 33]>( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT o.revision, a.state, a.reason FROM object_observations o JOIN observation_assets a ON a.uuid = o.asset_uuid WHERE o.uuid = ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + revision = sqlite3_column_int(stmt, 0 as ::core::ffi::c_int); + copy_column( + stmt, + 1 as ::core::ffi::c_int, + &raw mut state as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + copy_column( + stmt, + 2 as ::core::ffi::c_int, + &raw mut reason as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + ); + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } else if step == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_NOT_FOUND.0; + } + } + sqlite3_finalize(stmt); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + return rc; + } + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + b"UPDATE object_observations SET revision = ? WHERE uuid = ? AND revision = ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int( + stmt, + 1 as ::core::ffi::c_int, + revision + 1 as ::core::ffi::c_int, + ); + sqlite3_bind_text( + stmt, + 2 as ::core::ffi::c_int, + observation_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int(stmt, 3 as ::core::ffi::c_int, revision); + if sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(db) == 1 as ::core::ffi::c_int { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + return rc; + } + rc = write_change_locked( + db, + source, + observation_uuid, + revision + 1 as ::core::ffi::c_int, + cause, + &raw mut state as *mut ::core::ffi::c_char, + &raw mut reason as *mut ::core::ffi::c_char, + now_ms, + seq_out, + ); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && !revision_out.is_null() { + *revision_out = revision + 1 as ::core::ffi::c_int; + } + return rc; +} +unsafe extern "C" fn round6(mut value: ::core::ffi::c_double) -> ::core::ffi::c_double { + return round(value * 1e6f64) / 1e6f64; +} +unsafe extern "C" fn valid_box(mut object: *const observation_object_input_t) -> bool { + if !(*object).has_box { + return r#true != 0; + } + let mut x: ::core::ffi::c_double = (*object).box_x; + let mut y: ::core::ffi::c_double = (*object).box_y; + let mut w: ::core::ffi::c_double = (*object).box_width; + let mut h: ::core::ffi::c_double = (*object).box_height; + return x.is_finite() as ::core::ffi::c_int != 0 + && y.is_finite() as ::core::ffi::c_int != 0 + && w.is_finite() as ::core::ffi::c_int != 0 + && h.is_finite() as ::core::ffi::c_int != 0 + && x >= 0.0f64 + && y >= 0.0f64 + && w > 0.0f64 + && h > 0.0f64 + && x + w <= 1.000001f64 + && y + h <= 1.000001f64; +} +unsafe extern "C" fn valid_object(mut object: *const observation_object_input_t) -> bool { + if (*object).object_index < 0 as ::core::ffi::c_int || (*object).object_index >= OBS_MAX_BATCH { + return r#false != 0; + } + if !text_len_between( + &raw const (*object).label as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + 1 as size_t, + (OBS_LABEL_SIZE - 1 as ::core::ffi::c_int) as size_t, + ) { + return r#false != 0; + } + if (*object).confidence.is_finite() as ::core::ffi::c_int == 0 + || (*object).confidence < 0.0f64 + || (*object).confidence > 1.0f64 + { + return r#false != 0; + } + if !valid_box(object) { + return r#false != 0; + } + if (*object).has_track as ::core::ffi::c_int != 0 + && (!text_len_between( + &raw const (*object).track_id as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 1 as size_t, + 64 as size_t, + ) || !text_len_between( + &raw const (*object).track_namespace as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 1 as size_t, + 64 as size_t, + ) || !lightnvr_uuid_is_valid( + &raw const (*object).track_session as *const ::core::ffi::c_char, + )) + { + return r#false != 0; + } + return r#true != 0; +} +unsafe extern "C" fn valid_engine_type(mut r#type: *const ::core::ffi::c_char) -> bool { + static mut types: [*const ::core::ffi::c_char; 4] = [ + b"object\0".as_ptr() as *const ::core::ffi::c_char, + b"api\0".as_ptr() as *const ::core::ffi::c_char, + b"onvif\0".as_ptr() as *const ::core::ffi::c_char, + b"external\0".as_ptr() as *const ::core::ffi::c_char, + ]; + return reason_in( + r#type, + &raw const types as *const *const ::core::ffi::c_char, + 4 as size_t, + ); +} +unsafe extern "C" fn valid_batch(mut batch: *const observation_batch_input_t) -> bool { + if batch.is_null() + || !terminated( + &raw const (*batch).batch_id as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ) + || !lightnvr_uuid_is_valid(&raw const (*batch).batch_id as *const ::core::ffi::c_char) + || !terminated( + &raw const (*batch).camera_uuid as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ) + || !lightnvr_uuid_is_valid(&raw const (*batch).camera_uuid as *const ::core::ffi::c_char) + || !text_len_between( + &raw const (*batch).stream_name as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + 0 as size_t, + 63 as size_t, + ) + || (*batch).observed_at_ms <= 0 as int64_t + || !text_len_between( + &raw const (*batch).engine_key as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 1 as size_t, + 64 as size_t, + ) + || !terminated( + &raw const (*batch).engine_type as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ) + || !valid_engine_type(&raw const (*batch).engine_type as *const ::core::ffi::c_char) + || !text_len_between( + &raw const (*batch).model as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 129]>(), + 0 as size_t, + 128 as size_t, + ) + || !strchr( + &raw const (*batch).model as *const ::core::ffi::c_char, + '/' as ::core::ffi::c_int, + ) + .is_null() + || !strchr( + &raw const (*batch).model as *const ::core::ffi::c_char, + '\\' as ::core::ffi::c_int, + ) + .is_null() + || !text_len_between( + &raw const (*batch).model_version as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 0 as size_t, + 64 as size_t, + ) + || (*batch).recording_id < 0 as int64_t + || (*batch).object_count < 1 as ::core::ffi::c_int + || (*batch).object_count > OBS_MAX_BATCH + { + return r#false != 0; + } + let mut used: [bool; 20] = [ + r#false != 0, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + false, + ]; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < (*batch).object_count { + let mut object: *const observation_object_input_t = + (&raw const (*batch).objects as *const observation_object_input_t).offset(i as isize); + if !valid_object(object) || used[(*object).object_index as usize] as ::core::ffi::c_int != 0 + { + return r#false != 0; + } + used[(*object).object_index as usize] = r#true != 0; + i += 1; + } + return r#true != 0; +} +unsafe extern "C" fn valid_asset_input(mut asset: *const observation_asset_input_t) -> bool { + static mut origins: [*const ::core::ffi::c_char; 2] = [ + b"inference_input\0".as_ptr() as *const ::core::ffi::c_char, + b"submitted_image\0".as_ptr() as *const ::core::ffi::c_char, + ]; + if asset.is_null() + || !terminated( + &raw const (*asset).asset_uuid as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ) + || (*asset).asset_uuid[0usize] as ::core::ffi::c_int != 0 + && !lightnvr_uuid_is_valid(&raw const (*asset).asset_uuid as *const ::core::ffi::c_char) + || !terminated( + &raw const (*asset).reason as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + ) + || !terminated( + &raw const (*asset).origin as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + ) + || !terminated( + &raw const (*asset).media_type as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + ) + || !terminated( + &raw const (*asset).transform_json as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ) + || (*asset).captured_at_ms < 0 as int64_t + || (*asset).expires_at_ms < 0 as int64_t + { + return r#false != 0; + } + if (*asset).state.0 != observation_asset_state_t::OBS_ASSET_PENDING.0 + && (*asset).state.0 != observation_asset_state_t::OBS_ASSET_UNAVAILABLE.0 + { + return r#false != 0; + } + if !observation_reason_is_valid( + (*asset).state, + &raw const (*asset).reason as *const ::core::ffi::c_char, + ) { + return r#false != 0; + } + if (*asset).origin[0usize] as ::core::ffi::c_int != 0 + && !reason_in( + &raw const (*asset).origin as *const ::core::ffi::c_char, + &raw const origins as *const *const ::core::ffi::c_char, + 2 as size_t, + ) + { + return r#false != 0; + } + if (*asset).media_type[0usize] as ::core::ffi::c_int != 0 + && strcmp( + &raw const (*asset).media_type as *const ::core::ffi::c_char, + b"image/jpeg\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + return r#false != 0; + } + return r#true != 0; +} +unsafe extern "C" fn find_existing_batch( + mut db: *mut sqlite3, + mut batch: *const observation_batch_input_t, + mut result: *mut observation_insert_result_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + b"SELECT uuid, asset_uuid, camera_uuid FROM object_observations WHERE batch_id = ? ORDER BY object_index;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + &raw const (*batch).batch_id as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_OK.0; + let mut count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if step != SQLITE_ROW { + break; + } + let mut camera: [::core::ffi::c_char; 37] = [0; 37]; + copy_column( + stmt, + 2 as ::core::ffi::c_int, + &raw mut camera as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + if strcmp( + &raw mut camera as *mut ::core::ffi::c_char, + &raw const (*batch).camera_uuid as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + rc = C2Rust_Unnamed::DB_OBS_CONFLICT.0; + break; + } else { + if count < OBS_MAX_BATCH { + copy_column( + stmt, + 0 as ::core::ffi::c_int, + &raw mut *(&raw mut (*result).observation_uuids + as *mut [::core::ffi::c_char; 37]) + .offset(count as isize) as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + ); + if count == 0 as ::core::ffi::c_int { + copy_column( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*result).asset_uuid as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + ); + } + count += 1; + } + rc = C2Rust_Unnamed::DB_OBS_DUPLICATE.0; + } + } + if step != SQLITE_ROW && step != SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_finalize(stmt); + if rc == C2Rust_Unnamed::DB_OBS_DUPLICATE.0 { + (*result).object_count = count; + } + return rc; +} +unsafe extern "C" fn resolve_stream_name( + mut db: *mut sqlite3, + mut camera_uuid: *const ::core::ffi::c_char, + mut name: *mut ::core::ffi::c_char, + mut size: size_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + b"SELECT name FROM streams WHERE camera_uuid = ?;\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + camera_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + let mut rc: ::core::ffi::c_int = if step == SQLITE_ROW { + C2Rust_Unnamed::DB_OBS_OK.0 + } else if step == SQLITE_DONE { + C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 + } else { + C2Rust_Unnamed::DB_OBS_ERROR.0 + }; + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + copy_column(stmt, 0 as ::core::ffi::c_int, name, size); + } + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn insert_asset_row( + mut db: *mut sqlite3, + mut asset_uuid: *const ::core::ffi::c_char, + mut batch: *const observation_batch_input_t, + mut asset: *const observation_asset_input_t, + mut now_ms: int64_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"INSERT INTO observation_assets (uuid, camera_uuid, state, reason, origin, media_type, captured_at_ms, transform_json, protected, expires_at_ms, created_at_ms, updated_at_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?);\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 2 as ::core::ffi::c_int, + &raw const (*batch).camera_uuid as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 3 as ::core::ffi::c_int, + observation_asset_state_name((*asset).state), + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + bind_text_or_null( + stmt, + 4 as ::core::ffi::c_int, + &raw const (*asset).reason as *const ::core::ffi::c_char, + ); + bind_text_or_null( + stmt, + 5 as ::core::ffi::c_int, + &raw const (*asset).origin as *const ::core::ffi::c_char, + ); + bind_text_or_null( + stmt, + 6 as ::core::ffi::c_int, + &raw const (*asset).media_type as *const ::core::ffi::c_char, + ); + sqlite3_bind_int64( + stmt, + 7 as ::core::ffi::c_int, + (if (*asset).captured_at_ms > 0 as int64_t { + (*asset).captured_at_ms + } else { + (*batch).observed_at_ms + }) as sqlite3_int64, + ); + bind_text_or_null( + stmt, + 8 as ::core::ffi::c_int, + &raw const (*asset).transform_json as *const ::core::ffi::c_char, + ); + bind_positive_or_null(stmt, 9 as ::core::ffi::c_int, (*asset).expires_at_ms); + sqlite3_bind_int64(stmt, 10 as ::core::ffi::c_int, now_ms as sqlite3_int64); + sqlite3_bind_int64(stmt, 11 as ::core::ffi::c_int, now_ms as sqlite3_int64); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to insert observation asset: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn insert_observation_row( + mut db: *mut sqlite3, + mut uuid: *const ::core::ffi::c_char, + mut asset_uuid: *const ::core::ffi::c_char, + mut stream_name: *const ::core::ffi::c_char, + mut batch: *const observation_batch_input_t, + mut object: *const observation_object_input_t, + mut now_ms: int64_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"INSERT OR IGNORE INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index, observed_at_ms, created_at_ms, engine_key, engine_type, model, model_version, label, confidence, box_x, box_y, box_width, box_height, track_id, track_namespace, track_session, recording_id, asset_uuid, revision) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1);\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 2 as ::core::ffi::c_int, + &raw const (*batch).camera_uuid as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 3 as ::core::ffi::c_int, + stream_name, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 4 as ::core::ffi::c_int, + &raw const (*batch).batch_id as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_int(stmt, 5 as ::core::ffi::c_int, (*object).object_index); + sqlite3_bind_int64( + stmt, + 6 as ::core::ffi::c_int, + (*batch).observed_at_ms as sqlite3_int64, + ); + sqlite3_bind_int64(stmt, 7 as ::core::ffi::c_int, now_ms as sqlite3_int64); + sqlite3_bind_text( + stmt, + 8 as ::core::ffi::c_int, + &raw const (*batch).engine_key as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 9 as ::core::ffi::c_int, + &raw const (*batch).engine_type as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + bind_text_or_null( + stmt, + 10 as ::core::ffi::c_int, + &raw const (*batch).model as *const ::core::ffi::c_char, + ); + bind_text_or_null( + stmt, + 11 as ::core::ffi::c_int, + &raw const (*batch).model_version as *const ::core::ffi::c_char, + ); + sqlite3_bind_text( + stmt, + 12 as ::core::ffi::c_int, + &raw const (*object).label as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_double(stmt, 13 as ::core::ffi::c_int, (*object).confidence); + if (*object).has_box { + sqlite3_bind_double(stmt, 14 as ::core::ffi::c_int, round6((*object).box_x)); + sqlite3_bind_double(stmt, 15 as ::core::ffi::c_int, round6((*object).box_y)); + sqlite3_bind_double( + stmt, + 16 as ::core::ffi::c_int, + round6((*object).box_width), + ); + sqlite3_bind_double( + stmt, + 17 as ::core::ffi::c_int, + round6((*object).box_height), + ); + } else { + let mut i: ::core::ffi::c_int = 14 as ::core::ffi::c_int; + while i <= 17 as ::core::ffi::c_int { + sqlite3_bind_null(stmt, i); + i += 1; + } + } + if (*object).has_track { + sqlite3_bind_text( + stmt, + 18 as ::core::ffi::c_int, + &raw const (*object).track_id as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 19 as ::core::ffi::c_int, + &raw const (*object).track_namespace as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 20 as ::core::ffi::c_int, + &raw const (*object).track_session as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + } else { + let mut i_0: ::core::ffi::c_int = 18 as ::core::ffi::c_int; + while i_0 <= 20 as ::core::ffi::c_int { + sqlite3_bind_null(stmt, i_0); + i_0 += 1; + } + } + bind_positive_or_null(stmt, 21 as ::core::ffi::c_int, (*batch).recording_id); + sqlite3_bind_text( + stmt, + 22 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) == SQLITE_DONE + && sqlite3_changes(db) == 1 as ::core::ffi::c_int + { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to insert observation: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + } + sqlite3_finalize(stmt); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_insert_batch( + mut batch: *const observation_batch_input_t, + mut asset: *const observation_asset_input_t, + mut result: *mut observation_insert_result_t, +) -> ::core::ffi::c_int { + if result.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + if !valid_batch(batch) || !valid_asset_input(asset) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut source: [::core::ffi::c_char; 160] = [0; 160]; + if get_source(&raw mut source as *mut ::core::ffi::c_char) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut asset_uuid: [::core::ffi::c_char; 37] = [0; 37]; + let mut uuids: [[::core::ffi::c_char; 37]; 20] = [[0; 37]; 20]; + if (*asset).asset_uuid[0usize] != 0 { + safe_strcpy( + &raw mut asset_uuid as *mut ::core::ffi::c_char, + &raw const (*asset).asset_uuid as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + let mut c: *mut ::core::ffi::c_char = &raw mut asset_uuid as *mut ::core::ffi::c_char; + while *c != 0 { + if *c as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && *c as ::core::ffi::c_int <= 'F' as ::core::ffi::c_int + { + *c = (*c as ::core::ffi::c_int - 'A' as ::core::ffi::c_int + + 'a' as ::core::ffi::c_int) as ::core::ffi::c_char; + } + c = c.offset(1); + } + } else if lightnvr_uuid_generate_v4(&raw mut asset_uuid as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < (*batch).object_count { + if lightnvr_uuid_generate_v4( + &raw mut *(&raw mut uuids as *mut [::core::ffi::c_char; 37]).offset(i as isize) + as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + i += 1; + } + let mut order: [::core::ffi::c_int; 20] = [0; 20]; + let mut ordered: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut index: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while index < OBS_MAX_BATCH { + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < (*batch).object_count { + if (*batch).objects[i_0 as usize].object_index == index { + let c2rust_fresh0 = ordered; + ordered += 1; + order[c2rust_fresh0 as usize] = i_0; + } + i_0 += 1; + } + index += 1; + } + let mut now_ms: int64_t = current_time_ms(); + let mut tx: obs_tx_t = obs_tx_t { + mutex: ::core::ptr::null_mut::(), + db: ::core::ptr::null_mut::(), + connection_mutex: ::core::ptr::null_mut::(), + }; + if tx_begin(&raw mut tx) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut rc: ::core::ffi::c_int = find_existing_batch(tx.db, batch, result); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + let mut final_rc: ::core::ffi::c_int = rc; + tx_end(&raw mut tx, C2Rust_Unnamed::DB_OBS_ERROR.0); + if final_rc != C2Rust_Unnamed::DB_OBS_DUPLICATE.0 { + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + } + return final_rc; + } + let mut stream_name: [::core::ffi::c_char; 64] = [0; 64]; + rc = resolve_stream_name( + tx.db, + &raw const (*batch).camera_uuid as *const ::core::ffi::c_char, + &raw mut stream_name as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + ); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && (*batch).stream_name[0usize] as ::core::ffi::c_int != 0 + { + safe_strcpy( + &raw mut stream_name as *mut ::core::ffi::c_char, + &raw const (*batch).stream_name as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + 0 as size_t, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = insert_asset_row( + tx.db, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + batch, + asset, + now_ms, + ); + } + let mut state: *const ::core::ffi::c_char = observation_asset_state_name((*asset).state); + let mut n: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while rc == C2Rust_Unnamed::DB_OBS_OK.0 && n < ordered { + let mut i_1: ::core::ffi::c_int = order[n as usize]; + rc = insert_observation_row( + tx.db, + &raw mut *(&raw mut uuids as *mut [::core::ffi::c_char; 37]).offset(i_1 as isize) + as *mut ::core::ffi::c_char, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + &raw mut stream_name as *mut ::core::ffi::c_char, + batch, + (&raw const (*batch).objects as *const observation_object_input_t).offset(i_1 as isize), + now_ms, + ); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = write_change_locked( + tx.db, + &raw mut source as *mut ::core::ffi::c_char, + &raw mut *(&raw mut uuids as *mut [::core::ffi::c_char; 37]).offset(i_1 as isize) + as *mut ::core::ffi::c_char, + 1 as ::core::ffi::c_int, + observation_change_cause_t::OBS_CAUSE_CREATED, + state, + &raw const (*asset).reason as *const ::core::ffi::c_char, + now_ms, + &raw mut (*result).last_seq, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + safe_strcpy( + &raw mut *(&raw mut (*result).observation_uuids as *mut [::core::ffi::c_char; 37]) + .offset(n as isize) as *mut ::core::ffi::c_char, + &raw mut *(&raw mut uuids as *mut [::core::ffi::c_char; 37]).offset(i_1 as isize) + as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + } + n += 1; + } + rc = tx_end(&raw mut tx, rc); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + safe_strcpy( + &raw mut (*result).asset_uuid as *mut ::core::ffi::c_char, + &raw mut asset_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + (*result).object_count = ordered; + if ordered > 0 as ::core::ffi::c_int { + notify_publications_committed(); + } + } else { + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + } + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_asset_transition( + mut asset_uuid: *const ::core::ffi::c_char, + mut from: observation_asset_state_t, + mut to: observation_asset_state_t, + mut reason: *const ::core::ffi::c_char, + mut media: *const observation_asset_media_t, + mut result: *mut observation_transition_result_t, +) -> ::core::ffi::c_int { + if !result.is_null() { + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + } + if !lightnvr_uuid_is_valid(asset_uuid) || !transition_allowed(from, to, reason) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + if to.0 == observation_asset_state_t::OBS_ASSET_READY.0 + && (media.is_null() + || empty_text((*media).path) as ::core::ffi::c_int != 0 + || strlen((*media).path) >= MAX_PATH_LENGTH as size_t + || (*media).bytes < 0 as int64_t + || (*media).width <= 0 as ::core::ffi::c_int + || (*media).height <= 0 as ::core::ffi::c_int) + { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + if !media.is_null() + && ((*media).bytes < 0 as int64_t + || (*media).width < 0 as ::core::ffi::c_int + || (*media).height < 0 as ::core::ffi::c_int + || (*media).expires_at_ms < 0 as int64_t) + { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut source: [::core::ffi::c_char; 160] = [0; 160]; + if get_source(&raw mut source as *mut ::core::ffi::c_char) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut now_ms: int64_t = current_time_ms(); + let mut tx: obs_tx_t = obs_tx_t { + mutex: ::core::ptr::null_mut::(), + db: ::core::ptr::null_mut::(), + connection_mutex: ::core::ptr::null_mut::(), + }; + if tx_begin(&raw mut tx) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + tx.db, + b"UPDATE observation_assets SET state = ?, reason = ?, path = COALESCE(?, path), bytes = COALESCE(?, bytes), width = COALESCE(?, width), height = COALESCE(?, height), expires_at_ms = COALESCE(?, expires_at_ms), updated_at_ms = ? WHERE uuid = ? AND state = ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_asset_state_name(to), + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + bind_text_or_null(stmt, 2 as ::core::ffi::c_int, reason); + bind_text_or_null( + stmt, + 3 as ::core::ffi::c_int, + if !media.is_null() { + (*media).path + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }, + ); + if !media.is_null() && to.0 == observation_asset_state_t::OBS_ASSET_READY.0 { + sqlite3_bind_int64( + stmt, + 4 as ::core::ffi::c_int, + (*media).bytes as sqlite3_int64, + ); + } else { + sqlite3_bind_null(stmt, 4 as ::core::ffi::c_int); + } + bind_positive_or_null( + stmt, + 5 as ::core::ffi::c_int, + (if !media.is_null() { (*media).width } else { 0 as ::core::ffi::c_int }) + as int64_t, + ); + bind_positive_or_null( + stmt, + 6 as ::core::ffi::c_int, + (if !media.is_null() { (*media).height } else { 0 as ::core::ffi::c_int }) + as int64_t, + ); + bind_positive_or_null( + stmt, + 7 as ::core::ffi::c_int, + if !media.is_null() { (*media).expires_at_ms } else { 0 as int64_t }, + ); + sqlite3_bind_int64(stmt, 8 as ::core::ffi::c_int, now_ms as sqlite3_int64); + sqlite3_bind_text( + stmt, + 9 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + sqlite3_bind_text( + stmt, + 10 as ::core::ffi::c_int, + observation_asset_state_name(from), + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && sqlite3_changes(tx.db) == 0 as ::core::ffi::c_int { + stmt = ::core::ptr::null_mut::(); + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + tx.db, + b"SELECT 1 FROM observation_assets WHERE uuid = ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + rc = C2Rust_Unnamed::DB_OBS_CAS_LOST.0; + } else if step == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_NOT_FOUND.0; + } + } + sqlite3_finalize(stmt); + tx_end(&raw mut tx, C2Rust_Unnamed::DB_OBS_ERROR.0); + return rc; + } + let mut observations: *mut [::core::ffi::c_char; 37] = + ::core::ptr::null_mut::<[::core::ffi::c_char; 37]>(); + let mut count: size_t = 0 as size_t; + let mut capacity: size_t = 0 as size_t; + stmt = ::core::ptr::null_mut::(); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + tx.db, + b"SELECT uuid FROM object_observations WHERE asset_uuid = ? ORDER BY batch_id, object_index;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step_0: ::core::ffi::c_int = 0; + rc = C2Rust_Unnamed::DB_OBS_OK.0; + loop { + step_0 = sqlite3_step(stmt); + if step_0 != SQLITE_ROW { + break; + } + if count == capacity { + let mut next: size_t = if capacity != 0 { + capacity.wrapping_mul(2 as size_t) + } else { + OBS_MAX_BATCH as size_t + }; + let mut grown: *mut ::core::ffi::c_void = realloc( + observations as *mut ::core::ffi::c_void, + next + .wrapping_mul( + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ), + ); + if grown.is_null() { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + break; + } else { + observations = grown as *mut [::core::ffi::c_char; 37]; + capacity = next; + } + } + let c2rust_fresh1 = count; + count = count.wrapping_add(1); + copy_column( + stmt, + 0 as ::core::ffi::c_int, + &raw mut *observations.offset(c2rust_fresh1 as isize) + as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && step_0 != SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + } + sqlite3_finalize(stmt); + } + let mut last_seq: int64_t = 0 as int64_t; + let mut i: size_t = 0 as size_t; + while rc == C2Rust_Unnamed::DB_OBS_OK.0 && i < count { + rc = db_observation_bump_revision_locked( + tx.db, + &raw mut source as *mut ::core::ffi::c_char, + &raw mut *observations.offset(i as isize) as *mut ::core::ffi::c_char, + observation_change_cause_t::OBS_CAUSE_EVIDENCE, + now_ms, + &raw mut last_seq, + ::core::ptr::null_mut::<::core::ffi::c_int>(), + ); + i = i.wrapping_add(1); + } + free(observations as *mut ::core::ffi::c_void); + rc = tx_end(&raw mut tx, rc); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && !result.is_null() { + (*result).observations_bumped = count as ::core::ffi::c_int; + (*result).last_seq = last_seq; + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && count > 0 as size_t { + notify_publications_committed(); + } + return rc; +} +unsafe extern "C" fn ro_begin(mut db: *mut *mut sqlite3) -> ::core::ffi::c_int { + if db_open_readonly_connection(db) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + if sqlite3_exec( + *db, + b"BEGIN;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ) != SQLITE_OK + { + db_close_readonly_connection(*db); + *db = ::core::ptr::null_mut::(); + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + return C2Rust_Unnamed::DB_OBS_OK.0; +} +unsafe extern "C" fn ro_end(mut db: *mut sqlite3) { + if db.is_null() { + return; + } + sqlite3_exec( + db, + b"COMMIT;\0".as_ptr() as *const ::core::ffi::c_char, + None, + NULL, + ::core::ptr::null_mut::<*mut ::core::ffi::c_char>(), + ); + db_close_readonly_connection(db); +} +pub const RECORD_SELECT: [::core::ffi::c_char; 688] = unsafe { + ::core::mem::transmute::< + [u8; 688], + [::core::ffi::c_char; 688], + >( + *b"SELECT o.uuid, o.camera_uuid, o.stream_name_at_capture, o.batch_id, o.object_index, o.observed_at_ms, o.created_at_ms, o.engine_key, o.engine_type, o.model, o.model_version, o.label, o.confidence, o.box_x, o.box_y, o.box_width, o.box_height, o.track_id, o.track_namespace, o.track_session, o.recording_id, o.revision, a.uuid, a.camera_uuid, a.state, a.reason, a.origin, a.media_type, a.path, a.bytes, a.width, a.height, a.captured_at_ms, a.transform_json, a.protected, a.expires_at_ms, a.created_at_ms, a.updated_at_ms FROM object_observations o JOIN observation_assets a ON a.uuid = o.asset_uuid WHERE o.uuid = ? AND EXISTS (SELECT 1 FROM streams s WHERE s.camera_uuid = o.camera_uuid);\0", + ) +}; +unsafe extern "C" fn read_asset( + mut stmt: *mut sqlite3_stmt, + mut c: ::core::ffi::c_int, + mut asset: *mut observation_asset_record_t, +) { + memset( + asset as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + copy_column( + stmt, + c + 0 as ::core::ffi::c_int, + &raw mut (*asset).uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + copy_column( + stmt, + c + 1 as ::core::ffi::c_int, + &raw mut (*asset).camera_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + let mut state: [::core::ffi::c_char; 16] = [0; 16]; + copy_column( + stmt, + c + 2 as ::core::ffi::c_int, + &raw mut state as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + if observation_asset_state_parse( + &raw mut state as *mut ::core::ffi::c_char, + &raw mut (*asset).state, + ) != 0 as ::core::ffi::c_int + { + (*asset).state = observation_asset_state_t::OBS_ASSET_UNAVAILABLE; + } + copy_column( + stmt, + c + 3 as ::core::ffi::c_int, + &raw mut (*asset).reason as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + ); + copy_column( + stmt, + c + 4 as ::core::ffi::c_int, + &raw mut (*asset).origin as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + ); + copy_column( + stmt, + c + 5 as ::core::ffi::c_int, + &raw mut (*asset).media_type as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 24]>(), + ); + copy_column( + stmt, + c + 6 as ::core::ffi::c_int, + &raw mut (*asset).path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ); + (*asset).has_bytes = sqlite3_column_type(stmt, c + 7 as ::core::ffi::c_int) != SQLITE_NULL; + (*asset).bytes = sqlite3_column_int64(stmt, c + 7 as ::core::ffi::c_int) as int64_t; + (*asset).width = sqlite3_column_int(stmt, c + 8 as ::core::ffi::c_int); + (*asset).height = sqlite3_column_int(stmt, c + 9 as ::core::ffi::c_int); + (*asset).captured_at_ms = sqlite3_column_int64(stmt, c + 10 as ::core::ffi::c_int) as int64_t; + copy_column( + stmt, + c + 11 as ::core::ffi::c_int, + &raw mut (*asset).transform_json as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + (*asset).is_protected = + sqlite3_column_int(stmt, c + 12 as ::core::ffi::c_int) != 0 as ::core::ffi::c_int; + (*asset).expires_at_ms = sqlite3_column_int64(stmt, c + 13 as ::core::ffi::c_int) as int64_t; + (*asset).created_at_ms = sqlite3_column_int64(stmt, c + 14 as ::core::ffi::c_int) as int64_t; + (*asset).updated_at_ms = sqlite3_column_int64(stmt, c + 15 as ::core::ffi::c_int) as int64_t; +} +unsafe extern "C" fn read_record(mut stmt: *mut sqlite3_stmt, mut out: *mut observation_record_t) { + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + copy_column( + stmt, + 0 as ::core::ffi::c_int, + &raw mut (*out).uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + copy_column( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*out).camera_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + copy_column( + stmt, + 2 as ::core::ffi::c_int, + &raw mut (*out).stream_name_at_capture as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + ); + copy_column( + stmt, + 3 as ::core::ffi::c_int, + &raw mut (*out).batch_id as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*out).object_index = sqlite3_column_int(stmt, 4 as ::core::ffi::c_int); + (*out).observed_at_ms = sqlite3_column_int64(stmt, 5 as ::core::ffi::c_int) as int64_t; + (*out).created_at_ms = sqlite3_column_int64(stmt, 6 as ::core::ffi::c_int) as int64_t; + copy_column( + stmt, + 7 as ::core::ffi::c_int, + &raw mut (*out).engine_key as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + copy_column( + stmt, + 8 as ::core::ffi::c_int, + &raw mut (*out).engine_type as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + copy_column( + stmt, + 9 as ::core::ffi::c_int, + &raw mut (*out).model as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 129]>(), + ); + copy_column( + stmt, + 10 as ::core::ffi::c_int, + &raw mut (*out).model_version as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + copy_column( + stmt, + 11 as ::core::ffi::c_int, + &raw mut (*out).label as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ); + (*out).confidence = sqlite3_column_double(stmt, 12 as ::core::ffi::c_int); + (*out).has_box = sqlite3_column_type(stmt, 13 as ::core::ffi::c_int) != SQLITE_NULL; + if (*out).has_box { + (*out).box_x = sqlite3_column_double(stmt, 13 as ::core::ffi::c_int); + (*out).box_y = sqlite3_column_double(stmt, 14 as ::core::ffi::c_int); + (*out).box_width = sqlite3_column_double(stmt, 15 as ::core::ffi::c_int); + (*out).box_height = sqlite3_column_double(stmt, 16 as ::core::ffi::c_int); + } + (*out).has_track = sqlite3_column_type(stmt, 17 as ::core::ffi::c_int) != SQLITE_NULL; + copy_column( + stmt, + 17 as ::core::ffi::c_int, + &raw mut (*out).track_id as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + copy_column( + stmt, + 18 as ::core::ffi::c_int, + &raw mut (*out).track_namespace as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ); + copy_column( + stmt, + 19 as ::core::ffi::c_int, + &raw mut (*out).track_session as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*out).recording_id = sqlite3_column_int64(stmt, 20 as ::core::ffi::c_int) as int64_t; + (*out).revision = sqlite3_column_int(stmt, 21 as ::core::ffi::c_int); + read_asset(stmt, 22 as ::core::ffi::c_int, &raw mut (*out).asset); +} +unsafe extern "C" fn get_record_on( + mut db: *mut sqlite3, + mut cached: *mut *mut sqlite3_stmt, + mut uuid: *const ::core::ffi::c_char, + mut out: *mut observation_record_t, +) -> ::core::ffi::c_int { + if (*cached).is_null() + && sqlite3_prepare_v2( + db, + RECORD_SELECT.as_ptr(), + -1 as ::core::ffi::c_int, + cached, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to prepare observation read: %s\0".as_ptr() as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_reset(*cached); + sqlite3_clear_bindings(*cached); + sqlite3_bind_text( + *cached, + 1 as ::core::ffi::c_int, + uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(*cached); + if step == SQLITE_ROW { + read_record(*cached, out); + return C2Rust_Unnamed::DB_OBS_OK.0; + } + return if step == SQLITE_DONE { + C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 + } else { + C2Rust_Unnamed::DB_OBS_ERROR.0 + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_get( + mut observation_uuid: *const ::core::ffi::c_char, + mut out: *mut observation_record_t, +) -> ::core::ffi::c_int { + if out.is_null() || !lightnvr_uuid_is_valid(observation_uuid) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if ro_begin(&raw mut db) != C2Rust_Unnamed::DB_OBS_OK.0 { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = get_record_on(db, &raw mut stmt, observation_uuid, out); + sqlite3_finalize(stmt); + ro_end(db); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_asset_get( + mut asset_uuid: *const ::core::ffi::c_char, + mut out: *mut observation_asset_record_t, +) -> ::core::ffi::c_int { + if out.is_null() || !lightnvr_uuid_is_valid(asset_uuid) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if ro_begin(&raw mut db) != C2Rust_Unnamed::DB_OBS_OK.0 { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT a.uuid, a.camera_uuid, a.state, a.reason, a.origin, a.media_type, a.path, a.bytes, a.width, a.height, a.captured_at_ms, a.transform_json, a.protected, a.expires_at_ms, a.created_at_ms, a.updated_at_ms FROM observation_assets a WHERE a.uuid = ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + asset_uuid, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + read_asset(stmt, 0 as ::core::ffi::c_int, out); + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } else if step == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_NOT_FOUND.0; + } + } + sqlite3_finalize(stmt); + ro_end(db); + return rc; +} +unsafe extern "C" fn read_meta_on( + mut db: *mut sqlite3, + mut meta: *mut observation_meta_t, +) -> ::core::ffi::c_int { + memset( + meta as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT m.epoch_uuid, m.oldest_seq, (SELECT MAX(seq) FROM observation_changes) FROM observation_meta m WHERE m.id = 1;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK && sqlite3_step(stmt) == SQLITE_ROW + { + copy_column( + stmt, + 0 as ::core::ffi::c_int, + &raw mut (*meta).epoch_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*meta).oldest_seq = sqlite3_column_int64(stmt, 1 as ::core::ffi::c_int) + as int64_t; + let mut max_seq: int64_t = sqlite3_column_int64(stmt, 2 as ::core::ffi::c_int) + as int64_t; + (*meta).head_seq = if max_seq > (*meta).oldest_seq - 1 as int64_t { + max_seq + } else { + (*meta).oldest_seq - 1 as int64_t + }; + rc = if lightnvr_uuid_is_valid( + &raw mut (*meta).epoch_uuid as *mut ::core::ffi::c_char, + ) as ::core::ffi::c_int != 0 + { + C2Rust_Unnamed::DB_OBS_OK.0 + } else { + C2Rust_Unnamed::DB_OBS_ERROR.0 + }; + } + sqlite3_finalize(stmt); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_meta_get( + mut out: *mut observation_meta_t, +) -> ::core::ffi::c_int { + if out.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if ro_begin(&raw mut db) != C2Rust_Unnamed::DB_OBS_OK.0 { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut rc: ::core::ffi::c_int = read_meta_on(db, out); + ro_end(db); + return rc; +} +unsafe extern "C" fn diag_scalar( + mut db: *mut sqlite3, + mut sql: *const ::core::ffi::c_char, + mut value: *mut int64_t, + mut is_null: *mut bool, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + sql, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + if !is_null.is_null() { + *is_null = sqlite3_column_type(stmt, 0 as ::core::ffi::c_int) == SQLITE_NULL; + } + *value = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } else if step == SQLITE_DONE { + if !is_null.is_null() { + *is_null = r#true != 0; + } + *value = 0 as int64_t; + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_diagnostics_get( + mut out: *mut observation_db_diagnostics_t, +) -> ::core::ffi::c_int { + if out.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if ro_begin(&raw mut db) != C2Rust_Unnamed::DB_OBS_OK.0 { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut oldest_null: bool = r#true != 0; + let mut rc: ::core::ffi::c_int = read_meta_on(db, &raw mut (*out).meta); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = diag_scalar( + db, + b"SELECT MIN(observed_at_ms) FROM object_observations;\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*out).oldest_observed_at_ms, + &raw mut oldest_null, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = diag_scalar( + db, + b"SELECT COUNT(*) FROM object_observations;\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut (*out).observations, + ::core::ptr::null_mut::(), + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = diag_scalar( + db, + b"SELECT COUNT(*) FROM observation_publications WHERE state = 'pending';\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*out).publications_pending, + ::core::ptr::null_mut::(), + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = diag_scalar( + db, + b"SELECT COUNT(*) FROM observation_publications WHERE state = 'failed';\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*out).publications_failed, + ::core::ptr::null_mut::(), + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = diag_scalar( + db, + b"SELECT COUNT(*) FROM observation_publications WHERE state = 'expired';\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*out).publications_expired, + ::core::ptr::null_mut::(), + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = diag_scalar( + db, + b"SELECT c.created_at_ms FROM observation_publications p JOIN observation_changes c ON c.seq = p.change_seq WHERE p.state = 'pending' ORDER BY p.change_seq LIMIT 1;\0" + .as_ptr() as *const ::core::ffi::c_char, + &raw mut (*out).oldest_pending_created_at_ms, + ::core::ptr::null_mut::(), + ); + } + ro_end(db); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + (*out).has_oldest_observed_at = !oldest_null; + if oldest_null { + (*out).oldest_observed_at_ms = 0 as int64_t; + } + return C2Rust_Unnamed::DB_OBS_OK.0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_meta_regenerate_epoch() -> ::core::ffi::c_int { + let mut epoch: [::core::ffi::c_char; 37] = [0; 37]; + if lightnvr_uuid_generate_v4(&raw mut epoch as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut tx: obs_tx_t = obs_tx_t { + mutex: ::core::ptr::null_mut::(), + db: ::core::ptr::null_mut::(), + connection_mutex: ::core::ptr::null_mut::(), + }; + if tx_begin(&raw mut tx) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + tx.db, + b"UPDATE observation_meta SET epoch_uuid = ? WHERE id = 1;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + &raw mut epoch as *mut ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(tx.db) == 1 as ::core::ffi::c_int { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + rc = tx_end(&raw mut tx, rc); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Regenerated observation cursor epoch\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + return rc; +} +unsafe extern "C" fn valid_query(mut query: *const observation_list_query_t) -> bool { + if query.is_null() { + return r#false != 0; + } + let mut limit: ::core::ffi::c_int = if (*query).limit == 0 as ::core::ffi::c_int { + OBS_LIST_DEFAULT_LIMIT + } else { + (*query).limit + }; + if limit < 1 as ::core::ffi::c_int || limit > OBS_LIST_MAX_LIMIT { + return r#false != 0; + } + if (*query).label_count > OBS_QUERY_MAX_LABELS as size_t { + return r#false != 0; + } + let mut i: size_t = 0 as size_t; + while i < (*query).label_count { + if !text_len_between( + &raw const *(&raw const (*query).labels as *const [::core::ffi::c_char; 32]) + .offset(i as isize) as *const ::core::ffi::c_char, + OBS_LABEL_SIZE as size_t, + 1 as size_t, + (OBS_LABEL_SIZE - 1 as ::core::ffi::c_int) as size_t, + ) { + return r#false != 0; + } + i = i.wrapping_add(1); + } + if (*query).has_since as ::core::ffi::c_int != 0 + && (*query).has_until as ::core::ffi::c_int != 0 + && (*query).since_ms >= (*query).until_ms + { + return r#false != 0; + } + if !(*query).all_cameras + && (*query).camera_count > 0 as size_t + && (*query).camera_uuids.is_null() + { + return r#false != 0; + } + return r#true != 0; +} +unsafe extern "C" fn camera_allowed( + mut query: *const observation_list_query_t, + mut camera: *const ::core::ffi::c_char, +) -> bool { + if (*query).all_cameras { + return r#true != 0; + } + let mut i: size_t = 0 as size_t; + while i < (*query).camera_count { + if strcmp( + &raw const *(*query).camera_uuids.offset(i as isize) as *const ::core::ffi::c_char, + camera, + ) == 0 as ::core::ffi::c_int + { + return r#true != 0; + } + i = i.wrapping_add(1); + } + return r#false != 0; +} +unsafe extern "C" fn change_matches( + mut query: *const observation_list_query_t, + mut stmt: *mut sqlite3_stmt, +) -> bool { + if sqlite3_column_int(stmt, 5 as ::core::ffi::c_int) == 0 as ::core::ffi::c_int { + return r#false != 0; + } + let mut camera: *const ::core::ffi::c_char = + sqlite3_column_text(stmt, 2 as ::core::ffi::c_int) as *const ::core::ffi::c_char; + if camera.is_null() || !camera_allowed(query, camera) { + return r#false != 0; + } + let mut observed: int64_t = sqlite3_column_int64(stmt, 4 as ::core::ffi::c_int) as int64_t; + if (*query).has_since as ::core::ffi::c_int != 0 && observed < (*query).since_ms { + return r#false != 0; + } + if (*query).has_until as ::core::ffi::c_int != 0 && observed >= (*query).until_ms { + return r#false != 0; + } + if (*query).label_count > 0 as size_t { + let mut label: *const ::core::ffi::c_char = + sqlite3_column_text(stmt, 3 as ::core::ffi::c_int) as *const ::core::ffi::c_char; + let mut found: bool = r#false != 0; + let mut i: size_t = 0 as size_t; + while !label.is_null() && i < (*query).label_count && !found { + found = strcasecmp( + label, + &raw const *(&raw const (*query).labels as *const [::core::ffi::c_char; 32]) + .offset(i as isize) as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int; + i = i.wrapping_add(1); + } + if !found { + return r#false != 0; + } + } + return r#true != 0; +} +unsafe extern "C" fn fill_expired( + mut db: *mut sqlite3, + mut meta: *const observation_meta_t, + mut page: *mut observation_page_t, +) -> ::core::ffi::c_int { + if observation_cursor_encode( + (*meta).oldest_seq - 1 as int64_t, + &raw const (*meta).epoch_uuid as *const ::core::ffi::c_char, + &raw mut (*page).oldest_cursor as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 257]>(), + ) != 0 as ::core::ffi::c_int + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT MIN(observed_at_ms) FROM object_observations;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + && sqlite3_step(stmt) == SQLITE_ROW + { + (*page).has_oldest_observed_at = + sqlite3_column_type(stmt, 0 as ::core::ffi::c_int) != SQLITE_NULL; + (*page).oldest_observed_at_ms = + sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + rc = C2Rust_Unnamed::DB_OBS_CURSOR_EXPIRED.0; + } + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn scan_page( + mut db: *mut sqlite3, + mut query: *const observation_list_query_t, + mut start: int64_t, + mut watermark: int64_t, + mut limit: ::core::ffi::c_int, + mut selected: *mut [::core::ffi::c_char; 37], + mut selected_count: *mut size_t, + mut last_scanned: *mut int64_t, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + b"SELECT c.seq, o.uuid, o.camera_uuid, o.label, o.observed_at_ms, EXISTS (SELECT 1 FROM streams s WHERE s.camera_uuid = o.camera_uuid) FROM observation_changes c JOIN object_observations o ON o.uuid = c.observation_uuid WHERE c.seq > ? AND c.seq <= ? ORDER BY c.seq LIMIT ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"Failed to prepare observation scan: %s\0".as_ptr() + as *const ::core::ffi::c_char, + sqlite3_errmsg(db), + ); + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_bind_int64(stmt, 1 as ::core::ffi::c_int, start as sqlite3_int64); + sqlite3_bind_int64(stmt, 2 as ::core::ffi::c_int, watermark as sqlite3_int64); + sqlite3_bind_int(stmt, 3 as ::core::ffi::c_int, OBS_LIST_SCAN_CAP); + *last_scanned = start; + *selected_count = 0 as size_t; + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if step != SQLITE_ROW { + break; + } + if change_matches(query, stmt) { + let mut uuid: *const ::core::ffi::c_char = + sqlite3_column_text(stmt, 1 as ::core::ffi::c_int) as *const ::core::ffi::c_char; + let mut present: bool = r#false != 0; + let mut i: size_t = 0 as size_t; + while !uuid.is_null() && i < *selected_count && !present { + present = strcmp( + &raw mut *selected.offset(i as isize) as *mut ::core::ffi::c_char, + uuid, + ) == 0 as ::core::ffi::c_int; + i = i.wrapping_add(1); + } + if !present { + if *selected_count == limit as size_t { + break; + } + let c2rust_fresh2 = *selected_count; + *selected_count = (*selected_count).wrapping_add(1); + safe_strcpy( + &raw mut *selected.offset(c2rust_fresh2 as isize) as *mut ::core::ffi::c_char, + if !uuid.is_null() { + uuid + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + } + } + *last_scanned = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + } + let mut rc: ::core::ffi::c_int = if step == SQLITE_ROW || step == SQLITE_DONE { + C2Rust_Unnamed::DB_OBS_OK.0 + } else { + C2Rust_Unnamed::DB_OBS_ERROR.0 + }; + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn changes_exist( + mut db: *mut sqlite3, + mut after: int64_t, + mut watermark: int64_t, + mut exists: *mut bool, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT EXISTS (SELECT 1 FROM observation_changes WHERE seq > ? AND seq <= ?);\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int64(stmt, 1 as ::core::ffi::c_int, after as sqlite3_int64); + sqlite3_bind_int64(stmt, 2 as ::core::ffi::c_int, watermark as sqlite3_int64); + if sqlite3_step(stmt) == SQLITE_ROW { + *exists = sqlite3_column_int(stmt, 0 as ::core::ffi::c_int) != 0 as ::core::ffi::c_int; + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observations_list( + mut query: *const observation_list_query_t, + mut cursor: *const ::core::ffi::c_char, + mut page: *mut observation_page_t, +) -> ::core::ffi::c_int { + if page.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + memset( + page as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + if !valid_query(query) { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut limit: ::core::ffi::c_int = if (*query).limit == 0 as ::core::ffi::c_int { + OBS_LIST_DEFAULT_LIMIT + } else { + (*query).limit + }; + let mut has_cursor: bool = + !cursor.is_null() && *cursor.offset(0isize) as ::core::ffi::c_int != 0; + let mut head: bool = has_cursor as ::core::ffi::c_int != 0 + && strcmp(cursor, b"head\0".as_ptr() as *const ::core::ffi::c_char) + == 0 as ::core::ffi::c_int; + let mut start: int64_t = 0 as int64_t; + let mut epoch: [::core::ffi::c_char; 37] = + ::core::mem::transmute::<[u8; 37], [::core::ffi::c_char; 37]>( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + if has_cursor as ::core::ffi::c_int != 0 + && !head + && observation_cursor_decode( + cursor, + &raw mut start, + &raw mut epoch as *mut ::core::ffi::c_char, + ) != C2Rust_Unnamed::DB_OBS_OK.0 + { + return C2Rust_Unnamed::DB_OBS_INVALID_CURSOR.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if ro_begin(&raw mut db) != C2Rust_Unnamed::DB_OBS_OK.0 { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut meta: observation_meta_t = observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }; + let mut rc: ::core::ffi::c_int = read_meta_on(db, &raw mut meta); + let mut watermark: int64_t = meta.head_seq; + if rc == C2Rust_Unnamed::DB_OBS_OK.0 + && has_cursor as ::core::ffi::c_int != 0 + && !head + && (strcmp( + &raw mut epoch as *mut ::core::ffi::c_char, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + || start < meta.oldest_seq - 1 as int64_t + || start > watermark) + { + rc = fill_expired(db, &raw mut meta, page); + ro_end(db); + return rc; + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && head as ::core::ffi::c_int != 0 { + rc = if observation_cursor_encode( + watermark, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + &raw mut (*page).next_cursor as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 257]>(), + ) == 0 as ::core::ffi::c_int + { + C2Rust_Unnamed::DB_OBS_OK.0 + } else { + C2Rust_Unnamed::DB_OBS_ERROR.0 + }; + ro_end(db); + return rc; + } + if !has_cursor { + start = meta.oldest_seq - 1 as int64_t; + } + let mut selected: *mut [::core::ffi::c_char; 37] = + ::core::ptr::null_mut::<[::core::ffi::c_char; 37]>(); + let mut selected_count: size_t = 0 as size_t; + let mut last_scanned: int64_t = start; + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + selected = calloc( + limit as size_t, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ) as *mut [::core::ffi::c_char; 37]; + if selected.is_null() { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = scan_page( + db, + query, + start, + watermark, + limit, + selected, + &raw mut selected_count, + &raw mut last_scanned, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = changes_exist(db, last_scanned, watermark, &raw mut (*page).has_more); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 + && observation_cursor_encode( + last_scanned, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + &raw mut (*page).next_cursor as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 257]>(), + ) != 0 as ::core::ffi::c_int + { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && selected_count > 0 as size_t { + (*page).items = calloc( + selected_count, + ::core::mem::size_of::(), + ) as *mut observation_record_t; + if (*page).items.is_null() { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + } + let mut record_stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut i: size_t = 0 as size_t; + while rc == C2Rust_Unnamed::DB_OBS_OK.0 && i < selected_count { + let mut got: ::core::ffi::c_int = get_record_on( + db, + &raw mut record_stmt, + &raw mut *selected.offset(i as isize) as *mut ::core::ffi::c_char, + (*page).items.offset((*page).count as isize), + ); + if got == C2Rust_Unnamed::DB_OBS_OK.0 { + (*page).count = (*page).count.wrapping_add(1); + } else if got != C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 { + rc = got; + } + i = i.wrapping_add(1); + } + sqlite3_finalize(record_stmt); + free(selected as *mut ::core::ffi::c_void); + ro_end(db); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + db_observation_page_free(page); + } + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_page_free(mut page: *mut observation_page_t) { + if page.is_null() { + return; + } + free((*page).items as *mut ::core::ffi::c_void); + (*page).items = ::core::ptr::null_mut::(); + (*page).count = 0 as size_t; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_pending_publications( + mut out: *mut observation_publication_t, + mut max: ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if out.is_null() || max <= 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut db: *mut sqlite3 = ::core::ptr::null_mut::(); + if ro_begin(&raw mut db) != C2Rust_Unnamed::DB_OBS_OK.0 { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + db, + b"SELECT p.change_seq, p.message_id, p.attempts, c.observation_uuid, c.revision, c.cause, c.evidence_state, c.evidence_reason, c.created_at_ms, o.camera_uuid, o.label, o.observed_at_ms, o.asset_uuid FROM observation_publications p JOIN observation_changes c ON c.seq = p.change_seq JOIN object_observations o ON o.uuid = c.observation_uuid WHERE p.state = 'pending' ORDER BY p.change_seq LIMIT ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int(stmt, 1 as ::core::ffi::c_int, max); + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if step != SQLITE_ROW { + break; + } + let mut r#pub: *mut observation_publication_t = out.offset(count as isize); + memset( + r#pub as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*r#pub).change_seq = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) + as int64_t; + copy_column( + stmt, + 1 as ::core::ffi::c_int, + &raw mut (*r#pub).message_id as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*r#pub).attempts = sqlite3_column_int(stmt, 2 as ::core::ffi::c_int); + copy_column( + stmt, + 3 as ::core::ffi::c_int, + &raw mut (*r#pub).observation_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + (*r#pub).revision = sqlite3_column_int(stmt, 4 as ::core::ffi::c_int); + let mut text: [::core::ffi::c_char; 16] = [0; 16]; + copy_column( + stmt, + 5 as ::core::ffi::c_int, + &raw mut text as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + if cause_parse( + &raw mut text as *mut ::core::ffi::c_char, + &raw mut (*r#pub).cause, + ) != 0 as ::core::ffi::c_int + { + continue; + } + copy_column( + stmt, + 6 as ::core::ffi::c_int, + &raw mut text as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + ); + if observation_asset_state_parse( + &raw mut text as *mut ::core::ffi::c_char, + &raw mut (*r#pub).evidence_state, + ) != 0 as ::core::ffi::c_int + { + continue; + } + copy_column( + stmt, + 7 as ::core::ffi::c_int, + &raw mut (*r#pub).evidence_reason as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 33]>(), + ); + (*r#pub).change_created_at_ms = sqlite3_column_int64( + stmt, + 8 as ::core::ffi::c_int, + ) as int64_t; + copy_column( + stmt, + 9 as ::core::ffi::c_int, + &raw mut (*r#pub).camera_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + copy_column( + stmt, + 10 as ::core::ffi::c_int, + &raw mut (*r#pub).label as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ); + (*r#pub).observed_at_ms = sqlite3_column_int64( + stmt, + 11 as ::core::ffi::c_int, + ) as int64_t; + copy_column( + stmt, + 12 as ::core::ffi::c_int, + &raw mut (*r#pub).asset_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ); + count += 1; + } + if step == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + ro_end(db); + return if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + count + } else { + rc + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observation_publication_mark( + mut change_seq: int64_t, + mut state: observation_publication_state_t, + mut last_error: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if change_seq <= 0 as int64_t || observation_publication_state_name(state).is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut error_text: [::core::ffi::c_char; 256] = ::core::mem::transmute::< + [u8; 256], + [::core::ffi::c_char; 256], + >( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + if !last_error.is_null() { + safe_strcpy( + &raw mut error_text as *mut ::core::ffi::c_char, + last_error, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + 0 as size_t, + ); + } + let mut now_ms: int64_t = current_time_ms(); + let mut tx: obs_tx_t = obs_tx_t { + mutex: ::core::ptr::null_mut::(), + db: ::core::ptr::null_mut::(), + connection_mutex: ::core::ptr::null_mut::(), + }; + if tx_begin(&raw mut tx) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + tx.db, + b"UPDATE observation_publications SET state = ?, attempts = attempts + 1, last_error = ?, updated_at_ms = ? WHERE change_seq = ? AND state = 'pending';\0" + .as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + observation_publication_state_name(state), + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + bind_text_or_null( + stmt, + 2 as ::core::ffi::c_int, + &raw mut error_text as *mut ::core::ffi::c_char, + ); + sqlite3_bind_int64(stmt, 3 as ::core::ffi::c_int, now_ms as sqlite3_int64); + sqlite3_bind_int64(stmt, 4 as ::core::ffi::c_int, change_seq as sqlite3_int64); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && sqlite3_changes(tx.db) == 0 as ::core::ffi::c_int { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + tx.db, + b"SELECT 1 FROM observation_publications WHERE change_seq = ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int64(stmt, 1 as ::core::ffi::c_int, change_seq as sqlite3_int64); + let mut step: ::core::ffi::c_int = sqlite3_step(stmt); + if step == SQLITE_ROW { + rc = C2Rust_Unnamed::DB_OBS_CAS_LOST.0; + } else if step == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_NOT_FOUND.0; + } + } + sqlite3_finalize(stmt); + tx_end(&raw mut tx, C2Rust_Unnamed::DB_OBS_ERROR.0); + return rc; + } + return tx_end(&raw mut tx, rc); +} +unsafe extern "C" fn collect_texts( + mut db: *mut sqlite3, + mut sql: *const ::core::ffi::c_char, + mut bound: int64_t, + mut out: *mut *mut [::core::ffi::c_char; 37], + mut count: *mut size_t, +) -> ::core::ffi::c_int { + *out = ::core::ptr::null_mut::<[::core::ffi::c_char; 37]>(); + *count = 0 as size_t; + let mut capacity: size_t = 0 as size_t; + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + sql, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_bind_int64(stmt, 1 as ::core::ffi::c_int, bound as sqlite3_int64); + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_OK.0; + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if step != SQLITE_ROW { + break; + } + if sqlite3_column_type(stmt, 0 as ::core::ffi::c_int) == SQLITE_NULL { + continue; + } + if *count == capacity { + let mut next: size_t = if capacity != 0 { + capacity.wrapping_mul(2 as size_t) + } else { + 64 as size_t + }; + let mut grown: *mut ::core::ffi::c_void = realloc( + *out as *mut ::core::ffi::c_void, + next.wrapping_mul(::core::mem::size_of::<[::core::ffi::c_char; 37]>()), + ); + if grown.is_null() { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + break; + } else { + *out = grown as *mut [::core::ffi::c_char; 37]; + capacity = next; + } + } + let c2rust_fresh3 = *count; + *count = (*count).wrapping_add(1); + copy_column( + stmt, + 0 as ::core::ffi::c_int, + &raw mut *(*out).offset(c2rust_fresh3 as isize) as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && step != SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } + sqlite3_finalize(stmt); + return rc; +} +unsafe extern "C" fn delete_each( + mut db: *mut sqlite3, + mut sql: *const ::core::ffi::c_char, + mut uuids: *mut [::core::ffi::c_char; 37], + mut count: size_t, + mut deleted: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + db, + sql, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) != SQLITE_OK + { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_OK.0; + let mut i: size_t = 0 as size_t; + while i < count && rc == C2Rust_Unnamed::DB_OBS_OK.0 { + sqlite3_reset(stmt); + sqlite3_bind_text( + stmt, + 1 as ::core::ffi::c_int, + &raw mut *uuids.offset(i as isize) as *mut ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + ::core::mem::transmute::< + ::libc::intptr_t, + Option ()>, + >(-1 as ::core::ffi::c_int as ::libc::intptr_t), + ); + if sqlite3_step(stmt) != SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + } else { + *deleted += sqlite3_changes(db); + } + i = i.wrapping_add(1); + } + sqlite3_finalize(stmt); + return rc; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn db_observations_prune( + mut before_ms: int64_t, + mut batch: ::core::ffi::c_int, + mut result: *mut observation_prune_result_t, +) -> ::core::ffi::c_int { + if result.is_null() { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + if batch <= 0 as ::core::ffi::c_int || batch > OBS_PRUNE_MAX_BATCH { + return C2Rust_Unnamed::DB_OBS_INVALID.0; + } + let mut tx: obs_tx_t = obs_tx_t { + mutex: ::core::ptr::null_mut::(), + db: ::core::ptr::null_mut::(), + connection_mutex: ::core::ptr::null_mut::(), + }; + if tx_begin(&raw mut tx) != 0 as ::core::ffi::c_int { + return C2Rust_Unnamed::DB_OBS_ERROR.0; + } + let mut rc: ::core::ffi::c_int = C2Rust_Unnamed::DB_OBS_ERROR.0; + let mut oldest_seq: int64_t = 1 as int64_t; + let mut stmt: *mut sqlite3_stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + tx.db, + b"SELECT oldest_seq FROM observation_meta WHERE id = 1;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + && sqlite3_step(stmt) == SQLITE_ROW + { + oldest_seq = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + sqlite3_finalize(stmt); + let mut last_pruned: int64_t = 0 as int64_t; + stmt = ::core::ptr::null_mut::(); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + if sqlite3_prepare_v2( + tx.db, + b"SELECT seq, created_at_ms FROM observation_changes ORDER BY seq LIMIT ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int(stmt, 1 as ::core::ffi::c_int, batch); + let mut step: ::core::ffi::c_int = 0; + loop { + step = sqlite3_step(stmt); + if !(step == SQLITE_ROW + && sqlite3_column_int64(stmt, 1 as ::core::ffi::c_int) + < before_ms as sqlite3_int64) + { + break; + } + last_pruned = sqlite3_column_int64(stmt, 0 as ::core::ffi::c_int) as int64_t; + } + if step == SQLITE_ROW || step == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + } + let mut observations: *mut [::core::ffi::c_char; 37] = + ::core::ptr::null_mut::<[::core::ffi::c_char; 37]>(); + let mut assets: *mut [::core::ffi::c_char; 37] = + ::core::ptr::null_mut::<[::core::ffi::c_char; 37]>(); + let mut observation_count: size_t = 0 as size_t; + let mut asset_count: size_t = 0 as size_t; + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && last_pruned > 0 as int64_t { + rc = collect_texts( + tx.db, + b"SELECT DISTINCT observation_uuid FROM observation_changes WHERE seq <= ?;\0".as_ptr() + as *const ::core::ffi::c_char, + last_pruned, + &raw mut observations, + &raw mut observation_count, + ); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = collect_texts( + tx.db, + b"SELECT DISTINCT o.asset_uuid FROM object_observations o JOIN observation_changes c ON c.observation_uuid = o.uuid WHERE c.seq <= ?;\0" + .as_ptr() as *const ::core::ffi::c_char, + last_pruned, + &raw mut assets, + &raw mut asset_count, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + tx.db, + b"DELETE FROM observation_changes WHERE seq <= ?;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int64(stmt, 1 as ::core::ffi::c_int, last_pruned as sqlite3_int64); + if sqlite3_step(stmt) == SQLITE_DONE { + (*result).changes_deleted = sqlite3_changes(tx.db); + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = delete_each( + tx.db, + b"DELETE FROM object_observations WHERE uuid = ?1 AND NOT EXISTS (SELECT 1 FROM observation_changes WHERE observation_uuid = ?1);\0" + .as_ptr() as *const ::core::ffi::c_char, + observations, + observation_count, + &raw mut (*result).observations_deleted, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + rc = delete_each( + tx.db, + b"DELETE FROM observation_assets WHERE uuid = ?1 AND state IN ('unavailable', 'expired') AND NOT EXISTS (SELECT 1 FROM object_observations WHERE asset_uuid = ?1);\0" + .as_ptr() as *const ::core::ffi::c_char, + assets, + asset_count, + &raw mut (*result).assets_deleted, + ); + } + if rc == C2Rust_Unnamed::DB_OBS_OK.0 && last_pruned + 1 as int64_t > oldest_seq { + rc = C2Rust_Unnamed::DB_OBS_ERROR.0; + stmt = ::core::ptr::null_mut::(); + if sqlite3_prepare_v2( + tx.db, + b"UPDATE observation_meta SET oldest_seq = ? WHERE id = 1;\0".as_ptr() + as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + &raw mut stmt, + ::core::ptr::null_mut::<*const ::core::ffi::c_char>(), + ) == SQLITE_OK + { + sqlite3_bind_int64( + stmt, + 1 as ::core::ffi::c_int, + (last_pruned + 1 as int64_t) as sqlite3_int64, + ); + if sqlite3_step(stmt) == SQLITE_DONE { + rc = C2Rust_Unnamed::DB_OBS_OK.0; + } + } + sqlite3_finalize(stmt); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + oldest_seq = last_pruned + 1 as int64_t; + } + } + } + free(observations as *mut ::core::ffi::c_void); + free(assets as *mut ::core::ffi::c_void); + rc = tx_end(&raw mut tx, rc); + if rc == C2Rust_Unnamed::DB_OBS_OK.0 { + (*result).oldest_seq = oldest_seq; + } else { + memset( + result as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + } + return rc; +} +pub const __ATOMIC_ACQUIRE: ::core::ffi::c_int = 2 as ::core::ffi::c_int; +pub const __ATOMIC_RELEASE: ::core::ffi::c_int = 3 as ::core::ffi::c_int; diff --git a/rust/generated/src__database__db_streams.rs b/rust/generated/src__database__db_streams.rs index 75ac96999..1a268c2eb 100644 --- a/rust/generated/src__database__db_streams.rs +++ b/rust/generated/src__database__db_streams.rs @@ -125,6 +125,7 @@ unsafe extern "C" { ) -> ::core::ffi::c_long; } pub type size_t = usize; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct __pthread_internal_list { @@ -224,6 +225,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -302,6 +307,7 @@ impl C2Rust_Unnamed { pub const COL_LOCATION_UUID: Self = Self(54); pub const COL_PLAYBACK_TRANSPORT: Self = Self(55); pub const COL_EPTZ_CONFIG: Self = Self(56); + pub const COL_OBSERVATION_EVIDENCE_ENABLED: Self = Self(57); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -364,6 +370,7 @@ impl C2Rust_Unnamed_0 { pub const COL_LOCATION_UUID: Self = Self(54); pub const COL_PLAYBACK_TRANSPORT: Self = Self(55); pub const COL_EPTZ_CONFIG: Self = Self(56); + pub const COL_OBSERVATION_EVIDENCE_ENABLED: Self = Self(57); } pub const SQLITE_OK: ::core::ffi::c_int = 0 as ::core::ffi::c_int; pub const SQLITE_ROW: ::core::ffi::c_int = 100 as ::core::ffi::c_int; @@ -374,6 +381,9 @@ pub const MAX_PATH_LENGTH: ::core::ffi::c_int = 512 as ::core::ffi::c_int; pub const MAX_STREAM_NAME: ::core::ffi::c_int = 256 as ::core::ffi::c_int; pub const CAMERA_UUID_STRING_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; pub const MAX_URL_LENGTH: ::core::ffi::c_int = 512 as ::core::ffi::c_int; +pub const OBSERVATION_DEFAULT_MAX_AGE_DAYS: ::core::ffi::c_int = 7 as ::core::ffi::c_int; +pub const OBSERVATION_DEFAULT_MAX_BYTES: ::core::ffi::c_longlong = + 2147483648 as ::core::ffi::c_longlong; pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; unsafe extern "C" fn serialize_recording_schedule( @@ -477,6 +487,82 @@ unsafe extern "C" fn normalized_playback_transport( b"auto\0".as_ptr() as *const ::core::ffi::c_char }; } +unsafe extern "C" fn normalized_observation_max_age_days( + mut value: ::core::ffi::c_int, +) -> ::core::ffi::c_int { + return if value > 0 as ::core::ffi::c_int { + value + } else { + OBSERVATION_DEFAULT_MAX_AGE_DAYS + }; +} +unsafe extern "C" fn normalized_observation_max_bytes(mut value: int64_t) -> sqlite3_int64 { + return if value > 0 as int64_t { + value as sqlite3_int64 + } else { + OBSERVATION_DEFAULT_MAX_BYTES + }; +} +unsafe extern "C" fn bind_observation_settings( + mut stmt: *mut sqlite3_stmt, + mut first: ::core::ffi::c_int, + mut stream: *const stream_config_t, +) { + sqlite3_bind_int( + stmt, + first, + if (*stream).observation_evidence_enabled as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }, + ); + sqlite3_bind_text( + stmt, + first + 1 as ::core::ffi::c_int, + &raw const (*stream).observation_labels as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + SQLITE_STATIC, + ); + sqlite3_bind_int( + stmt, + first + 2 as ::core::ffi::c_int, + normalized_observation_max_age_days((*stream).observation_max_age_days), + ); + sqlite3_bind_int64( + stmt, + first + 3 as ::core::ffi::c_int, + normalized_observation_max_bytes((*stream).observation_max_bytes), + ); +} +unsafe extern "C" fn load_observation_settings( + mut stmt: *mut sqlite3_stmt, + mut first: ::core::ffi::c_int, + mut stream: *mut stream_config_t, +) { + (*stream).observation_evidence_enabled = + sqlite3_column_int(stmt, first) != 0 as ::core::ffi::c_int; + let mut labels: *const ::core::ffi::c_char = + sqlite3_column_text(stmt, first + 1 as ::core::ffi::c_int) as *const ::core::ffi::c_char; + safe_strcpy( + &raw mut (*stream).observation_labels as *mut ::core::ffi::c_char, + if !labels.is_null() { + labels + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + (*stream).observation_max_age_days = normalized_observation_max_age_days(sqlite3_column_int( + stmt, + first + 2 as ::core::ffi::c_int, + )); + (*stream).observation_max_bytes = normalized_observation_max_bytes(sqlite3_column_int64( + stmt, + first + 3 as ::core::ffi::c_int, + ) as int64_t) as int64_t; +} unsafe extern "C" fn normalized_go2rtc_source_override( mut value: *const ::core::ffi::c_char, ) -> *const ::core::ffi::c_char { @@ -618,7 +704,7 @@ pub unsafe extern "C" fn add_stream_config(mut stream: *const stream_config_t) - sqlite3_finalize(check_stmt); check_stmt = ::core::ptr::null_mut::(); } - let mut update_sql: *const ::core::ffi::c_char = b"UPDATE streams SET url = ?, enabled = ?, streaming_enabled = ?, width = ?, height = ?, fps = ?, codec = ?, priority = ?, record = ?, segment_duration = ?, detection_based_recording = ?, detection_model = ?, detection_threshold = ?, detection_interval = ?, pre_detection_buffer = ?, post_detection_buffer = ?, detection_api_url = ?, detection_object_filter = ?, detection_object_filter_list = ?, protocol = ?, is_onvif = ?, record_audio = ?, backchannel_enabled = ?, retention_days = ?, detection_retention_days = ?, max_storage_mb = ?, tier_critical_multiplier = ?, tier_important_multiplier = ?, tier_ephemeral_multiplier = ?, storage_priority = ?, ptz_enabled = ?, ptz_max_x = ?, ptz_max_y = ?, ptz_max_z = ?, ptz_has_home = ?, onvif_username = ?, onvif_password = ?, onvif_profile = ?, onvif_port = ?, record_on_schedule = ?, recording_schedule = ?, tags = ?, admin_url = ?, privacy_mode = ?, motion_trigger_source = ?, go2rtc_source_override = ?, sub_stream_url = ?, audio_voice_enhancement = ?, detection_url = ?, publish_url = ?, detection_record_on_schedule = ?, detection_recording_schedule = ?, playback_transport = ?, eptz_config = ? WHERE id = ?;\0" + let mut update_sql: *const ::core::ffi::c_char = b"UPDATE streams SET url = ?, enabled = ?, streaming_enabled = ?, width = ?, height = ?, fps = ?, codec = ?, priority = ?, record = ?, segment_duration = ?, detection_based_recording = ?, detection_model = ?, detection_threshold = ?, detection_interval = ?, pre_detection_buffer = ?, post_detection_buffer = ?, detection_api_url = ?, detection_object_filter = ?, detection_object_filter_list = ?, protocol = ?, is_onvif = ?, record_audio = ?, backchannel_enabled = ?, retention_days = ?, detection_retention_days = ?, max_storage_mb = ?, tier_critical_multiplier = ?, tier_important_multiplier = ?, tier_ephemeral_multiplier = ?, storage_priority = ?, ptz_enabled = ?, ptz_max_x = ?, ptz_max_y = ?, ptz_max_z = ?, ptz_has_home = ?, onvif_username = ?, onvif_password = ?, onvif_profile = ?, onvif_port = ?, record_on_schedule = ?, recording_schedule = ?, tags = ?, admin_url = ?, privacy_mode = ?, motion_trigger_source = ?, go2rtc_source_override = ?, sub_stream_url = ?, audio_voice_enhancement = ?, detection_url = ?, publish_url = ?, detection_record_on_schedule = ?, detection_recording_schedule = ?, playback_transport = ?, eptz_config = ?, observation_evidence_enabled = ?, observation_labels = ?, observation_max_age_days = ?, observation_max_bytes = ? WHERE id = ?;\0" .as_ptr() as *const ::core::ffi::c_char; rc = sqlite3_prepare_v2( db, @@ -971,7 +1057,8 @@ pub unsafe extern "C" fn add_stream_config(mut stream: *const stream_config_t) - -1 as ::core::ffi::c_int, SQLITE_STATIC, ); - sqlite3_bind_int64(stmt, 55 as ::core::ffi::c_int, existing_id as sqlite3_int64); + bind_observation_settings(stmt, 55 as ::core::ffi::c_int, stream); + sqlite3_bind_int64(stmt, 59 as ::core::ffi::c_int, existing_id as sqlite3_int64); let mut owns_transaction: bool = r#false != 0; if !stream_transaction_begin(db, &raw mut owns_transaction) { _log_message_ctx( @@ -1065,7 +1152,7 @@ pub unsafe extern "C" fn add_stream_config(mut stream: *const stream_config_t) - sqlite3_finalize(check_stmt); check_stmt = ::core::ptr::null_mut::(); } - let mut sql: *const ::core::ffi::c_char = b"INSERT INTO streams (name, url, enabled, streaming_enabled, width, height, fps, codec, priority, record, segment_duration, detection_based_recording, detection_model, detection_threshold, detection_interval, pre_detection_buffer, post_detection_buffer, detection_api_url, detection_object_filter, detection_object_filter_list, protocol, is_onvif, record_audio, backchannel_enabled, retention_days, detection_retention_days, max_storage_mb, tier_critical_multiplier, tier_important_multiplier, tier_ephemeral_multiplier, storage_priority, ptz_enabled, ptz_max_x, ptz_max_y, ptz_max_z, ptz_has_home, onvif_username, onvif_password, onvif_profile, onvif_port, record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, detection_record_on_schedule, detection_recording_schedule, playback_transport, eptz_config, camera_uuid, location_uuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)), 2) || '-' || substr('89ab', (abs(random()) % 4) + 1, 1) || substr(hex(randomblob(2)), 2) || '-' || hex(randomblob(6))), (SELECT uuid FROM camera_locations WHERE is_system = 1 LIMIT 1));\0" + let mut sql: *const ::core::ffi::c_char = b"INSERT INTO streams (name, url, enabled, streaming_enabled, width, height, fps, codec, priority, record, segment_duration, detection_based_recording, detection_model, detection_threshold, detection_interval, pre_detection_buffer, post_detection_buffer, detection_api_url, detection_object_filter, detection_object_filter_list, protocol, is_onvif, record_audio, backchannel_enabled, retention_days, detection_retention_days, max_storage_mb, tier_critical_multiplier, tier_important_multiplier, tier_ephemeral_multiplier, storage_priority, ptz_enabled, ptz_max_x, ptz_max_y, ptz_max_z, ptz_has_home, onvif_username, onvif_password, onvif_profile, onvif_port, record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, detection_record_on_schedule, detection_recording_schedule, playback_transport, eptz_config, observation_evidence_enabled, observation_labels, observation_max_age_days, observation_max_bytes, camera_uuid, location_uuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)), 2) || '-' || substr('89ab', (abs(random()) % 4) + 1, 1) || substr(hex(randomblob(2)), 2) || '-' || hex(randomblob(6))), (SELECT uuid FROM camera_locations WHERE is_system = 1 LIMIT 1));\0" .as_ptr() as *const ::core::ffi::c_char; rc = sqlite3_prepare_v2( db, @@ -1424,6 +1511,7 @@ pub unsafe extern "C" fn add_stream_config(mut stream: *const stream_config_t) - -1 as ::core::ffi::c_int, SQLITE_STATIC, ); + bind_observation_settings(stmt, 56 as ::core::ffi::c_int, stream); let mut owns_transaction_0: bool = r#false != 0; if !stream_transaction_begin(db, &raw mut owns_transaction_0) { _log_message_ctx( @@ -1602,7 +1690,7 @@ pub unsafe extern "C" fn update_stream_config( return -1 as ::core::ffi::c_int; } pthread_mutex_lock(db_mutex); - let mut sql: *const ::core::ffi::c_char = b"UPDATE streams SET name = ?, url = ?, enabled = ?, streaming_enabled = ?, width = ?, height = ?, fps = ?, codec = ?, priority = ?, record = ?, segment_duration = ?, detection_based_recording = ?, detection_model = ?, detection_threshold = ?, detection_interval = ?, pre_detection_buffer = ?, post_detection_buffer = ?, detection_api_url = ?, detection_object_filter = ?, detection_object_filter_list = ?, protocol = ?, is_onvif = ?, record_audio = ?, backchannel_enabled = ?, retention_days = ?, detection_retention_days = ?, max_storage_mb = ?, tier_critical_multiplier = ?, tier_important_multiplier = ?, tier_ephemeral_multiplier = ?, storage_priority = ?, ptz_enabled = ?, ptz_max_x = ?, ptz_max_y = ?, ptz_max_z = ?, ptz_has_home = ?, onvif_username = ?, onvif_password = ?, onvif_profile = ?, onvif_port = ?, record_on_schedule = ?, recording_schedule = ?, tags = ?, admin_url = ?, privacy_mode = COALESCE(?, privacy_mode), motion_trigger_source = ?, go2rtc_source_override = ?, sub_stream_url = ?, audio_voice_enhancement = ?, detection_url = ?, publish_url = ?, detection_record_on_schedule = ?, detection_recording_schedule = ?, playback_transport = ?, eptz_config = ? WHERE name = ?;\0" + let mut sql: *const ::core::ffi::c_char = b"UPDATE streams SET name = ?, url = ?, enabled = ?, streaming_enabled = ?, width = ?, height = ?, fps = ?, codec = ?, priority = ?, record = ?, segment_duration = ?, detection_based_recording = ?, detection_model = ?, detection_threshold = ?, detection_interval = ?, pre_detection_buffer = ?, post_detection_buffer = ?, detection_api_url = ?, detection_object_filter = ?, detection_object_filter_list = ?, protocol = ?, is_onvif = ?, record_audio = ?, backchannel_enabled = ?, retention_days = ?, detection_retention_days = ?, max_storage_mb = ?, tier_critical_multiplier = ?, tier_important_multiplier = ?, tier_ephemeral_multiplier = ?, storage_priority = ?, ptz_enabled = ?, ptz_max_x = ?, ptz_max_y = ?, ptz_max_z = ?, ptz_has_home = ?, onvif_username = ?, onvif_password = ?, onvif_profile = ?, onvif_port = ?, record_on_schedule = ?, recording_schedule = ?, tags = ?, admin_url = ?, privacy_mode = COALESCE(?, privacy_mode), motion_trigger_source = ?, go2rtc_source_override = ?, sub_stream_url = ?, audio_voice_enhancement = ?, detection_url = ?, publish_url = ?, detection_record_on_schedule = ?, detection_recording_schedule = ?, playback_transport = ?, eptz_config = ?, observation_evidence_enabled = ?, observation_labels = ?, observation_max_age_days = ?, observation_max_bytes = ? WHERE name = ?;\0" .as_ptr() as *const ::core::ffi::c_char; rc = sqlite3_prepare_v2( db, @@ -1953,9 +2041,10 @@ pub unsafe extern "C" fn update_stream_config( -1 as ::core::ffi::c_int, SQLITE_STATIC, ); + bind_observation_settings(stmt, 56 as ::core::ffi::c_int, stream); sqlite3_bind_text( stmt, - 56 as ::core::ffi::c_int, + 60 as ::core::ffi::c_int, name, -1 as ::core::ffi::c_int, SQLITE_STATIC, @@ -2408,7 +2497,7 @@ pub unsafe extern "C" fn get_stream_config_by_name( return -1 as ::core::ffi::c_int; } pthread_mutex_lock(db_mutex); - let mut sql: *const ::core::ffi::c_char = b"SELECT name, url, enabled, streaming_enabled, width, height, fps, codec, priority, record, segment_duration, detection_based_recording, detection_model, detection_threshold, detection_interval, pre_detection_buffer, post_detection_buffer, detection_api_url, detection_object_filter, detection_object_filter_list, protocol, is_onvif, record_audio, backchannel_enabled, retention_days, detection_retention_days, max_storage_mb, tier_critical_multiplier, tier_important_multiplier, tier_ephemeral_multiplier, storage_priority, ptz_enabled, ptz_max_x, ptz_max_y, ptz_max_z, ptz_has_home, onvif_username, onvif_password, onvif_profile, onvif_port, record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config FROM streams WHERE name = ?;\0" + let mut sql: *const ::core::ffi::c_char = b"SELECT name, url, enabled, streaming_enabled, width, height, fps, codec, priority, record, segment_duration, detection_based_recording, detection_model, detection_threshold, detection_interval, pre_detection_buffer, post_detection_buffer, detection_api_url, detection_object_filter, detection_object_filter_list, protocol, is_onvif, record_audio, backchannel_enabled, retention_days, detection_retention_days, max_storage_mb, tier_critical_multiplier, tier_important_multiplier, tier_ephemeral_multiplier, storage_priority, ptz_enabled, ptz_max_x, ptz_max_y, ptz_max_z, ptz_has_home, onvif_username, onvif_password, onvif_profile, onvif_port, record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config, observation_evidence_enabled, observation_labels, observation_max_age_days, observation_max_bytes FROM streams WHERE name = ?;\0" .as_ptr() as *const ::core::ffi::c_char; rc = sqlite3_prepare_v2( db, @@ -2976,6 +3065,11 @@ pub unsafe extern "C" fn get_stream_config_by_name( ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), 0 as size_t, ); + load_observation_settings( + stmt, + C2Rust_Unnamed::COL_OBSERVATION_EVIDENCE_ENABLED.0 as ::core::ffi::c_int, + stream, + ); result = 0 as ::core::ffi::c_int; } if !stmt.is_null() { @@ -3346,7 +3440,7 @@ unsafe extern "C" fn get_all_stream_configs_on_connection( ); return -1 as ::core::ffi::c_int; } - let mut sql: *const ::core::ffi::c_char = b"SELECT name, url, enabled, streaming_enabled, width, height, fps, codec, priority, record, segment_duration, detection_based_recording, detection_model, detection_threshold, detection_interval, pre_detection_buffer, post_detection_buffer, detection_api_url, detection_object_filter, detection_object_filter_list, protocol, is_onvif, record_audio, backchannel_enabled, retention_days, detection_retention_days, max_storage_mb, tier_critical_multiplier, tier_important_multiplier, tier_ephemeral_multiplier, storage_priority, ptz_enabled, ptz_max_x, ptz_max_y, ptz_max_z, ptz_has_home, onvif_username, onvif_password, onvif_profile, onvif_port, record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config FROM streams ORDER BY name;\0" + let mut sql: *const ::core::ffi::c_char = b"SELECT name, url, enabled, streaming_enabled, width, height, fps, codec, priority, record, segment_duration, detection_based_recording, detection_model, detection_threshold, detection_interval, pre_detection_buffer, post_detection_buffer, detection_api_url, detection_object_filter, detection_object_filter_list, protocol, is_onvif, record_audio, backchannel_enabled, retention_days, detection_retention_days, max_storage_mb, tier_critical_multiplier, tier_important_multiplier, tier_ephemeral_multiplier, storage_priority, ptz_enabled, ptz_max_x, ptz_max_y, ptz_max_z, ptz_has_home, onvif_username, onvif_password, onvif_profile, onvif_port, record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config, observation_evidence_enabled, observation_labels, observation_max_age_days, observation_max_bytes FROM streams ORDER BY name;\0" .as_ptr() as *const ::core::ffi::c_char; rc = sqlite3_prepare_v2( db, @@ -3923,6 +4017,11 @@ unsafe extern "C" fn get_all_stream_configs_on_connection( ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), 0 as size_t, ); + load_observation_settings( + stmt, + C2Rust_Unnamed_0::COL_OBSERVATION_EVIDENCE_ENABLED.0 as ::core::ffi::c_int, + s, + ); count += 1; } if !stmt.is_null() { diff --git a/rust/generated/src__storage__storage_deletion.rs b/rust/generated/src__storage__storage_deletion.rs index 70af9539d..9711f4666 100644 --- a/rust/generated/src__storage__storage_deletion.rs +++ b/rust/generated/src__storage__storage_deletion.rs @@ -367,7 +367,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -474,6 +474,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__storage__storage_manager.rs b/rust/generated/src__storage__storage_manager.rs index 6529cef0e..0d82ffbfb 100644 --- a/rust/generated/src__storage__storage_manager.rs +++ b/rust/generated/src__storage__storage_manager.rs @@ -239,6 +239,7 @@ unsafe extern "C" { payload: *const ::core::ffi::c_char, retain: bool, ) -> ::core::ffi::c_int; + unsafe fn observation_retention_maintenance_tick(); unsafe fn sanitize_stream_name( input: *const ::core::ffi::c_char, output: *mut ::core::ffi::c_char, @@ -508,6 +509,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -613,7 +618,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -3888,6 +3893,10 @@ unsafe extern "C" fn standard_cleanup_cycle() { publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; 's_81: { if s >= configured_count { @@ -4245,6 +4254,7 @@ unsafe extern "C" fn unified_storage_controller_func( ); } cleanup_pressured_targets(); + observation_retention_maintenance_tick(); unified_ctrl.last_heartbeat = now; if do_force { _log_message_ctx( diff --git a/rust/generated/src__storage__storage_manager_streams_cache.rs b/rust/generated/src__storage__storage_manager_streams_cache.rs index 7d26c54b3..631276d41 100644 --- a/rust/generated/src__storage__storage_manager_streams_cache.rs +++ b/rust/generated/src__storage__storage_manager_streams_cache.rs @@ -55,6 +55,7 @@ unsafe extern "C" { pub type size_t = usize; pub type __time_t = ::core::ffi::c_long; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct __pthread_internal_list { @@ -204,6 +205,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -309,7 +314,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__storage__storage_source.rs b/rust/generated/src__storage__storage_source.rs index 4af5acebe..6f6328466 100644 --- a/rust/generated/src__storage__storage_source.rs +++ b/rust/generated/src__storage__storage_source.rs @@ -330,6 +330,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -435,7 +439,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__utils__base64.rs b/rust/generated/src__utils__base64.rs index 29dcc7771..037f1d64a 100644 --- a/rust/generated/src__utils__base64.rs +++ b/rust/generated/src__utils__base64.rs @@ -141,4 +141,175 @@ pub unsafe extern "C" fn base64_encode_alloc( } return encoded; } +static mut BASE64URL_ALPHABET: [::core::ffi::c_char; 65] = unsafe { + ::core::mem::transmute::<[u8; 65], [::core::ffi::c_char; 65]>( + *b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\0", + ) +}; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn base64url_encode( + mut input: *const ::core::ffi::c_uchar, + mut input_len: size_t, + mut dst: *mut ::core::ffi::c_char, + mut dst_size: size_t, + mut out_len: *mut size_t, +) -> ::core::ffi::c_int { + if !out_len.is_null() { + *out_len = 0 as size_t; + } + if dst.is_null() || dst_size == 0 as size_t || input.is_null() && input_len > 0 as size_t { + return -1 as ::core::ffi::c_int; + } + if input_len + > (SIZE_MAX as size_t) + .wrapping_sub(3 as size_t) + .wrapping_div(4 as size_t) + { + return -1 as ::core::ffi::c_int; + } + let mut needed: size_t = input_len + .wrapping_mul(4 as size_t) + .wrapping_add(2 as size_t) + .wrapping_div(3 as size_t) + .wrapping_add(1 as size_t); + if dst_size < needed { + return -1 as ::core::ffi::c_int; + } + let mut written: size_t = 0 as size_t; + let mut i: size_t = 0 as size_t; + while i.wrapping_add(2 as size_t) < input_len { + let mut triple: uint32_t = (*input.offset(i as isize) as uint32_t) + << 16 as ::core::ffi::c_int + | (*input.offset(i.wrapping_add(1 as size_t) as isize) as uint32_t) + << 8 as ::core::ffi::c_int + | *input.offset(i.wrapping_add(2 as size_t) as isize) as uint32_t; + let c2rust_fresh12 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh12 as isize) = + BASE64URL_ALPHABET[(triple >> 18 as ::core::ffi::c_int & 0x3f as uint32_t) as usize]; + let c2rust_fresh13 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh13 as isize) = + BASE64URL_ALPHABET[(triple >> 12 as ::core::ffi::c_int & 0x3f as uint32_t) as usize]; + let c2rust_fresh14 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh14 as isize) = + BASE64URL_ALPHABET[(triple >> 6 as ::core::ffi::c_int & 0x3f as uint32_t) as usize]; + let c2rust_fresh15 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh15 as isize) = + BASE64URL_ALPHABET[(triple & 0x3f as uint32_t) as usize]; + i = i.wrapping_add(3 as size_t); + } + let mut remaining: size_t = input_len.wrapping_sub(i); + if remaining > 0 as size_t { + let mut triple_0: uint32_t = + (*input.offset(i as isize) as uint32_t) << 16 as ::core::ffi::c_int; + if remaining == 2 as size_t { + triple_0 |= (*input.offset(i.wrapping_add(1 as size_t) as isize) as uint32_t) + << 8 as ::core::ffi::c_int; + } + let c2rust_fresh16 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh16 as isize) = + BASE64URL_ALPHABET[(triple_0 >> 18 as ::core::ffi::c_int & 0x3f as uint32_t) as usize]; + let c2rust_fresh17 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh17 as isize) = + BASE64URL_ALPHABET[(triple_0 >> 12 as ::core::ffi::c_int & 0x3f as uint32_t) as usize]; + if remaining == 2 as size_t { + let c2rust_fresh18 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh18 as isize) = BASE64URL_ALPHABET + [(triple_0 >> 6 as ::core::ffi::c_int & 0x3f as uint32_t) as usize]; + } + } + *dst.offset(written as isize) = '\0' as ::core::ffi::c_char; + if !out_len.is_null() { + *out_len = written; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn base64url_value(mut c: ::core::ffi::c_char) -> ::core::ffi::c_int { + if c as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'Z' as ::core::ffi::c_int + { + return c as ::core::ffi::c_int - 'A' as ::core::ffi::c_int; + } + if c as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'z' as ::core::ffi::c_int + { + return c as ::core::ffi::c_int - 'a' as ::core::ffi::c_int + 26 as ::core::ffi::c_int; + } + if c as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= '9' as ::core::ffi::c_int + { + return c as ::core::ffi::c_int - '0' as ::core::ffi::c_int + 52 as ::core::ffi::c_int; + } + if c as ::core::ffi::c_int == '-' as ::core::ffi::c_int { + return 62 as ::core::ffi::c_int; + } + if c as ::core::ffi::c_int == '_' as ::core::ffi::c_int { + return 63 as ::core::ffi::c_int; + } + return -1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn base64url_decode( + mut src: *const ::core::ffi::c_char, + mut src_len: size_t, + mut dst: *mut ::core::ffi::c_uchar, + mut dst_size: size_t, + mut out_len: *mut size_t, +) -> ::core::ffi::c_int { + if !out_len.is_null() { + *out_len = 0 as size_t; + } + if src.is_null() && src_len > 0 as size_t || dst.is_null() && src_len > 0 as size_t { + return -1 as ::core::ffi::c_int; + } + if src_len.wrapping_rem(4 as size_t) == 1 as size_t { + return -1 as ::core::ffi::c_int; + } + let mut needed: size_t = src_len + .wrapping_div(4 as size_t) + .wrapping_mul(3 as size_t) + .wrapping_add(if src_len.wrapping_rem(4 as size_t) != 0 { + src_len.wrapping_rem(4 as size_t).wrapping_sub(1 as size_t) + } else { + 0 as size_t + }); + if needed > dst_size { + return -1 as ::core::ffi::c_int; + } + let mut written: size_t = 0 as size_t; + let mut buffer: uint32_t = 0 as uint32_t; + let mut bits: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: size_t = 0 as size_t; + while i < src_len { + let mut value: ::core::ffi::c_int = base64url_value(*src.offset(i as isize)); + if value < 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + buffer = buffer << 6 as ::core::ffi::c_int | value as uint32_t; + bits += 6 as ::core::ffi::c_int; + if bits >= 8 as ::core::ffi::c_int { + bits -= 8 as ::core::ffi::c_int; + let c2rust_fresh19 = written; + written = written.wrapping_add(1); + *dst.offset(c2rust_fresh19 as isize) = + (buffer >> bits & 0xff as uint32_t) as ::core::ffi::c_uchar; + } + i = i.wrapping_add(1); + } + if bits > 0 as ::core::ffi::c_int + && buffer & ((1 as uint32_t) << bits).wrapping_sub(1 as uint32_t) != 0 as uint32_t + { + return -1 as ::core::ffi::c_int; + } + if !out_len.is_null() { + *out_len = written; + } + return 0 as ::core::ffi::c_int; +} pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); diff --git a/rust/generated/src__utils__uuid.rs b/rust/generated/src__utils__uuid.rs index c742f3391..dc855594a 100644 --- a/rust/generated/src__utils__uuid.rs +++ b/rust/generated/src__utils__uuid.rs @@ -3,6 +3,7 @@ unsafe extern "C" { pub type _IO_wide_data; pub type _IO_codecvt; pub type _IO_marker; + pub type mbedtls_md_info_t; unsafe fn __ctype_b_loc() -> *mut *const ::core::ffi::c_ushort; unsafe fn __errno_location() -> *mut ::core::ffi::c_int; unsafe fn fclose(__stream: *mut FILE) -> ::core::ffi::c_int; @@ -21,6 +22,24 @@ unsafe extern "C" { __length: size_t, __flags: ::core::ffi::c_uint, ) -> ssize_t; + unsafe fn mbedtls_md_info_from_type(md_type: mbedtls_md_type_t) -> *const mbedtls_md_info_t; + unsafe fn mbedtls_md_init(ctx: *mut mbedtls_md_context_t); + unsafe fn mbedtls_md_free(ctx: *mut mbedtls_md_context_t); + unsafe fn mbedtls_md_setup( + ctx: *mut mbedtls_md_context_t, + md_info: *const mbedtls_md_info_t, + hmac: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn mbedtls_md_starts(ctx: *mut mbedtls_md_context_t) -> ::core::ffi::c_int; + unsafe fn mbedtls_md_update( + ctx: *mut mbedtls_md_context_t, + input: *const ::core::ffi::c_uchar, + ilen: size_t, + ) -> ::core::ffi::c_int; + unsafe fn mbedtls_md_finish( + ctx: *mut mbedtls_md_context_t, + output: *mut ::core::ffi::c_uchar, + ) -> ::core::ffi::c_int; unsafe fn strnlen(__string: *const ::core::ffi::c_char, __maxlen: size_t) -> size_t; } pub type size_t = usize; @@ -69,6 +88,30 @@ pub type _IO_lock_t = (); pub type FILE = _IO_FILE; #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] +pub struct mbedtls_md_type_t(pub ::core::ffi::c_uint); +impl mbedtls_md_type_t { + pub const MBEDTLS_MD_NONE: Self = Self(0); + pub const MBEDTLS_MD_MD5: Self = Self(3); + pub const MBEDTLS_MD_RIPEMD160: Self = Self(4); + pub const MBEDTLS_MD_SHA1: Self = Self(5); + pub const MBEDTLS_MD_SHA224: Self = Self(8); + pub const MBEDTLS_MD_SHA256: Self = Self(9); + pub const MBEDTLS_MD_SHA384: Self = Self(10); + pub const MBEDTLS_MD_SHA512: Self = Self(11); + pub const MBEDTLS_MD_SHA3_224: Self = Self(16); + pub const MBEDTLS_MD_SHA3_256: Self = Self(17); + pub const MBEDTLS_MD_SHA3_384: Self = Self(18); + pub const MBEDTLS_MD_SHA3_512: Self = Self(19); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct mbedtls_md_context_t { + pub private_md_info: *const mbedtls_md_info_t, + pub private_md_ctx: *mut ::core::ffi::c_void, + pub private_hmac_ctx: *mut ::core::ffi::c_void, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] pub struct C2Rust_Unnamed(pub ::core::ffi::c_uint); impl C2Rust_Unnamed { pub const _ISupper: Self = Self(256); @@ -204,5 +247,123 @@ pub unsafe extern "C" fn lightnvr_uuid_generate_v4( *output.offset(position as isize) = '\0' as ::core::ffi::c_char; return 0 as ::core::ffi::c_int; } +unsafe extern "C" fn hex_value(mut c: ::core::ffi::c_char) -> ::core::ffi::c_int { + if c as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= '9' as ::core::ffi::c_int + { + return c as ::core::ffi::c_int - '0' as ::core::ffi::c_int; + } + if c as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'f' as ::core::ffi::c_int + { + return c as ::core::ffi::c_int - 'a' as ::core::ffi::c_int + 10 as ::core::ffi::c_int; + } + if c as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'F' as ::core::ffi::c_int + { + return c as ::core::ffi::c_int - 'A' as ::core::ffi::c_int + 10 as ::core::ffi::c_int; + } + return -1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn lightnvr_uuid_generate_v5( + mut namespace_uuid: *const ::core::ffi::c_char, + mut name: *const ::core::ffi::c_char, + mut name_len: size_t, + mut output: *mut ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if output.is_null() || name.is_null() || !lightnvr_uuid_is_valid(namespace_uuid) { + return -1 as ::core::ffi::c_int; + } + let mut namespace_bytes: [::core::ffi::c_uchar; 16] = [0; 16]; + let mut byte: size_t = 0 as size_t; + let mut index: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while index < 36 as ::core::ffi::c_int { + if !(index == 8 as ::core::ffi::c_int + || index == 13 as ::core::ffi::c_int + || index == 18 as ::core::ffi::c_int + || index == 23 as ::core::ffi::c_int) + { + let mut high: ::core::ffi::c_int = hex_value(*namespace_uuid.offset(index as isize)); + index += 1; + let mut low: ::core::ffi::c_int = hex_value(*namespace_uuid.offset(index as isize)); + let c2rust_fresh3 = byte; + byte = byte.wrapping_add(1); + namespace_bytes[c2rust_fresh3] = + (high << 4 as ::core::ffi::c_int | low) as ::core::ffi::c_uchar; + } + index += 1; + } + let mut sha1: *const mbedtls_md_info_t = + mbedtls_md_info_from_type(mbedtls_md_type_t::MBEDTLS_MD_SHA1); + if sha1.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut context: mbedtls_md_context_t = mbedtls_md_context_t { + private_md_info: ::core::ptr::null::(), + private_md_ctx: ::core::ptr::null_mut::<::core::ffi::c_void>(), + private_hmac_ctx: ::core::ptr::null_mut::<::core::ffi::c_void>(), + }; + mbedtls_md_init(&raw mut context); + let mut digest: [::core::ffi::c_uchar; 20] = [0; 20]; + let mut rc: ::core::ffi::c_int = + mbedtls_md_setup(&raw mut context, sha1, 0 as ::core::ffi::c_int); + if rc == 0 as ::core::ffi::c_int { + rc = mbedtls_md_starts(&raw mut context); + } + if rc == 0 as ::core::ffi::c_int { + rc = mbedtls_md_update( + &raw mut context, + &raw mut namespace_bytes as *mut ::core::ffi::c_uchar, + ::core::mem::size_of::<[::core::ffi::c_uchar; 16]>(), + ); + } + if rc == 0 as ::core::ffi::c_int { + rc = mbedtls_md_update( + &raw mut context, + name as *const ::core::ffi::c_uchar, + name_len, + ); + } + if rc == 0 as ::core::ffi::c_int { + rc = mbedtls_md_finish( + &raw mut context, + &raw mut digest as *mut ::core::ffi::c_uchar, + ); + } + mbedtls_md_free(&raw mut context); + if rc != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + digest[6usize] = (digest[6usize] as ::core::ffi::c_uint & 0xf as ::core::ffi::c_uint + | 0x50 as ::core::ffi::c_uint) as ::core::ffi::c_uchar; + digest[8usize] = (digest[8usize] as ::core::ffi::c_uint & 0x3f as ::core::ffi::c_uint + | 0x80 as ::core::ffi::c_uint) as ::core::ffi::c_uchar; + let mut position: size_t = 0 as size_t; + let mut index_0: size_t = 0 as size_t; + while index_0 < 16 as size_t { + if index_0 == 4 as size_t + || index_0 == 6 as size_t + || index_0 == 8 as size_t + || index_0 == 10 as size_t + { + let c2rust_fresh4 = position; + position = position.wrapping_add(1); + *output.offset(c2rust_fresh4 as isize) = '-' as ::core::ffi::c_char; + } + let c2rust_fresh5 = position; + position = position.wrapping_add(1); + *output.offset(c2rust_fresh5 as isize) = hex_digit( + (digest[index_0] as ::core::ffi::c_int >> 4 as ::core::ffi::c_int) + as ::core::ffi::c_uchar, + ); + let c2rust_fresh6 = position; + position = position.wrapping_add(1); + *output.offset(c2rust_fresh6 as isize) = hex_digit(digest[index_0]); + index_0 = index_0.wrapping_add(1); + } + *output.offset(position as isize) = '\0' as ::core::ffi::c_char; + return 0 as ::core::ffi::c_int; +} pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; diff --git a/rust/generated/src__utils__webrtc_candidates.rs b/rust/generated/src__utils__webrtc_candidates.rs new file mode 100644 index 000000000..5a2f884d1 --- /dev/null +++ b/rust/generated/src__utils__webrtc_candidates.rs @@ -0,0 +1,363 @@ +unsafe extern "C" { + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn memchr( + __s: *const ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strncasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + __n: size_t, + ) -> ::core::ffi::c_int; +} +pub type size_t = usize; +pub const GO2RTC_CANDIDATE_SIZE: ::core::ffi::c_int = 128 as ::core::ffi::c_int; +unsafe extern "C" fn candidate_port_ok(mut p: *const ::core::ffi::c_char, mut n: size_t) -> bool { + if n == 0 as size_t || n > 5 as size_t { + return r#false != 0; + } + let mut v: ::core::ffi::c_long = 0 as ::core::ffi::c_long; + let mut i: size_t = 0 as size_t; + while i < n { + if (*p.offset(i as isize) as ::core::ffi::c_int) < '0' as ::core::ffi::c_int + || *p.offset(i as isize) as ::core::ffi::c_int > '9' as ::core::ffi::c_int + { + return r#false != 0; + } + v = v * 10 as ::core::ffi::c_long + + (*p.offset(i as isize) as ::core::ffi::c_int - '0' as ::core::ffi::c_int) + as ::core::ffi::c_long; + i = i.wrapping_add(1); + } + return v >= 1 as ::core::ffi::c_long && v <= 65535 as ::core::ffi::c_long; +} +unsafe extern "C" fn candidate_host_ok(mut h: *const ::core::ffi::c_char, mut n: size_t) -> bool { + if n == 0 as size_t || n > 253 as size_t { + return r#false != 0; + } + let mut label: size_t = 0 as size_t; + let mut i: size_t = 0 as size_t; + while i < n { + let mut c: ::core::ffi::c_char = *h.offset(i as isize); + if c as ::core::ffi::c_int == '.' as ::core::ffi::c_int { + if label == 0 as size_t + || *h.offset(i.wrapping_sub(1 as size_t) as isize) as ::core::ffi::c_int + == '-' as ::core::ffi::c_int + { + return r#false != 0; + } + label = 0 as size_t; + } else { + let mut alnum: bool = c as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'z' as ::core::ffi::c_int + || c as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'Z' as ::core::ffi::c_int + || c as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= '9' as ::core::ffi::c_int; + if !alnum && c as ::core::ffi::c_int != '-' as ::core::ffi::c_int { + return r#false != 0; + } + if c as ::core::ffi::c_int == '-' as ::core::ffi::c_int && label == 0 as size_t { + return r#false != 0; + } + label = label.wrapping_add(1); + if label > 63 as size_t { + return r#false != 0; + } + } + i = i.wrapping_add(1); + } + return label > 0 as size_t + && *h.offset(n.wrapping_sub(1 as size_t) as isize) as ::core::ffi::c_int + != '-' as ::core::ffi::c_int; +} +unsafe extern "C" fn candidate_ipv6_ok(mut h: *const ::core::ffi::c_char, mut n: size_t) -> bool { + if n < 2 as size_t || n > 45 as size_t { + return r#false != 0; + } + let mut colons: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: size_t = 0 as size_t; + while i < n { + let mut c: ::core::ffi::c_char = *h.offset(i as isize); + if c as ::core::ffi::c_int == ':' as ::core::ffi::c_int { + colons += 1; + } else { + let mut hex: bool = c as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= '9' as ::core::ffi::c_int + || c as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'f' as ::core::ffi::c_int + || c as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'F' as ::core::ffi::c_int; + if !hex && c as ::core::ffi::c_int != '.' as ::core::ffi::c_int { + return r#false != 0; + } + } + i = i.wrapping_add(1); + } + return colons >= 2 as ::core::ffi::c_int; +} +unsafe extern "C" fn candidate_address( + mut e: *const ::core::ffi::c_char, + mut n: size_t, + mut default_port: ::core::ffi::c_int, + mut port_optional_out: bool, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, +) -> bool { + let mut host: *const ::core::ffi::c_char = e; + let mut port: *const ::core::ffi::c_char = ::core::ptr::null::<::core::ffi::c_char>(); + let mut host_len: size_t = n; + let mut port_len: size_t = 0 as size_t; + let mut v6: bool = r#false != 0; + if n > 0 as size_t && *e.offset(0isize) as ::core::ffi::c_int == '[' as ::core::ffi::c_int { + let mut close: *const ::core::ffi::c_char = memchr( + e as *const ::core::ffi::c_void, + ']' as ::core::ffi::c_int, + n, + ) as *const ::core::ffi::c_char; + if close.is_null() { + return r#false != 0; + } + host = e.offset(1 as ::core::ffi::c_int as isize); + host_len = close.offset_from(host) as size_t; + let mut rest: size_t = n + .wrapping_sub(close.offset_from(e) as size_t) + .wrapping_sub(1 as size_t); + if rest > 0 as size_t { + if *close.offset(1isize) as ::core::ffi::c_int != ':' as ::core::ffi::c_int { + return r#false != 0; + } + port = close.offset(2 as ::core::ffi::c_int as isize); + port_len = rest.wrapping_sub(1 as size_t); + if port_len == 0 as size_t { + return r#false != 0; + } + } + if !candidate_ipv6_ok(host, host_len) { + return r#false != 0; + } + v6 = r#true != 0; + } else { + let mut first: *const ::core::ffi::c_char = memchr( + e as *const ::core::ffi::c_void, + ':' as ::core::ffi::c_int, + n, + ) as *const ::core::ffi::c_char; + if !first.is_null() + && !memchr( + first.offset(1 as ::core::ffi::c_int as isize) as *const ::core::ffi::c_void, + ':' as ::core::ffi::c_int, + n.wrapping_sub(first.offset_from(e) as size_t) + .wrapping_sub(1 as size_t), + ) + .is_null() + { + if !candidate_ipv6_ok(e, n) { + return r#false != 0; + } + v6 = r#true != 0; + } else if !first.is_null() { + host_len = first.offset_from(e) as size_t; + port = first.offset(1 as ::core::ffi::c_int as isize); + port_len = n.wrapping_sub(host_len).wrapping_sub(1 as size_t); + if port_len == 0 as size_t { + return r#false != 0; + } + } + if !v6 + && !(host_len == 1 as size_t + && *host.offset(0isize) as ::core::ffi::c_int == '*' as ::core::ffi::c_int) + && !candidate_host_ok(host, host_len) + { + return r#false != 0; + } + } + if !port.is_null() && !candidate_port_ok(port, port_len) { + return r#false != 0; + } + let mut w: ::core::ffi::c_int = 0; + if !port.is_null() { + w = snprintf( + out, + out_size, + if v6 as ::core::ffi::c_int != 0 { + b"[%.*s]:%.*s\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"%.*s:%.*s\0".as_ptr() as *const ::core::ffi::c_char + }, + host_len as ::core::ffi::c_int, + host, + port_len as ::core::ffi::c_int, + port, + ); + } else if port_optional_out { + w = snprintf( + out, + out_size, + if v6 as ::core::ffi::c_int != 0 { + b"[%.*s]\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"%.*s\0".as_ptr() as *const ::core::ffi::c_char + }, + host_len as ::core::ffi::c_int, + host, + ); + } else { + w = snprintf( + out, + out_size, + if v6 as ::core::ffi::c_int != 0 { + b"[%.*s]:%d\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"%.*s:%d\0".as_ptr() as *const ::core::ffi::c_char + }, + host_len as ::core::ffi::c_int, + host, + default_port, + ); + } + return w > 0 as ::core::ffi::c_int && (w as size_t) < out_size; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn go2rtc_parse_external_candidates( + mut list: *const ::core::ffi::c_char, + mut port: ::core::ffi::c_int, + mut out: *mut [::core::ffi::c_char; 128], + mut max: ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if list.is_null() { + return 0 as ::core::ffi::c_int; + } + if port <= 0 as ::core::ffi::c_int || port > 65535 as ::core::ffi::c_int { + port = 8555 as ::core::ffi::c_int; + } + let mut count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut p: *const ::core::ffi::c_char = list; + loop { + while *p as ::core::ffi::c_int == ',' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == ';' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == '\t' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == '\n' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == '\r' as ::core::ffi::c_int + { + p = p.offset(1); + } + if *p == 0 { + break; + } + let mut start: *const ::core::ffi::c_char = p; + while *p as ::core::ffi::c_int != 0 + && *p as ::core::ffi::c_int != ',' as ::core::ffi::c_int + && *p as ::core::ffi::c_int != ';' as ::core::ffi::c_int + && *p as ::core::ffi::c_int != ' ' as ::core::ffi::c_int + && *p as ::core::ffi::c_int != '\t' as ::core::ffi::c_int + && *p as ::core::ffi::c_int != '\n' as ::core::ffi::c_int + && *p as ::core::ffi::c_int != '\r' as ::core::ffi::c_int + { + p = p.offset(1); + } + let mut n: size_t = p.offset_from(start) as size_t; + let mut candidate: [::core::ffi::c_char; 128] = [0; 128]; + let mut ok: bool = false; + if n == 1 as size_t + && *start.offset(0isize) as ::core::ffi::c_int == '*' as ::core::ffi::c_int + || n == 4 as size_t + && strncasecmp( + start, + b"auto\0".as_ptr() as *const ::core::ffi::c_char, + 4 as size_t, + ) == 0 as ::core::ffi::c_int + { + ok = snprintf( + &raw mut candidate as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + b"*:%d\0".as_ptr() as *const ::core::ffi::c_char, + port, + ) > 0 as ::core::ffi::c_int; + } else if n > 5 as size_t + && strncasecmp( + start, + b"stun:\0".as_ptr() as *const ::core::ffi::c_char, + 5 as size_t, + ) == 0 as ::core::ffi::c_int + { + let mut addr: [::core::ffi::c_char; 128] = [0; 128]; + ok = candidate_address( + start.offset(5 as ::core::ffi::c_int as isize), + n.wrapping_sub(5 as size_t), + port, + r#true != 0, + &raw mut addr as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + ) as ::core::ffi::c_int + != 0 + && addr[0usize] as ::core::ffi::c_int != '*' as ::core::ffi::c_int + && snprintf( + &raw mut candidate as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + b"stun:%s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut addr as *mut ::core::ffi::c_char, + ) < ::core::mem::size_of::<[::core::ffi::c_char; 128]>() as ::core::ffi::c_int; + } else { + ok = candidate_address( + start, + n, + port, + r#false != 0, + &raw mut candidate as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + ); + } + if !ok { + return -1 as ::core::ffi::c_int; + } + let mut duplicate: bool = r#false != 0; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if strcmp( + &raw mut *out.offset(i as isize) as *mut ::core::ffi::c_char, + &raw mut candidate as *mut ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + { + duplicate = r#true != 0; + break; + } else { + i += 1; + } + } + if duplicate { + continue; + } + if count >= max { + return -1 as ::core::ffi::c_int; + } + safe_strcpy( + &raw mut *out.offset(count as isize) as *mut ::core::ffi::c_char, + &raw mut candidate as *mut ::core::ffi::c_char, + GO2RTC_CANDIDATE_SIZE as size_t, + 0 as size_t, + ); + count += 1; + } + return count; +} +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; diff --git a/rust/generated/src__video__api_detection.rs b/rust/generated/src__video__api_detection.rs index 0f1d34cf2..bebc6c278 100644 --- a/rust/generated/src__video__api_detection.rs +++ b/rust/generated/src__video__api_detection.rs @@ -8,6 +8,8 @@ unsafe extern "C" { __format: *const ::core::ffi::c_char, ... ) -> ::core::ffi::c_int; + unsafe fn floor(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; + unsafe fn round(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; unsafe fn time(__timer: *mut time_t) -> time_t; unsafe fn curl_mime_init(easy: *mut ::core::ffi::c_void) -> *mut curl_mime; unsafe fn curl_mime_free(mime: *mut curl_mime); @@ -81,6 +83,14 @@ unsafe extern "C" { ) -> bool; unsafe fn curl_init_global() -> ::core::ffi::c_int; unsafe fn is_shutdown_initiated() -> bool; + unsafe fn event_producer_publish_detection( + camera_uuid: *const ::core::ffi::c_char, + stream_name: *const ::core::ffi::c_char, + result: *const detection_result_t, + occurred_at: time_t, + error: *mut ::core::ffi::c_char, + error_size: size_t, + ) -> ::core::ffi::c_int; unsafe fn event_producer_publish_detection_for_stream( stream_name: *const ::core::ffi::c_char, result: *const detection_result_t, @@ -98,6 +108,21 @@ unsafe extern "C" { dst_size: size_t, src_size: size_t, ) -> ::core::ffi::c_int; + unsafe fn detection_evidence_observed_at_ms(frame_ts: time_t) -> int64_t; + unsafe fn detection_evidence_capture_jpeg( + camera: *const detection_evidence_camera_t, + engine: *const detection_evidence_engine_t, + detections: *const detection_t, + count: ::core::ffi::c_int, + observed_at_ms: int64_t, + jpeg: *mut uint8_t, + jpeg_size: size_t, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + transform: *const observation_evidence_transform_t, + submit_result: *mut observation_evidence_result_t, + asset_uuid_out: *mut ::core::ffi::c_char, + ) -> detection_evidence_status_t; unsafe fn filter_detections_by_zones( stream_name: *const ::core::ffi::c_char, result: *mut detection_result_t, @@ -125,6 +150,13 @@ unsafe extern "C" { timestamp: time_t, recording_id: uint64_t, ) -> ::core::ffi::c_int; + unsafe fn store_detections_in_db_for_camera( + camera_uuid: *const ::core::ffi::c_char, + stream_name: *const ::core::ffi::c_char, + result: *const detection_result_t, + timestamp: time_t, + recording_id: uint64_t, + ) -> ::core::ffi::c_int; unsafe fn go2rtc_get_snapshot( stream_name: *const ::core::ffi::c_char, jpeg_data: *mut *mut ::core::ffi::c_uchar, @@ -164,6 +196,10 @@ unsafe extern "C" { __needle: *const ::core::ffi::c_char, ) -> *mut ::core::ffi::c_char; unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; + unsafe fn strcasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; unsafe fn malloc(__size: size_t) -> *mut ::core::ffi::c_void; unsafe fn realloc(__ptr: *mut ::core::ffi::c_void, __size: size_t) -> *mut ::core::ffi::c_void; unsafe fn free(__ptr: *mut ::core::ffi::c_void); @@ -172,6 +208,7 @@ unsafe extern "C" { pub type size_t = usize; pub type __time_t = ::core::ffi::c_long; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct __pthread_internal_list { @@ -845,6 +882,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -950,7 +991,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -993,6 +1034,37 @@ pub struct detection_result_t { pub count: ::core::ffi::c_int, pub detections: [detection_t; 20], } +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_evidence_result_t(pub ::core::ffi::c_int); +impl observation_evidence_result_t { + pub const OBS_EVIDENCE_ACCEPTED: Self = Self(0); + pub const OBS_EVIDENCE_DROPPED_QUEUE_FULL: Self = Self(1); + pub const OBS_EVIDENCE_DROPPED_BUDGET: Self = Self(2); + pub const OBS_EVIDENCE_DISABLED: Self = Self(3); + pub const OBS_EVIDENCE_INVALID: Self = Self(-1); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_transform_mode_t(pub ::core::ffi::c_uint); +impl observation_transform_mode_t { + pub const OBS_TRANSFORM_NONE: Self = Self(0); + pub const OBS_TRANSFORM_RESIZE: Self = Self(1); + pub const OBS_TRANSFORM_LETTERBOX: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_transform_t { + pub mode: observation_transform_mode_t, + pub source_width: ::core::ffi::c_int, + pub source_height: ::core::ffi::c_int, + pub inference_width: ::core::ffi::c_int, + pub inference_height: ::core::ffi::c_int, + pub scale_x: ::core::ffi::c_double, + pub scale_y: ::core::ffi::c_double, + pub pad_x: ::core::ffi::c_double, + pub pad_y: ::core::ffi::c_double, +} #[derive(Copy, Clone)] #[repr(C)] pub struct api_detection_options_t { @@ -1002,11 +1074,70 @@ pub struct api_detection_options_t { } #[derive(Copy, Clone)] #[repr(C)] +pub struct api_detection_response_meta_t { + pub letterbox: bool, + pub inference_width: ::core::ffi::c_int, + pub inference_height: ::core::ffi::c_int, + pub scale_x: ::core::ffi::c_double, + pub scale_y: ::core::ffi::c_double, + pub pad_x: ::core::ffi::c_double, + pub pad_y: ::core::ffi::c_double, + pub pixel_units: bool, + pub model: [::core::ffi::c_char; 128], + pub model_version: [::core::ffi::c_char; 64], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct api_detection_evidence_t { + pub camera_uuid: *const ::core::ffi::c_char, + pub engine_key: *const ::core::ffi::c_char, + pub evidence_enabled: bool, + pub labels_csv: *const ::core::ffi::c_char, + pub observed_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] pub struct memory_struct_t { pub memory: *mut ::core::ffi::c_char, pub size: size_t, pub capacity: size_t, } +#[derive(Copy, Clone)] +#[repr(C)] +pub struct submitted_image_t { + pub jpeg: *mut uint8_t, + pub size: size_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub transform: observation_evidence_transform_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct detection_evidence_status_t(pub ::core::ffi::c_uint); +impl detection_evidence_status_t { + pub const DETECTION_EVIDENCE_SUBMITTED: Self = Self(0); + pub const DETECTION_EVIDENCE_NOT_ENABLED: Self = Self(1); + pub const DETECTION_EVIDENCE_NOT_ELIGIBLE: Self = Self(2); + pub const DETECTION_EVIDENCE_NOT_OBJECT_ENGINE: Self = Self(3); + pub const DETECTION_EVIDENCE_ERROR: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_evidence_engine_t { + pub engine_key: *const ::core::ffi::c_char, + pub engine_type: *const ::core::ffi::c_char, + pub model_path: *const ::core::ffi::c_char, + pub model_version: *const ::core::ffi::c_char, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_evidence_camera_t { + pub camera_uuid: *const ::core::ffi::c_char, + pub stream_name: *const ::core::ffi::c_char, + pub evidence_enabled: bool, + pub labels_csv: *const ::core::ffi::c_char, + pub recording_id: int64_t, +} pub type jpeg_encoder_cache_t = jpeg_encoder_cache; pub const SIZE_MAX: ::core::ffi::c_ulong = 18446744073709551615 as ::core::ffi::c_ulong; pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; @@ -1014,9 +1145,6 @@ pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core:: pub const MAX_DETECTIONS: ::core::ffi::c_int = 20 as ::core::ffi::c_int; pub const MAX_LABEL_LENGTH: ::core::ffi::c_int = 32 as ::core::ffi::c_int; pub const MAX_ZONE_ID_LENGTH: ::core::ffi::c_int = 32 as ::core::ffi::c_int; -pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); -pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; -pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; static mut initialized: bool = r#false != 0; static mut curl_mutex: pthread_mutex_t = pthread_mutex_t { __data: __pthread_mutex_s { @@ -1738,12 +1866,169 @@ unsafe extern "C" fn log_json_item( } free(json_str as *mut ::core::ffi::c_void); } +unsafe extern "C" fn parse_response_meta( + mut root: *const cJSON, + mut meta: *mut api_detection_response_meta_t, +) { + memset( + meta as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*meta).pad_y = -1.0f64; + (*meta).pad_x = (*meta).pad_y; + let mut model: *const cJSON = + cJSON_GetObjectItemCaseSensitive(root, b"model\0".as_ptr() as *const ::core::ffi::c_char); + if cJSON_IsString(model) != 0 + && *(*model).valuestring.offset(0isize) as ::core::ffi::c_int != '\0' as ::core::ffi::c_int + && strlen((*model).valuestring) < ::core::mem::size_of::<[::core::ffi::c_char; 128]>() + { + safe_strcpy( + &raw mut (*meta).model as *mut ::core::ffi::c_char, + (*model).valuestring, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + 0 as size_t, + ); + } + let mut version: *const cJSON = cJSON_GetObjectItemCaseSensitive( + root, + b"model_version\0".as_ptr() as *const ::core::ffi::c_char, + ); + if cJSON_IsString(version) != 0 + && *(*version).valuestring.offset(0isize) as ::core::ffi::c_int + != '\0' as ::core::ffi::c_int + && strlen((*version).valuestring) < ::core::mem::size_of::<[::core::ffi::c_char; 64]>() + { + safe_strcpy( + &raw mut (*meta).model_version as *mut ::core::ffi::c_char, + (*version).valuestring, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + 0 as size_t, + ); + } + let mut lb: *const cJSON = cJSON_GetObjectItemCaseSensitive( + root, + b"letterbox\0".as_ptr() as *const ::core::ffi::c_char, + ); + if lb.is_null() || cJSON_IsObject(lb) == 0 { + return; + } + let mut w: ::core::ffi::c_double = 0.0f64; + let mut h: ::core::ffi::c_double = 0.0f64; + let mut v: ::core::ffi::c_double = 0.0f64; + if !number_item( + lb, + b"width\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut w, + ) || !number_item( + lb, + b"height\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut h, + ) || !(w >= 1.0f64) + || !(h >= 1.0f64) + || w > 65535.0f64 + || h > 65535.0f64 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"API Detection: Ignoring letterbox declaration without a valid width/height\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return; + } + (*meta).inference_width = w as ::core::ffi::c_int; + (*meta).inference_height = h as ::core::ffi::c_int; + if number_item( + lb, + b"scale\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut v, + ) as ::core::ffi::c_int + != 0 + && v > 0.0f64 + { + (*meta).scale_y = v; + (*meta).scale_x = (*meta).scale_y; + } + if number_item( + lb, + b"scale_x\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut v, + ) as ::core::ffi::c_int + != 0 + && v > 0.0f64 + { + (*meta).scale_x = v; + } + if number_item( + lb, + b"scale_y\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut v, + ) as ::core::ffi::c_int + != 0 + && v > 0.0f64 + { + (*meta).scale_y = v; + } + if number_item( + lb, + b"pad_x\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut v, + ) as ::core::ffi::c_int + != 0 + && v >= 0.0f64 + { + (*meta).pad_x = v; + } + if number_item( + lb, + b"pad_y\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut v, + ) as ::core::ffi::c_int + != 0 + && v >= 0.0f64 + { + (*meta).pad_y = v; + } + let mut units: *const cJSON = + cJSON_GetObjectItemCaseSensitive(lb, b"box_units\0".as_ptr() as *const ::core::ffi::c_char); + (*meta).pixel_units = cJSON_IsString(units) != 0 + && strcasecmp( + (*units).valuestring, + b"pixels\0".as_ptr() as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int; + (*meta).letterbox = r#true != 0; +} #[unsafe(no_mangle)] pub unsafe extern "C" fn api_detection_parse_response( mut json: *const ::core::ffi::c_char, mut options: *const api_detection_options_t, mut result: *mut detection_result_t, ) -> ::core::ffi::c_int { + return api_detection_parse_response_meta( + json, + options, + result, + ::core::ptr::null_mut::(), + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn api_detection_parse_response_meta( + mut json: *const ::core::ffi::c_char, + mut options: *const api_detection_options_t, + mut result: *mut detection_result_t, + mut meta: *mut api_detection_response_meta_t, +) -> ::core::ffi::c_int { + if !meta.is_null() { + memset( + meta as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*meta).pad_y = -1.0f64; + (*meta).pad_x = (*meta).pad_y; + } if json.is_null() || result.is_null() { return -1 as ::core::ffi::c_int; } @@ -1776,6 +2061,9 @@ pub unsafe extern "C" fn api_detection_parse_response( ); return -1 as ::core::ffi::c_int; } + if !meta.is_null() && cJSON_IsObject(root) != 0 { + parse_response_meta(root, meta); + } if doods2 { let mut error: *const cJSON = cJSON_GetObjectItem(root, b"error\0".as_ptr() as *const ::core::ffi::c_char); @@ -1925,6 +2213,191 @@ pub unsafe extern "C" fn api_detection_parse_response( cJSON_Delete(root); return 0 as ::core::ffi::c_int; } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn api_detection_apply_response_geometry( + mut meta: *const api_detection_response_meta_t, + mut image_w: ::core::ffi::c_int, + mut image_h: ::core::ffi::c_int, + mut source_w: ::core::ffi::c_int, + mut source_h: ::core::ffi::c_int, + mut result: *mut detection_result_t, + mut transform: *mut observation_evidence_transform_t, +) -> ::core::ffi::c_int { + if image_w <= 0 as ::core::ffi::c_int || image_h <= 0 as ::core::ffi::c_int || result.is_null() + { + return -1 as ::core::ffi::c_int; + } + if source_w <= 0 as ::core::ffi::c_int || source_h <= 0 as ::core::ffi::c_int { + source_w = image_w; + source_h = image_h; + } + let mut t: observation_evidence_transform_t = observation_evidence_transform_t { + mode: observation_transform_mode_t::OBS_TRANSFORM_NONE, + source_width: 0, + source_height: 0, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + }; + memset( + &raw mut t as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + t.mode = observation_transform_mode_t::OBS_TRANSFORM_NONE; + t.source_width = source_w; + t.source_height = source_h; + t.inference_width = image_w; + t.inference_height = image_h; + t.scale_y = 1.0f64; + t.scale_x = t.scale_y; + if !meta.is_null() + && (*meta).letterbox as ::core::ffi::c_int != 0 + && (*meta).inference_width > 0 as ::core::ffi::c_int + && (*meta).inference_height > 0 as ::core::ffi::c_int + { + let iw: ::core::ffi::c_double = (*meta).inference_width as ::core::ffi::c_double; + let ih: ::core::ffi::c_double = (*meta).inference_height as ::core::ffi::c_double; + let mut sx: ::core::ffi::c_double = (*meta).scale_x; + let mut sy: ::core::ffi::c_double = (*meta).scale_y; + if !(sx > 0.0f64) || !(sy > 0.0f64) { + let mut uniform: ::core::ffi::c_double = if (iw / image_w as ::core::ffi::c_double) + < ih / image_h as ::core::ffi::c_double + { + iw / image_w as ::core::ffi::c_double + } else { + ih / image_h as ::core::ffi::c_double + }; + if !(sx > 0.0f64) { + sx = uniform; + } + if !(sy > 0.0f64) { + sy = uniform; + } + } + let mut px: ::core::ffi::c_double = (*meta).pad_x; + let mut py: ::core::ffi::c_double = (*meta).pad_y; + if px < 0.0f64 { + px = floor((iw - round(image_w as ::core::ffi::c_double * sx)) / 2.0f64); + } + if py < 0.0f64 { + py = floor((ih - round(image_h as ::core::ffi::c_double * sy)) / 2.0f64); + } + if px < 0.0f64 { + px = 0.0f64; + } + if py < 0.0f64 { + py = 0.0f64; + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < (*result).count { + let mut d: *mut detection_t = + (&raw mut (*result).detections as *mut detection_t).offset(i as isize); + let mut u: ::core::ffi::c_double = (*d).x as ::core::ffi::c_double; + let mut v: ::core::ffi::c_double = (*d).y as ::core::ffi::c_double; + let mut du: ::core::ffi::c_double = (*d).width as ::core::ffi::c_double; + let mut dv: ::core::ffi::c_double = (*d).height as ::core::ffi::c_double; + if !(*meta).pixel_units { + u *= iw; + du *= iw; + v *= ih; + dv *= ih; + } + (*d).x = ((u - px) / sx / image_w as ::core::ffi::c_double) as ::core::ffi::c_float; + (*d).y = ((v - py) / sy / image_h as ::core::ffi::c_double) as ::core::ffi::c_float; + (*d).width = (du / sx / image_w as ::core::ffi::c_double) as ::core::ffi::c_float; + (*d).height = (dv / sy / image_h as ::core::ffi::c_double) as ::core::ffi::c_float; + i += 1; + } + t.mode = observation_transform_mode_t::OBS_TRANSFORM_LETTERBOX; + t.inference_width = (*meta).inference_width; + t.inference_height = (*meta).inference_height; + t.scale_x = sx; + t.scale_y = sy; + t.pad_x = px; + t.pad_y = py; + } + if !transform.is_null() { + *transform = t; + } + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn api_detection_jpeg_dimensions( + mut jpeg: *const uint8_t, + mut size: size_t, + mut width: *mut ::core::ffi::c_int, + mut height: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if jpeg.is_null() + || size < 4 as size_t + || width.is_null() + || height.is_null() + || *jpeg.offset(0isize) as ::core::ffi::c_int != 0xff as ::core::ffi::c_int + || *jpeg.offset(1isize) as ::core::ffi::c_int != 0xd8 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut pos: size_t = 2 as size_t; + while pos.wrapping_add(4 as size_t) <= size { + if *jpeg.offset(pos as isize) as ::core::ffi::c_int != 0xff as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut marker: uint8_t = *jpeg.offset(pos.wrapping_add(1 as size_t) as isize); + if marker as ::core::ffi::c_int == 0xff as ::core::ffi::c_int { + pos = pos.wrapping_add(1); + } else if marker as ::core::ffi::c_int == 0xd8 as ::core::ffi::c_int + || marker as ::core::ffi::c_int == 0x1 as ::core::ffi::c_int + || marker as ::core::ffi::c_int >= 0xd0 as ::core::ffi::c_int + && marker as ::core::ffi::c_int <= 0xd7 as ::core::ffi::c_int + { + pos = pos.wrapping_add(2 as size_t); + } else { + if marker as ::core::ffi::c_int == 0xd9 as ::core::ffi::c_int + || marker as ::core::ffi::c_int == 0xda as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut seg: size_t = (*jpeg.offset(pos.wrapping_add(2 as size_t) as isize) as size_t) + << 8 as ::core::ffi::c_int + | *jpeg.offset(pos.wrapping_add(3 as size_t) as isize) as size_t; + if seg < 2 as size_t || pos.wrapping_add(2 as size_t).wrapping_add(seg) > size { + return -1 as ::core::ffi::c_int; + } + let mut sof: bool = marker as ::core::ffi::c_int >= 0xc0 as ::core::ffi::c_int + && marker as ::core::ffi::c_int <= 0xcf as ::core::ffi::c_int + && marker as ::core::ffi::c_int != 0xc4 as ::core::ffi::c_int + && marker as ::core::ffi::c_int != 0xc8 as ::core::ffi::c_int + && marker as ::core::ffi::c_int != 0xcc as ::core::ffi::c_int; + if sof { + if seg < 7 as size_t { + return -1 as ::core::ffi::c_int; + } + let mut h: ::core::ffi::c_int = (*jpeg + .offset(pos.wrapping_add(5 as size_t) as isize) + as ::core::ffi::c_int) + << 8 as ::core::ffi::c_int + | *jpeg.offset(pos.wrapping_add(6 as size_t) as isize) as ::core::ffi::c_int; + let mut w: ::core::ffi::c_int = (*jpeg + .offset(pos.wrapping_add(7 as size_t) as isize) + as ::core::ffi::c_int) + << 8 as ::core::ffi::c_int + | *jpeg.offset(pos.wrapping_add(8 as size_t) as isize) as ::core::ffi::c_int; + if w <= 0 as ::core::ffi::c_int || h <= 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + *width = w; + *height = h; + return 0 as ::core::ffi::c_int; + } + pos = pos.wrapping_add((2 as size_t).wrapping_add(seg)); + } + } + return -1 as ::core::ffi::c_int; +} unsafe extern "C" fn write_memory_callback( mut contents: *mut ::core::ffi::c_void, mut size: size_t, @@ -2168,6 +2641,7 @@ unsafe extern "C" fn perform_detection_request( mut request_id: *const ::core::ffi::c_char, mut options: *const api_detection_options_t, mut result: *mut detection_result_t, + mut meta: *mut api_detection_response_meta_t, ) -> ::core::ffi::c_int { let mut res: CURLcode = CURLcode::CURLE_OK; let mut http_code: ::core::ffi::c_long = 0; @@ -2398,7 +2872,7 @@ unsafe extern "C" fn perform_detection_request( context, &raw mut preview as *mut ::core::ffi::c_char, ); - ret = api_detection_parse_response(chunk.memory, options, result); + ret = api_detection_parse_response_meta(chunk.memory, options, result, meta); if ret != 0 as ::core::ffi::c_int { _log_message_ctx( log_level_t::LOG_LEVEL_ERROR, @@ -2421,12 +2895,179 @@ unsafe extern "C" fn perform_detection_request( curl_easy_cleanup(curl); return ret; } +unsafe extern "C" fn has_camera_uuid(mut evidence: *const api_detection_evidence_t) -> bool { + return !evidence.is_null() + && !(*evidence).camera_uuid.is_null() + && *(*evidence).camera_uuid.offset(0isize) as ::core::ffi::c_int + != '\0' as ::core::ffi::c_int; +} +unsafe extern "C" fn apply_submitted_geometry( + mut context: *const ::core::ffi::c_char, + mut stream_name: *const ::core::ffi::c_char, + mut meta: *const api_detection_response_meta_t, + mut source_w: ::core::ffi::c_int, + mut source_h: ::core::ffi::c_int, + mut result: *mut detection_result_t, + mut image: *mut submitted_image_t, +) { + if (*image).width <= 0 as ::core::ffi::c_int || (*image).height <= 0 as ::core::ffi::c_int { + if api_detection_jpeg_dimensions( + (*image).jpeg, + (*image).size, + &raw mut (*image).width, + &raw mut (*image).height, + ) != 0 as ::core::ffi::c_int + { + (*image).height = 0 as ::core::ffi::c_int; + (*image).width = (*image).height; + } + } + if (*image).width <= 0 as ::core::ffi::c_int || (*image).height <= 0 as ::core::ffi::c_int { + if (*meta).letterbox { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"%s: Cannot map the declared letterbox for %s: submitted JPEG size unknown\0" + .as_ptr() as *const ::core::ffi::c_char, + context, + if !stream_name.is_null() { + stream_name + } else { + b"(none)\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + } + return; + } + api_detection_apply_response_geometry( + meta, + (*image).width, + (*image).height, + source_w, + source_h, + result, + &raw mut (*image).transform, + ); +} +unsafe extern "C" fn submit_api_evidence( + mut context: *const ::core::ffi::c_char, + mut stream_name: *const ::core::ffi::c_char, + mut result: *const detection_result_t, + mut threshold: ::core::ffi::c_float, + mut recording_id: uint64_t, + mut options: *const api_detection_options_t, + mut evidence: *const api_detection_evidence_t, + mut meta: *const api_detection_response_meta_t, + mut image: *mut submitted_image_t, +) { + let mut jpeg: *mut uint8_t = (*image).jpeg; + (*image).jpeg = ::core::ptr::null_mut::(); + if !has_camera_uuid(evidence) + || !(*evidence).evidence_enabled + || (*result).count <= 0 as ::core::ffi::c_int + || jpeg.is_null() + || (*image).width <= 0 as ::core::ffi::c_int + || (*image).height <= 0 as ::core::ffi::c_int + { + free(jpeg as *mut ::core::ffi::c_void); + return; + } + let mut survivors: [detection_t; 20] = [detection_t { + label: [0; 32], + confidence: 0., + x: 0., + y: 0., + width: 0., + height: 0., + track_id: 0, + zone_id: [0; 32], + }; 20]; + let mut n: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < (*result).count && n < MAX_DETECTIONS { + if !(threshold > 0.0f32 && (*result).detections[i as usize].confidence < threshold) { + let c2rust_fresh0 = n; + n += 1; + survivors[c2rust_fresh0 as usize] = (*result).detections[i as usize]; + } + i += 1; + } + if n == 0 as ::core::ffi::c_int { + free(jpeg as *mut ::core::ffi::c_void); + return; + } + let mut model: *const ::core::ffi::c_char = ::core::ptr::null::<::core::ffi::c_char>(); + if (*meta).model[0usize] as ::core::ffi::c_int != '\0' as ::core::ffi::c_int { + model = &raw const (*meta).model as *const ::core::ffi::c_char; + } else if (*options).format.0 == api_detection_format_t::API_DETECTION_FORMAT_DOODS2.0 + && (*options).detector_name[0usize] as ::core::ffi::c_int != '\0' as ::core::ffi::c_int + && strcmp( + &raw const (*options).detector_name as *const ::core::ffi::c_char, + b"default\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + model = &raw const (*options).detector_name as *const ::core::ffi::c_char; + } + let mut camera: detection_evidence_camera_t = detection_evidence_camera_t { + camera_uuid: (*evidence).camera_uuid, + stream_name: stream_name, + evidence_enabled: r#true != 0, + labels_csv: (*evidence).labels_csv, + recording_id: recording_id as int64_t, + }; + let mut engine: detection_evidence_engine_t = detection_evidence_engine_t { + engine_key: (*evidence).engine_key, + engine_type: b"api\0".as_ptr() as *const ::core::ffi::c_char, + model_path: model, + model_version: if (*meta).model_version[0usize] as ::core::ffi::c_int != 0 { + &raw const (*meta).model_version as *const ::core::ffi::c_char + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }, + }; + let mut submitted: observation_evidence_result_t = + observation_evidence_result_t::OBS_EVIDENCE_INVALID; + let mut status: detection_evidence_status_t = detection_evidence_capture_jpeg( + &raw mut camera, + &raw mut engine, + &raw mut survivors as *mut detection_t, + n, + (*evidence).observed_at_ms, + jpeg, + (*image).size, + (*image).width, + (*image).height, + &raw mut (*image).transform, + &raw mut submitted, + ::core::ptr::null_mut::<::core::ffi::c_char>(), + ); + if status.0 == detection_evidence_status_t::DETECTION_EVIDENCE_SUBMITTED.0 + && submitted.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_DEBUG, + LOG_COMPONENT, + _log_stream_name, + b"%s: Observation evidence for %s not queued (%d)\0".as_ptr() + as *const ::core::ffi::c_char, + context, + stream_name, + submitted.0, + ); + } +} unsafe extern "C" fn finalize_detections( mut context: *const ::core::ffi::c_char, mut stream_name: *const ::core::ffi::c_char, mut result: *mut detection_result_t, mut frame_timestamp: time_t, mut recording_id: uint64_t, + mut threshold: ::core::ffi::c_float, + mut options: *const api_detection_options_t, + mut evidence: *const api_detection_evidence_t, + mut meta: *const api_detection_response_meta_t, + mut image: *mut submitted_image_t, ) -> ::core::ffi::c_int { if stream_name.is_null() || *stream_name.offset(0isize) as ::core::ffi::c_int == '\0' as ::core::ffi::c_int @@ -2439,6 +3080,8 @@ unsafe extern "C" fn finalize_detections( as *const ::core::ffi::c_char, context, ); + free((*image).jpeg as *mut ::core::ffi::c_void); + (*image).jpeg = ::core::ptr::null_mut::(); return 0 as ::core::ffi::c_int; } _log_message_ctx( @@ -2461,6 +3104,8 @@ unsafe extern "C" fn finalize_detections( context, stream_name, ); + free((*image).jpeg as *mut ::core::ffi::c_void); + (*image).jpeg = ::core::ptr::null_mut::(); return -1 as ::core::ffi::c_int; } filter_detections_by_stream_objects(stream_name, result); @@ -2469,7 +3114,18 @@ unsafe extern "C" fn finalize_detections( } else { time(::core::ptr::null_mut::()) }; - store_detections_in_db(stream_name, result, timestamp, recording_id); + let by_camera: bool = has_camera_uuid(evidence); + if by_camera { + store_detections_in_db_for_camera( + (*evidence).camera_uuid, + stream_name, + result, + timestamp, + recording_id, + ); + } else { + store_detections_in_db(stream_name, result, timestamp, recording_id); + } if (*result).count > 0 as ::core::ffi::c_int { let mut event_error: [::core::ffi::c_char; 256] = [ 0 as ::core::ffi::c_char, @@ -2729,14 +3385,25 @@ unsafe extern "C" fn finalize_detections( 0, 0, ]; - if event_producer_publish_detection_for_stream( - stream_name, - result, - timestamp, - &raw mut event_error as *mut ::core::ffi::c_char, - ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), - ) != 0 as ::core::ffi::c_int - { + let mut rc: ::core::ffi::c_int = if by_camera as ::core::ffi::c_int != 0 { + event_producer_publish_detection( + (*evidence).camera_uuid, + stream_name, + result, + timestamp, + &raw mut event_error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + ) + } else { + event_producer_publish_detection_for_stream( + stream_name, + result, + timestamp, + &raw mut event_error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + ) + }; + if rc != 0 as ::core::ffi::c_int { _log_message_ctx( log_level_t::LOG_LEVEL_DEBUG, LOG_COMPONENT, @@ -2748,8 +3415,33 @@ unsafe extern "C" fn finalize_detections( ); } } + submit_api_evidence( + context, + stream_name, + result, + threshold, + recording_id, + options, + evidence, + meta, + image, + ); return 0 as ::core::ffi::c_int; } +unsafe extern "C" fn stamp_evidence( + mut r#in: *const api_detection_evidence_t, + mut storage: *mut api_detection_evidence_t, + mut frame_timestamp: time_t, +) -> *const api_detection_evidence_t { + if r#in.is_null() { + return ::core::ptr::null::(); + } + *storage = *r#in; + if (*storage).observed_at_ms <= 0 as int64_t { + (*storage).observed_at_ms = detection_evidence_observed_at_ms(frame_timestamp); + } + return storage; +} #[unsafe(no_mangle)] pub unsafe extern "C" fn init_api_detection_system() -> ::core::ffi::c_int { pthread_mutex_lock(&raw mut curl_mutex); @@ -2823,9 +3515,48 @@ pub unsafe extern "C" fn detect_objects_api_with_options( mut recording_id: uint64_t, mut frame_timestamp: time_t, mut options: *const api_detection_options_t, +) -> ::core::ffi::c_int { + return detect_objects_api_with_evidence( + api_url, + frame_data, + width, + height, + channels, + result, + stream_name, + threshold, + recording_id, + frame_timestamp, + options, + ::core::ptr::null::(), + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detect_objects_api_with_evidence( + mut api_url: *const ::core::ffi::c_char, + mut frame_data: *const ::core::ffi::c_uchar, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut channels: ::core::ffi::c_int, + mut result: *mut detection_result_t, + mut stream_name: *const ::core::ffi::c_char, + mut threshold: ::core::ffi::c_float, + mut recording_id: uint64_t, + mut frame_timestamp: time_t, + mut options: *const api_detection_options_t, + mut evidence_in: *const api_detection_evidence_t, ) -> ::core::ffi::c_int { static mut context: *const ::core::ffi::c_char = b"API Detection\0".as_ptr() as *const ::core::ffi::c_char; + let mut evidence_storage: api_detection_evidence_t = api_detection_evidence_t { + camera_uuid: ::core::ptr::null::<::core::ffi::c_char>(), + engine_key: ::core::ptr::null::<::core::ffi::c_char>(), + evidence_enabled: false, + labels_csv: ::core::ptr::null::<::core::ffi::c_char>(), + observed_at_ms: 0, + }; + let mut evidence: *const api_detection_evidence_t = + stamp_evidence(evidence_in, &raw mut evidence_storage, frame_timestamp); if is_shutdown_initiated() { _log_message_ctx( log_level_t::LOG_LEVEL_INFO, @@ -2996,6 +3727,18 @@ pub unsafe extern "C" fn detect_objects_api_with_options( free(jpeg_data as *mut ::core::ffi::c_void); return -1 as ::core::ffi::c_int; } + let mut meta: api_detection_response_meta_t = api_detection_response_meta_t { + letterbox: false, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + pixel_units: false, + model: [0; 128], + model_version: [0; 64], + }; let mut ret: ::core::ffi::c_int = perform_detection_request( context, actual_api_url, @@ -3005,11 +3748,66 @@ pub unsafe extern "C" fn detect_objects_api_with_options( stream_name, options, result, + &raw mut meta, ); - free(jpeg_data as *mut ::core::ffi::c_void); if ret == 0 as ::core::ffi::c_int { - ret = finalize_detections(context, stream_name, result, frame_timestamp, recording_id); + let mut image: submitted_image_t = submitted_image_t { + jpeg: jpeg_data as *mut uint8_t, + size: jpeg_size, + width: if snapshot_ok as ::core::ffi::c_int != 0 { + 0 as ::core::ffi::c_int + } else { + width + }, + height: if snapshot_ok as ::core::ffi::c_int != 0 { + 0 as ::core::ffi::c_int + } else { + height + }, + transform: observation_evidence_transform_t { + mode: observation_transform_mode_t::OBS_TRANSFORM_NONE, + source_width: 0, + source_height: 0, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + }, + }; + jpeg_data = ::core::ptr::null_mut::<::core::ffi::c_uchar>(); + apply_submitted_geometry( + context, + stream_name, + &raw mut meta, + if snapshot_ok as ::core::ffi::c_int != 0 { + 0 as ::core::ffi::c_int + } else { + width + }, + if snapshot_ok as ::core::ffi::c_int != 0 { + 0 as ::core::ffi::c_int + } else { + height + }, + result, + &raw mut image, + ); + ret = finalize_detections( + context, + stream_name, + result, + frame_timestamp, + recording_id, + threshold, + options, + evidence, + &raw mut meta, + &raw mut image, + ); } + free(jpeg_data as *mut ::core::ffi::c_void); if ret != 0 as ::core::ffi::c_int { (*result).count = 0 as ::core::ffi::c_int; return ret; @@ -3060,9 +3858,40 @@ pub unsafe extern "C" fn detect_objects_api_snapshot_with_options( mut recording_id: uint64_t, mut frame_timestamp: time_t, mut options: *const api_detection_options_t, +) -> ::core::ffi::c_int { + return detect_objects_api_snapshot_with_evidence( + api_url, + stream_name, + result, + threshold, + recording_id, + frame_timestamp, + options, + ::core::ptr::null::(), + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detect_objects_api_snapshot_with_evidence( + mut api_url: *const ::core::ffi::c_char, + mut stream_name: *const ::core::ffi::c_char, + mut result: *mut detection_result_t, + mut threshold: ::core::ffi::c_float, + mut recording_id: uint64_t, + mut frame_timestamp: time_t, + mut options: *const api_detection_options_t, + mut evidence_in: *const api_detection_evidence_t, ) -> ::core::ffi::c_int { static mut context: *const ::core::ffi::c_char = b"API Detection (snapshot)\0".as_ptr() as *const ::core::ffi::c_char; + let mut evidence_storage: api_detection_evidence_t = api_detection_evidence_t { + camera_uuid: ::core::ptr::null::<::core::ffi::c_char>(), + engine_key: ::core::ptr::null::<::core::ffi::c_char>(), + evidence_enabled: false, + labels_csv: ::core::ptr::null::<::core::ffi::c_char>(), + observed_at_ms: 0, + }; + let mut evidence: *const api_detection_evidence_t = + stamp_evidence(evidence_in, &raw mut evidence_storage, frame_timestamp); if is_shutdown_initiated() { _log_message_ctx( log_level_t::LOG_LEVEL_INFO, @@ -3166,6 +3995,18 @@ pub unsafe extern "C" fn detect_objects_api_snapshot_with_options( free(jpeg_data as *mut ::core::ffi::c_void); return API_DETECTION_SNAPSHOT_UNAVAILABLE; } + let mut meta: api_detection_response_meta_t = api_detection_response_meta_t { + letterbox: false, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + pixel_units: false, + model: [0; 128], + model_version: [0; 64], + }; let mut ret: ::core::ffi::c_int = perform_detection_request( context, actual_api_url, @@ -3175,11 +4016,50 @@ pub unsafe extern "C" fn detect_objects_api_snapshot_with_options( stream_name, options, result, + &raw mut meta, ); - free(jpeg_data as *mut ::core::ffi::c_void); if ret == 0 as ::core::ffi::c_int { - ret = finalize_detections(context, stream_name, result, frame_timestamp, recording_id); + let mut image: submitted_image_t = submitted_image_t { + jpeg: jpeg_data as *mut uint8_t, + size: jpeg_size, + width: 0, + height: 0, + transform: observation_evidence_transform_t { + mode: observation_transform_mode_t::OBS_TRANSFORM_NONE, + source_width: 0, + source_height: 0, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + }, + }; + jpeg_data = ::core::ptr::null_mut::<::core::ffi::c_uchar>(); + apply_submitted_geometry( + context, + stream_name, + &raw mut meta, + 0 as ::core::ffi::c_int, + 0 as ::core::ffi::c_int, + result, + &raw mut image, + ); + ret = finalize_detections( + context, + stream_name, + result, + frame_timestamp, + recording_id, + threshold, + options, + evidence, + &raw mut meta, + &raw mut image, + ); } + free(jpeg_data as *mut ::core::ffi::c_void); if ret != 0 as ::core::ffi::c_int { (*result).count = 0 as ::core::ffi::c_int; return -1 as ::core::ffi::c_int; @@ -3213,3 +4093,6 @@ pub unsafe extern "C" fn detect_objects_api_snapshot( ::core::ptr::null::(), ); } +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; diff --git a/rust/generated/src__video__cross_stream_motion_trigger.rs b/rust/generated/src__video__cross_stream_motion_trigger.rs index d15bb4c07..c056f8cf0 100644 --- a/rust/generated/src__video__cross_stream_motion_trigger.rs +++ b/rust/generated/src__video__cross_stream_motion_trigger.rs @@ -36,6 +36,7 @@ unsafe extern "C" { pub type size_t = usize; pub type __time_t = ::core::ffi::c_long; pub type time_t = __time_t; +pub type int64_t = i64; pub type uint8_t = u8; pub type uint32_t = u32; pub type uint64_t = u64; @@ -121,6 +122,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -226,7 +231,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__detection.rs b/rust/generated/src__video__detection.rs index 615354a36..606a81191 100644 --- a/rust/generated/src__video__detection.rs +++ b/rust/generated/src__video__detection.rs @@ -54,8 +54,6 @@ unsafe extern "C" { ) -> ::core::ffi::c_int; unsafe fn init_onvif_detection_system() -> ::core::ffi::c_int; unsafe fn shutdown_onvif_detection_system(); - unsafe fn init_unified_detection_system() -> ::core::ffi::c_int; - unsafe fn shutdown_unified_detection_system(); unsafe fn comprehensive_ffmpeg_cleanup( input_ctx: *mut *mut AVFormatContext, codec_ctx: *mut *mut AVCodecContext, @@ -69,17 +67,19 @@ unsafe extern "C" { format: *const ::core::ffi::c_char, ... ); + unsafe fn url_redact_for_logging( + url: *const ::core::ffi::c_char, + out_url: *mut ::core::ffi::c_char, + out_size: size_t, + ) -> ::core::ffi::c_int; unsafe fn safe_strcpy( dest: *mut ::core::ffi::c_char, src: *const ::core::ffi::c_char, dst_size: size_t, src_size: size_t, ) -> ::core::ffi::c_int; - unsafe fn url_redact_for_logging( - url: *const ::core::ffi::c_char, - out_url: *mut ::core::ffi::c_char, - out_size: size_t, - ) -> ::core::ffi::c_int; + unsafe fn init_unified_detection_system() -> ::core::ffi::c_int; + unsafe fn shutdown_unified_detection_system(); unsafe fn memset( __s: *mut ::core::ffi::c_void, __c: ::core::ffi::c_int, diff --git a/rust/generated/src__video__detection_evidence.rs b/rust/generated/src__video__detection_evidence.rs new file mode 100644 index 000000000..870f282bd --- /dev/null +++ b/rust/generated/src__video__detection_evidence.rs @@ -0,0 +1,964 @@ +unsafe extern "C" { + unsafe fn observation_evidence_submit( + batch: *const observation_batch_input_t, + rgb24: *mut uint8_t, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + transform: *const observation_evidence_transform_t, + asset_uuid_out: *mut ::core::ffi::c_char, + ) -> observation_evidence_result_t; + unsafe fn observation_evidence_submit_jpeg( + batch: *const observation_batch_input_t, + jpeg: *mut uint8_t, + jpeg_size: size_t, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + transform: *const observation_evidence_transform_t, + asset_uuid_out: *mut ::core::ffi::c_char, + ) -> observation_evidence_result_t; + unsafe fn observation_evidence_submit_unavailable( + batch: *const observation_batch_input_t, + reason: *const ::core::ffi::c_char, + asset_uuid_out: *mut ::core::ffi::c_char, + ) -> observation_evidence_result_t; + unsafe fn lroundf(__x: ::core::ffi::c_float) -> ::core::ffi::c_long; + unsafe fn pthread_once( + __once_control: *mut pthread_once_t, + __init_routine: Option ()>, + ) -> ::core::ffi::c_int; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn gettimeofday( + __tv: *mut timeval, + __tz: *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_generate_v4(output: *mut ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn sod_model_input_size( + model: detection_model_t, + width: *mut ::core::ffi::c_int, + height: *mut ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn memcpy( + __dest: *mut ::core::ffi::c_void, + __src: *const ::core::ffi::c_void, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strrchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; + unsafe fn strnlen(__string: *const ::core::ffi::c_char, __maxlen: size_t) -> size_t; + unsafe fn strcasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strncasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + __n: size_t, + ) -> ::core::ffi::c_int; + unsafe fn malloc(__size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __time_t = ::core::ffi::c_long; +pub type __suseconds_t = ::core::ffi::c_long; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timeval { + pub tv_sec: __time_t, + pub tv_usec: __suseconds_t, +} +pub type pthread_once_t = ::core::ffi::c_int; +pub type uint8_t = u8; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_object_input_t { + pub object_index: ::core::ffi::c_int, + pub label: [::core::ffi::c_char; 32], + pub confidence: ::core::ffi::c_double, + pub has_box: bool, + pub box_x: ::core::ffi::c_double, + pub box_y: ::core::ffi::c_double, + pub box_width: ::core::ffi::c_double, + pub box_height: ::core::ffi::c_double, + pub has_track: bool, + pub track_id: [::core::ffi::c_char; 65], + pub track_namespace: [::core::ffi::c_char; 65], + pub track_session: [::core::ffi::c_char; 37], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_batch_input_t { + pub batch_id: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub stream_name: [::core::ffi::c_char; 64], + pub observed_at_ms: int64_t, + pub engine_key: [::core::ffi::c_char; 65], + pub engine_type: [::core::ffi::c_char; 16], + pub model: [::core::ffi::c_char; 129], + pub model_version: [::core::ffi::c_char; 65], + pub recording_id: int64_t, + pub object_count: ::core::ffi::c_int, + pub objects: [observation_object_input_t; 20], +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_evidence_result_t(pub ::core::ffi::c_int); +impl observation_evidence_result_t { + pub const OBS_EVIDENCE_ACCEPTED: Self = Self(0); + pub const OBS_EVIDENCE_DROPPED_QUEUE_FULL: Self = Self(1); + pub const OBS_EVIDENCE_DROPPED_BUDGET: Self = Self(2); + pub const OBS_EVIDENCE_DISABLED: Self = Self(3); + pub const OBS_EVIDENCE_INVALID: Self = Self(-1); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_transform_mode_t(pub ::core::ffi::c_uint); +impl observation_transform_mode_t { + pub const OBS_TRANSFORM_NONE: Self = Self(0); + pub const OBS_TRANSFORM_RESIZE: Self = Self(1); + pub const OBS_TRANSFORM_LETTERBOX: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_transform_t { + pub mode: observation_transform_mode_t, + pub source_width: ::core::ffi::c_int, + pub source_height: ::core::ffi::c_int, + pub inference_width: ::core::ffi::c_int, + pub inference_height: ::core::ffi::c_int, + pub scale_x: ::core::ffi::c_double, + pub scale_y: ::core::ffi::c_double, + pub pad_x: ::core::ffi::c_double, + pub pad_y: ::core::ffi::c_double, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_t { + pub label: [::core::ffi::c_char; 32], + pub confidence: ::core::ffi::c_float, + pub x: ::core::ffi::c_float, + pub y: ::core::ffi::c_float, + pub width: ::core::ffi::c_float, + pub height: ::core::ffi::c_float, + pub track_id: ::core::ffi::c_int, + pub zone_id: [::core::ffi::c_char; 32], +} +pub type detection_model_t = *mut ::core::ffi::c_void; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_evidence_engine_t { + pub engine_key: *const ::core::ffi::c_char, + pub engine_type: *const ::core::ffi::c_char, + pub model_path: *const ::core::ffi::c_char, + pub model_version: *const ::core::ffi::c_char, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_evidence_camera_t { + pub camera_uuid: *const ::core::ffi::c_char, + pub stream_name: *const ::core::ffi::c_char, + pub evidence_enabled: bool, + pub labels_csv: *const ::core::ffi::c_char, + pub recording_id: int64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct detection_evidence_status_t(pub ::core::ffi::c_uint); +impl detection_evidence_status_t { + pub const DETECTION_EVIDENCE_SUBMITTED: Self = Self(0); + pub const DETECTION_EVIDENCE_NOT_ENABLED: Self = Self(1); + pub const DETECTION_EVIDENCE_NOT_ELIGIBLE: Self = Self(2); + pub const DETECTION_EVIDENCE_NOT_OBJECT_ENGINE: Self = Self(3); + pub const DETECTION_EVIDENCE_ERROR: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct model_t { + pub r#type: [::core::ffi::c_char; 16], + pub c2rust_unnamed: C2Rust_Unnamed, + pub threshold: ::core::ffi::c_float, + pub path: [::core::ffi::c_char; 512], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub union C2Rust_Unnamed { + pub sod: *mut ::core::ffi::c_void, + pub sod_realnet: *mut ::core::ffi::c_void, + pub tflite: tflite_model_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct tflite_model_t { + pub engine: *mut ::core::ffi::c_void, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +pub const OBS_MAX_BATCH: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const OBS_LABEL_SIZE: ::core::ffi::c_int = 32 as ::core::ffi::c_int; +pub const MAX_DETECTIONS: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const MODEL_TYPE_SOD: [::core::ffi::c_char; 4] = + unsafe { ::core::mem::transmute::<[u8; 4], [::core::ffi::c_char; 4]>(*b"sod\0") }; +pub const PTHREAD_ONCE_INIT: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const DEFAULT_ENGINE_KEY: [::core::ffi::c_char; 15] = + unsafe { ::core::mem::transmute::<[u8; 15], [::core::ffi::c_char; 15]>(*b"legacy-primary\0") }; +static mut boot_once: pthread_once_t = PTHREAD_ONCE_INIT; +static mut boot_session: [::core::ffi::c_char; 37] = [0; 37]; +unsafe extern "C" fn boot_session_init() { + if lightnvr_uuid_generate_v4(&raw mut boot_session as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + boot_session[0usize] = '\0' as ::core::ffi::c_char; + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_boot_session() -> *const ::core::ffi::c_char { + pthread_once( + &raw mut boot_once, + Some(boot_session_init as unsafe extern "C" fn() -> ()), + ); + return &raw mut boot_session as *mut ::core::ffi::c_char; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_label_eligible( + mut labels_csv: *const ::core::ffi::c_char, + mut label: *const ::core::ffi::c_char, +) -> bool { + if label.is_null() || *label.offset(0isize) == 0 { + return r#false != 0; + } + if labels_csv.is_null() { + return r#true != 0; + } + let mut scan: *const ::core::ffi::c_char = labels_csv; + while *scan as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *scan as ::core::ffi::c_int == '\t' as ::core::ffi::c_int + { + scan = scan.offset(1); + } + if *scan as ::core::ffi::c_int == '\0' as ::core::ffi::c_int { + return r#true != 0; + } + let mut label_len: size_t = strlen(label); + let mut cursor: *const ::core::ffi::c_char = labels_csv; + loop { + let mut end: *const ::core::ffi::c_char = strchr(cursor, ',' as ::core::ffi::c_int); + let mut stop: *const ::core::ffi::c_char = if !end.is_null() { + end + } else { + cursor.offset(strlen(cursor) as isize) + }; + let mut first: *const ::core::ffi::c_char = cursor; + let mut last: *const ::core::ffi::c_char = stop; + while first < last + && (*first as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *first as ::core::ffi::c_int == '\t' as ::core::ffi::c_int) + { + first = first.offset(1); + } + while last > first + && (*last.offset(-1isize) as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *last.offset(-1isize) as ::core::ffi::c_int == '\t' as ::core::ffi::c_int) + { + last = last.offset(-1); + } + if last.offset_from(first) as size_t == label_len + && strncasecmp(first, label, label_len) == 0 as ::core::ffi::c_int + { + return r#true != 0; + } + if end.is_null() { + return r#false != 0; + } + cursor = end.offset(1 as ::core::ffi::c_int as isize); + } +} +unsafe extern "C" fn clamp01(mut v: ::core::ffi::c_double) -> ::core::ffi::c_double { + if !(v > 0.0f64) { + return 0.0f64; + } + return if v > 1.0f64 { 1.0f64 } else { v }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_clamp_box( + mut x: ::core::ffi::c_double, + mut y: ::core::ffi::c_double, + mut w: ::core::ffi::c_double, + mut h: ::core::ffi::c_double, + mut ox: *mut ::core::ffi::c_double, + mut oy: *mut ::core::ffi::c_double, + mut ow: *mut ::core::ffi::c_double, + mut oh: *mut ::core::ffi::c_double, +) -> bool { + if x.is_finite() as ::core::ffi::c_int == 0 + || y.is_finite() as ::core::ffi::c_int == 0 + || w.is_finite() as ::core::ffi::c_int == 0 + || h.is_finite() as ::core::ffi::c_int == 0 + { + return r#false != 0; + } + let mut x0: ::core::ffi::c_double = clamp01(x); + let mut y0: ::core::ffi::c_double = clamp01(y); + let mut x1: ::core::ffi::c_double = clamp01(x + w); + let mut y1: ::core::ffi::c_double = clamp01(y + h); + if x1 - x0 < 1e-6f64 || y1 - y0 < 1e-6f64 { + return r#false != 0; + } + if !ox.is_null() { + *ox = x0; + } + if !oy.is_null() { + *oy = y0; + } + if !ow.is_null() { + *ow = x1 - x0; + } + if !oh.is_null() { + *oh = y1 - y0; + } + return r#true != 0; +} +unsafe extern "C" fn basename_of( + mut path: *const ::core::ffi::c_char, +) -> *const ::core::ffi::c_char { + if path.is_null() { + return b"\0".as_ptr() as *const ::core::ffi::c_char; + } + let mut slash: *const ::core::ffi::c_char = strrchr(path, '/' as ::core::ffi::c_int); + return if !slash.is_null() { + slash.offset(1 as ::core::ffi::c_int as isize) + } else { + path + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_build_batch( + mut camera: *const detection_evidence_camera_t, + mut engine: *const detection_evidence_engine_t, + mut detections: *const detection_t, + mut count: ::core::ffi::c_int, + mut observed_at_ms: int64_t, + mut out: *mut observation_batch_input_t, +) -> ::core::ffi::c_int { + if camera.is_null() + || engine.is_null() + || out.is_null() + || count < 0 as ::core::ffi::c_int + || count > 0 as ::core::ffi::c_int && detections.is_null() + || (*camera).camera_uuid.is_null() + || *(*camera).camera_uuid.offset(0isize) == 0 + || observed_at_ms <= 0 as int64_t + { + return -1 as ::core::ffi::c_int; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + safe_strcpy( + &raw mut (*out).camera_uuid as *mut ::core::ffi::c_char, + (*camera).camera_uuid, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + if !(*camera).stream_name.is_null() { + safe_strcpy( + &raw mut (*out).stream_name as *mut ::core::ffi::c_char, + (*camera).stream_name, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + 0 as size_t, + ); + } + (*out).observed_at_ms = observed_at_ms; + (*out).recording_id = if (*camera).recording_id > 0 as int64_t { + (*camera).recording_id + } else { + 0 as int64_t + }; + let mut key: *const ::core::ffi::c_char = if !(*engine).engine_key.is_null() + && *(*engine).engine_key.offset(0isize) as ::core::ffi::c_int != 0 + { + (*engine).engine_key + } else { + DEFAULT_ENGINE_KEY.as_ptr() + }; + safe_strcpy( + &raw mut (*out).engine_key as *mut ::core::ffi::c_char, + key, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 0 as size_t, + ); + safe_strcpy( + &raw mut (*out).engine_type as *mut ::core::ffi::c_char, + if !(*engine).engine_type.is_null() + && *(*engine).engine_type.offset(0isize) as ::core::ffi::c_int != 0 + { + (*engine).engine_type + } else { + b"object\0".as_ptr() as *const ::core::ffi::c_char + }, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + 0 as size_t, + ); + let mut model: *const ::core::ffi::c_char = basename_of((*engine).model_path); + if strlen(model) < ::core::mem::size_of::<[::core::ffi::c_char; 129]>() { + safe_strcpy( + &raw mut (*out).model as *mut ::core::ffi::c_char, + model, + ::core::mem::size_of::<[::core::ffi::c_char; 129]>(), + 0 as size_t, + ); + } + if !(*engine).model_version.is_null() + && strlen((*engine).model_version) < ::core::mem::size_of::<[::core::ffi::c_char; 65]>() + { + safe_strcpy( + &raw mut (*out).model_version as *mut ::core::ffi::c_char, + (*engine).model_version, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 0 as size_t, + ); + } + let mut session: *const ::core::ffi::c_char = ::core::ptr::null::<::core::ffi::c_char>(); + let mut n: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count && n < OBS_MAX_BATCH { + let mut d: *const detection_t = detections.offset(i as isize); + let mut label_len: size_t = strnlen( + &raw const (*d).label as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ); + if !(label_len == 0 as size_t || label_len >= OBS_LABEL_SIZE as size_t) { + if detection_evidence_label_eligible( + (*camera).labels_csv, + &raw const (*d).label as *const ::core::ffi::c_char, + ) { + if (*d).confidence.is_finite() as ::core::ffi::c_int != 0 { + let mut o: *mut observation_object_input_t = (&raw mut (*out).objects + as *mut observation_object_input_t) + .offset(n as isize); + (*o).object_index = n; + memcpy( + &raw mut (*o).label as *mut ::core::ffi::c_char as *mut ::core::ffi::c_void, + &raw const (*d).label as *const ::core::ffi::c_char + as *const ::core::ffi::c_void, + label_len, + ); + (*o).label[label_len] = '\0' as ::core::ffi::c_char; + (*o).confidence = clamp01((*d).confidence as ::core::ffi::c_double); + (*o).has_box = detection_evidence_clamp_box( + (*d).x as ::core::ffi::c_double, + (*d).y as ::core::ffi::c_double, + (*d).width as ::core::ffi::c_double, + (*d).height as ::core::ffi::c_double, + &raw mut (*o).box_x, + &raw mut (*o).box_y, + &raw mut (*o).box_width, + &raw mut (*o).box_height, + ); + if (*d).track_id >= 0 as ::core::ffi::c_int { + if session.is_null() { + session = detection_evidence_boot_session(); + } + if *session.offset(0isize) != 0 { + (*o).has_track = r#true != 0; + snprintf( + &raw mut (*o).track_id as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + b"%d\0".as_ptr() as *const ::core::ffi::c_char, + (*d).track_id, + ); + safe_strcpy( + &raw mut (*o).track_namespace as *mut ::core::ffi::c_char, + &raw mut (*out).engine_key as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + 0 as size_t, + ); + safe_strcpy( + &raw mut (*o).track_session as *mut ::core::ffi::c_char, + session, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + } + } + n += 1; + } + } + } + i += 1; + } + (*out).object_count = n; + if n == 0 as ::core::ffi::c_int { + return 0 as ::core::ffi::c_int; + } + if lightnvr_uuid_generate_v4(&raw mut (*out).batch_id as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + return n; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_transform_compute( + mut frame_w: ::core::ffi::c_int, + mut frame_h: ::core::ffi::c_int, + mut net_w: ::core::ffi::c_int, + mut net_h: ::core::ffi::c_int, + mut letterbox: bool, + mut out: *mut observation_evidence_transform_t, +) { + if out.is_null() { + return; + } + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + (*out).mode = observation_transform_mode_t::OBS_TRANSFORM_NONE; + (*out).inference_width = frame_w; + (*out).source_width = (*out).inference_width; + (*out).inference_height = frame_h; + (*out).source_height = (*out).inference_height; + (*out).scale_y = 1.0f64; + (*out).scale_x = (*out).scale_y; + if frame_w <= 0 as ::core::ffi::c_int + || frame_h <= 0 as ::core::ffi::c_int + || net_w <= 0 as ::core::ffi::c_int + || net_h <= 0 as ::core::ffi::c_int + || net_w == frame_w && net_h == frame_h + { + return; + } + (*out).inference_width = net_w; + (*out).inference_height = net_h; + if !letterbox { + (*out).mode = observation_transform_mode_t::OBS_TRANSFORM_RESIZE; + (*out).scale_x = net_w as ::core::ffi::c_double / frame_w as ::core::ffi::c_double; + (*out).scale_y = net_h as ::core::ffi::c_double / frame_h as ::core::ffi::c_double; + return; + } + let mut sx: ::core::ffi::c_float = + net_w as ::core::ffi::c_float / frame_w as ::core::ffi::c_float; + let mut sy: ::core::ffi::c_float = + net_h as ::core::ffi::c_float / frame_h as ::core::ffi::c_float; + let mut scale: ::core::ffi::c_float = if sx < sy { sx } else { sy }; + let mut new_w: ::core::ffi::c_long = lroundf(frame_w as ::core::ffi::c_float * scale); + let mut new_h: ::core::ffi::c_long = lroundf(frame_h as ::core::ffi::c_float * scale); + if new_w < 1 as ::core::ffi::c_long { + new_w = 1 as ::core::ffi::c_long; + } + if new_h < 1 as ::core::ffi::c_long { + new_h = 1 as ::core::ffi::c_long; + } + (*out).mode = observation_transform_mode_t::OBS_TRANSFORM_LETTERBOX; + (*out).scale_y = scale as ::core::ffi::c_double; + (*out).scale_x = (*out).scale_y; + (*out).pad_x = + ((net_w - new_w as ::core::ffi::c_int) / 2 as ::core::ffi::c_int) as ::core::ffi::c_double; + (*out).pad_y = + ((net_h - new_h as ::core::ffi::c_int) / 2 as ::core::ffi::c_int) as ::core::ffi::c_double; + if (*out).pad_x < 0 as ::core::ffi::c_int as ::core::ffi::c_double { + (*out).pad_x = 0 as ::core::ffi::c_int as ::core::ffi::c_double; + } + if (*out).pad_y < 0 as ::core::ffi::c_int as ::core::ffi::c_double { + (*out).pad_y = 0 as ::core::ffi::c_int as ::core::ffi::c_double; + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_model_geometry( + mut model: detection_model_t, + mut net_w: *mut ::core::ffi::c_int, + mut net_h: *mut ::core::ffi::c_int, + mut letterbox: *mut bool, +) -> ::core::ffi::c_int { + if model.is_null() || net_w.is_null() || net_h.is_null() || letterbox.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut m: *const model_t = model as *const model_t; + if strcmp( + &raw const (*m).r#type as *const ::core::ffi::c_char, + MODEL_TYPE_SOD.as_ptr(), + ) == 0 as ::core::ffi::c_int + { + if sod_model_input_size(model, net_w, net_h) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + *letterbox = r#false != 0; + return 0 as ::core::ffi::c_int; + } + return -1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_observed_at_ms(mut frame_ts: time_t) -> int64_t { + let mut tv: timeval = timeval { + tv_sec: 0, + tv_usec: 0, + }; + gettimeofday(&raw mut tv, NULL); + if frame_ts <= 0 as time_t { + return tv.tv_sec as int64_t * 1000 as int64_t + tv.tv_usec as int64_t / 1000 as int64_t; + } + if tv.tv_sec == frame_ts { + return tv.tv_sec as int64_t * 1000 as int64_t + tv.tv_usec as int64_t / 1000 as int64_t; + } + return frame_ts as int64_t * 1000 as int64_t; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_capture( + mut camera: *const detection_evidence_camera_t, + mut engine: *const detection_evidence_engine_t, + mut detections: *const detection_t, + mut count: ::core::ffi::c_int, + mut observed_at_ms: int64_t, + mut rgb24: *mut uint8_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut transform: *const observation_evidence_transform_t, + mut submit_result: *mut observation_evidence_result_t, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> detection_evidence_status_t { + if !asset_uuid_out.is_null() { + *asset_uuid_out.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if camera.is_null() || engine.is_null() { + free(rgb24 as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + if !(*camera).evidence_enabled { + free(rgb24 as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ENABLED; + } + if (*engine).engine_type.is_null() + || strcmp( + (*engine).engine_type, + b"object\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + free(rgb24 as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_OBJECT_ENGINE; + } + if rgb24.is_null() || width <= 0 as ::core::ffi::c_int || height <= 0 as ::core::ffi::c_int { + free(rgb24 as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + let mut batch: *mut observation_batch_input_t = malloc(::core::mem::size_of::< + observation_batch_input_t, + >()) as *mut observation_batch_input_t; + if batch.is_null() { + free(rgb24 as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + let mut n: ::core::ffi::c_int = + detection_evidence_build_batch(camera, engine, detections, count, observed_at_ms, batch); + if n <= 0 as ::core::ffi::c_int { + free(batch as *mut ::core::ffi::c_void); + free(rgb24 as *mut ::core::ffi::c_void); + return detection_evidence_status_t( + (if n == 0 as ::core::ffi::c_int { + detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ELIGIBLE.0 as ::core::ffi::c_int + } else { + detection_evidence_status_t::DETECTION_EVIDENCE_ERROR.0 as ::core::ffi::c_int + }) as ::core::ffi::c_uint, + ); + } + let mut r: observation_evidence_result_t = + observation_evidence_submit(batch, rgb24, width, height, transform, asset_uuid_out); + if !submit_result.is_null() { + *submit_result = r; + } + if r.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_DEBUG, + LOG_COMPONENT, + _log_stream_name, + b"[%s] Observation evidence not accepted (result %d, %d object(s))\0".as_ptr() + as *const ::core::ffi::c_char, + if !(*camera).stream_name.is_null() { + (*camera).stream_name + } else { + b"?\0".as_ptr() as *const ::core::ffi::c_char + }, + r.0, + n, + ); + } + free(batch as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_SUBMITTED; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_capture_jpeg( + mut camera: *const detection_evidence_camera_t, + mut engine: *const detection_evidence_engine_t, + mut detections: *const detection_t, + mut count: ::core::ffi::c_int, + mut observed_at_ms: int64_t, + mut jpeg: *mut uint8_t, + mut jpeg_size: size_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, + mut transform: *const observation_evidence_transform_t, + mut submit_result: *mut observation_evidence_result_t, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> detection_evidence_status_t { + if !asset_uuid_out.is_null() { + *asset_uuid_out.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if camera.is_null() || engine.is_null() { + free(jpeg as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + if !(*camera).evidence_enabled { + free(jpeg as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ENABLED; + } + if (*engine).engine_type.is_null() + || strcmp( + (*engine).engine_type, + b"api\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + free(jpeg as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_OBJECT_ENGINE; + } + if jpeg.is_null() + || jpeg_size == 0 as size_t + || width <= 0 as ::core::ffi::c_int + || height <= 0 as ::core::ffi::c_int + { + free(jpeg as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + let mut batch: *mut observation_batch_input_t = malloc(::core::mem::size_of::< + observation_batch_input_t, + >()) as *mut observation_batch_input_t; + if batch.is_null() { + free(jpeg as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + let mut n: ::core::ffi::c_int = + detection_evidence_build_batch(camera, engine, detections, count, observed_at_ms, batch); + if n <= 0 as ::core::ffi::c_int { + free(batch as *mut ::core::ffi::c_void); + free(jpeg as *mut ::core::ffi::c_void); + return detection_evidence_status_t( + (if n == 0 as ::core::ffi::c_int { + detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ELIGIBLE.0 as ::core::ffi::c_int + } else { + detection_evidence_status_t::DETECTION_EVIDENCE_ERROR.0 as ::core::ffi::c_int + }) as ::core::ffi::c_uint, + ); + } + let mut r: observation_evidence_result_t = observation_evidence_submit_jpeg( + batch, + jpeg, + jpeg_size, + width, + height, + transform, + asset_uuid_out, + ); + if !submit_result.is_null() { + *submit_result = r; + } + if r.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_DEBUG, + LOG_COMPONENT, + _log_stream_name, + b"[%s] API observation evidence not accepted (result %d, %d object(s))\0".as_ptr() + as *const ::core::ffi::c_char, + if !(*camera).stream_name.is_null() { + (*camera).stream_name + } else { + b"?\0".as_ptr() as *const ::core::ffi::c_char + }, + r.0, + n, + ); + } + free(batch as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_SUBMITTED; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_is_motion_label( + mut label: *const ::core::ffi::c_char, +) -> bool { + return !label.is_null() + && strcasecmp(label, b"motion\0".as_ptr() as *const ::core::ffi::c_char) + == 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn detection_evidence_record_no_media( + mut camera: *const detection_evidence_camera_t, + mut engine: *const detection_evidence_engine_t, + mut detections: *const detection_t, + mut count: ::core::ffi::c_int, + mut observed_at_ms: int64_t, + mut keep_boxes: bool, + mut submit_result: *mut observation_evidence_result_t, + mut asset_uuid_out: *mut ::core::ffi::c_char, +) -> detection_evidence_status_t { + if !asset_uuid_out.is_null() { + *asset_uuid_out.offset(0isize) = '\0' as ::core::ffi::c_char; + } + if camera.is_null() + || engine.is_null() + || count < 0 as ::core::ffi::c_int + || count > 0 as ::core::ffi::c_int && detections.is_null() + { + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + if !(*camera).evidence_enabled { + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ENABLED; + } + if (*engine).engine_type.is_null() + || strcmp( + (*engine).engine_type, + b"onvif\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + && strcmp( + (*engine).engine_type, + b"external\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_OBJECT_ENGINE; + } + let mut objects: [detection_t; 20] = [detection_t { + label: [0; 32], + confidence: 0., + x: 0., + y: 0., + width: 0., + height: 0., + track_id: 0, + zone_id: [0; 32], + }; 20]; + let mut kept: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count && kept < MAX_DETECTIONS { + if !detection_evidence_is_motion_label( + &raw const (*detections.offset(i as isize)).label as *const ::core::ffi::c_char, + ) { + let c2rust_fresh0 = kept; + kept += 1; + objects[c2rust_fresh0 as usize] = *detections.offset(i as isize); + } + i += 1; + } + if kept == 0 as ::core::ffi::c_int { + return detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ELIGIBLE; + } + let mut batch: *mut observation_batch_input_t = malloc(::core::mem::size_of::< + observation_batch_input_t, + >()) as *mut observation_batch_input_t; + if batch.is_null() { + return detection_evidence_status_t::DETECTION_EVIDENCE_ERROR; + } + let mut n: ::core::ffi::c_int = detection_evidence_build_batch( + camera, + engine, + &raw mut objects as *mut detection_t, + kept, + observed_at_ms, + batch, + ); + if n <= 0 as ::core::ffi::c_int { + free(batch as *mut ::core::ffi::c_void); + return detection_evidence_status_t( + (if n == 0 as ::core::ffi::c_int { + detection_evidence_status_t::DETECTION_EVIDENCE_NOT_ELIGIBLE.0 as ::core::ffi::c_int + } else { + detection_evidence_status_t::DETECTION_EVIDENCE_ERROR.0 as ::core::ffi::c_int + }) as ::core::ffi::c_uint, + ); + } + if !keep_boxes { + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < n { + let mut o: *mut observation_object_input_t = + (&raw mut (*batch).objects as *mut observation_object_input_t).offset(i_0 as isize); + (*o).has_box = r#false != 0; + (*o).box_height = 0.0f64; + (*o).box_width = (*o).box_height; + (*o).box_y = (*o).box_width; + (*o).box_x = (*o).box_y; + i_0 += 1; + } + } + let mut r: observation_evidence_result_t = observation_evidence_submit_unavailable( + batch, + b"no_matching_media\0".as_ptr() as *const ::core::ffi::c_char, + asset_uuid_out, + ); + if !submit_result.is_null() { + *submit_result = r; + } + if r.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 { + _log_message_ctx( + log_level_t::LOG_LEVEL_DEBUG, + LOG_COMPONENT, + _log_stream_name, + b"[%s] %s observation not recorded (result %d, %d object(s))\0".as_ptr() + as *const ::core::ffi::c_char, + if !(*camera).stream_name.is_null() { + (*camera).stream_name + } else { + b"?\0".as_ptr() as *const ::core::ffi::c_char + }, + (*engine).engine_type, + r.0, + n, + ); + } + free(batch as *mut ::core::ffi::c_void); + return detection_evidence_status_t::DETECTION_EVIDENCE_SUBMITTED; +} diff --git a/rust/generated/src__video__detection_stream.rs b/rust/generated/src__video__detection_stream.rs index 2bd29bce8..465350ff3 100644 --- a/rust/generated/src__video__detection_stream.rs +++ b/rust/generated/src__video__detection_stream.rs @@ -1501,6 +1501,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -1606,7 +1610,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__ffmpeg_utils.rs b/rust/generated/src__video__ffmpeg_utils.rs index 8671c6476..b1c8f4393 100644 --- a/rust/generated/src__video__ffmpeg_utils.rs +++ b/rust/generated/src__video__ffmpeg_utils.rs @@ -2558,7 +2558,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -2665,6 +2665,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__video__go2rtc__go2rtc_consumer.rs b/rust/generated/src__video__go2rtc__go2rtc_consumer.rs index 25ec406db..db35876ef 100644 --- a/rust/generated/src__video__go2rtc__go2rtc_consumer.rs +++ b/rust/generated/src__video__go2rtc__go2rtc_consumer.rs @@ -53,6 +53,7 @@ unsafe extern "C" { ) -> ::core::ffi::c_int; } pub type size_t = usize; +pub type int64_t = i64; pub type CURL = (); #[derive(Copy, Clone)] #[repr(C)] @@ -685,7 +686,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -794,6 +795,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } pub type uint8_t = u8; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/rust/generated/src__video__go2rtc__go2rtc_integration.rs b/rust/generated/src__video__go2rtc__go2rtc_integration.rs index 84140258a..04e08632e 100644 --- a/rust/generated/src__video__go2rtc__go2rtc_integration.rs +++ b/rust/generated/src__video__go2rtc__go2rtc_integration.rs @@ -787,6 +787,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -892,7 +896,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -27372,6 +27376,10 @@ unsafe extern "C" fn ensure_stream_registered_with_go2rtc( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -27616,6 +27624,10 @@ unsafe extern "C" fn check_stream_consensus() -> bool { publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) == 0 as ::core::ffi::c_int { if config.enabled { @@ -28105,6 +28117,10 @@ unsafe extern "C" fn unified_health_monitor_thread( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) == 0 as ::core::ffi::c_int @@ -28855,6 +28871,10 @@ pub unsafe extern "C" fn go2rtc_integration_start_recording_with_trigger( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -29078,6 +29098,10 @@ pub unsafe extern "C" fn go2rtc_integration_start_hls( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -29993,6 +30017,10 @@ unsafe extern "C" fn go2rtc_integration_reload_stream_config_locked( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut have_config: bool = r#false != 0; if !stream.is_null() && get_stream_config(stream, &raw mut config) == 0 as ::core::ffi::c_int { @@ -30456,6 +30484,10 @@ pub unsafe extern "C" fn go2rtc_integration_register_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( diff --git a/rust/generated/src__video__go2rtc__go2rtc_process.rs b/rust/generated/src__video__go2rtc__go2rtc_process.rs index 39d466e30..a406b2ffb 100644 --- a/rust/generated/src__video__go2rtc__go2rtc_process.rs +++ b/rust/generated/src__video__go2rtc__go2rtc_process.rs @@ -130,6 +130,12 @@ unsafe extern "C" { unsafe fn curl_easy_perform(curl: *mut ::core::ffi::c_void) -> CURLcode; unsafe fn curl_easy_cleanup(curl: *mut ::core::ffi::c_void); unsafe fn curl_easy_getinfo(curl: *mut ::core::ffi::c_void, info: CURLINFO, ...) -> CURLcode; + unsafe fn go2rtc_parse_external_candidates( + list: *const ::core::ffi::c_char, + port: ::core::ffi::c_int, + out: *mut [::core::ffi::c_char; 128], + max: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; unsafe fn go2rtc_api_get_base_path() -> *const ::core::ffi::c_char; unsafe fn go2rtc_api_apply_base_path_override( yaml: *const ::core::ffi::c_char, @@ -274,6 +280,7 @@ pub type pid_t = __pid_t; pub type ssize_t = isize; pub type clockid_t = __clockid_t; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct __sigset_t { @@ -993,7 +1000,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1102,6 +1109,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } pub type uint8_t = u8; #[derive(Clone, Copy, PartialEq, Eq)] @@ -1195,6 +1206,7 @@ pub const GO2RTC_FFMPEG_H264: [::core::ffi::c_char; 215] = unsafe { pub const CLOCK_MONOTONIC: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const LIGHTNVR_VERSION_STRING: [::core::ffi::c_char; 7] = unsafe { ::core::mem::transmute::<[u8; 7], [::core::ffi::c_char; 7]>(*b"0.47.3\0") }; +pub const GO2RTC_MAX_EXTERNAL_CANDIDATES: ::core::ffi::c_int = 8 as ::core::ffi::c_int; pub const GO2RTC_BASE_PATH: [::core::ffi::c_char; 8] = unsafe { ::core::mem::transmute::<[u8; 8], [::core::ffi::c_char; 8]>(*b"/go2rtc\0") }; pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; @@ -12197,29 +12209,44 @@ unsafe extern "C" fn go2rtc_process_generate_config_locked( config_file, b" candidates:\n\0".as_ptr() as *const ::core::ffi::c_char, ); - if (*global_config).go2rtc_external_ip[0usize] as ::core::ffi::c_int - != '\0' as ::core::ffi::c_int - { + let mut webrtc_port: ::core::ffi::c_int = + if (*global_config).go2rtc_webrtc_listen_port > 0 as ::core::ffi::c_int { + (*global_config).go2rtc_webrtc_listen_port + } else { + 8555 as ::core::ffi::c_int + }; + let mut candidates: [[::core::ffi::c_char; 128]; 8] = [[0; 128]; 8]; + let mut candidate_count: ::core::ffi::c_int = go2rtc_parse_external_candidates( + &raw mut (*global_config).go2rtc_external_ip as *mut ::core::ffi::c_char, + webrtc_port, + &raw mut candidates as *mut [::core::ffi::c_char; 128], + GO2RTC_MAX_EXTERNAL_CANDIDATES, + ); + if candidate_count < 0 as ::core::ffi::c_int { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"go2rtc config: ignoring invalid external_ip list; using auto-detect\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + if candidate_count <= 0 as ::core::ffi::c_int { fprintf( config_file, - b" - \"%s:%d\"\n\0".as_ptr() as *const ::core::ffi::c_char, - &raw mut (*global_config).go2rtc_external_ip as *mut ::core::ffi::c_char, - if (*global_config).go2rtc_webrtc_listen_port > 0 as ::core::ffi::c_int { - (*global_config).go2rtc_webrtc_listen_port - } else { - 8555 as ::core::ffi::c_int - }, + b" - \"*:%d\"\n\0".as_ptr() as *const ::core::ffi::c_char, + webrtc_port, ); - } else { + } + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < candidate_count { fprintf( config_file, - b" - \"*:%d\"\n\0".as_ptr() as *const ::core::ffi::c_char, - if (*global_config).go2rtc_webrtc_listen_port > 0 as ::core::ffi::c_int { - (*global_config).go2rtc_webrtc_listen_port - } else { - 8555 as ::core::ffi::c_int - }, + b" - \"%s\"\n\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut *(&raw mut candidates as *mut [::core::ffi::c_char; 128]) + .offset(i as isize) as *mut ::core::ffi::c_char, ); + i += 1; } if (*global_config).go2rtc_stun_enabled { fprintf( @@ -12239,19 +12266,20 @@ unsafe extern "C" fn go2rtc_process_generate_config_locked( b"/usr/bin/lightnvr-ffmpeg-guard\0".as_ptr() as *const ::core::ffi::c_char, b"/usr/local/bin/lightnvr-ffmpeg-guard\0".as_ptr() as *const ::core::ffi::c_char, ]; - let mut i: size_t = 0 as size_t; - while i < ::core::mem::size_of::<[*const ::core::ffi::c_char; 3]>() - .wrapping_div(::core::mem::size_of::<*const ::core::ffi::c_char>()) + let mut i_0: size_t = 0 as size_t; + while i_0 + < ::core::mem::size_of::<[*const ::core::ffi::c_char; 3]>() + .wrapping_div(::core::mem::size_of::<*const ::core::ffi::c_char>()) { - if access(guard_paths[i], X_OK) == 0 as ::core::ffi::c_int { + if access(guard_paths[i_0], X_OK) == 0 as ::core::ffi::c_int { fprintf( config_file, b" bin: %s\n\0".as_ptr() as *const ::core::ffi::c_char, - guard_paths[i], + guard_paths[i_0], ); break; } else { - i = i.wrapping_add(1); + i_0 = i_0.wrapping_add(1); } } let mut escaped_transcode: [::core::ffi::c_char; 1024] = [0; 1024]; diff --git a/rust/generated/src__video__go2rtc__go2rtc_stream.rs b/rust/generated/src__video__go2rtc__go2rtc_stream.rs index 469b2b598..412404502 100644 --- a/rust/generated/src__video__go2rtc__go2rtc_stream.rs +++ b/rust/generated/src__video__go2rtc__go2rtc_stream.rs @@ -989,6 +989,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -1094,7 +1098,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1516,6 +1520,10 @@ unsafe extern "C" fn go2rtc_stream_register_locked( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memset( &raw mut pub_cfg as *mut ::core::ffi::c_void, diff --git a/rust/generated/src__video__hls__hls_context.rs b/rust/generated/src__video__hls__hls_context.rs index 9c696ca9d..3ccbc587e 100644 --- a/rust/generated/src__video__hls__hls_context.rs +++ b/rust/generated/src__video__hls__hls_context.rs @@ -184,6 +184,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -289,7 +293,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__hls__hls_directory.rs b/rust/generated/src__video__hls__hls_directory.rs index e87474c40..9a1545abf 100644 --- a/rust/generated/src__video__hls__hls_directory.rs +++ b/rust/generated/src__video__hls__hls_directory.rs @@ -242,6 +242,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -347,7 +351,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__hls__hls_unified_thread.rs b/rust/generated/src__video__hls__hls_unified_thread.rs index 03db964a7..a0faf80b3 100644 --- a/rust/generated/src__video__hls__hls_unified_thread.rs +++ b/rust/generated/src__video__hls__hls_unified_thread.rs @@ -2326,6 +2326,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -2431,7 +2435,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -12466,6 +12470,10 @@ pub unsafe extern "C" fn start_hls_unified_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( diff --git a/rust/generated/src__video__hls_streaming.rs b/rust/generated/src__video__hls_streaming.rs index d0a11b845..cc49c2d3b 100644 --- a/rust/generated/src__video__hls_streaming.rs +++ b/rust/generated/src__video__hls_streaming.rs @@ -167,6 +167,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -272,7 +276,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__mp4_recording_core.rs b/rust/generated/src__video__mp4_recording_core.rs index 271927c26..235be3bba 100644 --- a/rust/generated/src__video__mp4_recording_core.rs +++ b/rust/generated/src__video__mp4_recording_core.rs @@ -307,6 +307,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -412,7 +416,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -3048,6 +3052,10 @@ pub unsafe extern "C" fn start_mp4_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -3269,6 +3277,10 @@ pub unsafe extern "C" fn start_mp4_recording_with_url( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -3610,6 +3622,10 @@ pub unsafe extern "C" fn start_mp4_recording_with_trigger( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -3841,6 +3857,10 @@ pub unsafe extern "C" fn start_mp4_recording_with_url_and_trigger( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( diff --git a/rust/generated/src__video__mp4_recording_writer.rs b/rust/generated/src__video__mp4_recording_writer.rs index ec53ec831..3927dc939 100644 --- a/rust/generated/src__video__mp4_recording_writer.rs +++ b/rust/generated/src__video__mp4_recording_writer.rs @@ -214,6 +214,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -319,7 +323,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__mp4_segment_recorder.rs b/rust/generated/src__video__mp4_segment_recorder.rs index 16cbf4fed..762884751 100644 --- a/rust/generated/src__video__mp4_segment_recorder.rs +++ b/rust/generated/src__video__mp4_segment_recorder.rs @@ -2315,6 +2315,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -2420,7 +2424,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__mp4_writer.rs b/rust/generated/src__video__mp4_writer.rs index b4b096701..ee412c87f 100644 --- a/rust/generated/src__video__mp4_writer.rs +++ b/rust/generated/src__video__mp4_writer.rs @@ -1461,6 +1461,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -1566,7 +1570,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__mp4_writer_thread.rs b/rust/generated/src__video__mp4_writer_thread.rs index 38ea0960f..4aed97bc8 100644 --- a/rust/generated/src__video__mp4_writer_thread.rs +++ b/rust/generated/src__video__mp4_writer_thread.rs @@ -1664,6 +1664,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -1769,7 +1773,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -2437,6 +2441,10 @@ unsafe extern "C" fn mp4_writer_rtsp_thread( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut db_config_result: ::core::ffi::c_int = get_stream_config_by_name( &raw mut stream_name as *mut ::core::ffi::c_char, diff --git a/rust/generated/src__video__mp4_writer_utils.rs b/rust/generated/src__video__mp4_writer_utils.rs index a719ab8f6..d4552b3d2 100644 --- a/rust/generated/src__video__mp4_writer_utils.rs +++ b/rust/generated/src__video__mp4_writer_utils.rs @@ -2570,6 +2570,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -2675,7 +2679,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__onvif_detection.rs b/rust/generated/src__video__onvif_detection.rs index 217ad2d79..a19c3f96d 100644 --- a/rust/generated/src__video__onvif_detection.rs +++ b/rust/generated/src__video__onvif_detection.rs @@ -956,6 +956,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -4334,6 +4338,10 @@ unsafe extern "C" fn store_lpr_notifications( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name(stream_name, &raw mut stream) != 0 as ::core::ffi::c_int || stream.camera_uuid[0usize] == 0 diff --git a/rust/generated/src__video__onvif_device_management.rs b/rust/generated/src__video__onvif_device_management.rs index e22446662..324eeea9e 100644 --- a/rust/generated/src__video__onvif_device_management.rs +++ b/rust/generated/src__video__onvif_device_management.rs @@ -30,6 +30,11 @@ unsafe extern "C" { unsafe fn curl_easy_perform(curl: *mut ::core::ffi::c_void) -> CURLcode; unsafe fn curl_easy_cleanup(curl: *mut ::core::ffi::c_void); unsafe fn curl_easy_getinfo(curl: *mut ::core::ffi::c_void, info: CURLINFO, ...) -> CURLcode; + unsafe fn av_strerror( + errnum: ::core::ffi::c_int, + errbuf: *mut ::core::ffi::c_char, + errbuf_size: size_t, + ) -> ::core::ffi::c_int; unsafe fn avformat_close_input(s: *mut *mut AVFormatContext); unsafe fn ezxml_parse_str(s: *mut ::core::ffi::c_char, len: size_t) -> ezxml_t; unsafe fn ezxml_child(xml: ezxml_t, name: *const ::core::ffi::c_char) -> ezxml_t; @@ -98,6 +103,11 @@ unsafe extern "C" { __c: ::core::ffi::c_int, __n: size_t, ) -> *mut ::core::ffi::c_void; + unsafe fn memchr( + __s: *const ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; unsafe fn strcmp( __s1: *const ::core::ffi::c_char, __s2: *const ::core::ffi::c_char, @@ -107,6 +117,10 @@ unsafe extern "C" { __s: *const ::core::ffi::c_char, __c: ::core::ffi::c_int, ) -> *mut ::core::ffi::c_char; + unsafe fn strcspn( + __s: *const ::core::ffi::c_char, + __reject: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_ulong; unsafe fn strstr( __haystack: *const ::core::ffi::c_char, __needle: *const ::core::ffi::c_char, @@ -116,6 +130,11 @@ unsafe extern "C" { __s1: *const ::core::ffi::c_char, __s2: *const ::core::ffi::c_char, ) -> ::core::ffi::c_int; + unsafe fn strncasecmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + __n: size_t, + ) -> ::core::ffi::c_int; unsafe fn strtol( __nptr: *const ::core::ffi::c_char, __endptr: *mut *mut ::core::ffi::c_char, @@ -2048,6 +2067,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -2105,6 +2128,25 @@ impl log_level_t { pub const LOG_LEVEL_DEBUG: Self = Self(3); } pub type stream_handle_t = *mut stream_handle_s; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_3(pub ::core::ffi::c_int); +impl C2Rust_Unnamed_3 { + pub const ONVIF_TEST_OK: Self = Self(0); + pub const ONVIF_TEST_DEVICE_ERROR: Self = Self(-1); + pub const ONVIF_TEST_UNREACHABLE: Self = Self(-2); + pub const ONVIF_TEST_AUTH_FAILED: Self = Self(-3); + pub const ONVIF_TEST_STREAM_FAILED: Self = Self(-4); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_4(pub ::core::ffi::c_uint); +impl C2Rust_Unnamed_4 { + pub const SOAP_FAIL_NONE: Self = Self(0); + pub const SOAP_FAIL_TRANSPORT: Self = Self(1); + pub const SOAP_FAIL_AUTH: Self = Self(2); + pub const SOAP_FAIL_OTHER: Self = Self(3); +} pub const MAX_STREAM_NAME: ::core::ffi::c_int = 256 as ::core::ffi::c_int; pub const MAX_URL_LENGTH: ::core::ffi::c_int = 512 as ::core::ffi::c_int; unsafe extern "C" fn write_callback( @@ -2138,6 +2180,8 @@ unsafe extern "C" fn write_callback( *(*mem).memory.offset((*mem).size as isize) = 0 as ::core::ffi::c_char; return realsize; } +#[thread_local] +static mut g_soap_failure: ::core::ffi::c_int = 0; unsafe extern "C" fn send_soap_request( mut device_url: *const ::core::ffi::c_char, mut soap_action: *const ::core::ffi::c_char, @@ -2335,6 +2379,7 @@ unsafe extern "C" fn send_soap_request( security_header, request_body, ); + curl_easy_setopt(curl, CURLoption::CURLOPT_POSTFIELDS, soap_envelope); res = curl_easy_perform(curl); if res.0 != CURLcode::CURLE_OK.0 { _log_message_ctx( @@ -2363,10 +2408,28 @@ unsafe extern "C" fn send_soap_request( b"First SOAP format succeeded\0".as_ptr() as *const ::core::ffi::c_char, ); } + g_soap_failure = if res.0 == CURLcode::CURLE_OK.0 { + C2Rust_Unnamed_4::SOAP_FAIL_NONE.0 as ::core::ffi::c_int + } else { + C2Rust_Unnamed_4::SOAP_FAIL_TRANSPORT.0 as ::core::ffi::c_int + }; if res.0 == CURLcode::CURLE_OK.0 { let mut http_code: ::core::ffi::c_long = 0 as ::core::ffi::c_long; curl_easy_getinfo(curl, CURLINFO::CURLINFO_RESPONSE_CODE, &raw mut http_code); if http_code != 200 as ::core::ffi::c_long { + let mut not_authorized: bool = http_code == 401 as ::core::ffi::c_long + || http_code == 403 as ::core::ffi::c_long + || chunk.size > 0 as size_t + && !strstr( + chunk.memory, + b"NotAuthorized\0".as_ptr() as *const ::core::ffi::c_char, + ) + .is_null(); + g_soap_failure = if not_authorized as ::core::ffi::c_int != 0 { + C2Rust_Unnamed_4::SOAP_FAIL_AUTH.0 as ::core::ffi::c_int + } else { + C2Rust_Unnamed_4::SOAP_FAIL_OTHER.0 as ::core::ffi::c_int + }; _log_message_ctx( log_level_t::LOG_LEVEL_ERROR, LOG_COMPONENT, @@ -3420,6 +3483,10 @@ pub unsafe extern "C" fn add_onvif_device_as_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if device_info.is_null() || profile.is_null() || stream_name.is_null() { _log_message_ctx( @@ -3638,11 +3705,208 @@ pub unsafe extern "C" fn add_onvif_device_as_stream( return 0 as ::core::ffi::c_int; } #[unsafe(no_mangle)] -pub unsafe extern "C" fn test_onvif_connection( +pub unsafe extern "C" fn onvif_plain_rtsp_fallback_url( + mut uri: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, +) -> ::core::ffi::c_int { + if uri.is_null() + || out.is_null() + || out_size == 0 as size_t + || strncasecmp( + uri, + b"rtsps://\0".as_ptr() as *const ::core::ffi::c_char, + 8 as size_t, + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut authority: *const ::core::ffi::c_char = uri.offset(8 as ::core::ffi::c_int as isize); + let mut path: *const ::core::ffi::c_char = authority + .offset(strcspn(authority, b"/?#\0".as_ptr() as *const ::core::ffi::c_char) as isize); + let mut host: *const ::core::ffi::c_char = authority; + let mut p: *const ::core::ffi::c_char = authority; + while p < path { + if *p as ::core::ffi::c_int == '@' as ::core::ffi::c_int { + host = p.offset(1 as ::core::ffi::c_int as isize); + } + p = p.offset(1); + } + let mut host_end: *const ::core::ffi::c_char = ::core::ptr::null::<::core::ffi::c_char>(); + if *host as ::core::ffi::c_int == '[' as ::core::ffi::c_int { + let mut close: *const ::core::ffi::c_char = memchr( + host as *const ::core::ffi::c_void, + ']' as ::core::ffi::c_int, + path.offset_from(host) as size_t, + ) as *const ::core::ffi::c_char; + if close.is_null() { + return -1 as ::core::ffi::c_int; + } + host_end = close.offset(1 as ::core::ffi::c_int as isize); + } else { + let mut colon: *const ::core::ffi::c_char = memchr( + host as *const ::core::ffi::c_void, + ':' as ::core::ffi::c_int, + path.offset_from(host) as size_t, + ) as *const ::core::ffi::c_char; + host_end = if !colon.is_null() { colon } else { path }; + } + if host_end == host { + return -1 as ::core::ffi::c_int; + } + let mut written: ::core::ffi::c_int = snprintf( + out, + out_size, + b"rtsp://%.*s:554%s\0".as_ptr() as *const ::core::ffi::c_char, + host_end.offset_from(authority) as ::core::ffi::c_int, + authority, + path, + ); + return if written > 0 as ::core::ffi::c_int && (written as size_t) < out_size { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn probe_stream( + mut stream_url: *const ::core::ffi::c_char, + mut safe_url: *const ::core::ffi::c_char, + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, +) -> bool { + static mut protocols: [::core::ffi::c_int; 2] = [ + stream_protocol_t::STREAM_PROTOCOL_TCP.0 as ::core::ffi::c_int, + stream_protocol_t::STREAM_PROTOCOL_UDP.0 as ::core::ffi::c_int, + ]; + let mut ret: ::core::ffi::c_int = -1 as ::core::ffi::c_int; + let mut i: size_t = 0 as size_t; + while i < ::core::mem::size_of::<[::core::ffi::c_int; 2]>() + .wrapping_div(::core::mem::size_of::<::core::ffi::c_int>()) + { + let mut input_ctx: *mut AVFormatContext = ::core::ptr::null_mut::(); + ret = open_input_stream(&raw mut input_ctx, stream_url, protocols[i]); + if ret >= 0 as ::core::ffi::c_int { + avformat_close_input(&raw mut input_ctx); + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Successfully connected to stream: %s\0".as_ptr() as *const ::core::ffi::c_char, + safe_url, + ); + return r#true != 0; + } + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT, + _log_stream_name, + b"Failed to open stream %s over %s\0".as_ptr() as *const ::core::ffi::c_char, + safe_url, + if protocols[i] == stream_protocol_t::STREAM_PROTOCOL_TCP.0 as ::core::ffi::c_int { + b"TCP\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"UDP\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + i = i.wrapping_add(1); + } + let mut reason: [::core::ffi::c_char; 64] = [ + 0 as ::core::ffi::c_char, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + av_strerror( + ret, + &raw mut reason as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + ); + snprintf( + error, + error_size, + b"%s\0".as_ptr() as *const ::core::ffi::c_char, + if reason[0usize] as ::core::ffi::c_int != 0 { + &raw mut reason as *mut ::core::ffi::c_char + } else { + b"could not open stream\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + return r#false != 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn test_onvif_connection_detail( mut url: *const ::core::ffi::c_char, mut username: *const ::core::ffi::c_char, mut password: *const ::core::ffi::c_char, + mut detail: *mut ::core::ffi::c_char, + mut detail_size: size_t, ) -> ::core::ffi::c_int { + let mut scratch: [::core::ffi::c_char; 1] = [0; 1]; + if detail.is_null() || detail_size == 0 as size_t { + detail = &raw mut scratch as *mut ::core::ffi::c_char; + detail_size = ::core::mem::size_of::<[::core::ffi::c_char; 1]>() as size_t; + } + *detail.offset(0isize) = '\0' as ::core::ffi::c_char; let mut safe_device_url: [::core::ffi::c_char; 512] = [0; 512]; if url_redact_for_logging( url, @@ -3664,6 +3928,7 @@ pub unsafe extern "C" fn test_onvif_connection( b"Testing connection to ONVIF device: %s\0".as_ptr() as *const ::core::ffi::c_char, &raw mut safe_device_url as *mut ::core::ffi::c_char, ); + g_soap_failure = C2Rust_Unnamed_4::SOAP_FAIL_NONE.0 as ::core::ffi::c_int; let mut profiles: [onvif_profile_t; 1] = [onvif_profile_t { token: [0; 64], name: [0; 64], @@ -3692,7 +3957,37 @@ pub unsafe extern "C" fn test_onvif_connection( b"Failed to connect to ONVIF device: %s\0".as_ptr() as *const ::core::ffi::c_char, &raw mut safe_device_url as *mut ::core::ffi::c_char, ); - return -1 as ::core::ffi::c_int; + match g_soap_failure { + 2 => { + snprintf( + detail, + detail_size, + b"The camera rejected the ONVIF credentials. Check the username and password, that this ONVIF user may read media profiles, and that the camera's clock is correct.\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + return C2Rust_Unnamed_3::ONVIF_TEST_AUTH_FAILED.0; + } + 1 => { + snprintf( + detail, + detail_size, + b"Could not reach the camera's ONVIF service at %s.\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut safe_device_url as *mut ::core::ffi::c_char, + ); + return C2Rust_Unnamed_3::ONVIF_TEST_UNREACHABLE.0; + } + _ => { + snprintf( + detail, + detail_size, + b"The camera's ONVIF service at %s answered, but did not return a usable media profile.\0" + .as_ptr() as *const ::core::ffi::c_char, + &raw mut safe_device_url as *mut ::core::ffi::c_char, + ); + return C2Rust_Unnamed_3::ONVIF_TEST_DEVICE_ERROR.0; + } + } } _log_message_ctx( log_level_t::LOG_LEVEL_INFO, @@ -3701,46 +3996,100 @@ pub unsafe extern "C" fn test_onvif_connection( b"Successfully connected to ONVIF device: %s\0".as_ptr() as *const ::core::ffi::c_char, &raw mut safe_device_url as *mut ::core::ffi::c_char, ); - if strlen( + if profiles[0usize].stream_uri[0usize] as ::core::ffi::c_int == '\0' as ::core::ffi::c_int { + snprintf( + detail, + detail_size, + b"Connected to the camera's ONVIF service.\0".as_ptr() as *const ::core::ffi::c_char, + ); + return C2Rust_Unnamed_3::ONVIF_TEST_OK.0; + } + let mut user: *const ::core::ffi::c_char = + if !username.is_null() && *username.offset(0isize) as ::core::ffi::c_int != 0 { + username + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }; + let mut pass: *const ::core::ffi::c_char = + if !password.is_null() && *password.offset(0isize) as ::core::ffi::c_int != 0 { + password + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }; + let mut stream_url: [::core::ffi::c_char; 512] = [0; 512]; + let mut safe_stream_url: [::core::ffi::c_char; 512] = [0; 512]; + if url_apply_credentials( &raw mut (*(&raw mut profiles as *mut onvif_profile_t).offset(0isize)).stream_uri as *mut ::core::ffi::c_char, - ) > 0 as size_t + user, + pass, + &raw mut stream_url as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ) != 0 as ::core::ffi::c_int { - let mut stream_url: [::core::ffi::c_char; 512] = [0; 512]; - let mut safe_stream_url: [::core::ffi::c_char; 512] = [0; 512]; - if url_apply_credentials( + safe_strcpy( + &raw mut stream_url as *mut ::core::ffi::c_char, &raw mut (*(&raw mut profiles as *mut onvif_profile_t).offset(0isize)).stream_uri as *mut ::core::ffi::c_char, - if !username.is_null() && strlen(username) > 0 as size_t { - username - } else { - ::core::ptr::null::<::core::ffi::c_char>() - }, - if !password.is_null() && strlen(password) > 0 as size_t { - password - } else { - ::core::ptr::null::<::core::ffi::c_char>() - }, - &raw mut stream_url as *mut ::core::ffi::c_char, ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), - ) != 0 as ::core::ffi::c_int - { - safe_strcpy( - &raw mut stream_url as *mut ::core::ffi::c_char, - &raw mut (*(&raw mut profiles as *mut onvif_profile_t).offset(0isize)).stream_uri - as *mut ::core::ffi::c_char, - ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), - 0 as size_t, - ); - } - if url_redact_for_logging( - &raw mut stream_url as *mut ::core::ffi::c_char, + 0 as size_t, + ); + } + if url_redact_for_logging( + &raw mut stream_url as *mut ::core::ffi::c_char, + &raw mut safe_stream_url as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ) != 0 as ::core::ffi::c_int + { + safe_strcpy( + &raw mut safe_stream_url as *mut ::core::ffi::c_char, + b"[invalid-url]\0".as_ptr() as *const ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + } + _log_message_ctx( + log_level_t::LOG_LEVEL_INFO, + LOG_COMPONENT, + _log_stream_name, + b"Testing stream connection for profile: %s, URI: %s\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*(&raw mut profiles as *mut onvif_profile_t).offset(0isize)).token + as *mut ::core::ffi::c_char, + &raw mut safe_stream_url as *mut ::core::ffi::c_char, + ); + let mut error: [::core::ffi::c_char; 96] = [0; 96]; + if probe_stream( + &raw mut stream_url as *mut ::core::ffi::c_char, + &raw mut safe_stream_url as *mut ::core::ffi::c_char, + &raw mut error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 96]>(), + ) { + snprintf( + detail, + detail_size, + b"Connected to the camera and opened its stream at %s.\0".as_ptr() + as *const ::core::ffi::c_char, &raw mut safe_stream_url as *mut ::core::ffi::c_char, + ); + return C2Rust_Unnamed_3::ONVIF_TEST_OK.0; + } + let mut fallback_url: [::core::ffi::c_char; 512] = [0; 512]; + if onvif_plain_rtsp_fallback_url( + &raw mut stream_url as *mut ::core::ffi::c_char, + &raw mut fallback_url as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ) == 0 as ::core::ffi::c_int + { + let mut safe_fallback: [::core::ffi::c_char; 512] = [0; 512]; + if url_redact_for_logging( + &raw mut fallback_url as *mut ::core::ffi::c_char, + &raw mut safe_fallback as *mut ::core::ffi::c_char, ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), ) != 0 as ::core::ffi::c_int { safe_strcpy( - &raw mut safe_stream_url as *mut ::core::ffi::c_char, + &raw mut safe_fallback as *mut ::core::ffi::c_char, b"[invalid-url]\0".as_ptr() as *const ::core::ffi::c_char, ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), 0 as size_t, @@ -3750,77 +4099,73 @@ pub unsafe extern "C" fn test_onvif_connection( log_level_t::LOG_LEVEL_INFO, LOG_COMPONENT, _log_stream_name, - b"Testing stream connection for profile: %s, URI: %s\0".as_ptr() + b"Advertised secure stream failed; trying plain RTSP: %s\0".as_ptr() as *const ::core::ffi::c_char, - &raw mut (*(&raw mut profiles as *mut onvif_profile_t).offset(0isize)).token - as *mut ::core::ffi::c_char, - &raw mut safe_stream_url as *mut ::core::ffi::c_char, - ); - let mut input_ctx: *mut AVFormatContext = ::core::ptr::null_mut::(); - let mut ret: ::core::ffi::c_int = open_input_stream( - &raw mut input_ctx, - &raw mut stream_url as *mut ::core::ffi::c_char, - stream_protocol_t::STREAM_PROTOCOL_TCP.0 as ::core::ffi::c_int, + &raw mut safe_fallback as *mut ::core::ffi::c_char, ); - if ret < 0 as ::core::ffi::c_int { - _log_message_ctx( - log_level_t::LOG_LEVEL_WARN, - LOG_COMPONENT, - _log_stream_name, - b"Failed to connect to stream with TCP protocol, trying UDP: %s\0".as_ptr() - as *const ::core::ffi::c_char, + let mut fallback_error: [::core::ffi::c_char; 96] = [0; 96]; + if probe_stream( + &raw mut fallback_url as *mut ::core::ffi::c_char, + &raw mut safe_fallback as *mut ::core::ffi::c_char, + &raw mut fallback_error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 96]>(), + ) { + snprintf( + detail, + detail_size, + b"Connected to the camera. Its advertised stream %s failed (%s), but plain RTSP at %s works.\0" + .as_ptr() as *const ::core::ffi::c_char, &raw mut safe_stream_url as *mut ::core::ffi::c_char, + &raw mut error as *mut ::core::ffi::c_char, + &raw mut safe_fallback as *mut ::core::ffi::c_char, ); - ret = open_input_stream( - &raw mut input_ctx, - &raw mut stream_url as *mut ::core::ffi::c_char, - stream_protocol_t::STREAM_PROTOCOL_UDP.0 as ::core::ffi::c_int, - ); - if ret < 0 as ::core::ffi::c_int { - _log_message_ctx( - log_level_t::LOG_LEVEL_WARN, - LOG_COMPONENT, - _log_stream_name, - b"Failed with UDP protocol too, trying direct RTSP connection\0".as_ptr() - as *const ::core::ffi::c_char, - ); - _log_message_ctx( - log_level_t::LOG_LEVEL_INFO, - LOG_COMPONENT, - _log_stream_name, - b"Trying direct RTSP URL: %s\0".as_ptr() as *const ::core::ffi::c_char, - &raw mut safe_stream_url as *mut ::core::ffi::c_char, - ); - ret = open_input_stream( - &raw mut input_ctx, - &raw mut stream_url as *mut ::core::ffi::c_char, - stream_protocol_t::STREAM_PROTOCOL_TCP.0 as ::core::ffi::c_int, - ); - if ret < 0 as ::core::ffi::c_int { - _log_message_ctx( - log_level_t::LOG_LEVEL_ERROR, - LOG_COMPONENT, - _log_stream_name, - b"All connection attempts failed for stream: %s\0".as_ptr() - as *const ::core::ffi::c_char, - &raw mut safe_stream_url as *mut ::core::ffi::c_char, - ); - return -1 as ::core::ffi::c_int; - } - } + return C2Rust_Unnamed_3::ONVIF_TEST_OK.0; } - avformat_close_input(&raw mut input_ctx); - _log_message_ctx( - log_level_t::LOG_LEVEL_INFO, - LOG_COMPONENT, - _log_stream_name, - b"Successfully connected to stream: %s\0".as_ptr() as *const ::core::ffi::c_char, + snprintf( + detail, + detail_size, + b"ONVIF works, but the camera's stream could not be opened: %s (%s); also tried %s (%s).\0" + .as_ptr() as *const ::core::ffi::c_char, + &raw mut safe_stream_url as *mut ::core::ffi::c_char, + &raw mut error as *mut ::core::ffi::c_char, + &raw mut safe_fallback as *mut ::core::ffi::c_char, + &raw mut fallback_error as *mut ::core::ffi::c_char, + ); + } else { + snprintf( + detail, + detail_size, + b"ONVIF works, but the camera's stream could not be opened: %s (%s).\0".as_ptr() + as *const ::core::ffi::c_char, &raw mut safe_stream_url as *mut ::core::ffi::c_char, + &raw mut error as *mut ::core::ffi::c_char, ); } - return 0 as ::core::ffi::c_int; + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT, + _log_stream_name, + b"All connection attempts failed for stream: %s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut safe_stream_url as *mut ::core::ffi::c_char, + ); + return C2Rust_Unnamed_3::ONVIF_TEST_STREAM_FAILED.0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn test_onvif_connection( + mut url: *const ::core::ffi::c_char, + mut username: *const ::core::ffi::c_char, + mut password: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + return test_onvif_connection_detail( + url, + username, + password, + ::core::ptr::null_mut::<::core::ffi::c_char>(), + 0 as size_t, + ); } pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; diff --git a/rust/generated/src__video__onvif_discovery.rs b/rust/generated/src__video__onvif_discovery.rs index 96e546686..eb1ab7520 100644 --- a/rust/generated/src__video__onvif_discovery.rs +++ b/rust/generated/src__video__onvif_discovery.rs @@ -888,6 +888,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -993,7 +997,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__packet_buffer.rs b/rust/generated/src__video__packet_buffer.rs index a908d8949..f1068111b 100644 --- a/rust/generated/src__video__packet_buffer.rs +++ b/rust/generated/src__video__packet_buffer.rs @@ -300,6 +300,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -405,7 +409,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__recording.rs b/rust/generated/src__video__recording.rs index bff326065..8354059f8 100644 --- a/rust/generated/src__video__recording.rs +++ b/rust/generated/src__video__recording.rs @@ -268,6 +268,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -373,7 +377,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1991,6 +1995,10 @@ pub unsafe extern "C" fn start_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( diff --git a/rust/generated/src__video__recording_path.rs b/rust/generated/src__video__recording_path.rs index 3db692513..ec7b92509 100644 --- a/rust/generated/src__video__recording_path.rs +++ b/rust/generated/src__video__recording_path.rs @@ -160,6 +160,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -265,7 +269,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__sod_detection.rs b/rust/generated/src__video__sod_detection.rs index dce27b42e..248fb7fdb 100644 --- a/rust/generated/src__video__sod_detection.rs +++ b/rust/generated/src__video__sod_detection.rs @@ -936,6 +936,42 @@ pub unsafe extern "C" fn is_sod_available() -> bool { return sod_available; } #[unsafe(no_mangle)] +pub unsafe extern "C" fn sod_model_input_size( + mut model: detection_model_t, + mut width: *mut ::core::ffi::c_int, + mut height: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + if model.is_null() || width.is_null() || height.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut m: *mut model_t = model as *mut model_t; + if strcmp( + &raw mut (*m).r#type as *mut ::core::ffi::c_char, + MODEL_TYPE_SOD.as_ptr(), + ) != 0 as ::core::ffi::c_int + || (*m).c2rust_unnamed.sod.is_null() + { + return -1 as ::core::ffi::c_int; + } + let mut net_w: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut net_h: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut net_c: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if sod_cnn_get_network_size( + (*m).c2rust_unnamed.sod as *mut sod_cnn, + &raw mut net_w, + &raw mut net_h, + &raw mut net_c, + ) != 0 as ::core::ffi::c_int + || net_w <= 0 as ::core::ffi::c_int + || net_h <= 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + *width = net_w; + *height = net_h; + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] pub unsafe extern "C" fn load_sod_model( mut model_path: *const ::core::ffi::c_char, mut threshold: ::core::ffi::c_float, diff --git a/rust/generated/src__video__sod_integration.rs b/rust/generated/src__video__sod_integration.rs index dc5ddebc4..febb888b6 100644 --- a/rust/generated/src__video__sod_integration.rs +++ b/rust/generated/src__video__sod_integration.rs @@ -67,6 +67,7 @@ pub type size_t = usize; pub type __uint64_t = u64; pub type __off_t = ::core::ffi::c_long; pub type __off64_t = ::core::ffi::c_long; +pub type int64_t = i64; #[derive(Copy, Clone, ::c2rust_bitfields::BitfieldStruct)] #[repr(C)] pub struct _IO_FILE { @@ -200,6 +201,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -305,7 +310,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__video__stream_manager.rs b/rust/generated/src__video__stream_manager.rs index 41cb29dd9..0fa9f3cd0 100644 --- a/rust/generated/src__video__stream_manager.rs +++ b/rust/generated/src__video__stream_manager.rs @@ -303,6 +303,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -1013,6 +1017,10 @@ pub unsafe extern "C" fn get_stream_by_name( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name(name, &raw mut db_config) == 0 as ::core::ffi::c_int { let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; @@ -1210,6 +1218,10 @@ pub unsafe extern "C" fn set_stream_detection_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memcpy( &raw mut config_copy as *mut ::core::ffi::c_void, @@ -1425,6 +1437,10 @@ pub unsafe extern "C" fn set_stream_detection_params( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memcpy( &raw mut config_copy as *mut ::core::ffi::c_void, @@ -2080,6 +2096,10 @@ pub unsafe extern "C" fn set_stream_priority( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memcpy( &raw mut config_copy as *mut ::core::ffi::c_void, @@ -2241,6 +2261,10 @@ pub unsafe extern "C" fn set_stream_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memcpy( &raw mut config_copy as *mut ::core::ffi::c_void, @@ -2426,6 +2450,10 @@ pub unsafe extern "C" fn set_stream_streaming_enabled( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memcpy( &raw mut config_copy as *mut ::core::ffi::c_void, @@ -2582,6 +2610,10 @@ pub unsafe extern "C" fn set_stream_onvif_flag( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memcpy( &raw mut config_copy as *mut ::core::ffi::c_void, diff --git a/rust/generated/src__video__stream_state.rs b/rust/generated/src__video__stream_state.rs index dc20f7332..0f3a98d16 100644 --- a/rust/generated/src__video__stream_state.rs +++ b/rust/generated/src__video__stream_state.rs @@ -196,6 +196,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__video__stream_state_adapter.rs b/rust/generated/src__video__stream_state_adapter.rs index a291e51b9..d06f9f850 100644 --- a/rust/generated/src__video__stream_state_adapter.rs +++ b/rust/generated/src__video__stream_state_adapter.rs @@ -164,6 +164,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -728,6 +732,10 @@ pub unsafe extern "C" fn set_stream_priority_adapter( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; pthread_mutex_lock(&raw mut (*state).mutex); memcpy( @@ -854,6 +862,10 @@ pub unsafe extern "C" fn set_stream_detection_recording_adapter( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; pthread_mutex_lock(&raw mut (*state).mutex); memcpy( @@ -957,6 +969,10 @@ pub unsafe extern "C" fn set_stream_detection_params_adapter( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; pthread_mutex_lock(&raw mut (*state).mutex); memcpy( diff --git a/rust/generated/src__video__streams.rs b/rust/generated/src__video__streams.rs index 933f49fff..990dcf2dc 100644 --- a/rust/generated/src__video__streams.rs +++ b/rust/generated/src__video__streams.rs @@ -188,6 +188,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -293,7 +297,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1825,6 +1829,10 @@ pub unsafe extern "C" fn get_stream_hls_writer(mut stream: stream_handle_t) -> * publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { return ::core::ptr::null_mut::(); @@ -1933,7 +1941,7 @@ pub unsafe extern "C" fn get_streaming_config() -> *mut config_t { go2rtc_webrtc_listen_port: 0, go2rtc_stun_enabled: false, go2rtc_stun_server: [0; 256], - go2rtc_external_ip: [0; 64], + go2rtc_external_ip: [0; 256], go2rtc_ice_servers: [0; 512], webrtc_connection_timeout_ms: 0, webrtc_ice_recovery_timeout_ms: 0, diff --git a/rust/generated/src__video__unified_detection_thread.rs b/rust/generated/src__video__unified_detection_thread.rs index bdaf3d784..35a43a72a 100644 --- a/rust/generated/src__video__unified_detection_thread.rs +++ b/rust/generated/src__video__unified_detection_thread.rs @@ -166,6 +166,16 @@ unsafe extern "C" { src: *const ::core::ffi::c_char, size: size_t, ) -> ::core::ffi::c_int; + unsafe fn stream_frame_rate_select( + average: AVRational, + nominal: AVRational, + provisional: *mut bool, + ) -> ::core::ffi::c_double; + unsafe fn stream_frame_rate_round(fps: ::core::ffi::c_double) -> ::core::ffi::c_int; + unsafe fn stream_frame_rate_observe( + measurement: *mut stream_frame_rate_measurement_t, + now_us: int64_t, + ) -> ::core::ffi::c_int; unsafe fn init_packet_buffer_pool(memory_limit_mb: size_t) -> ::core::ffi::c_int; unsafe fn calculate_packet_buffer_pool_size() -> size_t; unsafe fn cleanup_packet_buffer_pool(); @@ -199,16 +209,6 @@ unsafe extern "C" { memory_usage: *mut size_t, duration: *mut ::core::ffi::c_int, ) -> ::core::ffi::c_int; - unsafe fn stream_frame_rate_select( - average: AVRational, - nominal: AVRational, - provisional: *mut bool, - ) -> ::core::ffi::c_double; - unsafe fn stream_frame_rate_round(fps: ::core::ffi::c_double) -> ::core::ffi::c_int; - unsafe fn stream_frame_rate_observe( - measurement: *mut stream_frame_rate_measurement_t, - now_us: int64_t, - ) -> ::core::ffi::c_int; unsafe fn load_detection_model( model_path: *const ::core::ffi::c_char, threshold: ::core::ffi::c_float, @@ -254,12 +254,54 @@ unsafe extern "C" { channels: ::core::ffi::c_int, result: *mut detection_result_t, ) -> ::core::ffi::c_int; + unsafe fn detection_evidence_label_eligible( + labels_csv: *const ::core::ffi::c_char, + label: *const ::core::ffi::c_char, + ) -> bool; + unsafe fn detection_evidence_transform_compute( + frame_w: ::core::ffi::c_int, + frame_h: ::core::ffi::c_int, + net_w: ::core::ffi::c_int, + net_h: ::core::ffi::c_int, + letterbox: bool, + out: *mut observation_evidence_transform_t, + ); + unsafe fn detection_evidence_model_geometry( + model: detection_model_t, + net_w: *mut ::core::ffi::c_int, + net_h: *mut ::core::ffi::c_int, + letterbox: *mut bool, + ) -> ::core::ffi::c_int; + unsafe fn detection_evidence_observed_at_ms(frame_ts: time_t) -> int64_t; + unsafe fn detection_evidence_capture( + camera: *const detection_evidence_camera_t, + engine: *const detection_evidence_engine_t, + detections: *const detection_t, + count: ::core::ffi::c_int, + observed_at_ms: int64_t, + rgb24: *mut uint8_t, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + transform: *const observation_evidence_transform_t, + submit_result: *mut observation_evidence_result_t, + asset_uuid_out: *mut ::core::ffi::c_char, + ) -> detection_evidence_status_t; + unsafe fn detection_evidence_record_no_media( + camera: *const detection_evidence_camera_t, + engine: *const detection_evidence_engine_t, + detections: *const detection_t, + count: ::core::ffi::c_int, + observed_at_ms: int64_t, + keep_boxes: bool, + submit_result: *mut observation_evidence_result_t, + asset_uuid_out: *mut ::core::ffi::c_char, + ) -> detection_evidence_status_t; unsafe fn api_detection_options_from_config(options: *mut api_detection_options_t); unsafe fn api_detection_options_apply_json( options: *mut api_detection_options_t, config_json: *const ::core::ffi::c_char, ) -> ::core::ffi::c_int; - unsafe fn detect_objects_api_with_options( + unsafe fn detect_objects_api_with_evidence( api_url: *const ::core::ffi::c_char, frame_data: *const ::core::ffi::c_uchar, width: ::core::ffi::c_int, @@ -271,6 +313,7 @@ unsafe extern "C" { recording_id: uint64_t, frame_timestamp: time_t, options: *const api_detection_options_t, + evidence: *const api_detection_evidence_t, ) -> ::core::ffi::c_int; unsafe fn configure_motion_detection( stream_name: *const ::core::ffi::c_char, @@ -291,6 +334,14 @@ unsafe extern "C" { stream_name: *const ::core::ffi::c_char, enabled: bool, ) -> ::core::ffi::c_int; + unsafe fn filter_detections_by_zones( + stream_name: *const ::core::ffi::c_char, + result: *mut detection_result_t, + ) -> ::core::ffi::c_int; + unsafe fn filter_detections_by_stream_objects( + stream_name: *const ::core::ffi::c_char, + result: *mut detection_result_t, + ) -> ::core::ffi::c_int; unsafe fn mp4_writer_initialize( writer: *mut mp4_writer_t, pkt: *const AVPacket, @@ -320,14 +371,6 @@ unsafe extern "C" { output: *mut ::core::ffi::c_char, output_size: size_t, ) -> ::core::ffi::c_int; - unsafe fn filter_detections_by_zones( - stream_name: *const ::core::ffi::c_char, - result: *mut detection_result_t, - ) -> ::core::ffi::c_int; - unsafe fn filter_detections_by_stream_objects( - stream_name: *const ::core::ffi::c_char, - result: *mut detection_result_t, - ) -> ::core::ffi::c_int; unsafe fn get_streaming_config() -> *mut config_t; unsafe fn get_stream_state_by_name( name: *const ::core::ffi::c_char, @@ -423,6 +466,11 @@ unsafe extern "C" { stream_name: *const ::core::ffi::c_char, fps: ::core::ffi::c_double, ); + unsafe fn memcpy( + __dest: *mut ::core::ffi::c_void, + __src: *const ::core::ffi::c_void, + __n: size_t, + ) -> *mut ::core::ffi::c_void; unsafe fn memset( __s: *mut ::core::ffi::c_void, __c: ::core::ffi::c_int, @@ -451,6 +499,7 @@ unsafe extern "C" { __s1: *const ::core::ffi::c_char, __s2: *const ::core::ffi::c_char, ) -> ::core::ffi::c_int; + unsafe fn malloc(__size: size_t) -> *mut ::core::ffi::c_void; unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; unsafe fn free(__ptr: *mut ::core::ffi::c_void); } @@ -2704,6 +2753,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -2809,7 +2862,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -3098,6 +3151,8 @@ pub struct unified_detection_ctx_t { pub engine_count: ::core::ffi::c_int, pub recording_grace_interval: ::core::ffi::c_int, pub has_onvif_engine: bool, + pub observation_evidence_enabled: atomic_int, + pub observation_labels: [::core::ffi::c_char; 512], pub last_packet_time: atomic_int_fast64_t, pub consecutive_failures: atomic_int, pub reconnect_attempt: ::core::ffi::c_int, @@ -3166,6 +3221,64 @@ pub struct recording_metadata_t { pub disk_pressure_eligible: bool, pub schedule_restricted: ::core::ffi::c_int, } +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_evidence_result_t(pub ::core::ffi::c_int); +impl observation_evidence_result_t { + pub const OBS_EVIDENCE_ACCEPTED: Self = Self(0); + pub const OBS_EVIDENCE_DROPPED_QUEUE_FULL: Self = Self(1); + pub const OBS_EVIDENCE_DROPPED_BUDGET: Self = Self(2); + pub const OBS_EVIDENCE_DISABLED: Self = Self(3); + pub const OBS_EVIDENCE_INVALID: Self = Self(-1); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct detection_evidence_status_t(pub ::core::ffi::c_uint); +impl detection_evidence_status_t { + pub const DETECTION_EVIDENCE_SUBMITTED: Self = Self(0); + pub const DETECTION_EVIDENCE_NOT_ENABLED: Self = Self(1); + pub const DETECTION_EVIDENCE_NOT_ELIGIBLE: Self = Self(2); + pub const DETECTION_EVIDENCE_NOT_OBJECT_ENGINE: Self = Self(3); + pub const DETECTION_EVIDENCE_ERROR: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_transform_t { + pub mode: observation_transform_mode_t, + pub source_width: ::core::ffi::c_int, + pub source_height: ::core::ffi::c_int, + pub inference_width: ::core::ffi::c_int, + pub inference_height: ::core::ffi::c_int, + pub scale_x: ::core::ffi::c_double, + pub scale_y: ::core::ffi::c_double, + pub pad_x: ::core::ffi::c_double, + pub pad_y: ::core::ffi::c_double, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_transform_mode_t(pub ::core::ffi::c_uint); +impl observation_transform_mode_t { + pub const OBS_TRANSFORM_NONE: Self = Self(0); + pub const OBS_TRANSFORM_RESIZE: Self = Self(1); + pub const OBS_TRANSFORM_LETTERBOX: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_evidence_engine_t { + pub engine_key: *const ::core::ffi::c_char, + pub engine_type: *const ::core::ffi::c_char, + pub model_path: *const ::core::ffi::c_char, + pub model_version: *const ::core::ffi::c_char, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_evidence_camera_t { + pub camera_uuid: *const ::core::ffi::c_char, + pub stream_name: *const ::core::ffi::c_char, + pub evidence_enabled: bool, + pub labels_csv: *const ::core::ffi::c_char, + pub recording_id: int64_t, +} #[derive(Copy, Clone)] #[repr(C)] pub struct stream_state_manager_t { @@ -3227,6 +3340,15 @@ impl stream_state_t { } #[derive(Copy, Clone)] #[repr(C)] +pub struct api_detection_evidence_t { + pub camera_uuid: *const ::core::ffi::c_char, + pub engine_key: *const ::core::ffi::c_char, + pub evidence_enabled: bool, + pub labels_csv: *const ::core::ffi::c_char, + pub observed_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] pub struct api_detection_options_t { pub format: api_detection_format_t, pub backend: [::core::ffi::c_char; 32], @@ -3261,14 +3383,16 @@ unsafe extern "C" fn av_q2d(mut a: AVRational) -> ::core::ffi::c_double { pub const AV_PKT_FLAG_KEY: ::core::ffi::c_int = 0x1 as ::core::ffi::c_int; pub const AV_CODEC_PROP_INTRA_ONLY: ::core::ffi::c_int = (1 as ::core::ffi::c_int) << 0 as ::core::ffi::c_int; +pub const RGB24_SWS_TAIL_PADDING: ::core::ffi::c_int = 128 as ::core::ffi::c_int; pub const LOG_COMPONENT: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const MAX_STREAMS: ::core::ffi::c_int = LIGHTNVR_MAX_STREAMS; +pub const OBSERVATION_LABELS_MAX: ::core::ffi::c_int = 512 as ::core::ffi::c_int; pub const MAX_DETECTIONS: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const STREAM_FRAME_RATE_DEFAULT: ::core::ffi::c_int = 15 as ::core::ffi::c_int; pub const MAX_BUFFER_SECONDS: ::core::ffi::c_int = 30 as ::core::ffi::c_int; pub const MIN_BUFFER_SECONDS: ::core::ffi::c_int = 5 as ::core::ffi::c_int; pub const PACKET_BUFFER_TRIGGER_HEADROOM_SECONDS: ::core::ffi::c_int = 5 as ::core::ffi::c_int; -pub const STREAM_FRAME_RATE_DEFAULT: ::core::ffi::c_int = 15 as ::core::ffi::c_int; pub const MAX_DETECTION_ENGINES_PER_STREAM: ::core::ffi::c_int = 8 as ::core::ffi::c_int; pub const MAX_UNIFIED_DETECTION_THREADS: ::core::ffi::c_int = MAX_STREAMS; pub const BASE_RECONNECT_DELAY_MS: ::core::ffi::c_int = 500 as ::core::ffi::c_int; @@ -4635,9 +4759,9 @@ unsafe extern "C" fn load_runtime_engine_config( let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; while i < count { if stored[i as usize].enabled { - let c2rust_fresh1 = (*ctx).engine_count; + let c2rust_fresh2 = (*ctx).engine_count; (*ctx).engine_count += 1; - (*ctx).engines[c2rust_fresh1 as usize] = stored[i as usize]; + (*ctx).engines[c2rust_fresh2 as usize] = stored[i as usize]; } i += 1; } @@ -5176,6 +5300,7 @@ unsafe extern "C" fn onvif_detection_thread_func( tv_nsec: 0, }; clock_gettime(CLOCK_MONOTONIC, &raw mut poll_start); + let mut poll_wall_ms: int64_t = detection_evidence_observed_at_ms(0 as time_t); let mut ret: ::core::ffi::c_int = detect_motion_onvif( &raw mut (*ctx).onvif_url_cached as *mut ::core::ffi::c_char, &raw mut (*ctx).onvif_username_cached as *mut ::core::ffi::c_char, @@ -5190,6 +5315,7 @@ unsafe extern "C" fn onvif_detection_thread_func( break; } if ret == 0 as ::core::ffi::c_int && result.count > 0 as ::core::ffi::c_int { + udt_record_onvif_observations(ctx, &raw mut result, poll_wall_ms); let mut seen: ::core::ffi::c_longlong = time(::core::ptr::null_mut::()) as ::core::ffi::c_longlong; if ::core::intrinsics::atomic_xchg::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( @@ -6158,6 +6284,10 @@ pub unsafe extern "C" fn start_unified_detection_thread( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -6230,6 +6360,20 @@ pub unsafe extern "C" fn start_unified_detection_thread( (*ctx).record_audio = detection_should_record_audio(&raw mut config, global_cfg); (*ctx).audio_voice_enhancement = config.audio_voice_enhancement; (*ctx).annotation_only = annotation_only; + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*ctx).observation_evidence_enabled, + if config.observation_evidence_enabled as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }, + ); + safe_strcpy( + &raw mut (*ctx).observation_labels as *mut ::core::ffi::c_char, + &raw mut config.observation_labels as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( &raw mut (*ctx).external_motion_trigger, 0 as ::core::ffi::c_int, @@ -6749,6 +6893,46 @@ pub unsafe extern "C" fn unified_detection_notify_motion( } pthread_mutex_unlock(&raw mut contexts_mutex); } +#[unsafe(no_mangle)] +pub unsafe extern "C" fn unified_detection_set_observation_settings( + mut stream_name: *const ::core::ffi::c_char, + mut enabled: bool, + mut labels_csv: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if stream_name.is_null() { + return -1 as ::core::ffi::c_int; + } + pthread_mutex_lock(&raw mut contexts_mutex); + let mut ctx: *mut unified_detection_ctx_t = find_context_by_name(stream_name); + if !ctx.is_null() { + pthread_mutex_lock(&raw mut (*ctx).mutex); + safe_strcpy( + &raw mut (*ctx).observation_labels as *mut ::core::ffi::c_char, + if !labels_csv.is_null() { + labels_csv + } else { + b"\0".as_ptr() as *const ::core::ffi::c_char + }, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + pthread_mutex_unlock(&raw mut (*ctx).mutex); + ::core::intrinsics::atomic_store::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*ctx).observation_evidence_enabled, + if enabled as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }, + ); + } + pthread_mutex_unlock(&raw mut contexts_mutex); + return if !ctx.is_null() { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} unsafe extern "C" fn connect_to_stream( mut ctx: *mut unified_detection_ctx_t, ) -> ::core::ffi::c_int { @@ -7186,6 +7370,10 @@ unsafe extern "C" fn udt_update_stream_video_params( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name( &raw mut (*ctx).stream_name as *mut ::core::ffi::c_char, @@ -8573,6 +8761,10 @@ unsafe extern "C" fn udt_start_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name( &raw mut (*ctx).stream_name as *mut ::core::ffi::c_char, @@ -9164,6 +9356,238 @@ unsafe extern "C" fn run_detection_on_frame( unsafe extern "C" fn frame_to_rgb24(mut frame: *const AVFrame) -> *mut uint8_t { return ffmpeg_frame_to_rgb24_padded(frame, ::core::ptr::null_mut::()); } +unsafe extern "C" fn udt_evidence_wanted( + mut ctx: *mut unified_detection_ctx_t, + mut detections: *const detection_t, + mut count: ::core::ffi::c_int, + mut labels: *mut ::core::ffi::c_char, +) -> bool { + if count <= 0 as ::core::ffi::c_int + || ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*ctx).observation_evidence_enabled, + ) == 0 + { + return r#false != 0; + } + pthread_mutex_lock(&raw mut (*ctx).mutex); + memcpy( + labels as *mut ::core::ffi::c_void, + &raw mut (*ctx).observation_labels as *mut ::core::ffi::c_char + as *const ::core::ffi::c_void, + OBSERVATION_LABELS_MAX as size_t, + ); + pthread_mutex_unlock(&raw mut (*ctx).mutex); + *labels.offset((OBSERVATION_LABELS_MAX - 1 as ::core::ffi::c_int) as isize) = + '\0' as ::core::ffi::c_char; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if detection_evidence_label_eligible( + labels, + &raw const (*detections.offset(i as isize)).label as *const ::core::ffi::c_char, + ) { + return r#true != 0; + } + i += 1; + } + return r#false != 0; +} +unsafe extern "C" fn udt_evidence_settings( + mut ctx: *mut unified_detection_ctx_t, + mut labels: *mut ::core::ffi::c_char, +) -> bool { + *labels.offset(0isize) = '\0' as ::core::ffi::c_char; + if ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*ctx).observation_evidence_enabled, + ) == 0 + { + return r#false != 0; + } + pthread_mutex_lock(&raw mut (*ctx).mutex); + memcpy( + labels as *mut ::core::ffi::c_void, + &raw mut (*ctx).observation_labels as *mut ::core::ffi::c_char + as *const ::core::ffi::c_void, + OBSERVATION_LABELS_MAX as size_t, + ); + pthread_mutex_unlock(&raw mut (*ctx).mutex); + *labels.offset((OBSERVATION_LABELS_MAX - 1 as ::core::ffi::c_int) as isize) = + '\0' as ::core::ffi::c_char; + return r#true != 0; +} +unsafe extern "C" fn udt_submit_evidence( + mut ctx: *mut unified_detection_ctx_t, + mut engine_key: *const ::core::ffi::c_char, + mut model_path: *const ::core::ffi::c_char, + mut model: detection_model_t, + mut detections: *const detection_t, + mut count: ::core::ffi::c_int, + mut labels: *const ::core::ffi::c_char, + mut now: time_t, + mut rgb: *mut uint8_t, + mut width: ::core::ffi::c_int, + mut height: ::core::ffi::c_int, +) { + let mut net_w: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut net_h: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut letterbox: bool = r#false != 0; + if detection_evidence_model_geometry(model, &raw mut net_w, &raw mut net_h, &raw mut letterbox) + != 0 as ::core::ffi::c_int + { + net_h = 0 as ::core::ffi::c_int; + net_w = net_h; + } + let mut transform: observation_evidence_transform_t = observation_evidence_transform_t { + mode: observation_transform_mode_t::OBS_TRANSFORM_NONE, + source_width: 0, + source_height: 0, + inference_width: 0, + inference_height: 0, + scale_x: 0., + scale_y: 0., + pad_x: 0., + pad_y: 0., + }; + detection_evidence_transform_compute( + width, + height, + net_w, + net_h, + letterbox, + &raw mut transform, + ); + let mut camera: detection_evidence_camera_t = detection_evidence_camera_t { + camera_uuid: &raw mut (*ctx).camera_uuid as *mut ::core::ffi::c_char, + stream_name: &raw mut (*ctx).stream_name as *mut ::core::ffi::c_char, + evidence_enabled: r#true != 0, + labels_csv: labels, + recording_id: detection_link_recording_id(ctx) as int64_t, + }; + let mut engine: detection_evidence_engine_t = detection_evidence_engine_t { + engine_key: engine_key, + engine_type: b"object\0".as_ptr() as *const ::core::ffi::c_char, + model_path: model_path, + model_version: ::core::ptr::null::<::core::ffi::c_char>(), + }; + let mut submitted: observation_evidence_result_t = + observation_evidence_result_t::OBS_EVIDENCE_INVALID; + let mut status: detection_evidence_status_t = detection_evidence_capture( + &raw mut camera, + &raw mut engine, + detections, + count, + detection_evidence_observed_at_ms(now), + rgb, + width, + height, + &raw mut transform, + &raw mut submitted, + ::core::ptr::null_mut::<::core::ffi::c_char>(), + ); + if status.0 == detection_evidence_status_t::DETECTION_EVIDENCE_SUBMITTED.0 + && submitted.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_DEBUG, + LOG_COMPONENT, + _log_stream_name, + b"[%s] Observation evidence for engine %s not queued (%d)\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut (*ctx).stream_name as *mut ::core::ffi::c_char, + if !engine_key.is_null() { + engine_key + } else { + b"legacy-primary\0".as_ptr() as *const ::core::ffi::c_char + }, + submitted.0, + ); + } +} +unsafe extern "C" fn udt_single_engine_key( + mut ctx: *const unified_detection_ctx_t, +) -> *const ::core::ffi::c_char { + return if (*ctx).engine_count == 1 as ::core::ffi::c_int + && (*ctx).engines[0usize].engine_key[0usize] as ::core::ffi::c_int != 0 + { + &raw const (*(&raw const (*ctx).engines as *const stream_detection_engine_t).offset(0isize)) + .engine_key as *const ::core::ffi::c_char + } else { + b"legacy-primary\0".as_ptr() as *const ::core::ffi::c_char + }; +} +unsafe extern "C" fn udt_record_onvif_observations( + mut ctx: *mut unified_detection_ctx_t, + mut result: *const detection_result_t, + mut observed_at_ms: int64_t, +) { + if ctx.is_null() + || result.is_null() + || (*result).count <= 0 as ::core::ffi::c_int + || (*ctx).camera_uuid[0usize] == 0 + { + return; + } + let mut labels: [::core::ffi::c_char; 512] = [0; 512]; + if !udt_evidence_settings(ctx, &raw mut labels as *mut ::core::ffi::c_char) { + return; + } + let mut engine_key: *const ::core::ffi::c_char = + b"legacy-primary\0".as_ptr() as *const ::core::ffi::c_char; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < (*ctx).engine_count { + if strcmp( + &raw mut (*(&raw mut (*ctx).engines as *mut stream_detection_engine_t) + .offset(i as isize)) + .engine_type as *mut ::core::ffi::c_char, + b"onvif\0".as_ptr() as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + && (*ctx).engines[i as usize].engine_key[0usize] as ::core::ffi::c_int != 0 + { + engine_key = &raw mut (*(&raw mut (*ctx).engines as *mut stream_detection_engine_t) + .offset(i as isize)) + .engine_key as *mut ::core::ffi::c_char; + break; + } else { + i += 1; + } + } + let mut camera: detection_evidence_camera_t = detection_evidence_camera_t { + camera_uuid: &raw mut (*ctx).camera_uuid as *mut ::core::ffi::c_char, + stream_name: &raw mut (*ctx).stream_name as *mut ::core::ffi::c_char, + evidence_enabled: r#true != 0, + labels_csv: &raw mut labels as *mut ::core::ffi::c_char, + recording_id: detection_link_recording_id(ctx) as int64_t, + }; + let mut engine: detection_evidence_engine_t = detection_evidence_engine_t { + engine_key: engine_key, + engine_type: b"onvif\0".as_ptr() as *const ::core::ffi::c_char, + model_path: ::core::ptr::null::<::core::ffi::c_char>(), + model_version: ::core::ptr::null::<::core::ffi::c_char>(), + }; + let mut submitted: observation_evidence_result_t = + observation_evidence_result_t::OBS_EVIDENCE_INVALID; + let mut status: detection_evidence_status_t = detection_evidence_record_no_media( + &raw mut camera, + &raw mut engine, + &raw const (*result).detections as *const detection_t, + (*result).count, + observed_at_ms, + r#false != 0, + &raw mut submitted, + ::core::ptr::null_mut::<::core::ffi::c_char>(), + ); + if status.0 == detection_evidence_status_t::DETECTION_EVIDENCE_SUBMITTED.0 + && submitted.0 != observation_evidence_result_t::OBS_EVIDENCE_ACCEPTED.0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_DEBUG, + LOG_COMPONENT, + _log_stream_name, + b"[%s] ONVIF observation not queued (%d)\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut (*ctx).stream_name as *mut ::core::ffi::c_char, + submitted.0, + ); + } +} unsafe extern "C" fn append_engine_result( mut ctx: *mut unified_detection_ctx_t, mut engine: *const stream_detection_engine_t, @@ -9184,9 +9608,9 @@ unsafe extern "C" fn append_engine_result( let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; while i < (*candidate).count && (*combined).count < MAX_DETECTIONS { if (*candidate).detections[i as usize].confidence >= (*engine).threshold { - let c2rust_fresh0 = (*combined).count; + let c2rust_fresh1 = (*combined).count; (*combined).count += 1; - (*combined).detections[c2rust_fresh0 as usize] = (*candidate).detections[i as usize]; + (*combined).detections[c2rust_fresh1 as usize] = (*candidate).detections[i as usize]; _log_message_ctx( log_level_t::LOG_LEVEL_INFO, LOG_COMPONENT, @@ -9274,11 +9698,13 @@ unsafe extern "C" fn detect_with_runtime_engines( if any_due_frame_engine as ::core::ffi::c_int != 0 && rgb.is_null() { return onvif_triggered; } + let mut slice_start: [::core::ffi::c_int; 8] = [0 as ::core::ffi::c_int; 8]; + let mut slice_count: [::core::ffi::c_int; 8] = [0 as ::core::ffi::c_int; 8]; let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; while i_0 < (*ctx).engine_count { let mut engine_0: *mut stream_detection_engine_t = (&raw mut (*ctx).engines as *mut stream_detection_engine_t).offset(i_0 as isize); - 's_98: { + 's_102: { if !(!(*engine_0).enabled || (*ctx).engine_last_run[i_0 as usize] > 0 as time_t && now - (*ctx).engine_last_run[i_0 as usize] @@ -9353,11 +9779,14 @@ unsafe extern "C" fn detect_with_runtime_engines( ); } } else { - break 's_98; + break 's_102; } (*ctx).engine_last_run[i_0 as usize] = now; if result == 0 as ::core::ffi::c_int { + let mut before: ::core::ffi::c_int = (*combined).count; append_engine_result(ctx, engine_0, combined, &raw mut candidate); + slice_start[i_0 as usize] = before; + slice_count[i_0 as usize] = (*combined).count - before; } else if !(*ctx).engine_load_failed[i_0 as usize] { _log_message_ctx( log_level_t::LOG_LEVEL_WARN, @@ -9372,6 +9801,81 @@ unsafe extern "C" fn detect_with_runtime_engines( } i_0 += 1; } + let mut labels: [::core::ffi::c_char; 512] = [0; 512]; + let mut wanted: [::core::ffi::c_int; 8] = [0; 8]; + let mut wanted_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i_1: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while !rgb.is_null() && i_1 < (*ctx).engine_count { + if slice_count[i_1 as usize] > 0 as ::core::ffi::c_int + && strcmp( + &raw mut (*(&raw mut (*ctx).engines as *mut stream_detection_engine_t) + .offset(i_1 as isize)) + .engine_type as *mut ::core::ffi::c_char, + b"object\0".as_ptr() as *const ::core::ffi::c_char, + ) == 0 as ::core::ffi::c_int + && udt_evidence_wanted( + ctx, + (&raw mut (*combined).detections as *mut detection_t) + .offset(slice_start[i_1 as usize] as isize), + slice_count[i_1 as usize], + &raw mut labels as *mut ::core::ffi::c_char, + ) as ::core::ffi::c_int + != 0 + { + let c2rust_fresh0 = wanted_count; + wanted_count += 1; + wanted[c2rust_fresh0 as usize] = i_1; + } + i_1 += 1; + } + let mut w: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while w < wanted_count { + let mut i_2: ::core::ffi::c_int = wanted[w as usize]; + let mut owned: *mut uint8_t = rgb; + 's_260: { + if (w + 1 as ::core::ffi::c_int) < wanted_count { + let mut size: size_t = ((*frame).width as size_t) + .wrapping_mul((*frame).height as size_t) + .wrapping_mul(3 as size_t); + owned = malloc(size.wrapping_add(RGB24_SWS_TAIL_PADDING as size_t)) as *mut uint8_t; + if owned.is_null() { + break 's_260; + } else { + memcpy( + owned as *mut ::core::ffi::c_void, + rgb as *const ::core::ffi::c_void, + size, + ); + memset( + owned.offset(size as isize) as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + RGB24_SWS_TAIL_PADDING as size_t, + ); + } + } else { + rgb = ::core::ptr::null_mut::(); + } + udt_submit_evidence( + ctx, + &raw mut (*(&raw mut (*ctx).engines as *mut stream_detection_engine_t) + .offset(i_2 as isize)) + .engine_key as *mut ::core::ffi::c_char, + &raw mut (*(&raw mut (*ctx).engines as *mut stream_detection_engine_t) + .offset(i_2 as isize)) + .model_path as *mut ::core::ffi::c_char, + (*ctx).engine_models[i_2 as usize], + (&raw mut (*combined).detections as *mut detection_t) + .offset(slice_start[i_2 as usize] as isize), + slice_count[i_2 as usize], + &raw mut labels as *mut ::core::ffi::c_char, + now, + owned, + (*frame).width, + (*frame).height, + ); + } + w += 1; + } free(rgb as *mut ::core::ffi::c_void); return (*combined).count > 0 as ::core::ffi::c_int || onvif_triggered as ::core::ffi::c_int != 0; @@ -9395,6 +9899,7 @@ unsafe extern "C" fn detect_on_decoded_frame( } let mut width: ::core::ffi::c_int = (*frame).width; let mut height: ::core::ffi::c_int = (*frame).height; + let mut evidence_rgb: *mut uint8_t = ::core::ptr::null_mut::(); if is_api_detection(&raw mut (*ctx).model_path as *mut ::core::ffi::c_char) { let mut rec_id: uint64_t = detection_link_recording_id(ctx); let mut rgb_buf: *mut uint8_t = frame_to_rgb24(frame); @@ -9411,8 +9916,19 @@ unsafe extern "C" fn detect_on_decoded_frame( detector_name: [0; 64], }; resolve_api_detection_options(ctx, &raw mut api_options); + let mut labels: [::core::ffi::c_char; 512] = [0; 512]; + let mut evidence: api_detection_evidence_t = api_detection_evidence_t { + camera_uuid: &raw mut (*ctx).camera_uuid as *mut ::core::ffi::c_char, + engine_key: udt_single_engine_key(ctx), + evidence_enabled: udt_evidence_settings( + ctx, + &raw mut labels as *mut ::core::ffi::c_char, + ), + labels_csv: &raw mut labels as *mut ::core::ffi::c_char, + observed_at_ms: detection_evidence_observed_at_ms(now), + }; let mut ret: ::core::ffi::c_int = if !api_url.is_null() { - detect_objects_api_with_options( + detect_objects_api_with_evidence( api_url, rgb_buf as *const ::core::ffi::c_uchar, width, @@ -9424,6 +9940,7 @@ unsafe extern "C" fn detect_on_decoded_frame( rec_id, now, &raw mut api_options, + &raw mut evidence, ) } else { -1 as ::core::ffi::c_int @@ -9543,8 +10060,8 @@ unsafe extern "C" fn detect_on_decoded_frame( 3 as ::core::ffi::c_int, result, ); - free(rgb_buf_1 as *mut ::core::ffi::c_void); if ret_1 != 0 as ::core::ffi::c_int { + free(rgb_buf_1 as *mut ::core::ffi::c_void); _log_message_ctx( log_level_t::LOG_LEVEL_WARN, LOG_COMPONENT, @@ -9556,8 +10073,18 @@ unsafe extern "C" fn detect_on_decoded_frame( ); return r#false != 0; } + if (*result).count > 0 as ::core::ffi::c_int + && ::core::intrinsics::atomic_load::<_, { ::core::intrinsics::AtomicOrdering::SeqCst }>( + &raw mut (*ctx).observation_evidence_enabled, + ) != 0 + { + evidence_rgb = rgb_buf_1; + } else { + free(rgb_buf_1 as *mut ::core::ffi::c_void); + } } if (*result).count == 0 as ::core::ffi::c_int { + free(evidence_rgb as *mut ::core::ffi::c_void); return r#false != 0; } filter_detections_by_zones( @@ -9608,6 +10135,32 @@ unsafe extern "C" fn detect_on_decoded_frame( i += 1; } (*result).count = kept; + let mut labels_0: [::core::ffi::c_char; 512] = [0; 512]; + if !evidence_rgb.is_null() + && udt_evidence_wanted( + ctx, + &raw mut (*result).detections as *mut detection_t, + (*result).count, + &raw mut labels_0 as *mut ::core::ffi::c_char, + ) as ::core::ffi::c_int + != 0 + { + udt_submit_evidence( + ctx, + udt_single_engine_key(ctx), + &raw mut (*ctx).model_path as *mut ::core::ffi::c_char, + (*ctx).model, + &raw mut (*result).detections as *mut detection_t, + (*result).count, + &raw mut labels_0 as *mut ::core::ffi::c_char, + now, + evidence_rgb, + width, + height, + ); + } else { + free(evidence_rgb as *mut ::core::ffi::c_void); + } return (*result).count > 0 as ::core::ffi::c_int; } unsafe extern "C" fn report_detections( @@ -10031,8 +10584,8 @@ pub const ONVIF_PULL_DROP_RETRY_DELAY_MS: ::core::ffi::c_int = 250 as ::core::ff pub const ONVIF_DETECT_RETRY: ::core::ffi::c_int = -2 as ::core::ffi::c_int; pub const STREAM_ERR_MODEL_LOAD: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const RETENTION_TIER_IMPORTANT: ::core::ffi::c_int = 1 as ::core::ffi::c_int; -pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); pub const LIGHTNVR_MAX_STREAMS: ::core::ffi::c_int = 1024 as ::core::ffi::c_int; pub const __ATOMIC_SEQ_CST: ::core::ffi::c_int = 5 as ::core::ffi::c_int; diff --git a/rust/generated/src__video__zone_filter.rs b/rust/generated/src__video__zone_filter.rs index b48911f9c..6b68dfb8d 100644 --- a/rust/generated/src__video__zone_filter.rs +++ b/rust/generated/src__video__zone_filter.rs @@ -43,6 +43,7 @@ unsafe extern "C" { unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; } pub type size_t = usize; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct detection_t { @@ -150,6 +151,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } pub type uint8_t = u8; #[derive(Clone, Copy, PartialEq, Eq)] @@ -589,6 +594,10 @@ pub unsafe extern "C" fn filter_detections_by_stream_objects( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memset( &raw mut config as *mut ::core::ffi::c_void, diff --git a/rust/generated/src__web__api_handlers_audit.rs b/rust/generated/src__web__api_handlers_audit.rs index d7d2cf322..874cac0e6 100644 --- a/rust/generated/src__web__api_handlers_audit.rs +++ b/rust/generated/src__web__api_handlers_audit.rs @@ -365,7 +365,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -1366,10 +1367,10 @@ pub unsafe extern "C" fn handle_put_audit_settings( pthread_mutex_lock(&raw mut audit_settings_put_mutex); let mut changes: *mut cJSON = ::core::ptr::null_mut::(); let mut deleted_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; - let mut previous: [audit_decision_mode_t; 19] = - [audit_decision_mode_t::AUDIT_DECISION_MODE_RECORD; 19]; - let mut next: [audit_decision_mode_t; 19] = - [audit_decision_mode_t::AUDIT_DECISION_MODE_RECORD; 19]; + let mut previous: [audit_decision_mode_t; 20] = + [audit_decision_mode_t::AUDIT_DECISION_MODE_RECORD; 20]; + let mut next: [audit_decision_mode_t; 20] = + [audit_decision_mode_t::AUDIT_DECISION_MODE_RECORD; 20]; let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; while i < authorization_action_t::AUTHZ_ACTION_COUNT.0 { next[i as usize] = audit_log_get_decision_mode(authorization_action_t(i)); diff --git a/rust/generated/src__web__api_handlers_auth_backend_agnostic.rs b/rust/generated/src__web__api_handlers_auth_backend_agnostic.rs index 620fde3eb..434b819e4 100644 --- a/rust/generated/src__web__api_handlers_auth_backend_agnostic.rs +++ b/rust/generated/src__web__api_handlers_auth_backend_agnostic.rs @@ -311,6 +311,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -416,7 +420,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__web__api_handlers_authorization.rs b/rust/generated/src__web__api_handlers_authorization.rs index 494943979..36c0c5dd8 100644 --- a/rust/generated/src__web__api_handlers_authorization.rs +++ b/rust/generated/src__web__api_handlers_authorization.rs @@ -368,7 +368,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_camera_collections.rs b/rust/generated/src__web__api_handlers_camera_collections.rs index 0147f1dcc..5415689f6 100644 --- a/rust/generated/src__web__api_handlers_camera_collections.rs +++ b/rust/generated/src__web__api_handlers_camera_collections.rs @@ -410,7 +410,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const CAMERA_UUID_STRING_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; pub const CAMERA_COLLECTION_MAX_MEMBERS: ::core::ffi::c_int = 4096 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_camera_favorites.rs b/rust/generated/src__web__api_handlers_camera_favorites.rs index 8d5e05a2f..a520f5739 100644 --- a/rust/generated/src__web__api_handlers_camera_favorites.rs +++ b/rust/generated/src__web__api_handlers_camera_favorites.rs @@ -265,7 +265,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_camera_tags.rs b/rust/generated/src__web__api_handlers_camera_tags.rs index e04abd980..7d145b1e3 100644 --- a/rust/generated/src__web__api_handlers_camera_tags.rs +++ b/rust/generated/src__web__api_handlers_camera_tags.rs @@ -323,7 +323,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_common_utils.rs b/rust/generated/src__web__api_handlers_common_utils.rs index ba022d4f6..fc0765463 100644 --- a/rust/generated/src__web__api_handlers_common_utils.rs +++ b/rust/generated/src__web__api_handlers_common_utils.rs @@ -25,6 +25,7 @@ unsafe extern "C" { ... ); } +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct cJSON { @@ -123,6 +124,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -228,7 +233,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__web__api_handlers_detection.rs b/rust/generated/src__web__api_handlers_detection.rs index 9bae1c7ba..bdd45cf8f 100644 --- a/rust/generated/src__web__api_handlers_detection.rs +++ b/rust/generated/src__web__api_handlers_detection.rs @@ -74,9 +74,19 @@ unsafe extern "C" { ) -> ::core::ffi::c_int; unsafe fn get_stream_by_name(name: *const ::core::ffi::c_char) -> stream_handle_t; unsafe fn go2rtc_integration_is_initialized() -> bool; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; unsafe fn get_unified_detection_effective_status( stream_name: *const ::core::ffi::c_char, ) -> stream_status_t; + unsafe fn db_observation_link_detections( + camera_uuid: *const ::core::ffi::c_char, + refs: *const observation_detection_ref_t, + count: size_t, + out: *mut observation_detection_link_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_detection_link_to_json( + link: *const observation_detection_link_t, + ) -> *mut cJSON; unsafe fn get_detection_results_for_camera_readonly( camera_uuid: *const ::core::ffi::c_char, legacy_stream_name: *const ::core::ffi::c_char, @@ -97,6 +107,7 @@ unsafe extern "C" { __endptr: *mut *mut ::core::ffi::c_char, __base: ::core::ffi::c_int, ) -> ::core::ffi::c_longlong; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; unsafe fn free(__ptr: *mut ::core::ffi::c_void); } pub type size_t = usize; @@ -197,6 +208,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -288,19 +303,55 @@ impl log_level_t { pub const LOG_LEVEL_INFO: Self = Self(2); pub const LOG_LEVEL_DEBUG: Self = Self(3); } +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_detection_link_t { + pub linked: bool, + pub observation_uuid: [::core::ffi::c_char; 37], + pub has_classification: bool, + pub owner_confirmed: bool, + pub classification: observation_classification_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_t { + pub key: [::core::ffi::c_char; 66], + pub writer_namespace: [::core::ffi::c_char; 33], + pub writer_user_id: int64_t, + pub classifier: [::core::ffi::c_char; 65], + pub model_version: [::core::ffi::c_char; 65], + pub label: [::core::ffi::c_char; 129], + pub taxonomy_id: [::core::ffi::c_char; 65], + pub confidence: ::core::ffi::c_double, + pub classified_at_ms: int64_t, + pub input_asset_uuid: [::core::ffi::c_char; 37], + pub input_revision: ::core::ffi::c_int, + pub kind: observation_classification_kind_t, + pub annotation_revision: ::core::ffi::c_int, + pub created_at_ms: int64_t, + pub updated_at_ms: int64_t, +} #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] -pub struct stream_status_t(pub ::core::ffi::c_uint); -impl stream_status_t { - pub const STREAM_STATUS_UNKNOWN: Self = Self(0); - pub const STREAM_STATUS_STOPPED: Self = Self(1); - pub const STREAM_STATUS_STARTING: Self = Self(2); - pub const STREAM_STATUS_RUNNING: Self = Self(3); - pub const STREAM_STATUS_STOPPING: Self = Self(4); - pub const STREAM_STATUS_ERROR: Self = Self(5); - pub const STREAM_STATUS_RECONNECTING: Self = Self(6); +pub struct observation_classification_kind_t(pub ::core::ffi::c_uint); +impl observation_classification_kind_t { + pub const OBS_CLS_KIND_AUTOMATED: Self = Self(0); + pub const OBS_CLS_KIND_OWNER_CONFIRMED: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct api_phase_timer_t { + pub started: timespec, + pub request: *const http_request_t, + pub phase: *const ::core::ffi::c_char, + pub valid: bool, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_detection_ref_t { + pub label: *const ::core::ffi::c_char, + pub timestamp: int64_t, } -pub type stream_handle_t = *mut stream_handle_s; #[derive(Copy, Clone)] #[repr(C)] pub struct fleet_camera_t { @@ -359,14 +410,19 @@ pub struct fleet_camera_tag_t { pub uuid: [::core::ffi::c_char; 37], pub label: [::core::ffi::c_char; 256], } -#[derive(Copy, Clone)] -#[repr(C)] -pub struct api_phase_timer_t { - pub started: timespec, - pub request: *const http_request_t, - pub phase: *const ::core::ffi::c_char, - pub valid: bool, +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_status_t(pub ::core::ffi::c_uint); +impl stream_status_t { + pub const STREAM_STATUS_UNKNOWN: Self = Self(0); + pub const STREAM_STATUS_STOPPED: Self = Self(1); + pub const STREAM_STATUS_STARTING: Self = Self(2); + pub const STREAM_STATUS_RUNNING: Self = Self(3); + pub const STREAM_STATUS_STOPPING: Self = Self(4); + pub const STREAM_STATUS_ERROR: Self = Self(5); + pub const STREAM_STATUS_RECONNECTING: Self = Self(6); } +pub type stream_handle_t = *mut stream_handle_s; #[derive(Copy, Clone)] #[repr(C)] pub struct authorization_evaluation_t { @@ -449,7 +505,22 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); } pub const LOG_COMPONENT: [::core::ffi::c_char; 13] = unsafe { ::core::mem::transmute::<[u8; 13], [::core::ffi::c_char; 13]>(*b"DetectionAPI\0") }; @@ -826,6 +897,10 @@ pub unsafe extern "C" fn handle_get_detection_results( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut stream_cfg) == 0 as ::core::ffi::c_int && stream_cfg.detection_interval > 0 as ::core::ffi::c_int @@ -1025,6 +1100,10 @@ pub unsafe extern "C" fn handle_get_detection_results( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memset( &raw mut scfg as *mut ::core::ffi::c_void, @@ -1076,8 +1155,55 @@ pub unsafe extern "C" fn handle_get_detection_results( b"stream_status\0".as_ptr() as *const ::core::ffi::c_char, status_str, ); - let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; - while i < result.count { + let mut links: *mut observation_detection_link_t = + ::core::ptr::null_mut::(); + if result.count > 0 as ::core::ffi::c_int + && lightnvr_uuid_is_valid(&raw mut camera.camera_uuid as *mut ::core::ffi::c_char) + as ::core::ffi::c_int + != 0 + { + links = calloc( + result.count as size_t, + ::core::mem::size_of::(), + ) as *mut observation_detection_link_t; + } + if !links.is_null() { + let mut refs: [observation_detection_ref_t; 20] = [observation_detection_ref_t { + label: ::core::ptr::null::<::core::ffi::c_char>(), + timestamp: 0, + }; 20]; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < result.count { + refs[i as usize].label = &raw mut (*(&raw mut result.detections as *mut detection_t) + .offset(i as isize)) + .label as *mut ::core::ffi::c_char; + refs[i as usize].timestamp = timestamps[i as usize] as int64_t; + i += 1; + } + phase = api_phase_begin( + req, + b"detection observation link\0".as_ptr() as *const ::core::ffi::c_char, + ); + if db_observation_link_detections( + &raw mut camera.camera_uuid as *mut ::core::ffi::c_char, + &raw mut refs as *mut observation_detection_ref_t, + result.count as size_t, + links, + ) != C2Rust_Unnamed::DB_OBS_OK.0 + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT.as_ptr(), + _log_stream_name, + b"Observation links unavailable for stream %s\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut stream_name as *mut ::core::ffi::c_char, + ); + } + api_phase_end(&raw mut phase); + } + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < result.count { let mut detection: *mut cJSON = cJSON_CreateObject(); if detection.is_null() { _log_message_ctx( @@ -1090,48 +1216,61 @@ pub unsafe extern "C" fn handle_get_detection_results( cJSON_AddStringToObject( detection, b"label\0".as_ptr() as *const ::core::ffi::c_char, - &raw mut (*(&raw mut result.detections as *mut detection_t).offset(i as isize)) + &raw mut (*(&raw mut result.detections as *mut detection_t).offset(i_0 as isize)) .label as *mut ::core::ffi::c_char, ); cJSON_AddNumberToObject( detection, b"confidence\0".as_ptr() as *const ::core::ffi::c_char, - result.detections[i as usize].confidence as ::core::ffi::c_double, + result.detections[i_0 as usize].confidence as ::core::ffi::c_double, ); cJSON_AddNumberToObject( detection, b"x\0".as_ptr() as *const ::core::ffi::c_char, - result.detections[i as usize].x as ::core::ffi::c_double, + result.detections[i_0 as usize].x as ::core::ffi::c_double, ); cJSON_AddNumberToObject( detection, b"y\0".as_ptr() as *const ::core::ffi::c_char, - result.detections[i as usize].y as ::core::ffi::c_double, + result.detections[i_0 as usize].y as ::core::ffi::c_double, ); cJSON_AddNumberToObject( detection, b"width\0".as_ptr() as *const ::core::ffi::c_char, - result.detections[i as usize].width as ::core::ffi::c_double, + result.detections[i_0 as usize].width as ::core::ffi::c_double, ); cJSON_AddNumberToObject( detection, b"height\0".as_ptr() as *const ::core::ffi::c_char, - result.detections[i as usize].height as ::core::ffi::c_double, + result.detections[i_0 as usize].height as ::core::ffi::c_double, ); cJSON_AddNumberToObject( detection, b"timestamp\0".as_ptr() as *const ::core::ffi::c_char, - timestamps[i as usize] as ::core::ffi::c_double, + timestamps[i_0 as usize] as ::core::ffi::c_double, ); cJSON_AddNumberToObject( detection, b"end_timestamp\0".as_ptr() as *const ::core::ffi::c_char, - end_timestamps[i as usize] as ::core::ffi::c_double, + end_timestamps[i_0 as usize] as ::core::ffi::c_double, ); + let mut observation: *mut cJSON = if !links.is_null() { + observation_detection_link_to_json(links.offset(i_0 as isize)) + } else { + ::core::ptr::null_mut::() + }; + if !observation.is_null() { + cJSON_AddItemToObject( + detection, + b"observation\0".as_ptr() as *const ::core::ffi::c_char, + observation, + ); + } cJSON_AddItemToArray(detections_array, detection); } - i += 1; + i_0 += 1; } + free(links as *mut ::core::ffi::c_void); let mut json_str: *mut ::core::ffi::c_char = cJSON_PrintUnformatted(response); if json_str.is_null() { _log_message_ctx( diff --git a/rust/generated/src__web__api_handlers_detection_engines.rs b/rust/generated/src__web__api_handlers_detection_engines.rs index 8b0f505b9..2364026a4 100644 --- a/rust/generated/src__web__api_handlers_detection_engines.rs +++ b/rust/generated/src__web__api_handlers_detection_engines.rs @@ -221,7 +221,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_detection_models.rs b/rust/generated/src__web__api_handlers_detection_models.rs index 2110752aa..e3801142b 100644 --- a/rust/generated/src__web__api_handlers_detection_models.rs +++ b/rust/generated/src__web__api_handlers_detection_models.rs @@ -227,6 +227,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -332,7 +336,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -519,7 +523,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_detection_results.rs b/rust/generated/src__web__api_handlers_detection_results.rs index 6468e0294..91711a8d3 100644 --- a/rust/generated/src__web__api_handlers_detection_results.rs +++ b/rust/generated/src__web__api_handlers_detection_results.rs @@ -238,6 +238,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -343,7 +347,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -585,7 +589,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const MAX_STREAMS: ::core::ffi::c_int = LIGHTNVR_MAX_STREAMS; #[inline] diff --git a/rust/generated/src__web__api_handlers_event_destinations.rs b/rust/generated/src__web__api_handlers_event_destinations.rs index d2233dcae..2d6a0bf6d 100644 --- a/rust/generated/src__web__api_handlers_event_destinations.rs +++ b/rust/generated/src__web__api_handlers_event_destinations.rs @@ -391,7 +391,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const EVENT_DESTINATION_UUID_MAX: ::core::ffi::c_int = 37 as ::core::ffi::c_int; pub const EVENT_DESTINATION_PASSWORD_MAX: ::core::ffi::c_int = 256 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_event_routes.rs b/rust/generated/src__web__api_handlers_event_routes.rs index 09e1750ff..d4e3c982d 100644 --- a/rust/generated/src__web__api_handlers_event_routes.rs +++ b/rust/generated/src__web__api_handlers_event_routes.rs @@ -431,7 +431,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_fleet.rs b/rust/generated/src__web__api_handlers_fleet.rs index 392728046..ac2b156b2 100644 --- a/rust/generated/src__web__api_handlers_fleet.rs +++ b/rust/generated/src__web__api_handlers_fleet.rs @@ -266,6 +266,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -371,7 +375,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -516,7 +520,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_go2rtc_proxy.rs b/rust/generated/src__web__api_handlers_go2rtc_proxy.rs index 2d71eacf5..87b59ea02 100644 --- a/rust/generated/src__web__api_handlers_go2rtc_proxy.rs +++ b/rust/generated/src__web__api_handlers_go2rtc_proxy.rs @@ -870,6 +870,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -975,7 +979,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1680,7 +1684,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_health.rs b/rust/generated/src__web__api_handlers_health.rs index d0ce0e8f7..36c27ef23 100644 --- a/rust/generated/src__web__api_handlers_health.rs +++ b/rust/generated/src__web__api_handlers_health.rs @@ -885,6 +885,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -990,7 +994,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__web__api_handlers_ice_servers.rs b/rust/generated/src__web__api_handlers_ice_servers.rs index 20911fcee..e74510733 100644 --- a/rust/generated/src__web__api_handlers_ice_servers.rs +++ b/rust/generated/src__web__api_handlers_ice_servers.rs @@ -165,6 +165,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -270,7 +274,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -458,7 +462,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_imaging.rs b/rust/generated/src__web__api_handlers_imaging.rs index 878e96c5e..706cfe5f6 100644 --- a/rust/generated/src__web__api_handlers_imaging.rs +++ b/rust/generated/src__web__api_handlers_imaging.rs @@ -161,6 +161,7 @@ unsafe extern "C" { unsafe fn free(__ptr: *mut ::core::ffi::c_void); } pub type size_t = usize; +pub type int64_t = i64; pub type uint8_t = u8; pub type uint64_t = u64; #[derive(Clone, Copy, PartialEq, Eq)] @@ -231,6 +232,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -349,7 +354,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -1559,6 +1565,10 @@ pub unsafe extern "C" fn handle_imaging_get_settings( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut imaging_url: [::core::ffi::c_char; 512] = [0; 512]; let mut video_source_token: [::core::ffi::c_char; 64] = [0; 64]; @@ -1735,6 +1745,10 @@ pub unsafe extern "C" fn handle_imaging_get_options( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut imaging_url: [::core::ffi::c_char; 512] = [0; 512]; let mut video_source_token: [::core::ffi::c_char; 64] = [0; 64]; @@ -1932,6 +1946,10 @@ pub unsafe extern "C" fn handle_imaging_put_settings( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut imaging_url: [::core::ffi::c_char; 512] = [0; 512]; let mut video_source_token: [::core::ffi::c_char; 64] = [0; 64]; @@ -2273,6 +2291,10 @@ pub unsafe extern "C" fn handle_daynight_get( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut imaging_url: [::core::ffi::c_char; 512] = [0; 512]; let mut video_source_token: [::core::ffi::c_char; 64] = [0; 64]; @@ -2571,6 +2593,10 @@ pub unsafe extern "C" fn handle_daynight_put( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut imaging_url: [::core::ffi::c_char; 512] = [0; 512]; let mut video_source_token: [::core::ffi::c_char; 64] = [0; 64]; diff --git a/rust/generated/src__web__api_handlers_investigation_bookmarks.rs b/rust/generated/src__web__api_handlers_investigation_bookmarks.rs index d37574709..3343953ac 100644 --- a/rust/generated/src__web__api_handlers_investigation_bookmarks.rs +++ b/rust/generated/src__web__api_handlers_investigation_bookmarks.rs @@ -235,6 +235,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -349,7 +353,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -1133,6 +1138,10 @@ unsafe extern "C" fn authorize_cameras_in_context( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut camera: fleet_camera_t = fleet_camera_t { camera_uuid: [ diff --git a/rust/generated/src__web__api_handlers_investigations.rs b/rust/generated/src__web__api_handlers_investigations.rs index 2ef4907bd..91217a6a6 100644 --- a/rust/generated/src__web__api_handlers_investigations.rs +++ b/rust/generated/src__web__api_handlers_investigations.rs @@ -287,6 +287,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -392,7 +396,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -635,7 +639,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -1148,6 +1153,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1208,6 +1217,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1268,6 +1281,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1328,6 +1345,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1388,6 +1409,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1448,6 +1473,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1508,6 +1537,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1568,6 +1601,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1628,6 +1665,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1688,6 +1729,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1748,6 +1793,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1808,6 +1857,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1868,6 +1921,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1928,6 +1985,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -1988,6 +2049,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -2048,6 +2113,10 @@ unsafe extern "C" fn resolve_authorized_search_cameras( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, ]; let mut explicit_cameras: [fleet_camera_t; 16] = [ @@ -3102,6 +3171,10 @@ pub unsafe extern "C" fn handle_post_investigation_timeline( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; 16]; let mut fleet_cameras: [fleet_camera_t; 16] = [fleet_camera_t { camera_uuid: [0; 37], @@ -3596,6 +3669,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -3656,6 +3733,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -3716,6 +3797,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -3776,6 +3861,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -3836,6 +3925,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -3896,6 +3989,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -3956,6 +4053,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4016,6 +4117,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4076,6 +4181,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4136,6 +4245,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4196,6 +4309,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4256,6 +4373,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4316,6 +4437,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4376,6 +4501,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4436,6 +4565,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -4496,6 +4629,10 @@ pub unsafe extern "C" fn handle_post_investigation_segment_at( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, ]; let mut fleet_cameras: [fleet_camera_t; 16] = [ @@ -5292,6 +5429,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5352,6 +5493,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5412,6 +5557,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5472,6 +5621,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5532,6 +5685,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5592,6 +5749,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5652,6 +5813,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5712,6 +5877,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5772,6 +5941,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5832,6 +6005,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5892,6 +6069,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -5952,6 +6133,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -6012,6 +6197,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -6072,6 +6261,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -6132,6 +6325,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -6192,6 +6389,10 @@ pub unsafe extern "C" fn handle_post_investigation_recording_preview( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, ]; let mut fleet_cameras: [fleet_camera_t; 16] = [ @@ -7279,6 +7480,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7339,6 +7544,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7399,6 +7608,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7459,6 +7672,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7519,6 +7736,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7579,6 +7800,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7639,6 +7864,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7699,6 +7928,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7759,6 +7992,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7819,6 +8056,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7879,6 +8120,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7939,6 +8184,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -7999,6 +8248,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -8059,6 +8312,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -8119,6 +8376,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, stream_config_t { camera_uuid: [0; 37], @@ -8179,6 +8440,10 @@ pub unsafe extern "C" fn handle_post_investigation_thumbnail_samples( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }, ]; let mut fleet_cameras: [fleet_camera_t; 16] = [ diff --git a/rust/generated/src__web__api_handlers_live_layouts.rs b/rust/generated/src__web__api_handlers_live_layouts.rs index 6f3aa40a5..d795a63b0 100644 --- a/rust/generated/src__web__api_handlers_live_layouts.rs +++ b/rust/generated/src__web__api_handlers_live_layouts.rs @@ -313,7 +313,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_locations.rs b/rust/generated/src__web__api_handlers_locations.rs index edae937d9..f6564980d 100644 --- a/rust/generated/src__web__api_handlers_locations.rs +++ b/rust/generated/src__web__api_handlers_locations.rs @@ -229,6 +229,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -334,7 +338,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -547,7 +551,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_lpr.rs b/rust/generated/src__web__api_handlers_lpr.rs index 7fb9eeb2f..e6c393021 100644 --- a/rust/generated/src__web__api_handlers_lpr.rs +++ b/rust/generated/src__web__api_handlers_lpr.rs @@ -382,7 +382,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const INT64_MAX: ::core::ffi::c_long = 9223372036854775807 as ::core::ffi::c_long; pub const LPR_CRYPTO_HMAC_SIZE: ::core::ffi::c_int = 32 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_metrics.rs b/rust/generated/src__web__api_handlers_metrics.rs index ed8db2ba5..9bbb3de0e 100644 --- a/rust/generated/src__web__api_handlers_metrics.rs +++ b/rust/generated/src__web__api_handlers_metrics.rs @@ -93,6 +93,14 @@ unsafe extern "C" { capacity: size_t, ) -> size_t; unsafe fn mqtt_presence_get_stats(stats: *mut mqtt_presence_stats_t); + unsafe fn observation_diagnostics_collect( + out: *mut observation_diagnostics_t, + now_ms: int64_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_diagnostics_free(diagnostics: *mut observation_diagnostics_t); + unsafe fn observation_diagnostics_relay_lag_seconds( + diagnostics: *const observation_diagnostics_t, + ) -> ::core::ffi::c_double; unsafe fn get_total_stream_count() -> ::core::ffi::c_int; unsafe fn get_storage_health(health: *mut storage_health_t) -> ::core::ffi::c_int; unsafe fn safe_strcpy( @@ -612,6 +620,130 @@ impl system_health_sampling_tier_t { pub const SYSTEM_HEALTH_TIER_SLOW: Self = Self(2); pub const SYSTEM_HEALTH_TIER_DEVICE: Self = Self(3); } +pub type observation_diagnostics_t = observation_diagnostics_snapshot; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_diagnostics_snapshot { + pub collected_at_ms: int64_t, + pub evidence: observation_evidence_stats_t, + pub relay: observation_relay_stats_t, + pub db_available: bool, + pub db: observation_db_diagnostics_t, + pub cameras: *mut observation_retention_camera_status_t, + pub camera_count: ::core::ffi::c_int, + pub budget_exhausted_cameras: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_retention_camera_status_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub retained_bytes: int64_t, + pub max_bytes: int64_t, + pub max_age_days: ::core::ffi::c_int, + pub budget_exhausted: bool, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_db_diagnostics_t { + pub meta: observation_meta_t, + pub has_oldest_observed_at: bool, + pub oldest_observed_at_ms: int64_t, + pub observations: int64_t, + pub publications_pending: int64_t, + pub publications_failed: int64_t, + pub publications_expired: int64_t, + pub oldest_pending_created_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_stats_t { + pub running: bool, + pub passes: uint64_t, + pub relayed: uint64_t, + pub duplicates: uint64_t, + pub relayed_no_route: uint64_t, + pub expired: uint64_t, + pub failed: uint64_t, + pub enqueue_full: uint64_t, + pub enqueue_errors: uint64_t, + pub mark_errors: uint64_t, + pub read_errors: uint64_t, + pub cap_pauses: uint64_t, + pub backoffs: uint64_t, + pub sidecar_writes: uint64_t, + pub sidecar_errors: uint64_t, + pub epoch_regenerations: uint64_t, + pub last_relayed_seq: int64_t, + pub oldest_pending_created_at_ms: int64_t, + pub last_pass_pending: ::core::ffi::c_int, + pub outbox_type_pending: int64_t, + pub consecutive_failures: ::core::ffi::c_int, + pub backoff_until_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_stats_t { + pub initialized: bool, + pub worker_running: bool, + pub allocated: bool, + pub accepted: uint64_t, + pub dropped_queue_full: uint64_t, + pub dropped_budget: uint64_t, + pub dropped_disabled: uint64_t, + pub dropped_untracked: uint64_t, + pub unavailable_submitted: uint64_t, + pub ready: uint64_t, + pub failed_encode: uint64_t, + pub failed_write: uint64_t, + pub failed_db: uint64_t, + pub disabled_at_encode: uint64_t, + pub interrupted: uint64_t, + pub bytes_written: uint64_t, + pub backlog_items: uint32_t, + pub backlog_bytes: uint64_t, + pub drop_ring_depth: uint32_t, + pub high_water_items: uint32_t, + pub high_water_bytes: uint64_t, + pub budget_exhausted_cameras: uint32_t, + pub limit_items: uint32_t, + pub limit_camera_items: uint32_t, + pub limit_bytes: uint64_t, + pub limit_camera_bytes: uint64_t, + pub camera_high_water_items: uint32_t, + pub submit_latency_count: uint64_t, + pub submit_latency_sum_ns: uint64_t, + pub submit_latency_max_ns: uint64_t, + pub submit_latency_bucket: [uint64_t; 10], + pub discard_deferred: uint64_t, + pub discard_inline: uint64_t, + pub discard_pending_bytes: uint64_t, + pub limit_discard_bytes: uint64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed { + pub outcome: *const ::core::ffi::c_char, + pub value: uint64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed_0 { + pub reason: *const ::core::ffi::c_char, + pub value: uint64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct C2Rust_Unnamed_1 { + pub reason: *const ::core::ffi::c_char, + pub value: uint64_t, +} #[derive(Copy, Clone)] #[repr(C)] pub struct system_health_process_run_t { @@ -741,7 +873,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -765,6 +898,7 @@ pub const EVENT_OUTBOX_DEFAULT_MAX_BYTES: ::core::ffi::c_longlong = 64 as ::core * 1024 as ::core::ffi::c_longlong; pub const MQTT_EVENT_OUTBOX_DESTINATION: [::core::ffi::c_char; 13] = unsafe { ::core::mem::transmute::<[u8; 13], [::core::ffi::c_char; 13]>(*b"mqtt:default\0") }; +pub const OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS: ::core::ffi::c_int = 10 as ::core::ffi::c_int; unsafe extern "C" fn prom_buf_init(mut b: *mut prom_buf_t, mut initial_cap: size_t) { (*b).data = malloc(initial_cap) as *mut ::core::ffi::c_char; (*b).len = 0 as size_t; @@ -3949,6 +4083,637 @@ pub unsafe extern "C" fn api_metrics_render_system_health( ); return buffer.data; } +unsafe extern "C" fn prom_family( + mut b: *mut prom_buf_t, + mut name: *const ::core::ffi::c_char, + mut r#type: *const ::core::ffi::c_char, + mut help: *const ::core::ffi::c_char, +) { + prom_buf_append( + b, + b"# HELP %s %s\n\0".as_ptr() as *const ::core::ffi::c_char, + name, + help, + ); + prom_buf_append( + b, + b"# TYPE %s %s\n\0".as_ptr() as *const ::core::ffi::c_char, + name, + r#type, + ); +} +unsafe extern "C" fn prom_u64( + mut b: *mut prom_buf_t, + mut name: *const ::core::ffi::c_char, + mut r#type: *const ::core::ffi::c_char, + mut help: *const ::core::ffi::c_char, + mut value: uint64_t, +) { + prom_family(b, name, r#type, help); + prom_buf_append( + b, + b"%s %llu\n\0".as_ptr() as *const ::core::ffi::c_char, + name, + value as ::core::ffi::c_ulonglong, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn api_metrics_render_observations( + mut d: *const observation_diagnostics_t, +) -> *mut ::core::ffi::c_char { + if d.is_null() { + return ::core::ptr::null_mut::<::core::ffi::c_char>(); + } + let mut e: *const observation_evidence_stats_t = &raw const (*d).evidence; + let mut r: *const observation_relay_stats_t = &raw const (*d).relay; + let mut b: prom_buf_t = prom_buf_t { + data: ::core::ptr::null_mut::<::core::ffi::c_char>(), + len: 0, + cap: 0, + }; + prom_buf_init(&raw mut b, 8192 as size_t); + if b.data.is_null() { + return ::core::ptr::null_mut::<::core::ffi::c_char>(); + } + prom_u64( + &raw mut b, + b"lightnvr_observation_accepted_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation batches whose evidence frame was queued\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).accepted, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_dropped_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation evidence submissions dropped, by reason\0".as_ptr() + as *const ::core::ffi::c_char, + ); + let drops: [C2Rust_Unnamed_1; 4] = [ + C2Rust_Unnamed_1 { + reason: b"queue_full\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).dropped_queue_full, + }, + C2Rust_Unnamed_1 { + reason: b"budget_exhausted\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).dropped_budget, + }, + C2Rust_Unnamed_1 { + reason: b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).dropped_disabled, + }, + C2Rust_Unnamed_1 { + reason: b"untracked\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).dropped_untracked, + }, + ]; + let mut i: size_t = 0 as size_t; + while i < ::core::mem::size_of::<[C2Rust_Unnamed_1; 4]>() + .wrapping_div(::core::mem::size_of::()) + { + prom_buf_append( + &raw mut b, + b"lightnvr_observation_dropped_total{reason=\"%s\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + drops[i].reason, + drops[i].value as ::core::ffi::c_ulonglong, + ); + i = i.wrapping_add(1); + } + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_unavailable_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation batches recorded without media (no_matching_media)\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).unavailable_submitted, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_ready_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Evidence assets committed as ready\0".as_ptr() as *const ::core::ffi::c_char, + (*e).ready, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_failures_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Evidence assets that became unavailable after admission, by reason\0".as_ptr() + as *const ::core::ffi::c_char, + ); + let failures: [C2Rust_Unnamed_0; 5] = [ + C2Rust_Unnamed_0 { + reason: b"encode_failed\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).failed_encode, + }, + C2Rust_Unnamed_0 { + reason: b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).failed_write, + }, + C2Rust_Unnamed_0 { + reason: b"db_error\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).failed_db, + }, + C2Rust_Unnamed_0 { + reason: b"disabled\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).disabled_at_encode, + }, + C2Rust_Unnamed_0 { + reason: b"interrupted\0".as_ptr() as *const ::core::ffi::c_char, + value: (*e).interrupted, + }, + ]; + let mut i_0: size_t = 0 as size_t; + while i_0 + < ::core::mem::size_of::<[C2Rust_Unnamed_0; 5]>() + .wrapping_div(::core::mem::size_of::()) + { + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_failures_total{reason=\"%s\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + failures[i_0].reason, + failures[i_0].value as ::core::ffi::c_ulonglong, + ); + i_0 = i_0.wrapping_add(1); + } + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_bytes_written_total\0".as_ptr() + as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Evidence JPEG bytes written\0".as_ptr() as *const ::core::ffi::c_char, + (*e).bytes_written, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_backlog_items\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Evidence frames queued for encoding\0".as_ptr() as *const ::core::ffi::c_char, + (*e).backlog_items as uint64_t, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_backlog_bytes\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Bytes held by queued evidence frames\0".as_ptr() as *const ::core::ffi::c_char, + (*e).backlog_bytes, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_drop_ring_depth\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Drop records waiting to be written\0".as_ptr() as *const ::core::ffi::c_char, + (*e).drop_ring_depth as uint64_t, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_high_water_items\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Highest evidence backlog in items this process run\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).high_water_items as uint64_t, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_high_water_bytes\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Highest evidence backlog in bytes this process run\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).high_water_bytes, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_queue_allocated\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Whether the evidence queue or worker exists (0 while evidence is unused)\0".as_ptr() + as *const ::core::ffi::c_char, + (if (*e).allocated as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_uint + } else { + 0 as ::core::ffi::c_uint + }) as uint64_t, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_worker_running\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Whether the evidence encoder worker is running\0".as_ptr() as *const ::core::ffi::c_char, + (if (*e).worker_running as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_uint + } else { + 0 as ::core::ffi::c_uint + }) as uint64_t, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_camera_high_water_items\0".as_ptr() + as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Highest evidence backlog held by one camera this process run\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).camera_high_water_items as uint64_t, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_limit_items\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Configured evidence queue depth bound, global and per camera\0".as_ptr() + as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_limit_items{scope=\"global\"} %u\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).limit_items as ::core::ffi::c_uint, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_limit_items{scope=\"camera\"} %u\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).limit_camera_items as ::core::ffi::c_uint, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_limit_bytes\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Configured evidence queue byte bound, global and per camera\0".as_ptr() + as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_limit_bytes{scope=\"global\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).limit_bytes as ::core::ffi::c_ulonglong, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_limit_bytes{scope=\"camera\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).limit_camera_bytes as ::core::ffi::c_ulonglong, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_limit_bytes{scope=\"discard\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).limit_discard_bytes as ::core::ffi::c_ulonglong, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_discards_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Rejected evidence frames released by the worker (deferred) or by the producer (inline)\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_discards_total{mode=\"deferred\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).discard_deferred as ::core::ffi::c_ulonglong, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_discards_total{mode=\"inline\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).discard_inline as ::core::ffi::c_ulonglong, + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_discard_pending_bytes\0".as_ptr() + as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Rejected evidence frame bytes waiting for the worker to release them\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).discard_pending_bytes, + ); + static mut submit_le: [*const ::core::ffi::c_char; 10] = [ + b"0.000001\0".as_ptr() as *const ::core::ffi::c_char, + b"0.000005\0".as_ptr() as *const ::core::ffi::c_char, + b"0.00001\0".as_ptr() as *const ::core::ffi::c_char, + b"0.00005\0".as_ptr() as *const ::core::ffi::c_char, + b"0.0001\0".as_ptr() as *const ::core::ffi::c_char, + b"0.00025\0".as_ptr() as *const ::core::ffi::c_char, + b"0.0005\0".as_ptr() as *const ::core::ffi::c_char, + b"0.001\0".as_ptr() as *const ::core::ffi::c_char, + b"0.01\0".as_ptr() as *const ::core::ffi::c_char, + b"+Inf\0".as_ptr() as *const ::core::ffi::c_char, + ]; + prom_family( + &raw mut b, + b"lightnvr_observation_submit_latency_seconds\0".as_ptr() as *const ::core::ffi::c_char, + b"histogram\0".as_ptr() as *const ::core::ffi::c_char, + b"Producer time inside evidence admission this process run\0".as_ptr() + as *const ::core::ffi::c_char, + ); + let mut cumulative: ::core::ffi::c_ulonglong = 0 as ::core::ffi::c_ulonglong; + let mut i_1: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_1 < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS { + cumulative = cumulative + .wrapping_add((*e).submit_latency_bucket[i_1 as usize] as ::core::ffi::c_ulonglong); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_submit_latency_seconds_bucket{le=\"%s\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + submit_le[i_1 as usize], + cumulative, + ); + i_1 += 1; + } + prom_buf_append( + &raw mut b, + b"lightnvr_observation_submit_latency_seconds_sum %.9f\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).submit_latency_sum_ns as ::core::ffi::c_double / 1e9f64, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_submit_latency_seconds_count %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).submit_latency_count as ::core::ffi::c_ulonglong, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_submit_latency_max_seconds\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Slowest producer evidence admission this process run\0".as_ptr() + as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_submit_latency_max_seconds %.9f\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*e).submit_latency_max_ns as ::core::ffi::c_double / 1e9f64, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_retained_bytes\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Ready evidence bytes retained per camera as of the last retention pass\0".as_ptr() + as *const ::core::ffi::c_char, + ); + let mut i_2: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_2 < (*d).camera_count { + let mut uuid: [::core::ffi::c_char; 75] = [0; 75]; + prom_label_escape( + &raw mut (*(*d).cameras.offset(i_2 as isize)).camera_uuid as *mut ::core::ffi::c_char, + &raw mut uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 75]>(), + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_retained_bytes{camera_uuid=\"%s\"} %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut uuid as *mut ::core::ffi::c_char, + (*(*d).cameras.offset(i_2 as isize)).retained_bytes as ::core::ffi::c_longlong, + ); + i_2 += 1; + } + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_budget_bytes\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Per-camera evidence byte budget\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut i_3: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_3 < (*d).camera_count { + let mut uuid_0: [::core::ffi::c_char; 75] = [0; 75]; + prom_label_escape( + &raw mut (*(*d).cameras.offset(i_3 as isize)).camera_uuid as *mut ::core::ffi::c_char, + &raw mut uuid_0 as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 75]>(), + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_budget_bytes{camera_uuid=\"%s\"} %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut uuid_0 as *mut ::core::ffi::c_char, + (*(*d).cameras.offset(i_3 as isize)).max_bytes as ::core::ffi::c_longlong, + ); + i_3 += 1; + } + prom_family( + &raw mut b, + b"lightnvr_observation_evidence_budget_exhausted\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Whether a camera is over budget with only protected evidence left\0".as_ptr() + as *const ::core::ffi::c_char, + ); + let mut i_4: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_4 < (*d).camera_count { + let mut uuid_1: [::core::ffi::c_char; 75] = [0; 75]; + prom_label_escape( + &raw mut (*(*d).cameras.offset(i_4 as isize)).camera_uuid as *mut ::core::ffi::c_char, + &raw mut uuid_1 as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 75]>(), + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_evidence_budget_exhausted{camera_uuid=\"%s\"} %d\n\0".as_ptr() + as *const ::core::ffi::c_char, + &raw mut uuid_1 as *mut ::core::ffi::c_char, + if (*(*d).cameras.offset(i_4 as isize)).budget_exhausted as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }, + ); + i_4 += 1; + } + prom_u64( + &raw mut b, + b"lightnvr_observation_evidence_budget_exhausted_cameras\0".as_ptr() + as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Cameras whose evidence admission is paused by the byte budget\0".as_ptr() + as *const ::core::ffi::c_char, + if (*d).budget_exhausted_cameras > 0 as ::core::ffi::c_int { + (*d).budget_exhausted_cameras as uint64_t + } else { + 0 as uint64_t + }, + ); + if (*d).db_available { + prom_u64( + &raw mut b, + b"lightnvr_observation_history_observations\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Retained observation rows\0".as_ptr() as *const ::core::ffi::c_char, + (*d).db.observations as uint64_t, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_history_oldest_seq\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Oldest retained change seq (oldest recoverable cursor is seq - 1)\0".as_ptr() + as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_history_oldest_seq %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*d).db.meta.oldest_seq as ::core::ffi::c_longlong, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_history_head_seq\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Newest change seq\0".as_ptr() as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_history_head_seq %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*d).db.meta.head_seq as ::core::ffi::c_longlong, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_history_oldest_observed_timestamp_seconds\0".as_ptr() + as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"observed_at of the oldest retained observation (Unix seconds)\0".as_ptr() + as *const ::core::ffi::c_char, + ); + if (*d).db.has_oldest_observed_at { + prom_buf_append( + &raw mut b, + b"lightnvr_observation_history_oldest_observed_timestamp_seconds %.3f\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*d).db.oldest_observed_at_ms as ::core::ffi::c_double / 1000.0f64, + ); + } + prom_family( + &raw mut b, + b"lightnvr_observation_publications\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation change publications by state\0".as_ptr() as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_publications{state=\"pending\"} %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*d).db.publications_pending as ::core::ffi::c_longlong, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_publications{state=\"failed\"} %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*d).db.publications_failed as ::core::ffi::c_longlong, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_publications{state=\"expired\"} %lld\n\0".as_ptr() + as *const ::core::ffi::c_char, + (*d).db.publications_expired as ::core::ffi::c_longlong, + ); + } + prom_family( + &raw mut b, + b"lightnvr_observation_relay_lag_seconds\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Age of the oldest pending observation publication\0".as_ptr() + as *const ::core::ffi::c_char, + ); + prom_buf_append( + &raw mut b, + b"lightnvr_observation_relay_lag_seconds %.3f\n\0".as_ptr() as *const ::core::ffi::c_char, + observation_diagnostics_relay_lag_seconds(d), + ); + prom_u64( + &raw mut b, + b"lightnvr_observation_relay_running\0".as_ptr() as *const ::core::ffi::c_char, + b"gauge\0".as_ptr() as *const ::core::ffi::c_char, + b"Whether the observation publication relay is running\0".as_ptr() + as *const ::core::ffi::c_char, + (if (*r).running as ::core::ffi::c_int != 0 { + 1 as ::core::ffi::c_uint + } else { + 0 as ::core::ffi::c_uint + }) as uint64_t, + ); + prom_family( + &raw mut b, + b"lightnvr_observation_relay_events_total\0".as_ptr() as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation publication relay outcomes this process run\0".as_ptr() + as *const ::core::ffi::c_char, + ); + let relay: [C2Rust_Unnamed; 12] = [ + C2Rust_Unnamed { + outcome: b"relayed\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).relayed, + }, + C2Rust_Unnamed { + outcome: b"duplicate\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).duplicates, + }, + C2Rust_Unnamed { + outcome: b"no_route\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).relayed_no_route, + }, + C2Rust_Unnamed { + outcome: b"expired\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).expired, + }, + C2Rust_Unnamed { + outcome: b"failed\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).failed, + }, + C2Rust_Unnamed { + outcome: b"outbox_full\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).enqueue_full, + }, + C2Rust_Unnamed { + outcome: b"enqueue_error\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).enqueue_errors, + }, + C2Rust_Unnamed { + outcome: b"mark_error\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).mark_errors, + }, + C2Rust_Unnamed { + outcome: b"read_error\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).read_errors, + }, + C2Rust_Unnamed { + outcome: b"cap_pause\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).cap_pauses, + }, + C2Rust_Unnamed { + outcome: b"backoff\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).backoffs, + }, + C2Rust_Unnamed { + outcome: b"sidecar_error\0".as_ptr() as *const ::core::ffi::c_char, + value: (*r).sidecar_errors, + }, + ]; + let mut i_5: size_t = 0 as size_t; + while i_5 + < ::core::mem::size_of::<[C2Rust_Unnamed; 12]>() + .wrapping_div(::core::mem::size_of::()) + { + prom_buf_append( + &raw mut b, + b"lightnvr_observation_relay_events_total{outcome=\"%s\"} %llu\n\0".as_ptr() + as *const ::core::ffi::c_char, + relay[i_5].outcome, + relay[i_5].value as ::core::ffi::c_ulonglong, + ); + i_5 = i_5.wrapping_add(1); + } + prom_u64( + &raw mut b, + b"lightnvr_observation_history_epoch_regenerations_total\0".as_ptr() + as *const ::core::ffi::c_char, + b"counter\0".as_ptr() as *const ::core::ffi::c_char, + b"Cursor epoch regenerations after a detected database rewind\0".as_ptr() + as *const ::core::ffi::c_char, + (*r).epoch_regenerations, + ); + return b.data; +} unsafe extern "C" fn get_go2rtc_rss_bytes() -> uint64_t { unsafe extern "C" { #[link_name = "get_go2rtc_memory_usage"] @@ -4559,6 +5324,103 @@ pub unsafe extern "C" fn handle_get_metrics( free(health_metrics as *mut ::core::ffi::c_void); } } + let mut observations: observation_diagnostics_t = observation_diagnostics_t { + collected_at_ms: 0, + evidence: observation_evidence_stats_t { + initialized: false, + worker_running: false, + allocated: false, + accepted: 0, + dropped_queue_full: 0, + dropped_budget: 0, + dropped_disabled: 0, + dropped_untracked: 0, + unavailable_submitted: 0, + ready: 0, + failed_encode: 0, + failed_write: 0, + failed_db: 0, + disabled_at_encode: 0, + interrupted: 0, + bytes_written: 0, + backlog_items: 0, + backlog_bytes: 0, + drop_ring_depth: 0, + high_water_items: 0, + high_water_bytes: 0, + budget_exhausted_cameras: 0, + limit_items: 0, + limit_camera_items: 0, + limit_bytes: 0, + limit_camera_bytes: 0, + camera_high_water_items: 0, + submit_latency_count: 0, + submit_latency_sum_ns: 0, + submit_latency_max_ns: 0, + submit_latency_bucket: [0; 10], + discard_deferred: 0, + discard_inline: 0, + discard_pending_bytes: 0, + limit_discard_bytes: 0, + }, + relay: observation_relay_stats_t { + running: false, + passes: 0, + relayed: 0, + duplicates: 0, + relayed_no_route: 0, + expired: 0, + failed: 0, + enqueue_full: 0, + enqueue_errors: 0, + mark_errors: 0, + read_errors: 0, + cap_pauses: 0, + backoffs: 0, + sidecar_writes: 0, + sidecar_errors: 0, + epoch_regenerations: 0, + last_relayed_seq: 0, + oldest_pending_created_at_ms: 0, + last_pass_pending: 0, + outbox_type_pending: 0, + consecutive_failures: 0, + backoff_until_ms: 0, + }, + db_available: false, + db: observation_db_diagnostics_t { + meta: observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }, + has_oldest_observed_at: false, + oldest_observed_at_ms: 0, + observations: 0, + publications_pending: 0, + publications_failed: 0, + publications_expired: 0, + oldest_pending_created_at_ms: 0, + }, + cameras: ::core::ptr::null_mut::(), + camera_count: 0, + budget_exhausted_cameras: 0, + }; + if observation_diagnostics_collect(&raw mut observations, 0 as int64_t) + == 0 as ::core::ffi::c_int + { + let mut observation_metrics: *mut ::core::ffi::c_char = + api_metrics_render_observations(&raw mut observations); + if !observation_metrics.is_null() { + prom_buf_append( + &raw mut buf, + b"%s\0".as_ptr() as *const ::core::ffi::c_char, + observation_metrics, + ); + free(observation_metrics as *mut ::core::ffi::c_void); + } + observation_diagnostics_free(&raw mut observations); + } let mut self_metrics: *mut ::core::ffi::c_char = api_metrics_render_self_observability(); if !self_metrics.is_null() { prom_buf_append( diff --git a/rust/generated/src__web__api_handlers_motion.rs b/rust/generated/src__web__api_handlers_motion.rs index 6378ad596..c94cd769a 100644 --- a/rust/generated/src__web__api_handlers_motion.rs +++ b/rust/generated/src__web__api_handlers_motion.rs @@ -159,6 +159,7 @@ pub type size_t = usize; pub type __time_t = ::core::ffi::c_long; pub type __syscall_slong_t = ::core::ffi::c_long; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct timespec { @@ -311,6 +312,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -428,7 +433,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const UINT64_MAX: ::core::ffi::c_ulong = 18446744073709551615 as ::core::ffi::c_ulong; pub const LOG_COMPONENT: [::core::ffi::c_char; 10] = @@ -2359,6 +2365,10 @@ pub unsafe extern "C" fn handle_post_motion_trigger( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memset( &raw mut cfg as *mut ::core::ffi::c_void, diff --git a/rust/generated/src__web__api_handlers_observations.rs b/rust/generated/src__web__api_handlers_observations.rs new file mode 100644 index 000000000..6467613eb --- /dev/null +++ b/rust/generated/src__web__api_handlers_observations.rs @@ -0,0 +1,5193 @@ +unsafe extern "C" { + unsafe fn cJSON_Parse(value: *const ::core::ffi::c_char) -> *mut cJSON; + unsafe fn cJSON_PrintUnformatted(item: *const cJSON) -> *mut ::core::ffi::c_char; + unsafe fn cJSON_Delete(item: *mut cJSON); + unsafe fn cJSON_GetObjectItemCaseSensitive( + object: *const cJSON, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_IsNull(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsNumber(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsString(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsObject(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_CreateNull() -> *mut cJSON; + unsafe fn cJSON_CreateString(string: *const ::core::ffi::c_char) -> *mut cJSON; + unsafe fn cJSON_CreateObject() -> *mut cJSON; + unsafe fn cJSON_AddItemToArray(array: *mut cJSON, item: *mut cJSON) -> cJSON_bool; + unsafe fn cJSON_AddItemToObject( + object: *mut cJSON, + string: *const ::core::ffi::c_char, + item: *mut cJSON, + ) -> cJSON_bool; + unsafe fn cJSON_AddNullToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_AddBoolToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + boolean: cJSON_bool, + ) -> *mut cJSON; + unsafe fn cJSON_AddNumberToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + number: ::core::ffi::c_double, + ) -> *mut cJSON; + unsafe fn cJSON_AddStringToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_AddObjectToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_AddArrayToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_free(object: *mut ::core::ffi::c_void); + static mut g_config: config_t; + unsafe fn observation_asset_state_name( + state: observation_asset_state_t, + ) -> *const ::core::ffi::c_char; + unsafe fn observation_cursor_encode( + seq: int64_t, + epoch_uuid: *const ::core::ffi::c_char, + out: *mut ::core::ffi::c_char, + out_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_get( + observation_uuid: *const ::core::ffi::c_char, + out: *mut observation_record_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_asset_get( + asset_uuid: *const ::core::ffi::c_char, + out: *mut observation_asset_record_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observations_list( + query: *const observation_list_query_t, + cursor: *const ::core::ffi::c_char, + page: *mut observation_page_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_page_free(page: *mut observation_page_t); + unsafe fn db_observation_meta_get(out: *mut observation_meta_t) -> ::core::ffi::c_int; + unsafe fn observation_classification_kind_name( + kind: observation_classification_kind_t, + ) -> *const ::core::ffi::c_char; + unsafe fn observation_classification_kind_parse( + name: *const ::core::ffi::c_char, + kind: *mut observation_classification_kind_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_classification_segment_is_valid( + segment: *const ::core::ffi::c_char, + ) -> bool; + unsafe fn db_observation_classification_put( + observation_uuid: *const ::core::ffi::c_char, + input: *const observation_classification_input_t, + result: *mut observation_classification_result_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_classifications_list( + observation_uuid: *const ::core::ffi::c_char, + out: *mut observation_classification_set_t, + ) -> ::core::ffi::c_int; + unsafe fn db_observation_classifications_list_many( + observation_uuids: *const *const ::core::ffi::c_char, + count: size_t, + visit: observation_classification_visit_fn, + context: *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int; + unsafe fn http_request_get_header( + req: *const http_request_t, + name: *const ::core::ffi::c_char, + ) -> *const ::core::ffi::c_char; + unsafe fn http_request_get_query_param( + req: *const http_request_t, + name: *const ::core::ffi::c_char, + value: *mut ::core::ffi::c_char, + value_len: size_t, + ) -> ::core::ffi::c_int; + unsafe fn http_request_extract_path_param( + req: *const http_request_t, + prefix: *const ::core::ffi::c_char, + param_buf: *mut ::core::ffi::c_char, + buf_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn http_response_add_header( + res: *mut http_response_t, + name: *const ::core::ffi::c_char, + value: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn http_response_set_json( + res: *mut http_response_t, + status_code: ::core::ffi::c_int, + json_str: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn http_response_set_json_error( + res: *mut http_response_t, + status_code: ::core::ffi::c_int, + error_message: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn http_serve_file( + req: *const http_request_t, + res: *const http_response_t, + file_path: *const ::core::ffi::c_char, + content_type: *const ::core::ffi::c_char, + extra_headers: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn __ctype_b_loc() -> *mut *const ::core::ffi::c_ushort; + unsafe fn __errno_location() -> *mut ::core::ffi::c_int; + unsafe fn floor(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn stat(__file: *const ::core::ffi::c_char, __buf: *mut stat) -> ::core::ffi::c_int; + unsafe fn gmtime_r(__timer: *const time_t, __tp: *mut tm) -> *mut tm; + unsafe fn timegm(__tp: *mut tm) -> time_t; + unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; + unsafe fn authorization_action_metadata( + action: authorization_action_t, + ) -> *const authorization_action_metadata_t; + unsafe fn authorization_decision_source_name( + source: authorization_decision_source_t, + ) -> *const ::core::ffi::c_char; + unsafe fn authorization_filter_cameras( + user: *const user_t, + action: authorization_action_t, + cameras: *mut fleet_camera_t, + count: *mut ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn event_identity_get_source( + output: *mut ::core::ffi::c_char, + output_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_evidence_get_stats(out: *mut observation_evidence_stats_t); + unsafe fn observation_snapshot_retry_after_seconds( + asset_age_ms: int64_t, + items_ahead: uint64_t, + ) -> ::core::ffi::c_int; + unsafe fn db_fleet_camera_load_readonly( + cameras: *mut *mut fleet_camera_t, + count: *mut ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn db_get_system_setting( + key: *const ::core::ffi::c_char, + out: *mut ::core::ffi::c_char, + out_len: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn lightnvr_uuid_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn lightnvr_uuid_generate_v4(output: *mut ::core::ffi::c_char) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn audit_log_append( + req: *const http_request_t, + user: *const user_t, + action: *const ::core::ffi::c_char, + target_type: *const ::core::ffi::c_char, + target_uuid: *const ::core::ffi::c_char, + outcome: *const ::core::ffi::c_char, + details: *const cJSON, + ); + unsafe fn audit_log_operation( + req: *const http_request_t, + user: *const user_t, + action: *const ::core::ffi::c_char, + target_type: *const ::core::ffi::c_char, + target_uuid: *const ::core::ffi::c_char, + operation: *const ::core::ffi::c_char, + outcome: *const ::core::ffi::c_char, + context: *const cJSON, + ); + unsafe fn audit_log_authorization( + req: *const http_request_t, + user: *const user_t, + action: authorization_action_t, + camera: *const fleet_camera_t, + evaluation: *const authorization_evaluation_t, + outcome: *const ::core::ffi::c_char, + ); + unsafe fn audit_log_get_decision_mode(action: authorization_action_t) -> audit_decision_mode_t; + unsafe fn httpd_parse_json_body(req: *const http_request_t) -> *mut cJSON; + unsafe fn httpd_check_action_access( + req: *const http_request_t, + user: *mut user_t, + ) -> ::core::ffi::c_int; + unsafe fn httpd_evaluate_camera_identity_action( + user: *const user_t, + action: authorization_action_t, + camera_uuid: *const ::core::ffi::c_char, + legacy_stream_name: *const ::core::ffi::c_char, + evaluation: *mut authorization_evaluation_t, + ) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn get_all_stream_configs_readonly( + streams: *mut stream_config_t, + max_count: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn memcpy( + __dest: *mut ::core::ffi::c_void, + __src: *const ::core::ffi::c_void, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn memset( + __s: *mut ::core::ffi::c_void, + __c: ::core::ffi::c_int, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strncmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + __n: size_t, + ) -> ::core::ffi::c_int; + unsafe fn strchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strstr( + __haystack: *const ::core::ffi::c_char, + __needle: *const ::core::ffi::c_char, + ) -> *mut ::core::ffi::c_char; + unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; + unsafe fn strnlen(__string: *const ::core::ffi::c_char, __maxlen: size_t) -> size_t; + unsafe fn strtol( + __nptr: *const ::core::ffi::c_char, + __endptr: *mut *mut ::core::ffi::c_char, + __base: ::core::ffi::c_int, + ) -> ::core::ffi::c_long; + unsafe fn calloc(__nmemb: size_t, __size: size_t) -> *mut ::core::ffi::c_void; + unsafe fn free(__ptr: *mut ::core::ffi::c_void); +} +pub type size_t = usize; +pub type __dev_t = ::core::ffi::c_ulong; +pub type __uid_t = ::core::ffi::c_uint; +pub type __gid_t = ::core::ffi::c_uint; +pub type __ino_t = ::core::ffi::c_ulong; +pub type __mode_t = ::core::ffi::c_uint; +pub type __nlink_t = ::core::ffi::c_ulong; +pub type __off_t = ::core::ffi::c_long; +pub type __time_t = ::core::ffi::c_long; +pub type __clockid_t = ::core::ffi::c_int; +pub type __blksize_t = ::core::ffi::c_long; +pub type __blkcnt_t = ::core::ffi::c_long; +pub type __syscall_slong_t = ::core::ffi::c_long; +pub type clockid_t = __clockid_t; +pub type time_t = __time_t; +pub type int64_t = i64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct timespec { + pub tv_sec: __time_t, + pub tv_nsec: __syscall_slong_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct tm { + pub tm_sec: ::core::ffi::c_int, + pub tm_min: ::core::ffi::c_int, + pub tm_hour: ::core::ffi::c_int, + pub tm_mday: ::core::ffi::c_int, + pub tm_mon: ::core::ffi::c_int, + pub tm_year: ::core::ffi::c_int, + pub tm_wday: ::core::ffi::c_int, + pub tm_yday: ::core::ffi::c_int, + pub tm_isdst: ::core::ffi::c_int, + pub tm_gmtoff: ::core::ffi::c_long, + pub tm_zone: *const ::core::ffi::c_char, +} +pub type uint8_t = u8; +pub type uint32_t = u32; +pub type uint64_t = u64; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +pub type cJSON_bool = ::core::ffi::c_int; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct system_health_policy_settings_t { + pub enabled: bool, + pub profile: [::core::ffi::c_char; 16], + pub fast_interval_seconds: uint32_t, + pub normal_interval_seconds: uint32_t, + pub slow_interval_seconds: uint32_t, + pub device_interval_seconds: uint32_t, + pub write_probe_enabled: bool, + pub hardware_provider: [::core::ffi::c_char; 16], + pub presence_interval_seconds: uint32_t, + pub incident_retention_days: uint32_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_protocol_t(pub ::core::ffi::c_uint); +impl stream_protocol_t { + pub const STREAM_PROTOCOL_TCP: Self = Self(0); + pub const STREAM_PROTOCOL_UDP: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stream_config_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub location_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub url: [::core::ffi::c_char; 512], + pub enabled: bool, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub fps: ::core::ffi::c_int, + pub codec: [::core::ffi::c_char; 16], + pub priority: ::core::ffi::c_int, + pub record: bool, + pub segment_duration: ::core::ffi::c_int, + pub detection_based_recording: bool, + pub detection_model: [::core::ffi::c_char; 512], + pub detection_interval: ::core::ffi::c_int, + pub detection_threshold: ::core::ffi::c_float, + pub pre_detection_buffer: ::core::ffi::c_int, + pub post_detection_buffer: ::core::ffi::c_int, + pub detection_api_url: [::core::ffi::c_char; 512], + pub detection_object_filter: [::core::ffi::c_char; 16], + pub detection_object_filter_list: [::core::ffi::c_char; 256], + pub streaming_enabled: bool, + pub protocol: stream_protocol_t, + pub record_audio: bool, + pub audio_voice_enhancement: bool, + pub onvif_username: [::core::ffi::c_char; 64], + pub onvif_password: [::core::ffi::c_char; 64], + pub onvif_profile: [::core::ffi::c_char; 64], + pub onvif_port: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub is_onvif: bool, + pub backchannel_enabled: bool, + pub retention_days: ::core::ffi::c_int, + pub detection_retention_days: ::core::ffi::c_int, + pub max_storage_mb: ::core::ffi::c_int, + pub tier_critical_multiplier: ::core::ffi::c_double, + pub tier_important_multiplier: ::core::ffi::c_double, + pub tier_ephemeral_multiplier: ::core::ffi::c_double, + pub storage_priority: ::core::ffi::c_int, + pub ptz_enabled: bool, + pub ptz_max_x: ::core::ffi::c_int, + pub ptz_max_y: ::core::ffi::c_int, + pub ptz_max_z: ::core::ffi::c_int, + pub ptz_has_home: bool, + pub record_on_schedule: bool, + pub recording_schedule: [uint8_t; 168], + pub detection_record_on_schedule: bool, + pub detection_recording_schedule: [uint8_t; 168], + pub tags: [::core::ffi::c_char; 256], + pub admin_url: [::core::ffi::c_char; 512], + pub privacy_mode: bool, + pub motion_trigger_source: [::core::ffi::c_char; 256], + pub go2rtc_source_override: [::core::ffi::c_char; 8192], + pub sub_stream_url: [::core::ffi::c_char; 512], + pub detection_url: [::core::ffi::c_char; 512], + pub publish_url: [::core::ffi::c_char; 512], + pub playback_transport: [::core::ffi::c_char; 24], + pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct detection_engine_config_t { + pub enabled: bool, + pub num_threads: ::core::ffi::c_int, + pub delegate: [::core::ffi::c_char; 16], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct config_t { + pub pid_file: [::core::ffi::c_char; 512], + pub log_file: [::core::ffi::c_char; 512], + pub log_level: ::core::ffi::c_int, + pub syslog_enabled: bool, + pub syslog_ident: [::core::ffi::c_char; 64], + pub syslog_facility: ::core::ffi::c_int, + pub storage_path: [::core::ffi::c_char; 512], + pub storage_path_hls: [::core::ffi::c_char; 512], + pub max_storage_size: uint64_t, + pub retention_days: ::core::ffi::c_int, + pub auto_delete_oldest: bool, + pub mp4_faststart: bool, + pub storage_min_free_pct: ::core::ffi::c_int, + pub storage_pressure_warning_pct: ::core::ffi::c_double, + pub storage_pressure_critical_pct: ::core::ffi::c_double, + pub storage_pressure_emergency_pct: ::core::ffi::c_double, + pub generate_thumbnails: bool, + pub thumbnails_per_recording: ::core::ffi::c_int, + pub record_mp4_directly: bool, + pub mp4_storage_path: [::core::ffi::c_char; 512], + pub mp4_directory_format: [::core::ffi::c_char; 32], + pub mp4_segment_duration: ::core::ffi::c_int, + pub mp4_retention_days: ::core::ffi::c_int, + pub models_path: [::core::ffi::c_char; 512], + pub api_detection_url: [::core::ffi::c_char; 512], + pub api_detection_backend: [::core::ffi::c_char; 32], + pub api_detection_format: [::core::ffi::c_char; 32], + pub api_detection_detector_name: [::core::ffi::c_char; 64], + pub default_detection_threshold: ::core::ffi::c_int, + pub default_pre_detection_buffer: ::core::ffi::c_int, + pub default_post_detection_buffer: ::core::ffi::c_int, + pub default_playback_transport: [::core::ffi::c_char; 24], + pub detection_grace_period: ::core::ffi::c_int, + pub db_path: [::core::ffi::c_char; 512], + pub db_backup_interval_minutes: ::core::ffi::c_int, + pub db_backup_retention_count: ::core::ffi::c_int, + pub db_post_backup_script: [::core::ffi::c_char; 512], + pub db_startup_check: ::core::ffi::c_int, + pub db_backup_verify: ::core::ffi::c_int, + pub web_thread_pool_size: ::core::ffi::c_int, + pub web_port: ::core::ffi::c_int, + pub web_bind_ip: [::core::ffi::c_char; 32], + pub web_root: [::core::ffi::c_char; 512], + pub web_ssl_enabled: bool, + pub web_ssl_cert_path: [::core::ffi::c_char; 512], + pub web_ssl_key_path: [::core::ffi::c_char; 512], + pub web_auth_enabled: bool, + pub web_username: [::core::ffi::c_char; 32], + pub web_password: [::core::ffi::c_char; 32], + pub audio_disabled: bool, + pub auto_disabled: bool, + pub webrtc_disabled: bool, + pub hls_disabled: bool, + pub mse_disabled: bool, + pub auth_timeout_hours: ::core::ffi::c_int, + pub auth_absolute_timeout_hours: ::core::ffi::c_int, + pub trusted_device_days: ::core::ffi::c_int, + pub trusted_proxy_cidrs: [::core::ffi::c_char; 1024], + pub demo_mode: bool, + pub force_mfa_on_login: bool, + pub login_rate_limit_enabled: bool, + pub login_rate_limit_max_attempts: ::core::ffi::c_int, + pub login_rate_limit_window_seconds: ::core::ffi::c_int, + pub web_compression_enabled: bool, + pub web_use_minified_assets: bool, + pub web_cache_max_age_html: ::core::ffi::c_int, + pub web_cache_max_age_css: ::core::ffi::c_int, + pub web_cache_max_age_js: ::core::ffi::c_int, + pub web_cache_max_age_images: ::core::ffi::c_int, + pub web_cache_max_age_fonts: ::core::ffi::c_int, + pub web_cache_max_age_default: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub onvif_discovery_interval: ::core::ffi::c_int, + pub onvif_discovery_network: [::core::ffi::c_char; 64], + pub max_streams: ::core::ffi::c_int, + pub streams: *mut stream_config_t, + pub buffer_size: ::core::ffi::c_int, + pub use_swap: bool, + pub swap_file: [::core::ffi::c_char; 512], + pub swap_size: uint64_t, + pub memory_constrained: bool, + pub hw_accel_enabled: bool, + pub hw_accel_device: [::core::ffi::c_char; 32], + pub go2rtc_enabled: bool, + pub go2rtc_binary_path: [::core::ffi::c_char; 512], + pub go2rtc_config_dir: [::core::ffi::c_char; 512], + pub go2rtc_api_port: ::core::ffi::c_int, + pub go2rtc_rtsp_port: ::core::ffi::c_int, + pub go2rtc_force_native_hls: bool, + pub go2rtc_proxy_max_inflight: ::core::ffi::c_int, + pub go2rtc_webrtc_enabled: bool, + pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, + pub go2rtc_stun_enabled: bool, + pub go2rtc_stun_server: [::core::ffi::c_char; 256], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], + pub go2rtc_ice_servers: [::core::ffi::c_char; 512], + pub webrtc_connection_timeout_ms: ::core::ffi::c_int, + pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, + pub turn_enabled: bool, + pub turn_server_url: [::core::ffi::c_char; 256], + pub turn_username: [::core::ffi::c_char; 64], + pub turn_password: [::core::ffi::c_char; 64], + pub mqtt_enabled: bool, + pub mqtt_broker_host: [::core::ffi::c_char; 256], + pub mqtt_broker_port: ::core::ffi::c_int, + pub mqtt_username: [::core::ffi::c_char; 64], + pub mqtt_password: [::core::ffi::c_char; 128], + pub mqtt_client_id: [::core::ffi::c_char; 64], + pub mqtt_topic_prefix: [::core::ffi::c_char; 128], + pub mqtt_tls_enabled: bool, + pub mqtt_keepalive: ::core::ffi::c_int, + pub mqtt_qos: ::core::ffi::c_int, + pub mqtt_retain: bool, + pub mqtt_ha_discovery: bool, + pub mqtt_ha_discovery_prefix: [::core::ffi::c_char; 128], + pub mqtt_ha_snapshot_interval: ::core::ffi::c_int, + pub health: system_health_policy_settings_t, + pub detection_engine: detection_engine_config_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const DB_OBS_OK: Self = Self(0); + pub const DB_OBS_DUPLICATE: Self = Self(1); + pub const DB_OBS_CAS_LOST: Self = Self(2); + pub const DB_OBS_ERROR: Self = Self(-1); + pub const DB_OBS_INVALID: Self = Self(-2); + pub const DB_OBS_NOT_FOUND: Self = Self(-3); + pub const DB_OBS_INVALID_CURSOR: Self = Self(-4); + pub const DB_OBS_CURSOR_EXPIRED: Self = Self(-5); + pub const DB_OBS_CONFLICT: Self = Self(-6); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_asset_state_t(pub ::core::ffi::c_uint); +impl observation_asset_state_t { + pub const OBS_ASSET_PENDING: Self = Self(0); + pub const OBS_ASSET_READY: Self = Self(1); + pub const OBS_ASSET_UNAVAILABLE: Self = Self(2); + pub const OBS_ASSET_EXPIRED: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_asset_record_t { + pub uuid: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub state: observation_asset_state_t, + pub reason: [::core::ffi::c_char; 33], + pub origin: [::core::ffi::c_char; 24], + pub media_type: [::core::ffi::c_char; 24], + pub path: [::core::ffi::c_char; 512], + pub has_bytes: bool, + pub bytes: int64_t, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub captured_at_ms: int64_t, + pub transform_json: [::core::ffi::c_char; 1024], + pub is_protected: bool, + pub expires_at_ms: int64_t, + pub created_at_ms: int64_t, + pub updated_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_record_t { + pub uuid: [::core::ffi::c_char; 37], + pub camera_uuid: [::core::ffi::c_char; 37], + pub stream_name_at_capture: [::core::ffi::c_char; 64], + pub batch_id: [::core::ffi::c_char; 37], + pub object_index: ::core::ffi::c_int, + pub observed_at_ms: int64_t, + pub created_at_ms: int64_t, + pub engine_key: [::core::ffi::c_char; 65], + pub engine_type: [::core::ffi::c_char; 16], + pub model: [::core::ffi::c_char; 129], + pub model_version: [::core::ffi::c_char; 65], + pub label: [::core::ffi::c_char; 32], + pub confidence: ::core::ffi::c_double, + pub has_box: bool, + pub box_x: ::core::ffi::c_double, + pub box_y: ::core::ffi::c_double, + pub box_width: ::core::ffi::c_double, + pub box_height: ::core::ffi::c_double, + pub has_track: bool, + pub track_id: [::core::ffi::c_char; 65], + pub track_namespace: [::core::ffi::c_char; 65], + pub track_session: [::core::ffi::c_char; 37], + pub recording_id: int64_t, + pub revision: ::core::ffi::c_int, + pub asset: observation_asset_record_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_list_query_t { + pub camera_uuids: *const [::core::ffi::c_char; 37], + pub camera_count: size_t, + pub all_cameras: bool, + pub labels: [[::core::ffi::c_char; 32]; 8], + pub label_count: size_t, + pub has_since: bool, + pub since_ms: int64_t, + pub has_until: bool, + pub until_ms: int64_t, + pub limit: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_page_t { + pub items: *mut observation_record_t, + pub count: size_t, + pub next_cursor: [::core::ffi::c_char; 257], + pub has_more: bool, + pub oldest_cursor: [::core::ffi::c_char; 257], + pub has_oldest_observed_at: bool, + pub oldest_observed_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct observation_classification_kind_t(pub ::core::ffi::c_uint); +impl observation_classification_kind_t { + pub const OBS_CLS_KIND_AUTOMATED: Self = Self(0); + pub const OBS_CLS_KIND_OWNER_CONFIRMED: Self = Self(1); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_0(pub ::core::ffi::c_int); +impl C2Rust_Unnamed_0 { + pub const DB_OBS_CLS_CREATED: Self = Self(0); + pub const DB_OBS_CLS_UPDATED: Self = Self(1); + pub const DB_OBS_CLS_NOOP: Self = Self(2); + pub const DB_OBS_CLS_REVISION_CONFLICT: Self = Self(3); + pub const DB_OBS_CLS_OWNER_LOCKED: Self = Self(4); + pub const DB_OBS_CLS_EVIDENCE_MISMATCH: Self = Self(5); + pub const DB_OBS_CLS_WRITER_MISMATCH: Self = Self(6); + pub const DB_OBS_CLS_LIMIT: Self = Self(7); + pub const DB_OBS_CLS_ERROR: Self = Self(-1); + pub const DB_OBS_CLS_INVALID: Self = Self(-2); + pub const DB_OBS_CLS_NOT_FOUND: Self = Self(-3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_t { + pub key: [::core::ffi::c_char; 66], + pub writer_namespace: [::core::ffi::c_char; 33], + pub writer_user_id: int64_t, + pub classifier: [::core::ffi::c_char; 65], + pub model_version: [::core::ffi::c_char; 65], + pub label: [::core::ffi::c_char; 129], + pub taxonomy_id: [::core::ffi::c_char; 65], + pub confidence: ::core::ffi::c_double, + pub classified_at_ms: int64_t, + pub input_asset_uuid: [::core::ffi::c_char; 37], + pub input_revision: ::core::ffi::c_int, + pub kind: observation_classification_kind_t, + pub annotation_revision: ::core::ffi::c_int, + pub created_at_ms: int64_t, + pub updated_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_input_t { + pub writer_namespace: *const ::core::ffi::c_char, + pub writer_user_id: int64_t, + pub name: *const ::core::ffi::c_char, + pub classifier: *const ::core::ffi::c_char, + pub model_version: *const ::core::ffi::c_char, + pub label: *const ::core::ffi::c_char, + pub taxonomy_id: *const ::core::ffi::c_char, + pub confidence: ::core::ffi::c_double, + pub classified_at_ms: int64_t, + pub input_asset_uuid: *const ::core::ffi::c_char, + pub input_revision: ::core::ffi::c_int, + pub kind: observation_classification_kind_t, + pub expected_revision: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_result_t { + pub has_annotation: bool, + pub annotation: observation_classification_t, + pub observation_revision: ::core::ffi::c_int, + pub change_seq: int64_t, + pub observation_asset_uuid: [::core::ffi::c_char; 37], + pub evidence_state: observation_asset_state_t, + pub limit: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_classification_set_t { + pub automated_count: ::core::ffi::c_int, + pub automated: [observation_classification_t; 8], + pub owner_confirmed_count: ::core::ffi::c_int, + pub owner_confirmed: [observation_classification_t; 8], +} +pub type observation_classification_visit_fn = Option< + unsafe extern "C" fn( + size_t, + *const observation_classification_set_t, + *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int, +>; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_detection_link_t { + pub linked: bool, + pub observation_uuid: [::core::ffi::c_char; 37], + pub has_classification: bool, + pub owner_confirmed: bool, + pub classification: observation_classification_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct http_method_t(pub ::core::ffi::c_uint); +impl http_method_t { + pub const HTTP_METHOD_GET: Self = Self(0); + pub const HTTP_METHOD_POST: Self = Self(1); + pub const HTTP_METHOD_PUT: Self = Self(2); + pub const HTTP_METHOD_DELETE: Self = Self(3); + pub const HTTP_METHOD_OPTIONS: Self = Self(4); + pub const HTTP_METHOD_HEAD: Self = Self(5); + pub const HTTP_METHOD_PATCH: Self = Self(6); + pub const HTTP_METHOD_UNKNOWN: Self = Self(7); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct http_header_t { + pub name: [::core::ffi::c_char; 128], + pub value: [::core::ffi::c_char; 1024], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct http_request_t { + pub method: http_method_t, + pub method_str: [::core::ffi::c_char; 16], + pub path: [::core::ffi::c_char; 512], + pub uri: [::core::ffi::c_char; 1536], + pub query_string: [::core::ffi::c_char; 1024], + pub content_type: [::core::ffi::c_char; 128], + pub content_length: uint64_t, + pub user_agent: [::core::ffi::c_char; 256], + pub body: *mut ::core::ffi::c_void, + pub body_len: size_t, + pub headers: [http_header_t; 50], + pub num_headers: ::core::ffi::c_int, + pub client_ip: [::core::ffi::c_char; 64], + pub request_id: [::core::ffi::c_char; 65], + pub user_data: *mut ::core::ffi::c_void, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct http_response_t { + pub status_code: ::core::ffi::c_int, + pub content_type: [::core::ffi::c_char; 128], + pub body_length: size_t, + pub body: *mut ::core::ffi::c_void, + pub body_allocated: bool, + pub headers: [http_header_t; 50], + pub num_headers: ::core::ffi::c_int, + pub user_data: *mut ::core::ffi::c_void, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct fleet_camera_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub address: [::core::ffi::c_char; 512], + pub legacy_tags: [::core::ffi::c_char; 256], + pub location_uuid: [::core::ffi::c_char; 37], + pub location_name: [::core::ffi::c_char; 128], + pub location_path: [::core::ffi::c_char; 1024], + pub location_ancestor_uuids: [[::core::ffi::c_char; 37]; 32], + pub location_depth: ::core::ffi::c_int, + pub tags: [fleet_camera_tag_t; 64], + pub tag_count: ::core::ffi::c_int, + pub manufacturer: [::core::ffi::c_char; 128], + pub model: [::core::ffi::c_char; 128], + pub enabled: bool, + pub streaming_enabled: bool, + pub record: bool, + pub detection_based_recording: bool, + pub is_onvif: bool, + pub ptz_enabled: bool, + pub backchannel_enabled: bool, + pub health: fleet_health_state_t, + pub availability: fleet_availability_state_t, + pub health_changed_at: int64_t, + pub first_video_at: int64_t, + pub last_frame_ts: int64_t, + pub last_recording_at: int64_t, + pub current_fps: ::core::ffi::c_double, + pub recording_active: bool, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct fleet_availability_state_t(pub ::core::ffi::c_uint); +impl fleet_availability_state_t { + pub const FLEET_AVAILABILITY_LIVE: Self = Self(0); + pub const FLEET_AVAILABILITY_OFFLINE: Self = Self(1); + pub const FLEET_AVAILABILITY_NEVER_CONNECTED: Self = Self(2); + pub const FLEET_AVAILABILITY_DISABLED: Self = Self(3); + pub const FLEET_AVAILABILITY_COUNT: Self = Self(4); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct fleet_health_state_t(pub ::core::ffi::c_uint); +impl fleet_health_state_t { + pub const FLEET_HEALTH_UNKNOWN: Self = Self(0); + pub const FLEET_HEALTH_UP: Self = Self(1); + pub const FLEET_HEALTH_DEGRADED: Self = Self(2); + pub const FLEET_HEALTH_DOWN: Self = Self(3); + pub const FLEET_HEALTH_DISABLED: Self = Self(4); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct fleet_camera_tag_t { + pub uuid: [::core::ffi::c_char; 37], + pub label: [::core::ffi::c_char; 256], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct user_t { + pub id: int64_t, + pub username: [::core::ffi::c_char; 64], + pub email: [::core::ffi::c_char; 128], + pub role: user_role_t, + pub api_key: [::core::ffi::c_char; 64], + pub created_at: int64_t, + pub updated_at: int64_t, + pub last_login: int64_t, + pub is_active: bool, + pub password_change_locked: bool, + pub must_change_password: bool, + pub totp_enabled: bool, + pub allowed_login_cidrs: [::core::ffi::c_char; 1024], + pub has_login_cidr_restriction: bool, + pub authorization_mode: [::core::ffi::c_char; 16], + pub authenticated_via_scoped_token: bool, + pub api_token_uuid: [::core::ffi::c_char; 37], + pub authentication_method: [::core::ffi::c_char; 24], +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct user_role_t(pub ::core::ffi::c_uint); +impl user_role_t { + pub const USER_ROLE_ADMIN: Self = Self(0); + pub const USER_ROLE_USER: Self = Self(1); + pub const USER_ROLE_VIEWER: Self = Self(2); + pub const USER_ROLE_API: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct authorization_action_t(pub ::core::ffi::c_int); +impl authorization_action_t { + pub const AUTHZ_ACTION_INVALID: Self = Self(-1); + pub const AUTHZ_LIVE_VIEW: Self = Self(0); + pub const AUTHZ_AUDIO_LISTEN: Self = Self(1); + pub const AUTHZ_AUDIO_TALK: Self = Self(2); + pub const AUTHZ_RECORDINGS_REPLAY: Self = Self(3); + pub const AUTHZ_RECORDINGS_EXPORT: Self = Self(4); + pub const AUTHZ_SNAPSHOT_CREATE: Self = Self(5); + pub const AUTHZ_PTZ_CONTROL: Self = Self(6); + pub const AUTHZ_EVIDENCE_PROTECT: Self = Self(7); + pub const AUTHZ_RECORDING_DELETE: Self = Self(8); + pub const AUTHZ_CAMERA_CONFIGURE: Self = Self(9); + pub const AUTHZ_FLEET_EXECUTE_JOB: Self = Self(10); + pub const AUTHZ_STORAGE_CONFIGURE: Self = Self(11); + pub const AUTHZ_EVENTS_CONFIGURE: Self = Self(12); + pub const AUTHZ_USERS_MANAGE: Self = Self(13); + pub const AUTHZ_SYSTEM_ADMIN: Self = Self(14); + pub const AUTHZ_LPR_READ: Self = Self(15); + pub const AUTHZ_LPR_SEARCH: Self = Self(16); + pub const AUTHZ_LPR_EXPORT: Self = Self(17); + pub const AUTHZ_LPR_DELETE: Self = Self(18); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct page_item_context_t { + pub page: *const observation_page_t, + pub source: *const ::core::ffi::c_char, + pub items: *mut cJSON, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct authorization_evaluation_t { + pub decision: authorization_decision_t, + pub source: authorization_decision_source_t, + pub policy_version: int64_t, + pub grant_uuid: [::core::ffi::c_char; 37], + pub token_uuid: [::core::ffi::c_char; 37], + pub role_uuid: [::core::ffi::c_char; 37], + pub role_name: [::core::ffi::c_char; 128], + pub explanation: [::core::ffi::c_char; 256], +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct authorization_decision_source_t(pub ::core::ffi::c_uint); +impl authorization_decision_source_t { + pub const AUTHZ_SOURCE_NONE: Self = Self(0); + pub const AUTHZ_SOURCE_LEGACY_ROLE: Self = Self(1); + pub const AUTHZ_SOURCE_POLICY_GRANT: Self = Self(2); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct authorization_decision_t(pub ::core::ffi::c_uint); +impl authorization_decision_t { + pub const AUTHZ_DECISION_DENY: Self = Self(0); + pub const AUTHZ_DECISION_ALLOW: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_stats_t { + pub initialized: bool, + pub worker_running: bool, + pub allocated: bool, + pub accepted: uint64_t, + pub dropped_queue_full: uint64_t, + pub dropped_budget: uint64_t, + pub dropped_disabled: uint64_t, + pub dropped_untracked: uint64_t, + pub unavailable_submitted: uint64_t, + pub ready: uint64_t, + pub failed_encode: uint64_t, + pub failed_write: uint64_t, + pub failed_db: uint64_t, + pub disabled_at_encode: uint64_t, + pub interrupted: uint64_t, + pub bytes_written: uint64_t, + pub backlog_items: uint32_t, + pub backlog_bytes: uint64_t, + pub drop_ring_depth: uint32_t, + pub high_water_items: uint32_t, + pub high_water_bytes: uint64_t, + pub budget_exhausted_cameras: uint32_t, + pub limit_items: uint32_t, + pub limit_camera_items: uint32_t, + pub limit_bytes: uint64_t, + pub limit_camera_bytes: uint64_t, + pub camera_high_water_items: uint32_t, + pub submit_latency_count: uint64_t, + pub submit_latency_sum_ns: uint64_t, + pub submit_latency_max_ns: uint64_t, + pub submit_latency_bucket: [uint64_t; 10], + pub discard_deferred: uint64_t, + pub discard_inline: uint64_t, + pub discard_pending_bytes: uint64_t, + pub limit_discard_bytes: uint64_t, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stat { + pub st_dev: __dev_t, + pub st_ino: __ino_t, + pub st_nlink: __nlink_t, + pub st_mode: __mode_t, + pub st_uid: __uid_t, + pub st_gid: __gid_t, + pub __pad0: ::core::ffi::c_int, + pub st_rdev: __dev_t, + pub st_size: __off_t, + pub st_blksize: __blksize_t, + pub st_blocks: __blkcnt_t, + pub st_atim: timespec, + pub st_mtim: timespec, + pub st_ctim: timespec, + pub __glibc_reserved: [__syscall_slong_t; 3], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct authorization_action_metadata_t { + pub action: authorization_action_t, + pub key: *const ::core::ffi::c_char, + pub category: *const ::core::ffi::c_char, + pub description: *const ::core::ffi::c_char, + pub camera_scoped: bool, + pub destructive: bool, + pub enforced: bool, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct audit_decision_mode_t(pub ::core::ffi::c_uint); +impl audit_decision_mode_t { + pub const AUDIT_DECISION_MODE_RECORD: Self = Self(0); + pub const AUDIT_DECISION_MODE_SUMMARIZE: Self = Self(1); + pub const AUDIT_DECISION_MODE_OFF: Self = Self(2); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct put_target_t { + pub id: [::core::ffi::c_char; 37], + pub ns: [::core::ffi::c_char; 33], + pub name: [::core::ffi::c_char; 33], +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct annotate_audit_t { + pub req: *const http_request_t, + pub user: *const user_t, + pub observation_uuid: *const ::core::ffi::c_char, + pub camera_uuid: *const ::core::ffi::c_char, + pub key: [::core::ffi::c_char; 66], + pub kind: *const ::core::ffi::c_char, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct put_body_t { + pub classifier: [::core::ffi::c_char; 65], + pub model_version: [::core::ffi::c_char; 65], + pub label: [::core::ffi::c_char; 129], + pub taxonomy_id: [::core::ffi::c_char; 65], + pub confidence: ::core::ffi::c_double, + pub classified_at_ms: int64_t, + pub input_asset_uuid: [::core::ffi::c_char; 37], + pub input_revision: ::core::ffi::c_int, + pub kind: observation_classification_kind_t, + pub has_kind: bool, + pub expected_revision: ::core::ffi::c_int, +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct C2Rust_Unnamed_1(pub ::core::ffi::c_uint); +impl C2Rust_Unnamed_1 { + pub const _ISupper: Self = Self(256); + pub const _ISlower: Self = Self(512); + pub const _ISalpha: Self = Self(1024); + pub const _ISdigit: Self = Self(2048); + pub const _ISxdigit: Self = Self(4096); + pub const _ISspace: Self = Self(8192); + pub const _ISprint: Self = Self(16384); + pub const _ISgraph: Self = Self(32768); + pub const _ISblank: Self = Self(1); + pub const _IScntrl: Self = Self(2); + pub const _ISpunct: Self = Self(4); + pub const _ISalnum: Self = Self(8); +} +pub const MAX_STREAMS: ::core::ffi::c_int = LIGHTNVR_MAX_STREAMS; +#[inline] +unsafe extern "C" fn configured_stream_slots() -> ::core::ffi::c_int { + let mut slots: ::core::ffi::c_int = g_config.max_streams; + if slots <= 0 as ::core::ffi::c_int || slots > MAX_STREAMS { + return MAX_STREAMS; + } + return slots; +} +pub const OBS_UUID_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; +pub const OBS_MAX_BATCH: ::core::ffi::c_int = 20 as ::core::ffi::c_int; +pub const OBS_LABEL_SIZE: ::core::ffi::c_int = 32 as ::core::ffi::c_int; +pub const OBS_LIST_DEFAULT_LIMIT: ::core::ffi::c_int = 100 as ::core::ffi::c_int; +pub const OBS_LIST_MAX_LIMIT: ::core::ffi::c_int = 500 as ::core::ffi::c_int; +pub const OBS_QUERY_MAX_LABELS: ::core::ffi::c_int = 8 as ::core::ffi::c_int; +pub const OBS_CLS_SEGMENT_MAX: ::core::ffi::c_int = 32 as ::core::ffi::c_int; +pub const OBS_CLS_CLASSIFIER_SIZE: ::core::ffi::c_int = 65 as ::core::ffi::c_int; +pub const OBS_CLS_MODEL_VERSION_SIZE: ::core::ffi::c_int = 65 as ::core::ffi::c_int; +pub const OBS_CLS_LABEL_SIZE: ::core::ffi::c_int = 129 as ::core::ffi::c_int; +pub const OBS_CLS_TAXONOMY_SIZE: ::core::ffi::c_int = 65 as ::core::ffi::c_int; +pub const OBS_WRITER_NAMESPACE_SIZE: ::core::ffi::c_int = 33 as ::core::ffi::c_int; +pub const OBS_PUT_BODY_MAX: ::core::ffi::c_int = 4096 as ::core::ffi::c_int; +pub const CLOCK_REALTIME: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const OBS_SNAPSHOT_RETRY_AFTER_MAX: ::core::ffi::c_int = 30 as ::core::ffi::c_int; +pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; +pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); +pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +pub const INT_MAX: ::core::ffi::c_int = __INT_MAX__; +pub const __S_IFMT: ::core::ffi::c_int = 0o170000 as ::core::ffi::c_int; +pub const LIGHTNVR_VERSION_STRING: [::core::ffi::c_char; 7] = + unsafe { ::core::mem::transmute::<[u8; 7], [::core::ffi::c_char; 7]>(*b"0.47.3\0") }; +pub const LOG_COMPONENT: [::core::ffi::c_char; 16] = + unsafe { ::core::mem::transmute::<[u8; 16], [::core::ffi::c_char; 16]>(*b"ObservationsAPI\0") }; +pub const OBS_SOURCE_TEXT_SIZE: ::core::ffi::c_int = 128 as ::core::ffi::c_int; +pub const OBS_FILTER_CAMERAS_MAX: ::core::ffi::c_int = 10 as ::core::ffi::c_int; +pub const OBS_CAPABILITY_CAMERAS_MAX: ::core::ffi::c_int = 1024 as ::core::ffi::c_int; +pub const OBS_SNAPSHOT_RETRY_MAX: ::core::ffi::c_int = OBS_SNAPSHOT_RETRY_AFTER_MAX; +pub const OBS_SNAPSHOT_CACHE_CONTROL: [::core::ffi::c_char; 21] = unsafe { + ::core::mem::transmute::<[u8; 21], [::core::ffi::c_char; 21]>(*b"private, max-age=300\0") +}; +pub const OBS_HISTORY_RETENTION_DEFAULT: ::core::ffi::c_int = 30 as ::core::ffi::c_int; +pub const OBS_EVENT_TYPE: [::core::ffi::c_char; 45] = unsafe { + ::core::mem::transmute::<[u8; 45], [::core::ffi::c_char; 45]>( + *b"io.lightnvr.detection.observation_changed.v1\0", + ) +}; +pub const OBS_ANNOTATIONS_MAX: ::core::ffi::c_int = 8 as ::core::ffi::c_int; +pub const URN_PREFIX: [::core::ffi::c_char; 14] = + unsafe { ::core::mem::transmute::<[u8; 14], [::core::ffi::c_char; 14]>(*b"urn:lightnvr:\0") }; +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_format_time( + mut ms: int64_t, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, +) -> ::core::ffi::c_int { + if out.is_null() || out_size < 25 as size_t { + return -1 as ::core::ffi::c_int; + } + let mut seconds: int64_t = ms / 1000 as int64_t; + let mut millis: ::core::ffi::c_int = (ms % 1000 as int64_t) as ::core::ffi::c_int; + if millis < 0 as ::core::ffi::c_int { + millis += 1000 as ::core::ffi::c_int; + seconds -= 1; + } + let mut t: time_t = seconds as time_t; + let mut tm: tm = tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: ::core::ptr::null::<::core::ffi::c_char>(), + }; + if gmtime_r(&raw mut t, &raw mut tm).is_null() { + return -1 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = snprintf( + out, + out_size, + b"%04d-%02d-%02dT%02d:%02d:%02d.%03dZ\0".as_ptr() as *const ::core::ffi::c_char, + tm.tm_year + 1900 as ::core::ffi::c_int, + tm.tm_mon + 1 as ::core::ffi::c_int, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + millis, + ); + return if n > 0 as ::core::ffi::c_int && (n as size_t) < out_size { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn digits( + mut text: *const ::core::ffi::c_char, + mut count: ::core::ffi::c_int, + mut value: *mut ::core::ffi::c_int, +) -> bool { + let mut result: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if *(*__ctype_b_loc()) + .offset(*text.offset(i as isize) as ::core::ffi::c_uchar as ::core::ffi::c_int as isize) + as ::core::ffi::c_int + & C2Rust_Unnamed_1::_ISdigit.0 as ::core::ffi::c_int as ::core::ffi::c_ushort + as ::core::ffi::c_int + == 0 + { + return r#false != 0; + } + result = result * 10 as ::core::ffi::c_int + + (*text.offset(i as isize) as ::core::ffi::c_int - '0' as ::core::ffi::c_int); + i += 1; + } + *value = result; + return r#true != 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_parse_time( + mut text: *const ::core::ffi::c_char, + mut ms: *mut int64_t, +) -> ::core::ffi::c_int { + if text.is_null() || ms.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut year: ::core::ffi::c_int = 0; + let mut month: ::core::ffi::c_int = 0; + let mut day: ::core::ffi::c_int = 0; + let mut hour: ::core::ffi::c_int = 0; + let mut minute: ::core::ffi::c_int = 0; + let mut second: ::core::ffi::c_int = 0; + if strnlen(text, 20 as size_t) < 20 as size_t + || !digits(text, 4 as ::core::ffi::c_int, &raw mut year) + || *text.offset(4isize) as ::core::ffi::c_int != '-' as ::core::ffi::c_int + || !digits( + text.offset(5 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut month, + ) + || *text.offset(7isize) as ::core::ffi::c_int != '-' as ::core::ffi::c_int + || !digits( + text.offset(8 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut day, + ) + || *text.offset(10isize) as ::core::ffi::c_int != 'T' as ::core::ffi::c_int + && *text.offset(10isize) as ::core::ffi::c_int != 't' as ::core::ffi::c_int + || !digits( + text.offset(11 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut hour, + ) + || *text.offset(13isize) as ::core::ffi::c_int != ':' as ::core::ffi::c_int + || !digits( + text.offset(14 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut minute, + ) + || *text.offset(16isize) as ::core::ffi::c_int != ':' as ::core::ffi::c_int + || !digits( + text.offset(17 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut second, + ) + { + return -1 as ::core::ffi::c_int; + } + let mut p: *const ::core::ffi::c_char = text.offset(19 as ::core::ffi::c_int as isize); + let mut millis: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if *p as ::core::ffi::c_int == '.' as ::core::ffi::c_int { + p = p.offset(1); + let mut count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while *(*__ctype_b_loc()).offset(*p as ::core::ffi::c_uchar as ::core::ffi::c_int as isize) + as ::core::ffi::c_int + & C2Rust_Unnamed_1::_ISdigit.0 as ::core::ffi::c_int as ::core::ffi::c_ushort + as ::core::ffi::c_int + != 0 + { + if count < 3 as ::core::ffi::c_int { + millis = millis * 10 as ::core::ffi::c_int + + (*p as ::core::ffi::c_int - '0' as ::core::ffi::c_int); + } + count += 1; + p = p.offset(1); + } + if count == 0 as ::core::ffi::c_int || count > 9 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + let mut i: ::core::ffi::c_int = count; + while i < 3 as ::core::ffi::c_int { + millis *= 10 as ::core::ffi::c_int; + i += 1; + } + } + let mut offset_minutes: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if *p as ::core::ffi::c_int == 'Z' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == 'z' as ::core::ffi::c_int + { + p = p.offset(1); + } else if *p as ::core::ffi::c_int == '+' as ::core::ffi::c_int + || *p as ::core::ffi::c_int == '-' as ::core::ffi::c_int + { + let mut oh: ::core::ffi::c_int = 0; + let mut om: ::core::ffi::c_int = 0; + if !digits( + p.offset(1 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut oh, + ) || *p.offset(3isize) as ::core::ffi::c_int != ':' as ::core::ffi::c_int + || !digits( + p.offset(4 as ::core::ffi::c_int as isize), + 2 as ::core::ffi::c_int, + &raw mut om, + ) + || oh > 23 as ::core::ffi::c_int + || om > 59 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + offset_minutes = (oh * 60 as ::core::ffi::c_int + om) + * if *p as ::core::ffi::c_int == '-' as ::core::ffi::c_int { + -1 as ::core::ffi::c_int + } else { + 1 as ::core::ffi::c_int + }; + p = p.offset(6 as ::core::ffi::c_int as isize); + } else { + return -1 as ::core::ffi::c_int; + } + if *p as ::core::ffi::c_int != '\0' as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + if year < 1970 as ::core::ffi::c_int + || month < 1 as ::core::ffi::c_int + || month > 12 as ::core::ffi::c_int + || day < 1 as ::core::ffi::c_int + || day > 31 as ::core::ffi::c_int + || hour > 23 as ::core::ffi::c_int + || minute > 59 as ::core::ffi::c_int + || second > 59 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut tm: tm = tm { + tm_sec: 0 as ::core::ffi::c_int, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: ::core::ptr::null::<::core::ffi::c_char>(), + }; + tm.tm_year = year - 1900 as ::core::ffi::c_int; + tm.tm_mon = month - 1 as ::core::ffi::c_int; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = minute; + tm.tm_sec = second; + let mut t: time_t = timegm(&raw mut tm); + let mut check: tm = tm { + tm_sec: 0, + tm_min: 0, + tm_hour: 0, + tm_mday: 0, + tm_mon: 0, + tm_year: 0, + tm_wday: 0, + tm_yday: 0, + tm_isdst: 0, + tm_gmtoff: 0, + tm_zone: ::core::ptr::null::<::core::ffi::c_char>(), + }; + if t == -1 as ::core::ffi::c_int as time_t + || gmtime_r(&raw mut t, &raw mut check).is_null() + || check.tm_mday != day + || check.tm_mon != month - 1 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + *ms = (t as int64_t - offset_minutes as int64_t * 60 as int64_t) * 1000 as int64_t + + millis as int64_t; + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_writer_namespace( + mut username: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, +) -> ::core::ffi::c_int { + static mut reserved: [*const ::core::ffi::c_char; 4] = [ + b"lightnvr\0".as_ptr() as *const ::core::ffi::c_char, + b"system\0".as_ptr() as *const ::core::ffi::c_char, + b"owner\0".as_ptr() as *const ::core::ffi::c_char, + b"local\0".as_ptr() as *const ::core::ffi::c_char, + ]; + if out.is_null() { + return -1 as ::core::ffi::c_int; + } + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + if username.is_null() { + return -1 as ::core::ffi::c_int; + } + let mut length: size_t = strlen(username); + if length < 1 as size_t + || length > (OBS_WRITER_NAMESPACE_SIZE - 1 as ::core::ffi::c_int) as size_t + { + return -1 as ::core::ffi::c_int; + } + let mut normalized: [::core::ffi::c_char; 33] = [0; 33]; + let mut i: size_t = 0 as size_t; + while i < length { + let mut c: ::core::ffi::c_uchar = *username.offset(i as isize) as ::core::ffi::c_uchar; + if c as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'Z' as ::core::ffi::c_int + { + c = (c as ::core::ffi::c_int - 'A' as ::core::ffi::c_int + 'a' as ::core::ffi::c_int) + as ::core::ffi::c_uchar; + } + let mut allowed: bool = c as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= 'z' as ::core::ffi::c_int + || c as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && c as ::core::ffi::c_int <= '9' as ::core::ffi::c_int + || c as ::core::ffi::c_int == '_' as ::core::ffi::c_int + || c as ::core::ffi::c_int == '.' as ::core::ffi::c_int + || c as ::core::ffi::c_int == '-' as ::core::ffi::c_int; + normalized[i] = (if allowed as ::core::ffi::c_int != 0 { + c as ::core::ffi::c_char as ::core::ffi::c_int + } else { + '-' as ::core::ffi::c_int + }) as ::core::ffi::c_char; + i = i.wrapping_add(1); + } + normalized[length] = '\0' as ::core::ffi::c_char; + let mut first: ::core::ffi::c_char = normalized[0usize]; + if !(first as ::core::ffi::c_int >= 'a' as ::core::ffi::c_int + && first as ::core::ffi::c_int <= 'z' as ::core::ffi::c_int + || first as ::core::ffi::c_int >= '0' as ::core::ffi::c_int + && first as ::core::ffi::c_int <= '9' as ::core::ffi::c_int) + { + return -1 as ::core::ffi::c_int; + } + let mut i_0: size_t = 0 as size_t; + while i_0 + < ::core::mem::size_of::<[*const ::core::ffi::c_char; 4]>() + .wrapping_div(::core::mem::size_of::<*const ::core::ffi::c_char>()) + { + if strcmp( + &raw mut normalized as *mut ::core::ffi::c_char, + reserved[i_0], + ) == 0 as ::core::ffi::c_int + { + return -2 as ::core::ffi::c_int; + } + i_0 = i_0.wrapping_add(1); + } + memcpy( + out as *mut ::core::ffi::c_void, + &raw mut normalized as *mut ::core::ffi::c_char as *const ::core::ffi::c_void, + length.wrapping_add(1 as size_t), + ); + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn time_or_null(mut ms: int64_t) -> *mut cJSON { + let mut text: [::core::ffi::c_char; 32] = [0; 32]; + if ms <= 0 as int64_t + || observation_format_time( + ms, + &raw mut text as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ) != 0 as ::core::ffi::c_int + { + return cJSON_CreateNull(); + } + return cJSON_CreateString(&raw mut text as *mut ::core::ffi::c_char); +} +unsafe extern "C" fn text_or_null(mut value: *const ::core::ffi::c_char) -> *mut cJSON { + return if !value.is_null() && *value.offset(0isize) as ::core::ffi::c_int != 0 { + cJSON_CreateString(value) + } else { + cJSON_CreateNull() + }; +} +unsafe extern "C" fn time_text(mut ms: int64_t) -> *mut cJSON { + let mut text: [::core::ffi::c_char; 32] = [0; 32]; + if ms < 0 as int64_t + || observation_format_time( + ms, + &raw mut text as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>(), + ) != 0 as ::core::ffi::c_int + { + return cJSON_CreateNull(); + } + return cJSON_CreateString(&raw mut text as *mut ::core::ffi::c_char); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_classification_to_json( + mut annotation: *const observation_classification_t, +) -> *mut cJSON { + if annotation.is_null() { + return ::core::ptr::null_mut::(); + } + let mut json: *mut cJSON = cJSON_CreateObject(); + if json.is_null() { + return ::core::ptr::null_mut::(); + } + cJSON_AddStringToObject( + json, + b"key\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*annotation).key as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + json, + b"writer_namespace\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*annotation).writer_namespace as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + json, + b"classifier\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*annotation).classifier as *const ::core::ffi::c_char, + ); + cJSON_AddItemToObject( + json, + b"model_version\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(&raw const (*annotation).model_version as *const ::core::ffi::c_char), + ); + cJSON_AddStringToObject( + json, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*annotation).label as *const ::core::ffi::c_char, + ); + cJSON_AddItemToObject( + json, + b"taxonomy_id\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(&raw const (*annotation).taxonomy_id as *const ::core::ffi::c_char), + ); + cJSON_AddNumberToObject( + json, + b"confidence\0".as_ptr() as *const ::core::ffi::c_char, + (*annotation).confidence, + ); + cJSON_AddItemToObject( + json, + b"classified_at\0".as_ptr() as *const ::core::ffi::c_char, + time_text((*annotation).classified_at_ms), + ); + cJSON_AddStringToObject( + json, + b"input_asset_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*annotation).input_asset_uuid as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + json, + b"input_revision\0".as_ptr() as *const ::core::ffi::c_char, + (*annotation).input_revision as ::core::ffi::c_double, + ); + cJSON_AddStringToObject( + json, + b"kind\0".as_ptr() as *const ::core::ffi::c_char, + observation_classification_kind_name((*annotation).kind), + ); + cJSON_AddNumberToObject( + json, + b"annotation_revision\0".as_ptr() as *const ::core::ffi::c_char, + (*annotation).annotation_revision as ::core::ffi::c_double, + ); + cJSON_AddItemToObject( + json, + b"created_at\0".as_ptr() as *const ::core::ffi::c_char, + time_text((*annotation).created_at_ms), + ); + cJSON_AddItemToObject( + json, + b"updated_at\0".as_ptr() as *const ::core::ffi::c_char, + time_text((*annotation).updated_at_ms), + ); + return json; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_detection_link_to_json( + mut link: *const observation_detection_link_t, +) -> *mut cJSON { + if link.is_null() || !(*link).linked { + return ::core::ptr::null_mut::(); + } + let mut json: *mut cJSON = cJSON_CreateObject(); + if json.is_null() { + return ::core::ptr::null_mut::(); + } + cJSON_AddStringToObject( + json, + b"id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*link).observation_uuid as *const ::core::ffi::c_char, + ); + cJSON_AddBoolToObject( + json, + b"owner_confirmed\0".as_ptr() as *const ::core::ffi::c_char, + (*link).owner_confirmed as cJSON_bool, + ); + let mut classification: *mut cJSON = if (*link).has_classification as ::core::ffi::c_int != 0 { + observation_classification_to_json(&raw const (*link).classification) + } else { + cJSON_CreateNull() + }; + if classification.is_null() + || cJSON_AddItemToObject( + json, + b"classification\0".as_ptr() as *const ::core::ffi::c_char, + classification, + ) == 0 + { + cJSON_Delete(classification); + cJSON_Delete(json); + return ::core::ptr::null_mut::(); + } + return json; +} +unsafe extern "C" fn add_annotations( + mut list: *mut cJSON, + mut items: *const observation_classification_t, + mut count: ::core::ffi::c_int, +) -> bool { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + let mut item: *mut cJSON = observation_classification_to_json(items.offset(i as isize)); + if item.is_null() { + return r#false != 0; + } + cJSON_AddItemToArray(list, item); + i += 1; + } + return r#true != 0; +} +unsafe extern "C" fn evidence_to_json(mut record: *const observation_record_t) -> *mut cJSON { + let mut asset: *const observation_asset_record_t = &raw const (*record).asset; + let mut evidence: *mut cJSON = cJSON_CreateObject(); + if evidence.is_null() { + return ::core::ptr::null_mut::(); + } + let mut has_media: bool = + (*asset).origin[0usize] as ::core::ffi::c_int != '\0' as ::core::ffi::c_int; + let mut sized: bool = has_media as ::core::ffi::c_int != 0 + && (*asset).state.0 != observation_asset_state_t::OBS_ASSET_PENDING.0; + let mut reasoned: bool = (*asset).state.0 == observation_asset_state_t::OBS_ASSET_UNAVAILABLE.0 + || (*asset).state.0 == observation_asset_state_t::OBS_ASSET_EXPIRED.0; + cJSON_AddStringToObject( + evidence, + b"asset_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*asset).uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + evidence, + b"state\0".as_ptr() as *const ::core::ffi::c_char, + observation_asset_state_name((*asset).state), + ); + cJSON_AddItemToObject( + evidence, + b"reason\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(if reasoned as ::core::ffi::c_int != 0 { + &raw const (*asset).reason as *const ::core::ffi::c_char + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }), + ); + cJSON_AddItemToObject( + evidence, + b"origin\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(if has_media as ::core::ffi::c_int != 0 { + &raw const (*asset).origin as *const ::core::ffi::c_char + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }), + ); + cJSON_AddItemToObject( + evidence, + b"media_type\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(if has_media as ::core::ffi::c_int != 0 { + &raw const (*asset).media_type as *const ::core::ffi::c_char + } else { + ::core::ptr::null::<::core::ffi::c_char>() + }), + ); + if sized as ::core::ffi::c_int != 0 && (*asset).width > 0 as ::core::ffi::c_int { + cJSON_AddNumberToObject( + evidence, + b"width\0".as_ptr() as *const ::core::ffi::c_char, + (*asset).width as ::core::ffi::c_double, + ); + } else { + cJSON_AddNullToObject(evidence, b"width\0".as_ptr() as *const ::core::ffi::c_char); + } + if sized as ::core::ffi::c_int != 0 && (*asset).height > 0 as ::core::ffi::c_int { + cJSON_AddNumberToObject( + evidence, + b"height\0".as_ptr() as *const ::core::ffi::c_char, + (*asset).height as ::core::ffi::c_double, + ); + } else { + cJSON_AddNullToObject(evidence, b"height\0".as_ptr() as *const ::core::ffi::c_char); + } + if sized as ::core::ffi::c_int != 0 && (*asset).has_bytes as ::core::ffi::c_int != 0 { + cJSON_AddNumberToObject( + evidence, + b"bytes\0".as_ptr() as *const ::core::ffi::c_char, + (*asset).bytes as ::core::ffi::c_double, + ); + } else { + cJSON_AddNullToObject(evidence, b"bytes\0".as_ptr() as *const ::core::ffi::c_char); + } + cJSON_AddItemToObject( + evidence, + b"captured_at\0".as_ptr() as *const ::core::ffi::c_char, + if has_media as ::core::ffi::c_int != 0 { + time_or_null((*asset).captured_at_ms) + } else { + cJSON_CreateNull() + }, + ); + cJSON_AddItemToObject( + evidence, + b"expires_at\0".as_ptr() as *const ::core::ffi::c_char, + if sized as ::core::ffi::c_int != 0 { + time_or_null((*asset).expires_at_ms) + } else { + cJSON_CreateNull() + }, + ); + let mut transform: *mut cJSON = if has_media as ::core::ffi::c_int != 0 + && (*asset).transform_json[0usize] as ::core::ffi::c_int != 0 + { + cJSON_Parse(&raw const (*asset).transform_json as *const ::core::ffi::c_char) + } else { + ::core::ptr::null_mut::() + }; + if !transform.is_null() && cJSON_IsObject(transform) == 0 { + cJSON_Delete(transform); + transform = ::core::ptr::null_mut::(); + } + cJSON_AddItemToObject( + evidence, + b"transform\0".as_ptr() as *const ::core::ffi::c_char, + if !transform.is_null() { + transform + } else { + cJSON_CreateNull() + }, + ); + if (*asset).state.0 == observation_asset_state_t::OBS_ASSET_READY.0 { + let mut url: [::core::ffi::c_char; 96] = [0; 96]; + snprintf( + &raw mut url as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 96]>(), + b"/api/observations/%s/snapshot\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + evidence, + b"snapshot_url\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut url as *mut ::core::ffi::c_char, + ); + } + return evidence; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn observation_record_to_json( + mut record: *const observation_record_t, + mut source: *const ::core::ffi::c_char, + mut classifications: *const observation_classification_set_t, +) -> *mut cJSON { + if record.is_null() || source.is_null() { + return ::core::ptr::null_mut::(); + } + let mut root: *mut cJSON = cJSON_CreateObject(); + if root.is_null() { + return ::core::ptr::null_mut::(); + } + cJSON_AddStringToObject( + root, + b"id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + root, + b"source\0".as_ptr() as *const ::core::ffi::c_char, + source, + ); + cJSON_AddStringToObject( + root, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).camera_uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + root, + b"stream_name_at_capture\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).stream_name_at_capture as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + root, + b"batch_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).batch_id as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + root, + b"object_index\0".as_ptr() as *const ::core::ffi::c_char, + (*record).object_index as ::core::ffi::c_double, + ); + cJSON_AddItemToObject( + root, + b"observed_at\0".as_ptr() as *const ::core::ffi::c_char, + time_or_null((*record).observed_at_ms), + ); + cJSON_AddItemToObject( + root, + b"created_at\0".as_ptr() as *const ::core::ffi::c_char, + time_or_null((*record).created_at_ms), + ); + let mut detector: *mut cJSON = + cJSON_AddObjectToObject(root, b"detector\0".as_ptr() as *const ::core::ffi::c_char); + if !detector.is_null() { + cJSON_AddStringToObject( + detector, + b"engine_key\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).engine_key as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + detector, + b"engine_type\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).engine_type as *const ::core::ffi::c_char, + ); + cJSON_AddItemToObject( + detector, + b"model\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(&raw const (*record).model as *const ::core::ffi::c_char), + ); + cJSON_AddItemToObject( + detector, + b"model_version\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(&raw const (*record).model_version as *const ::core::ffi::c_char), + ); + } + cJSON_AddStringToObject( + root, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).label as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + root, + b"confidence\0".as_ptr() as *const ::core::ffi::c_char, + (*record).confidence, + ); + if (*record).has_box { + let mut r#box: *mut cJSON = + cJSON_AddObjectToObject(root, b"box\0".as_ptr() as *const ::core::ffi::c_char); + if !r#box.is_null() { + cJSON_AddStringToObject( + r#box, + b"format\0".as_ptr() as *const ::core::ffi::c_char, + b"xywh_normalized\0".as_ptr() as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + r#box, + b"x\0".as_ptr() as *const ::core::ffi::c_char, + (*record).box_x, + ); + cJSON_AddNumberToObject( + r#box, + b"y\0".as_ptr() as *const ::core::ffi::c_char, + (*record).box_y, + ); + cJSON_AddNumberToObject( + r#box, + b"width\0".as_ptr() as *const ::core::ffi::c_char, + (*record).box_width, + ); + cJSON_AddNumberToObject( + r#box, + b"height\0".as_ptr() as *const ::core::ffi::c_char, + (*record).box_height, + ); + } + } else { + cJSON_AddNullToObject(root, b"box\0".as_ptr() as *const ::core::ffi::c_char); + } + let mut evidence: *mut cJSON = evidence_to_json(record); + if !evidence.is_null() { + cJSON_AddItemToObject( + root, + b"evidence\0".as_ptr() as *const ::core::ffi::c_char, + evidence, + ); + } + if (*record).has_track as ::core::ffi::c_int != 0 + && (*record).track_id[0usize] as ::core::ffi::c_int != 0 + { + let mut track: *mut cJSON = + cJSON_AddObjectToObject(root, b"track\0".as_ptr() as *const ::core::ffi::c_char); + if !track.is_null() { + cJSON_AddStringToObject( + track, + b"id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).track_id as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + track, + b"namespace\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).track_namespace as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + track, + b"session\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).track_session as *const ::core::ffi::c_char, + ); + } + } else { + cJSON_AddNullToObject(root, b"track\0".as_ptr() as *const ::core::ffi::c_char); + } + if (*record).recording_id > 0 as int64_t { + let mut recording: *mut cJSON = cJSON_AddObjectToObject( + root, + b"recording_reference\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !recording.is_null() { + cJSON_AddNumberToObject( + recording, + b"recording_id\0".as_ptr() as *const ::core::ffi::c_char, + (*record).recording_id as ::core::ffi::c_double, + ); + } + } else { + cJSON_AddNullToObject( + root, + b"recording_reference\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + cJSON_AddNumberToObject( + root, + b"revision\0".as_ptr() as *const ::core::ffi::c_char, + (*record).revision as ::core::ffi::c_double, + ); + let mut lists: *mut cJSON = cJSON_AddObjectToObject( + root, + b"classifications\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut listed: bool = r#false != 0; + if !lists.is_null() { + let mut automated: *mut cJSON = + cJSON_AddArrayToObject(lists, b"automated\0".as_ptr() as *const ::core::ffi::c_char); + let mut owner: *mut cJSON = cJSON_AddArrayToObject( + lists, + b"owner_confirmed\0".as_ptr() as *const ::core::ffi::c_char, + ); + listed = !automated.is_null() && !owner.is_null(); + if listed as ::core::ffi::c_int != 0 && !classifications.is_null() { + listed = add_annotations( + automated, + &raw const (*classifications).automated as *const observation_classification_t, + (*classifications).automated_count, + ) as ::core::ffi::c_int + != 0 + && add_annotations( + owner, + &raw const (*classifications).owner_confirmed + as *const observation_classification_t, + (*classifications).owner_confirmed_count, + ) as ::core::ffi::c_int + != 0; + } + } + if detector.is_null() || evidence.is_null() || !listed { + cJSON_Delete(root); + return ::core::ptr::null_mut::(); + } + return root; +} +unsafe extern "C" fn send_json( + mut res: *mut http_response_t, + mut status: ::core::ffi::c_int, + mut body: *mut cJSON, +) { + let mut json: *mut ::core::ffi::c_char = if !body.is_null() { + cJSON_PrintUnformatted(body) + } else { + ::core::ptr::null_mut::<::core::ffi::c_char>() + }; + cJSON_Delete(body); + if json.is_null() { + http_response_set_json_error( + res, + 500 as ::core::ffi::c_int, + b"Internal server error\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + http_response_set_json(res, status, json); + cJSON_free(json as *mut ::core::ffi::c_void); +} +unsafe extern "C" fn error_body( + mut message: *const ::core::ffi::c_char, + mut code: *const ::core::ffi::c_char, +) -> *mut cJSON { + let mut body: *mut cJSON = cJSON_CreateObject(); + if !body.is_null() { + cJSON_AddStringToObject( + body, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + message, + ); + cJSON_AddStringToObject(body, b"code\0".as_ptr() as *const ::core::ffi::c_char, code); + } + return body; +} +unsafe extern "C" fn send_error( + mut res: *mut http_response_t, + mut status: ::core::ffi::c_int, + mut code: *const ::core::ffi::c_char, + mut message: *const ::core::ffi::c_char, +) { + send_json(res, status, error_body(message, code)); +} +unsafe extern "C" fn send_invalid( + mut res: *mut http_response_t, + mut field: *const ::core::ffi::c_char, + mut message: *const ::core::ffi::c_char, +) { + let mut body: *mut cJSON = error_body( + message, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body.is_null() && !field.is_null() { + cJSON_AddStringToObject( + body, + b"field\0".as_ptr() as *const ::core::ffi::c_char, + field, + ); + } + send_json(res, 400 as ::core::ffi::c_int, body); +} +unsafe extern "C" fn send_internal(mut res: *mut http_response_t) { + send_error( + res, + 500 as ::core::ffi::c_int, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + b"Internal server error\0".as_ptr() as *const ::core::ffi::c_char, + ); +} +unsafe extern "C" fn send_not_found(mut res: *mut http_response_t) { + send_error( + res, + 404 as ::core::ffi::c_int, + b"observation_not_found\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation not found\0".as_ptr() as *const ::core::ffi::c_char, + ); +} +unsafe extern "C" fn send_unavailable(mut res: *mut http_response_t) { + http_response_add_header( + res, + b"Retry-After\0".as_ptr() as *const ::core::ffi::c_char, + b"5\0".as_ptr() as *const ::core::ffi::c_char, + ); + send_error( + res, + 503 as ::core::ffi::c_int, + b"service_unavailable\0".as_ptr() as *const ::core::ffi::c_char, + b"Service temporarily unavailable\0".as_ptr() as *const ::core::ffi::c_char, + ); +} +unsafe extern "C" fn authenticate( + mut req: *const http_request_t, + mut res: *mut http_response_t, + mut user: *mut user_t, +) -> bool { + memset( + user as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + if httpd_check_action_access(req, user) == 0 { + http_response_set_json_error( + res, + 401 as ::core::ffi::c_int, + b"Unauthorized\0".as_ptr() as *const ::core::ffi::c_char, + ); + return r#false != 0; + } + return r#true != 0; +} +unsafe extern "C" fn installation_source(mut source: *mut ::core::ffi::c_char) -> bool { + return event_identity_get_source(source, OBS_SOURCE_TEXT_SIZE as size_t) + == 0 as ::core::ffi::c_int + && strncmp(source, URN_PREFIX.as_ptr(), strlen(URN_PREFIX.as_ptr())) + == 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn lowercase(mut text: *mut ::core::ffi::c_char) { + while *text != 0 { + if *text as ::core::ffi::c_int >= 'A' as ::core::ffi::c_int + && *text as ::core::ffi::c_int <= 'Z' as ::core::ffi::c_int + { + *text = (*text as ::core::ffi::c_int - 'A' as ::core::ffi::c_int + + 'a' as ::core::ffi::c_int) as ::core::ffi::c_char; + } + text = text.offset(1); + } +} +unsafe extern "C" fn path_observation_id( + mut req: *const http_request_t, + mut suffix: *const ::core::ffi::c_char, + mut id: *mut ::core::ffi::c_char, +) -> bool { + let mut param: [::core::ffi::c_char; 256] = [0; 256]; + if http_request_extract_path_param( + req, + b"/api/observations/\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut param as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + ) != 0 as ::core::ffi::c_int + { + return r#false != 0; + } + if !suffix.is_null() { + let mut length: size_t = strlen(&raw mut param as *mut ::core::ffi::c_char); + let mut suffix_length: size_t = strlen(suffix); + if length <= suffix_length + || strcmp( + (&raw mut param as *mut ::core::ffi::c_char) + .offset(length as isize) + .offset(-(suffix_length as isize)), + suffix, + ) != 0 as ::core::ffi::c_int + { + return r#false != 0; + } + param[length.wrapping_sub(suffix_length)] = '\0' as ::core::ffi::c_char; + } + lowercase(&raw mut param as *mut ::core::ffi::c_char); + if !lightnvr_uuid_is_valid(&raw mut param as *mut ::core::ffi::c_char) + || strlen(&raw mut param as *mut ::core::ffi::c_char) + != (OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as size_t + { + return r#false != 0; + } + memcpy( + id as *mut ::core::ffi::c_void, + &raw mut param as *mut ::core::ffi::c_char as *const ::core::ffi::c_void, + OBS_UUID_SIZE as size_t, + ); + return r#true != 0; +} +unsafe extern "C" fn audit_camera_decision( + mut req: *const http_request_t, + mut user: *const user_t, + mut action: authorization_action_t, + mut camera_uuid: *const ::core::ffi::c_char, + mut evaluation: *const authorization_evaluation_t, + mut outcome: *const ::core::ffi::c_char, +) { + let mut camera: *mut fleet_camera_t = + calloc(1 as size_t, ::core::mem::size_of::()) as *mut fleet_camera_t; + if camera.is_null() { + return; + } + safe_strcpy( + &raw mut (*camera).camera_uuid as *mut ::core::ffi::c_char, + camera_uuid, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + audit_log_authorization(req, user, action, camera, evaluation, outcome); + free(camera as *mut ::core::ffi::c_void); +} +unsafe extern "C" fn audit_snapshot( + mut req: *const http_request_t, + mut user: *const user_t, + mut record: *const observation_record_t, + mut evaluation: *const authorization_evaluation_t, + mut outcome: *const ::core::ffi::c_char, +) { + if strcmp(outcome, b"allowed\0".as_ptr() as *const ::core::ffi::c_char) + == 0 as ::core::ffi::c_int + { + let mut mode: audit_decision_mode_t = + audit_log_get_decision_mode(authorization_action_t::AUTHZ_SNAPSHOT_CREATE); + if mode.0 == audit_decision_mode_t::AUDIT_DECISION_MODE_OFF.0 { + return; + } + if mode.0 == audit_decision_mode_t::AUDIT_DECISION_MODE_SUMMARIZE.0 { + audit_camera_decision( + req, + user, + authorization_action_t::AUTHZ_SNAPSHOT_CREATE, + &raw const (*record).camera_uuid as *const ::core::ffi::c_char, + evaluation, + outcome, + ); + return; + } + } + let mut metadata: *const authorization_action_metadata_t = + authorization_action_metadata(authorization_action_t::AUTHZ_SNAPSHOT_CREATE); + let mut details: *mut cJSON = cJSON_CreateObject(); + if !details.is_null() { + cJSON_AddStringToObject( + details, + b"event_type\0".as_ptr() as *const ::core::ffi::c_char, + b"authorization.decision\0".as_ptr() as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + details, + b"method\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*req).method_str as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + details, + b"path\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*req).path as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + details, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).camera_uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + details, + b"asset_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).asset.uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + details, + b"evidence_state\0".as_ptr() as *const ::core::ffi::c_char, + observation_asset_state_name((*record).asset.state), + ); + if !evaluation.is_null() { + cJSON_AddStringToObject( + details, + b"decision_source\0".as_ptr() as *const ::core::ffi::c_char, + authorization_decision_source_name((*evaluation).source), + ); + cJSON_AddStringToObject( + details, + b"explanation\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*evaluation).explanation as *const ::core::ffi::c_char, + ); + } + } + audit_log_append( + req, + user, + if !metadata.is_null() { + (*metadata).key + } else { + b"snapshot.create\0".as_ptr() as *const ::core::ffi::c_char + }, + b"observation\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).uuid as *const ::core::ffi::c_char, + outcome, + details, + ); + cJSON_Delete(details); +} +unsafe extern "C" fn authorized_cameras( + mut user: *const user_t, + mut action: authorization_action_t, + mut cameras: *mut *mut fleet_camera_t, + mut count: *mut ::core::ffi::c_int, +) -> ::core::ffi::c_int { + *cameras = ::core::ptr::null_mut::(); + *count = 0 as ::core::ffi::c_int; + if db_fleet_camera_load_readonly(cameras, count) != 0 as ::core::ffi::c_int { + return -1 as ::core::ffi::c_int; + } + if *count > 0 as ::core::ffi::c_int + && authorization_filter_cameras(user, action, *cameras, count) != 0 as ::core::ffi::c_int + { + free(*cameras as *mut ::core::ffi::c_void); + *cameras = ::core::ptr::null_mut::(); + *count = 0 as ::core::ffi::c_int; + return -1 as ::core::ffi::c_int; + } + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn camera_in( + mut cameras: *const fleet_camera_t, + mut count: ::core::ffi::c_int, + mut uuid: *const ::core::ffi::c_char, +) -> bool { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < count { + if strcmp( + &raw const (*cameras.offset(i as isize)).camera_uuid as *const ::core::ffi::c_char, + uuid, + ) == 0 as ::core::ffi::c_int + { + return r#true != 0; + } + i += 1; + } + return r#false != 0; +} +unsafe extern "C" fn add_operation( + mut operations: *mut cJSON, + mut name: *const ::core::ffi::c_char, + mut supported: bool, + mut authorized: bool, + mut reason: *const ::core::ffi::c_char, +) { + let mut op: *mut cJSON = cJSON_AddObjectToObject(operations, name); + if op.is_null() { + return; + } + cJSON_AddBoolToObject( + op, + b"supported\0".as_ptr() as *const ::core::ffi::c_char, + supported as cJSON_bool, + ); + cJSON_AddBoolToObject( + op, + b"authorized\0".as_ptr() as *const ::core::ffi::c_char, + authorized as cJSON_bool, + ); + if !reason.is_null() && (!supported || !authorized) { + cJSON_AddStringToObject( + op, + b"reason\0".as_ptr() as *const ::core::ffi::c_char, + reason, + ); + } +} +unsafe extern "C" fn history_retention_days() -> ::core::ffi::c_int { + let mut value: [::core::ffi::c_char; 32] = [ + 0 as ::core::ffi::c_char, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ]; + if db_get_system_setting( + b"observation_history_retention_days\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 32]>() as ::core::ffi::c_int, + ) != 0 as ::core::ffi::c_int + { + return OBS_HISTORY_RETENTION_DEFAULT; + } + let mut end: *mut ::core::ffi::c_char = ::core::ptr::null_mut::<::core::ffi::c_char>(); + *__errno_location() = 0 as ::core::ffi::c_int; + let mut days: ::core::ffi::c_long = strtol( + &raw mut value as *mut ::core::ffi::c_char, + &raw mut end, + 10 as ::core::ffi::c_int, + ); + if *__errno_location() != 0 as ::core::ffi::c_int + || end == &raw mut value as *mut ::core::ffi::c_char + || *end as ::core::ffi::c_int != '\0' as ::core::ffi::c_int + || days < 1 as ::core::ffi::c_long + || days > 365 as ::core::ffi::c_long + { + return OBS_HISTORY_RETENTION_DEFAULT; + } + return days as ::core::ffi::c_int; +} +unsafe extern "C" fn history_bounds(mut history: *mut cJSON) -> ::core::ffi::c_int { + let mut meta: observation_meta_t = observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }; + if db_observation_meta_get(&raw mut meta) != C2Rust_Unnamed::DB_OBS_OK.0 { + return -1 as ::core::ffi::c_int; + } + let mut oldest: [::core::ffi::c_char; 257] = [0; 257]; + if observation_cursor_encode( + meta.oldest_seq - 1 as int64_t, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + &raw mut oldest as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 257]>(), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut other_epoch: [::core::ffi::c_char; 37] = [0; 37]; + loop { + if lightnvr_uuid_generate_v4(&raw mut other_epoch as *mut ::core::ffi::c_char) + != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if strcmp( + &raw mut other_epoch as *mut ::core::ffi::c_char, + &raw mut meta.epoch_uuid as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + break; + } + } + let mut probe: [::core::ffi::c_char; 257] = [0; 257]; + if observation_cursor_encode( + 0 as int64_t, + &raw mut other_epoch as *mut ::core::ffi::c_char, + &raw mut probe as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 257]>(), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut query: observation_list_query_t = observation_list_query_t { + camera_uuids: ::core::ptr::null::<[::core::ffi::c_char; 37]>(), + camera_count: 0, + all_cameras: false, + labels: [[0; 32]; 8], + label_count: 0, + has_since: false, + since_ms: 0, + has_until: false, + until_ms: 0, + limit: 0, + }; + memset( + &raw mut query as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut page: observation_page_t = observation_page_t { + items: ::core::ptr::null_mut::(), + count: 0, + next_cursor: [0; 257], + has_more: false, + oldest_cursor: [0; 257], + has_oldest_observed_at: false, + oldest_observed_at_ms: 0, + }; + if db_observations_list( + &raw mut query, + &raw mut probe as *mut ::core::ffi::c_char, + &raw mut page, + ) != C2Rust_Unnamed::DB_OBS_CURSOR_EXPIRED.0 + { + db_observation_page_free(&raw mut page); + return -1 as ::core::ffi::c_int; + } + cJSON_AddStringToObject( + history, + b"oldest_cursor\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut oldest as *mut ::core::ffi::c_char, + ); + cJSON_AddItemToObject( + history, + b"oldest_observed_at\0".as_ptr() as *const ::core::ffi::c_char, + if page.has_oldest_observed_at as ::core::ffi::c_int != 0 { + time_or_null(page.oldest_observed_at_ms) + } else { + cJSON_CreateNull() + }, + ); + db_observation_page_free(&raw mut page); + return 0 as ::core::ffi::c_int; +} +unsafe extern "C" fn camera_evidence_enabled( + mut streams: *const stream_config_t, + mut count: ::core::ffi::c_int, + mut uuid: *const ::core::ffi::c_char, +) -> bool { + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while !streams.is_null() && i < count { + if strcmp( + &raw const (*streams.offset(i as isize)).camera_uuid as *const ::core::ffi::c_char, + uuid, + ) == 0 as ::core::ffi::c_int + { + return (*streams.offset(i as isize)).observation_evidence_enabled; + } + i += 1; + } + return r#false != 0; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn handle_get_integration_capabilities( + mut req: *const http_request_t, + mut res: *mut http_response_t, +) { + let mut user: user_t = user_t { + id: 0, + username: [0; 64], + email: [0; 128], + role: user_role_t::USER_ROLE_ADMIN, + api_key: [0; 64], + created_at: 0, + updated_at: 0, + last_login: 0, + is_active: false, + password_change_locked: false, + must_change_password: false, + totp_enabled: false, + allowed_login_cidrs: [0; 1024], + has_login_cidr_restriction: false, + authorization_mode: [0; 16], + authenticated_via_scoped_token: false, + api_token_uuid: [0; 37], + authentication_method: [0; 24], + }; + if !authenticate(req, res, &raw mut user) { + return; + } + let mut source: [::core::ffi::c_char; 128] = [0; 128]; + if !installation_source(&raw mut source as *mut ::core::ffi::c_char) { + send_unavailable(res); + return; + } + let mut viewable: *mut fleet_camera_t = ::core::ptr::null_mut::(); + let mut snapshot: *mut fleet_camera_t = ::core::ptr::null_mut::(); + let mut annotatable: *mut fleet_camera_t = ::core::ptr::null_mut::(); + let mut viewable_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut snapshot_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut annotatable_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if authorized_cameras( + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut viewable, + &raw mut viewable_count, + ) != 0 as ::core::ffi::c_int + || authorized_cameras( + &raw mut user, + authorization_action_t::AUTHZ_SNAPSHOT_CREATE, + &raw mut snapshot, + &raw mut snapshot_count, + ) != 0 as ::core::ffi::c_int + || authorized_cameras( + &raw mut user, + authorization_action_t::AUTHZ_OBSERVATIONS_ANNOTATE, + &raw mut annotatable, + &raw mut annotatable_count, + ) != 0 as ::core::ffi::c_int + { + free(viewable as *mut ::core::ffi::c_void); + free(snapshot as *mut ::core::ffi::c_void); + free(annotatable as *mut ::core::ffi::c_void); + send_error( + res, + 500 as ::core::ffi::c_int, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + b"Authorization policy evaluation failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut slots: ::core::ffi::c_int = configured_stream_slots(); + let mut streams: *mut stream_config_t = + calloc(slots as size_t, ::core::mem::size_of::()) as *mut stream_config_t; + let mut stream_count: ::core::ffi::c_int = if !streams.is_null() { + get_all_stream_configs_readonly(streams, slots) + } else { + -1 as ::core::ffi::c_int + }; + let mut root: *mut cJSON = cJSON_CreateObject(); + let mut history: *mut cJSON = ::core::ptr::null_mut::(); + let mut ok: bool = !root.is_null() && stream_count >= 0 as ::core::ffi::c_int; + if ok { + let mut versions: *mut cJSON = cJSON_AddObjectToObject( + root, + b"contract_versions\0".as_ptr() as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + versions, + b"observations\0".as_ptr() as *const ::core::ffi::c_char, + 1 as ::core::ffi::c_int as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + versions, + b"classifications\0".as_ptr() as *const ::core::ffi::c_char, + 1 as ::core::ffi::c_int as ::core::ffi::c_double, + ); + cJSON_AddStringToObject( + versions, + b"observation_event\0".as_ptr() as *const ::core::ffi::c_char, + OBS_EVENT_TYPE.as_ptr(), + ); + let mut src: *mut cJSON = + cJSON_AddObjectToObject(root, b"source\0".as_ptr() as *const ::core::ffi::c_char); + cJSON_AddStringToObject( + src, + b"id\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut source as *mut ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + src, + b"kind\0".as_ptr() as *const ::core::ffi::c_char, + b"lightnvr\0".as_ptr() as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + src, + b"installation_uuid\0".as_ptr() as *const ::core::ffi::c_char, + (&raw mut source as *mut ::core::ffi::c_char) + .offset(strlen(URN_PREFIX.as_ptr()) as isize), + ); + cJSON_AddStringToObject( + src, + b"product_version\0".as_ptr() as *const ::core::ffi::c_char, + LIGHTNVR_VERSION_STRING.as_ptr(), + ); + let mut writer: [::core::ffi::c_char; 33] = [0; 33]; + let mut namespace_rc: ::core::ffi::c_int = observation_writer_namespace( + &raw mut user.username as *mut ::core::ffi::c_char, + &raw mut writer as *mut ::core::ffi::c_char, + ); + let mut principal: *mut cJSON = + cJSON_AddObjectToObject(root, b"principal\0".as_ptr() as *const ::core::ffi::c_char); + cJSON_AddItemToObject( + principal, + b"writer_namespace\0".as_ptr() as *const ::core::ffi::c_char, + if namespace_rc == 0 as ::core::ffi::c_int { + cJSON_CreateString(&raw mut writer as *mut ::core::ffi::c_char) + } else { + cJSON_CreateNull() + }, + ); + let mut operations: *mut cJSON = + cJSON_AddObjectToObject(root, b"operations\0".as_ptr() as *const ::core::ffi::c_char); + add_operation( + operations, + b"observations.history\0".as_ptr() as *const ::core::ffi::c_char, + r#true != 0, + viewable_count > 0 as ::core::ffi::c_int, + b"not_authorized\0".as_ptr() as *const ::core::ffi::c_char, + ); + add_operation( + operations, + b"observations.snapshot\0".as_ptr() as *const ::core::ffi::c_char, + r#true != 0, + snapshot_count > 0 as ::core::ffi::c_int, + b"not_authorized\0".as_ptr() as *const ::core::ffi::c_char, + ); + add_operation( + operations, + b"observations.annotate\0".as_ptr() as *const ::core::ffi::c_char, + r#true != 0, + namespace_rc == 0 as ::core::ffi::c_int && annotatable_count > 0 as ::core::ffi::c_int, + if namespace_rc == -2 as ::core::ffi::c_int { + b"reserved_writer_namespace\0".as_ptr() as *const ::core::ffi::c_char + } else if namespace_rc != 0 as ::core::ffi::c_int { + b"invalid_writer_namespace\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"not_authorized\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + add_operation( + operations, + b"track_lifecycle\0".as_ptr() as *const ::core::ffi::c_char, + r#false != 0, + r#false != 0, + b"not_implemented\0".as_ptr() as *const ::core::ffi::c_char, + ); + add_operation( + operations, + b"recorded_frame\0".as_ptr() as *const ::core::ffi::c_char, + r#false != 0, + r#false != 0, + b"not_implemented\0".as_ptr() as *const ::core::ffi::c_char, + ); + add_operation( + operations, + b"event_clip\0".as_ptr() as *const ::core::ffi::c_char, + r#false != 0, + r#false != 0, + b"not_implemented\0".as_ptr() as *const ::core::ffi::c_char, + ); + add_operation( + operations, + b"range_clip\0".as_ptr() as *const ::core::ffi::c_char, + r#false != 0, + r#false != 0, + b"not_implemented\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut limits: *mut cJSON = + cJSON_AddObjectToObject(root, b"limits\0".as_ptr() as *const ::core::ffi::c_char); + cJSON_AddNumberToObject( + limits, + b"history_page_default\0".as_ptr() as *const ::core::ffi::c_char, + OBS_LIST_DEFAULT_LIMIT as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"history_page_max\0".as_ptr() as *const ::core::ffi::c_char, + OBS_LIST_MAX_LIMIT as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"history_filter_cameras_max\0".as_ptr() as *const ::core::ffi::c_char, + OBS_FILTER_CAMERAS_MAX as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"history_filter_labels_max\0".as_ptr() as *const ::core::ffi::c_char, + OBS_QUERY_MAX_LABELS as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"max_objects_per_batch\0".as_ptr() as *const ::core::ffi::c_char, + OBS_MAX_BATCH as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"label_max_bytes\0".as_ptr() as *const ::core::ffi::c_char, + (OBS_LABEL_SIZE - 1 as ::core::ffi::c_int) as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"snapshot_retry_after_max_seconds\0".as_ptr() as *const ::core::ffi::c_char, + OBS_SNAPSHOT_RETRY_MAX as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + limits, + b"annotations_per_observation_max\0".as_ptr() as *const ::core::ffi::c_char, + OBS_ANNOTATIONS_MAX as ::core::ffi::c_double, + ); + history = + cJSON_AddObjectToObject(root, b"history\0".as_ptr() as *const ::core::ffi::c_char); + cJSON_AddNumberToObject( + history, + b"retention_days\0".as_ptr() as *const ::core::ffi::c_char, + history_retention_days() as ::core::ffi::c_double, + ); + ok = !history.is_null() && history_bounds(history) == 0 as ::core::ffi::c_int; + let mut delivery: *mut cJSON = + cJSON_AddObjectToObject(root, b"delivery\0".as_ptr() as *const ::core::ffi::c_char); + cJSON_AddBoolToObject( + delivery, + b"mqtt_enabled\0".as_ptr() as *const ::core::ffi::c_char, + g_config.mqtt_enabled as cJSON_bool, + ); + if g_config.mqtt_topic_prefix[0usize] != 0 { + let mut topic: [::core::ffi::c_char; 256] = [0; 256]; + snprintf( + &raw mut topic as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), + b"%s/v1/events/%s/{camera_uuid}\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut g_config.mqtt_topic_prefix as *mut ::core::ffi::c_char, + OBS_EVENT_TYPE.as_ptr(), + ); + cJSON_AddStringToObject( + delivery, + b"topic\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut topic as *mut ::core::ffi::c_char, + ); + } else { + cJSON_AddNullToObject(delivery, b"topic\0".as_ptr() as *const ::core::ffi::c_char); + } + let mut cameras: *mut cJSON = + cJSON_AddArrayToObject(root, b"cameras\0".as_ptr() as *const ::core::ffi::c_char); + let mut all: *mut fleet_camera_t = ::core::ptr::null_mut::(); + let mut all_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if db_fleet_camera_load_readonly(&raw mut all, &raw mut all_count) + != 0 as ::core::ffi::c_int + { + ok = r#false != 0; + } + let mut listed: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while ok as ::core::ffi::c_int != 0 && i < all_count && listed < OBS_CAPABILITY_CAMERAS_MAX + { + let mut uuid: *const ::core::ffi::c_char = + &raw mut (*all.offset(i as isize)).camera_uuid as *mut ::core::ffi::c_char; + let mut live: bool = camera_in(viewable, viewable_count, uuid); + let mut snap: bool = camera_in(snapshot, snapshot_count, uuid); + let mut annotate: bool = camera_in(annotatable, annotatable_count, uuid); + if !(!live && !snap && !annotate) { + let mut camera: *mut cJSON = cJSON_CreateObject(); + cJSON_AddStringToObject( + camera, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + uuid, + ); + cJSON_AddBoolToObject( + camera, + b"evidence_enabled\0".as_ptr() as *const ::core::ffi::c_char, + camera_evidence_enabled(streams, stream_count, uuid) as cJSON_bool, + ); + let mut actions: *mut cJSON = cJSON_AddArrayToObject( + camera, + b"actions\0".as_ptr() as *const ::core::ffi::c_char, + ); + if live { + cJSON_AddItemToArray( + actions, + cJSON_CreateString(b"live.view\0".as_ptr() as *const ::core::ffi::c_char), + ); + } + if snap { + cJSON_AddItemToArray( + actions, + cJSON_CreateString( + b"snapshot.create\0".as_ptr() as *const ::core::ffi::c_char + ), + ); + } + if annotate { + cJSON_AddItemToArray( + actions, + cJSON_CreateString( + b"observations.annotate\0".as_ptr() as *const ::core::ffi::c_char + ), + ); + } + cJSON_AddItemToArray(cameras, camera); + listed += 1; + } + i += 1; + } + free(all as *mut ::core::ffi::c_void); + } + free(streams as *mut ::core::ffi::c_void); + free(viewable as *mut ::core::ffi::c_void); + free(snapshot as *mut ::core::ffi::c_void); + free(annotatable as *mut ::core::ffi::c_void); + if !ok { + cJSON_Delete(root); + send_internal(res); + return; + } + send_json(res, 200 as ::core::ffi::c_int, root); +} +unsafe extern "C" fn query_has( + mut query: *const ::core::ffi::c_char, + mut name: *const ::core::ffi::c_char, +) -> bool { + let mut length: size_t = strlen(name); + let mut p: *const ::core::ffi::c_char = query; + while !p.is_null() && *p as ::core::ffi::c_int != 0 { + if strncmp(p, name, length) == 0 as ::core::ffi::c_int + && *p.offset(length as isize) as ::core::ffi::c_int == '=' as ::core::ffi::c_int + { + return r#true != 0; + } + p = strchr(p, '&' as ::core::ffi::c_int); + if !p.is_null() { + p = p.offset(1); + } + } + return r#false != 0; +} +unsafe extern "C" fn query_value( + mut req: *const http_request_t, + mut name: *const ::core::ffi::c_char, + mut value: *mut ::core::ffi::c_char, + mut size: size_t, +) -> ::core::ffi::c_int { + *value.offset(0isize) = '\0' as ::core::ffi::c_char; + if !query_has( + &raw const (*req).query_string as *const ::core::ffi::c_char, + name, + ) { + return -1 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = http_request_get_query_param(req, name, value, size); + return if n < 0 as ::core::ffi::c_int { + -2 as ::core::ffi::c_int + } else { + n + }; +} +unsafe extern "C" fn parse_limit( + mut text: *const ::core::ffi::c_char, + mut limit: *mut ::core::ffi::c_int, +) -> bool { + let mut end: *mut ::core::ffi::c_char = ::core::ptr::null_mut::<::core::ffi::c_char>(); + *__errno_location() = 0 as ::core::ffi::c_int; + let mut value: ::core::ffi::c_long = strtol(text, &raw mut end, 10 as ::core::ffi::c_int); + if *__errno_location() != 0 as ::core::ffi::c_int + || end == text as *mut ::core::ffi::c_char + || *end as ::core::ffi::c_int != '\0' as ::core::ffi::c_int + || *(*__ctype_b_loc()) + .offset(*text.offset(0isize) as ::core::ffi::c_uchar as ::core::ffi::c_int as isize) + as ::core::ffi::c_int + & C2Rust_Unnamed_1::_ISdigit.0 as ::core::ffi::c_int as ::core::ffi::c_ushort + as ::core::ffi::c_int + == 0 + { + return r#false != 0; + } + if value < 1 as ::core::ffi::c_long || value > OBS_LIST_MAX_LIMIT as ::core::ffi::c_long { + return r#false != 0; + } + *limit = value as ::core::ffi::c_int; + return r#true != 0; +} +unsafe extern "C" fn parse_labels( + mut text: *mut ::core::ffi::c_char, + mut query: *mut observation_list_query_t, +) -> bool { + let mut count: size_t = 0 as size_t; + let mut start: *mut ::core::ffi::c_char = text; + loop { + let mut comma: *mut ::core::ffi::c_char = strchr(start, ',' as ::core::ffi::c_int); + let mut length: size_t = if !comma.is_null() { + comma.offset_from(start) as size_t + } else { + strlen(start) + }; + if length < 1 as size_t + || length > (OBS_LABEL_SIZE - 1 as ::core::ffi::c_int) as size_t + || count >= OBS_QUERY_MAX_LABELS as size_t + { + return r#false != 0; + } + let mut i: size_t = 0 as size_t; + while i < length { + if (*start.offset(i as isize) as ::core::ffi::c_uchar as ::core::ffi::c_int) + < 0x20 as ::core::ffi::c_int + || *start.offset(i as isize) as ::core::ffi::c_int == 0x7f as ::core::ffi::c_int + { + return r#false != 0; + } + i = i.wrapping_add(1); + } + memcpy( + &raw mut *(&raw mut (*query).labels as *mut [::core::ffi::c_char; 32]) + .offset(count as isize) as *mut ::core::ffi::c_char + as *mut ::core::ffi::c_void, + start as *const ::core::ffi::c_void, + length, + ); + (*query).labels[count][length] = '\0' as ::core::ffi::c_char; + count = count.wrapping_add(1); + if comma.is_null() { + break; + } + start = comma.offset(1 as ::core::ffi::c_int as isize); + } + (*query).label_count = count; + return r#true != 0; +} +unsafe extern "C" fn parse_cameras( + mut text: *mut ::core::ffi::c_char, + mut cameras: *mut [::core::ffi::c_char; 37], + mut count: *mut size_t, +) -> bool { + *count = 0 as size_t; + let mut start: *mut ::core::ffi::c_char = text; + loop { + let mut comma: *mut ::core::ffi::c_char = strchr(start, ',' as ::core::ffi::c_int); + let mut length: size_t = if !comma.is_null() { + comma.offset_from(start) as size_t + } else { + strlen(start) + }; + if length != (OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as size_t + || *count >= OBS_FILTER_CAMERAS_MAX as size_t + { + return r#false != 0; + } + memcpy( + &raw mut *cameras.offset(*count as isize) as *mut ::core::ffi::c_char + as *mut ::core::ffi::c_void, + start as *const ::core::ffi::c_void, + length, + ); + (*cameras.offset(*count as isize))[length] = '\0' as ::core::ffi::c_char; + lowercase(&raw mut *cameras.offset(*count as isize) as *mut ::core::ffi::c_char); + if !lightnvr_uuid_is_valid( + &raw mut *cameras.offset(*count as isize) as *mut ::core::ffi::c_char + ) { + return r#false != 0; + } + *count = (*count).wrapping_add(1); + if comma.is_null() { + break; + } + start = comma.offset(1 as ::core::ffi::c_int as isize); + } + return r#true != 0; +} +unsafe extern "C" fn send_cursor_expired( + mut res: *mut http_response_t, + mut page: *const observation_page_t, +) { + let mut body: *mut cJSON = error_body( + b"Cursor is older than retained observation history\0".as_ptr() + as *const ::core::ffi::c_char, + b"cursor_expired\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body.is_null() { + cJSON_AddStringToObject( + body, + b"oldest_cursor\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*page).oldest_cursor as *const ::core::ffi::c_char, + ); + cJSON_AddItemToObject( + body, + b"oldest_observed_at\0".as_ptr() as *const ::core::ffi::c_char, + if (*page).has_oldest_observed_at as ::core::ffi::c_int != 0 { + time_or_null((*page).oldest_observed_at_ms) + } else { + cJSON_CreateNull() + }, + ); + } + send_json(res, 410 as ::core::ffi::c_int, body); +} +unsafe extern "C" fn add_page_item( + mut index: size_t, + mut set: *const observation_classification_set_t, + mut context: *mut ::core::ffi::c_void, +) -> ::core::ffi::c_int { + let mut page: *mut page_item_context_t = context as *mut page_item_context_t; + let mut item: *mut cJSON = observation_record_to_json( + (*(*page).page).items.offset(index as isize), + (*page).source, + set, + ); + if item.is_null() { + return 1 as ::core::ffi::c_int; + } + cJSON_AddItemToArray((*page).items, item); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn handle_get_observations( + mut req: *const http_request_t, + mut res: *mut http_response_t, +) { + let mut user: user_t = user_t { + id: 0, + username: [0; 64], + email: [0; 128], + role: user_role_t::USER_ROLE_ADMIN, + api_key: [0; 64], + created_at: 0, + updated_at: 0, + last_login: 0, + is_active: false, + password_change_locked: false, + must_change_password: false, + totp_enabled: false, + allowed_login_cidrs: [0; 1024], + has_login_cidr_restriction: false, + authorization_mode: [0; 16], + authenticated_via_scoped_token: false, + api_token_uuid: [0; 37], + authentication_method: [0; 24], + }; + if !authenticate(req, res, &raw mut user) { + return; + } + let mut source: [::core::ffi::c_char; 128] = [0; 128]; + if !installation_source(&raw mut source as *mut ::core::ffi::c_char) { + send_unavailable(res); + return; + } + let mut query: observation_list_query_t = observation_list_query_t { + camera_uuids: ::core::ptr::null::<[::core::ffi::c_char; 37]>(), + camera_count: 0, + all_cameras: false, + labels: [[0; 32]; 8], + label_count: 0, + has_since: false, + since_ms: 0, + has_until: false, + until_ms: 0, + limit: 0, + }; + memset( + &raw mut query as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut value: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut n: ::core::ffi::c_int = 0; + n = query_value( + req, + b"limit\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + if n != -1 as ::core::ffi::c_int + && (n <= 0 as ::core::ffi::c_int + || !parse_limit( + &raw mut value as *mut ::core::ffi::c_char, + &raw mut query.limit, + )) + { + send_invalid( + res, + b"limit\0".as_ptr() as *const ::core::ffi::c_char, + b"limit must be an integer from 1 to 500\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + n = query_value( + req, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + if n != -1 as ::core::ffi::c_int + && (n <= 0 as ::core::ffi::c_int + || !parse_labels(&raw mut value as *mut ::core::ffi::c_char, &raw mut query)) + { + send_invalid( + res, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + b"label must list 1 to 8 labels of 1 to 31 bytes\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return; + } + n = query_value( + req, + b"since\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + if n != -1 as ::core::ffi::c_int { + if n <= 0 as ::core::ffi::c_int + || observation_parse_time( + &raw mut value as *mut ::core::ffi::c_char, + &raw mut query.since_ms, + ) != 0 as ::core::ffi::c_int + { + send_invalid( + res, + b"since\0".as_ptr() as *const ::core::ffi::c_char, + b"since must be an RFC 3339 time\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + query.has_since = r#true != 0; + } + n = query_value( + req, + b"until\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + if n != -1 as ::core::ffi::c_int { + if n <= 0 as ::core::ffi::c_int + || observation_parse_time( + &raw mut value as *mut ::core::ffi::c_char, + &raw mut query.until_ms, + ) != 0 as ::core::ffi::c_int + { + send_invalid( + res, + b"until\0".as_ptr() as *const ::core::ffi::c_char, + b"until must be an RFC 3339 time\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + query.has_until = r#true != 0; + } + if query.has_since as ::core::ffi::c_int != 0 + && query.has_until as ::core::ffi::c_int != 0 + && query.since_ms >= query.until_ms + { + send_invalid( + res, + b"until\0".as_ptr() as *const ::core::ffi::c_char, + b"since must be before until\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut cursor: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut has_cursor: bool = r#false != 0; + n = query_value( + req, + b"cursor\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut cursor as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + if n != -1 as ::core::ffi::c_int { + if n <= 0 as ::core::ffi::c_int { + send_invalid( + res, + b"cursor\0".as_ptr() as *const ::core::ffi::c_char, + b"cursor must not be empty\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + has_cursor = r#true != 0; + } + let mut requested: [[::core::ffi::c_char; 37]; 10] = [[0; 37]; 10]; + let mut requested_count: size_t = 0 as size_t; + n = query_value( + req, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut value as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); + if n != -1 as ::core::ffi::c_int + && (n <= 0 as ::core::ffi::c_int + || !parse_cameras( + &raw mut value as *mut ::core::ffi::c_char, + &raw mut requested as *mut [::core::ffi::c_char; 37], + &raw mut requested_count, + )) + { + send_invalid( + res, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + b"camera_uuid must list 1 to 10 camera UUIDs\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut cameras: *mut fleet_camera_t = ::core::ptr::null_mut::(); + let mut camera_count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + if authorized_cameras( + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut cameras, + &raw mut camera_count, + ) != 0 as ::core::ffi::c_int + { + send_error( + res, + 500 as ::core::ffi::c_int, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + b"Authorization policy evaluation failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut scope: *mut [::core::ffi::c_char; 37] = + ::core::ptr::null_mut::<[::core::ffi::c_char; 37]>(); + if requested_count > 0 as size_t { + let mut i: size_t = 0 as size_t; + while i < requested_count { + if !camera_in( + cameras, + camera_count, + &raw mut *(&raw mut requested as *mut [::core::ffi::c_char; 37]).offset(i as isize) + as *mut ::core::ffi::c_char, + ) { + audit_camera_decision( + req, + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut *(&raw mut requested as *mut [::core::ffi::c_char; 37]) + .offset(i as isize) as *mut ::core::ffi::c_char, + ::core::ptr::null::(), + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + ); + free(cameras as *mut ::core::ffi::c_void); + send_error( + res, + 403 as ::core::ffi::c_int, + b"forbidden\0".as_ptr() as *const ::core::ffi::c_char, + b"Forbidden\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + i = i.wrapping_add(1); + } + query.camera_uuids = &raw mut requested as *mut [::core::ffi::c_char; 37] + as *const [::core::ffi::c_char; 37]; + query.camera_count = requested_count; + } else if camera_count > 0 as ::core::ffi::c_int { + scope = calloc( + camera_count as size_t, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + ) as *mut [::core::ffi::c_char; 37]; + if scope.is_null() { + free(cameras as *mut ::core::ffi::c_void); + send_internal(res); + return; + } + let mut i_0: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i_0 < camera_count { + safe_strcpy( + &raw mut *scope.offset(i_0 as isize) as *mut ::core::ffi::c_char, + &raw mut (*cameras.offset(i_0 as isize)).camera_uuid as *mut ::core::ffi::c_char, + OBS_UUID_SIZE as size_t, + 0 as size_t, + ); + i_0 += 1; + } + query.camera_uuids = scope as *const [::core::ffi::c_char; 37]; + query.camera_count = camera_count as size_t; + } + free(cameras as *mut ::core::ffi::c_void); + let mut page: observation_page_t = observation_page_t { + items: ::core::ptr::null_mut::(), + count: 0, + next_cursor: [0; 257], + has_more: false, + oldest_cursor: [0; 257], + has_oldest_observed_at: false, + oldest_observed_at_ms: 0, + }; + let mut rc: ::core::ffi::c_int = db_observations_list( + &raw mut query, + if has_cursor as ::core::ffi::c_int != 0 { + &raw mut cursor as *mut ::core::ffi::c_char + } else { + ::core::ptr::null_mut::<::core::ffi::c_char>() + }, + &raw mut page, + ); + free(scope as *mut ::core::ffi::c_void); + match rc { + 0 => {} + -4 => { + send_error( + res, + 400 as ::core::ffi::c_int, + b"invalid_cursor\0".as_ptr() as *const ::core::ffi::c_char, + b"Malformed cursor\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + -5 => { + send_cursor_expired(res, &raw mut page); + db_observation_page_free(&raw mut page); + return; + } + -2 => { + send_invalid( + res, + ::core::ptr::null::<::core::ffi::c_char>(), + b"Invalid observation query\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + _ => { + send_internal(res); + return; + } + } + let mut body: *mut cJSON = cJSON_CreateObject(); + let mut items: *mut cJSON = if !body.is_null() { + cJSON_AddArrayToObject(body, b"items\0".as_ptr() as *const ::core::ffi::c_char) + } else { + ::core::ptr::null_mut::() + }; + let mut ok: bool = !items.is_null(); + if ok as ::core::ffi::c_int != 0 && page.count > 0 as size_t { + let mut ids: *mut *const ::core::ffi::c_char = calloc( + page.count, + ::core::mem::size_of::<*const ::core::ffi::c_char>(), + ) as *mut *const ::core::ffi::c_char; + ok = !ids.is_null(); + let mut i_1: size_t = 0 as size_t; + while ok as ::core::ffi::c_int != 0 && i_1 < page.count { + *ids.offset(i_1 as isize) = + &raw mut (*page.items.offset(i_1 as isize)).uuid as *mut ::core::ffi::c_char; + i_1 = i_1.wrapping_add(1); + } + let mut context: page_item_context_t = page_item_context_t { + page: &raw mut page, + source: &raw mut source as *mut ::core::ffi::c_char, + items: items, + }; + ok = ok as ::core::ffi::c_int != 0 + && db_observation_classifications_list_many( + ids, + page.count, + Some( + add_page_item + as unsafe extern "C" fn( + size_t, + *const observation_classification_set_t, + *mut ::core::ffi::c_void, + ) -> ::core::ffi::c_int, + ), + &raw mut context as *mut ::core::ffi::c_void, + ) == C2Rust_Unnamed::DB_OBS_OK.0; + free(ids as *mut ::core::ffi::c_void); + } + if ok { + cJSON_AddStringToObject( + body, + b"next_cursor\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut page.next_cursor as *mut ::core::ffi::c_char, + ); + cJSON_AddBoolToObject( + body, + b"has_more\0".as_ptr() as *const ::core::ffi::c_char, + page.has_more as cJSON_bool, + ); + } + db_observation_page_free(&raw mut page); + if !ok { + cJSON_Delete(body); + send_internal(res); + return; + } + send_json(res, 200 as ::core::ffi::c_int, body); +} +unsafe extern "C" fn load_observation( + mut req: *const http_request_t, + mut res: *mut http_response_t, + mut suffix: *const ::core::ffi::c_char, + mut record: *mut observation_record_t, +) -> ::core::ffi::c_int { + let mut id: [::core::ffi::c_char; 37] = [0; 37]; + if !path_observation_id(req, suffix, &raw mut id as *mut ::core::ffi::c_char) { + send_invalid( + res, + b"id\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation ID must be a UUID\0".as_ptr() as *const ::core::ffi::c_char, + ); + return -1 as ::core::ffi::c_int; + } + let mut rc: ::core::ffi::c_int = + db_observation_get(&raw mut id as *mut ::core::ffi::c_char, record); + if rc == C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 { + return 0 as ::core::ffi::c_int; + } + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + send_internal(res); + return -1 as ::core::ffi::c_int; + } + return 1 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn handle_get_observation( + mut req: *const http_request_t, + mut res: *mut http_response_t, +) { + let mut user: user_t = user_t { + id: 0, + username: [0; 64], + email: [0; 128], + role: user_role_t::USER_ROLE_ADMIN, + api_key: [0; 64], + created_at: 0, + updated_at: 0, + last_login: 0, + is_active: false, + password_change_locked: false, + must_change_password: false, + totp_enabled: false, + allowed_login_cidrs: [0; 1024], + has_login_cidr_restriction: false, + authorization_mode: [0; 16], + authenticated_via_scoped_token: false, + api_token_uuid: [0; 37], + authentication_method: [0; 24], + }; + if !authenticate(req, res, &raw mut user) { + return; + } + let mut source: [::core::ffi::c_char; 128] = [0; 128]; + if !installation_source(&raw mut source as *mut ::core::ffi::c_char) { + send_unavailable(res); + return; + } + let mut record: *mut observation_record_t = + calloc(1 as size_t, ::core::mem::size_of::()) + as *mut observation_record_t; + if record.is_null() { + send_internal(res); + return; + } + let mut found: ::core::ffi::c_int = + load_observation(req, res, ::core::ptr::null::<::core::ffi::c_char>(), record); + if found <= 0 as ::core::ffi::c_int { + if found == 0 as ::core::ffi::c_int { + send_not_found(res); + } + free(record as *mut ::core::ffi::c_void); + return; + } + let mut evaluation: authorization_evaluation_t = authorization_evaluation_t { + decision: authorization_decision_t::AUTHZ_DECISION_DENY, + source: authorization_decision_source_t::AUTHZ_SOURCE_NONE, + policy_version: 0, + grant_uuid: [0; 37], + token_uuid: [0; 37], + role_uuid: [0; 37], + role_name: [0; 128], + explanation: [0; 256], + }; + let mut rc: ::core::ffi::c_int = httpd_evaluate_camera_identity_action( + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut evaluation, + ); + if rc < 0 as ::core::ffi::c_int { + audit_camera_decision( + req, + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + ::core::ptr::null::(), + b"error\0".as_ptr() as *const ::core::ffi::c_char, + ); + send_internal(res); + free(record as *mut ::core::ffi::c_void); + return; + } + if rc > 0 as ::core::ffi::c_int + || evaluation.decision.0 != authorization_decision_t::AUTHZ_DECISION_ALLOW.0 + { + if rc == 0 as ::core::ffi::c_int { + audit_camera_decision( + req, + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + &raw mut evaluation, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + send_not_found(res); + free(record as *mut ::core::ffi::c_void); + return; + } + audit_camera_decision( + req, + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + &raw mut evaluation, + b"allowed\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut set: *mut observation_classification_set_t = calloc( + 1 as size_t, + ::core::mem::size_of::(), + ) + as *mut observation_classification_set_t; + let mut body: *mut cJSON = if !set.is_null() + && db_observation_classifications_list( + &raw mut (*record).uuid as *mut ::core::ffi::c_char, + set, + ) == C2Rust_Unnamed::DB_OBS_OK.0 + { + observation_record_to_json(record, &raw mut source as *mut ::core::ffi::c_char, set) + } else { + ::core::ptr::null_mut::() + }; + free(set as *mut ::core::ffi::c_void); + free(record as *mut ::core::ffi::c_void); + if body.is_null() { + send_internal(res); + return; + } + send_json(res, 200 as ::core::ffi::c_int, body); +} +unsafe extern "C" fn resolve_asset_file( + mut stored: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) -> ::core::ffi::c_int { + let mut root: [::core::ffi::c_char; 512] = [0; 512]; + safe_strcpy( + &raw mut root as *mut ::core::ffi::c_char, + &raw mut g_config.storage_path as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + let mut root_length: size_t = strlen(&raw mut root as *mut ::core::ffi::c_char); + while root_length > 1 as size_t + && root[root_length.wrapping_sub(1 as size_t)] as ::core::ffi::c_int + == '/' as ::core::ffi::c_int + { + root_length = root_length.wrapping_sub(1); + root[root_length] = '\0' as ::core::ffi::c_char; + } + if stored.is_null() || *stored.offset(0isize) == 0 || root_length == 0 as size_t { + return -1 as ::core::ffi::c_int; + } + let mut relative: *const ::core::ffi::c_char = stored; + if *stored.offset(0isize) as ::core::ffi::c_int == '/' as ::core::ffi::c_int { + if strncmp( + stored, + &raw mut root as *mut ::core::ffi::c_char, + root_length, + ) != 0 as ::core::ffi::c_int + || *stored.offset(root_length as isize) as ::core::ffi::c_int + != '/' as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + relative = stored + .offset(root_length as isize) + .offset(1 as ::core::ffi::c_int as isize); + } + if strncmp( + relative, + b"observations/\0".as_ptr() as *const ::core::ffi::c_char, + strlen(b"observations/\0".as_ptr() as *const ::core::ffi::c_char), + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + if !strchr(relative, '\\' as ::core::ffi::c_int).is_null() + || !strstr(relative, b"//\0".as_ptr() as *const ::core::ffi::c_char).is_null() + { + return -1 as ::core::ffi::c_int; + } + let mut segment: *const ::core::ffi::c_char = relative; + while !segment.is_null() { + if strncmp( + segment, + b"..\0".as_ptr() as *const ::core::ffi::c_char, + 2 as size_t, + ) == 0 as ::core::ffi::c_int + && (*segment.offset(2isize) as ::core::ffi::c_int == '/' as ::core::ffi::c_int + || *segment.offset(2isize) as ::core::ffi::c_int == '\0' as ::core::ffi::c_int) + { + return -1 as ::core::ffi::c_int; + } + if *segment.offset(0isize) as ::core::ffi::c_int == '.' as ::core::ffi::c_int + && (*segment.offset(1isize) as ::core::ffi::c_int == '/' as ::core::ffi::c_int + || *segment.offset(1isize) as ::core::ffi::c_int == '\0' as ::core::ffi::c_int) + { + return -1 as ::core::ffi::c_int; + } + segment = strchr(segment, '/' as ::core::ffi::c_int); + if !segment.is_null() { + segment = segment.offset(1); + } + } + let mut length: size_t = strlen(relative); + if length < 5 as size_t + || strcmp( + relative + .offset(length as isize) + .offset(-(4 as ::core::ffi::c_int as isize)), + b".jpg\0".as_ptr() as *const ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + let mut n: ::core::ffi::c_int = snprintf( + out, + size, + b"%s/%s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut root as *mut ::core::ffi::c_char, + relative, + ); + return if n > 0 as ::core::ffi::c_int && (n as size_t) < size { + 0 as ::core::ffi::c_int + } else { + -1 as ::core::ffi::c_int + }; +} +unsafe extern "C" fn now_ms() -> int64_t { + let mut ts: timespec = timespec { + tv_sec: 0, + tv_nsec: 0, + }; + clock_gettime(CLOCK_REALTIME, &raw mut ts); + return ts.tv_sec as int64_t * 1000 as int64_t + ts.tv_nsec as int64_t / 1000000 as int64_t; +} +unsafe extern "C" fn retry_after_seconds( + mut asset: *const observation_asset_record_t, +) -> ::core::ffi::c_int { + let mut stats: observation_evidence_stats_t = observation_evidence_stats_t { + initialized: false, + worker_running: false, + allocated: false, + accepted: 0, + dropped_queue_full: 0, + dropped_budget: 0, + dropped_disabled: 0, + dropped_untracked: 0, + unavailable_submitted: 0, + ready: 0, + failed_encode: 0, + failed_write: 0, + failed_db: 0, + disabled_at_encode: 0, + interrupted: 0, + bytes_written: 0, + backlog_items: 0, + backlog_bytes: 0, + drop_ring_depth: 0, + high_water_items: 0, + high_water_bytes: 0, + budget_exhausted_cameras: 0, + limit_items: 0, + limit_camera_items: 0, + limit_bytes: 0, + limit_camera_bytes: 0, + camera_high_water_items: 0, + submit_latency_count: 0, + submit_latency_sum_ns: 0, + submit_latency_max_ns: 0, + submit_latency_bucket: [0; 10], + discard_deferred: 0, + discard_inline: 0, + discard_pending_bytes: 0, + limit_discard_bytes: 0, + }; + observation_evidence_get_stats(&raw mut stats); + return observation_snapshot_retry_after_seconds( + now_ms() - (*asset).created_at_ms, + stats.backlog_items as uint64_t, + ); +} +unsafe extern "C" fn send_evidence_unavailable( + mut res: *mut http_response_t, + mut reason: *const ::core::ffi::c_char, +) { + let mut body: *mut cJSON = error_body( + b"Evidence is unavailable\0".as_ptr() as *const ::core::ffi::c_char, + b"evidence_unavailable\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body.is_null() { + cJSON_AddStringToObject( + body, + b"reason\0".as_ptr() as *const ::core::ffi::c_char, + if !reason.is_null() && *reason.offset(0isize) as ::core::ffi::c_int != 0 { + reason + } else { + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + } + send_json(res, 404 as ::core::ffi::c_int, body); +} +unsafe extern "C" fn send_evidence_expired( + mut res: *mut http_response_t, + mut asset: *const observation_asset_record_t, +) { + let mut body: *mut cJSON = error_body( + b"Evidence media has expired\0".as_ptr() as *const ::core::ffi::c_char, + b"evidence_expired\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body.is_null() { + cJSON_AddStringToObject( + body, + b"reason\0".as_ptr() as *const ::core::ffi::c_char, + if (*asset).reason[0usize] as ::core::ffi::c_int != 0 { + &raw const (*asset).reason as *const ::core::ffi::c_char + } else { + b"retention_expired\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + cJSON_AddItemToObject( + body, + b"expired_at\0".as_ptr() as *const ::core::ffi::c_char, + time_or_null((*asset).updated_at_ms), + ); + } + send_json(res, 410 as ::core::ffi::c_int, body); +} +unsafe extern "C" fn etag_matches( + mut req: *const http_request_t, + mut etag: *const ::core::ffi::c_char, +) -> bool { + let mut header: *const ::core::ffi::c_char = http_request_get_header( + req, + b"If-None-Match\0".as_ptr() as *const ::core::ffi::c_char, + ); + if header.is_null() { + return r#false != 0; + } + while *header as ::core::ffi::c_int == ' ' as ::core::ffi::c_int { + header = header.offset(1); + } + if strcmp(header, b"*\0".as_ptr() as *const ::core::ffi::c_char) == 0 as ::core::ffi::c_int { + return r#true != 0; + } + return !strstr(header, etag).is_null(); +} +unsafe extern "C" fn serve_ready_snapshot( + mut req: *const http_request_t, + mut res: *mut http_response_t, + mut record: *const observation_record_t, +) { + let mut file: [::core::ffi::c_char; 512] = [0; 512]; + if resolve_asset_file( + &raw const (*record).asset.path as *const ::core::ffi::c_char, + &raw mut file as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + ) != 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_ERROR, + LOG_COMPONENT.as_ptr(), + _log_stream_name, + b"Observation asset %s has a path outside observation storage\0".as_ptr() + as *const ::core::ffi::c_char, + &raw const (*record).asset.uuid as *const ::core::ffi::c_char, + ); + send_internal(res); + return; + } + let mut st: stat = stat { + st_dev: 0, + st_ino: 0, + st_nlink: 0, + st_mode: 0, + st_uid: 0, + st_gid: 0, + __pad0: 0, + st_rdev: 0, + st_size: 0, + st_blksize: 0, + st_blocks: 0, + st_atim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + st_mtim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + st_ctim: timespec { + tv_sec: 0, + tv_nsec: 0, + }, + __glibc_reserved: [0; 3], + }; + if stat(&raw mut file as *mut ::core::ffi::c_char, &raw mut st) != 0 as ::core::ffi::c_int + || !(st.st_mode & __S_IFMT as __mode_t == 0o100000 as __mode_t) + { + let mut current: observation_asset_record_t = observation_asset_record_t { + uuid: [0; 37], + camera_uuid: [0; 37], + state: observation_asset_state_t::OBS_ASSET_PENDING, + reason: [0; 33], + origin: [0; 24], + media_type: [0; 24], + path: [0; 512], + has_bytes: false, + bytes: 0, + width: 0, + height: 0, + captured_at_ms: 0, + transform_json: [0; 1024], + is_protected: false, + expires_at_ms: 0, + created_at_ms: 0, + updated_at_ms: 0, + }; + if db_observation_asset_get( + &raw const (*record).asset.uuid as *const ::core::ffi::c_char, + &raw mut current, + ) == C2Rust_Unnamed::DB_OBS_OK.0 + && current.state.0 == observation_asset_state_t::OBS_ASSET_EXPIRED.0 + { + send_evidence_expired(res, &raw mut current); + return; + } + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT.as_ptr(), + _log_stream_name, + b"Ready observation asset %s has no media file\0".as_ptr() + as *const ::core::ffi::c_char, + &raw const (*record).asset.uuid as *const ::core::ffi::c_char, + ); + send_evidence_unavailable( + res, + b"write_failed\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut etag: [::core::ffi::c_char; 39] = [0; 39]; + snprintf( + &raw mut etag as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 39]>(), + b"\"%s\"\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*record).asset.uuid as *const ::core::ffi::c_char, + ); + if etag_matches(req, &raw mut etag as *mut ::core::ffi::c_char) { + (*res).status_code = 304 as ::core::ffi::c_int; + http_response_add_header( + res, + b"ETag\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut etag as *mut ::core::ffi::c_char, + ); + http_response_add_header( + res, + b"Cache-Control\0".as_ptr() as *const ::core::ffi::c_char, + OBS_SNAPSHOT_CACHE_CONTROL.as_ptr(), + ); + return; + } + let mut headers: [::core::ffi::c_char; 160] = [0; 160]; + snprintf( + &raw mut headers as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 160]>(), + b"Cache-Control: %s\r\nETag: %s\r\n\0".as_ptr() as *const ::core::ffi::c_char, + OBS_SNAPSHOT_CACHE_CONTROL.as_ptr(), + &raw mut etag as *mut ::core::ffi::c_char, + ); + if http_serve_file( + req, + res, + &raw mut file as *mut ::core::ffi::c_char, + b"image/jpeg\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut headers as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + send_internal(res); + } +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn handle_get_observation_snapshot( + mut req: *const http_request_t, + mut res: *mut http_response_t, +) { + let mut user: user_t = user_t { + id: 0, + username: [0; 64], + email: [0; 128], + role: user_role_t::USER_ROLE_ADMIN, + api_key: [0; 64], + created_at: 0, + updated_at: 0, + last_login: 0, + is_active: false, + password_change_locked: false, + must_change_password: false, + totp_enabled: false, + allowed_login_cidrs: [0; 1024], + has_login_cidr_restriction: false, + authorization_mode: [0; 16], + authenticated_via_scoped_token: false, + api_token_uuid: [0; 37], + authentication_method: [0; 24], + }; + if !authenticate(req, res, &raw mut user) { + return; + } + let mut record: *mut observation_record_t = + calloc(1 as size_t, ::core::mem::size_of::()) + as *mut observation_record_t; + if record.is_null() { + send_internal(res); + return; + } + let mut found: ::core::ffi::c_int = load_observation( + req, + res, + b"/snapshot\0".as_ptr() as *const ::core::ffi::c_char, + record, + ); + if found <= 0 as ::core::ffi::c_int { + if found == 0 as ::core::ffi::c_int { + send_not_found(res); + } + free(record as *mut ::core::ffi::c_void); + return; + } + let mut evaluation: authorization_evaluation_t = authorization_evaluation_t { + decision: authorization_decision_t::AUTHZ_DECISION_DENY, + source: authorization_decision_source_t::AUTHZ_SOURCE_NONE, + policy_version: 0, + grant_uuid: [0; 37], + token_uuid: [0; 37], + role_uuid: [0; 37], + role_name: [0; 128], + explanation: [0; 256], + }; + let mut rc: ::core::ffi::c_int = httpd_evaluate_camera_identity_action( + &raw mut user, + authorization_action_t::AUTHZ_SNAPSHOT_CREATE, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut evaluation, + ); + if rc < 0 as ::core::ffi::c_int { + audit_snapshot( + req, + &raw mut user, + record, + ::core::ptr::null::(), + b"error\0".as_ptr() as *const ::core::ffi::c_char, + ); + send_internal(res); + free(record as *mut ::core::ffi::c_void); + return; + } + if rc > 0 as ::core::ffi::c_int { + send_not_found(res); + free(record as *mut ::core::ffi::c_void); + return; + } + if evaluation.decision.0 != authorization_decision_t::AUTHZ_DECISION_ALLOW.0 { + audit_snapshot( + req, + &raw mut user, + record, + &raw mut evaluation, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut view: authorization_evaluation_t = authorization_evaluation_t { + decision: authorization_decision_t::AUTHZ_DECISION_DENY, + source: authorization_decision_source_t::AUTHZ_SOURCE_NONE, + policy_version: 0, + grant_uuid: [0; 37], + token_uuid: [0; 37], + role_uuid: [0; 37], + role_name: [0; 128], + explanation: [0; 256], + }; + let mut view_rc: ::core::ffi::c_int = httpd_evaluate_camera_identity_action( + &raw mut user, + authorization_action_t::AUTHZ_LIVE_VIEW, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut view, + ); + if view_rc < 0 as ::core::ffi::c_int { + send_internal(res); + } else if view_rc == 0 as ::core::ffi::c_int + && view.decision.0 == authorization_decision_t::AUTHZ_DECISION_ALLOW.0 + { + send_error( + res, + 403 as ::core::ffi::c_int, + b"forbidden\0".as_ptr() as *const ::core::ffi::c_char, + b"Forbidden\0".as_ptr() as *const ::core::ffi::c_char, + ); + } else { + send_not_found(res); + } + free(record as *mut ::core::ffi::c_void); + return; + } + audit_snapshot( + req, + &raw mut user, + record, + &raw mut evaluation, + b"allowed\0".as_ptr() as *const ::core::ffi::c_char, + ); + match (*record).asset.state { + observation_asset_state_t::OBS_ASSET_READY => { + serve_ready_snapshot(req, res, record); + } + observation_asset_state_t::OBS_ASSET_PENDING => { + let mut seconds: ::core::ffi::c_int = retry_after_seconds(&raw mut (*record).asset); + let mut header: [::core::ffi::c_char; 16] = [0; 16]; + snprintf( + &raw mut header as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 16]>(), + b"%d\0".as_ptr() as *const ::core::ffi::c_char, + seconds, + ); + http_response_add_header( + res, + b"Retry-After\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut header as *mut ::core::ffi::c_char, + ); + let mut body: *mut cJSON = error_body( + b"Evidence is not ready yet\0".as_ptr() as *const ::core::ffi::c_char, + b"evidence_pending\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body.is_null() { + cJSON_AddNumberToObject( + body, + b"retry_after_seconds\0".as_ptr() as *const ::core::ffi::c_char, + seconds as ::core::ffi::c_double, + ); + } + send_json(res, 202 as ::core::ffi::c_int, body); + } + observation_asset_state_t::OBS_ASSET_EXPIRED => { + send_evidence_expired(res, &raw mut (*record).asset); + } + observation_asset_state_t::OBS_ASSET_UNAVAILABLE | _ => { + send_evidence_unavailable( + res, + &raw mut (*record).asset.reason as *mut ::core::ffi::c_char, + ); + } + } + free(record as *mut ::core::ffi::c_void); +} +pub const OBS_PUT_PREFIX: [::core::ffi::c_char; 19] = unsafe { + ::core::mem::transmute::<[u8; 19], [::core::ffi::c_char; 19]>(*b"/api/observations/\0") +}; +pub const OBS_PUT_INFIX: [::core::ffi::c_char; 18] = unsafe { + ::core::mem::transmute::<[u8; 18], [::core::ffi::c_char; 18]>(*b"/classifications/\0") +}; +pub const OBS_ANNOTATE_ACTION: [::core::ffi::c_char; 22] = unsafe { + ::core::mem::transmute::<[u8; 22], [::core::ffi::c_char; 22]>(*b"observations.annotate\0") +}; +unsafe extern "C" fn copy_segment( + mut start: *const ::core::ffi::c_char, + mut length: size_t, + mut out: *mut ::core::ffi::c_char, +) { + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + if length == 0 as size_t || length > OBS_CLS_SEGMENT_MAX as size_t { + return; + } + memcpy( + out as *mut ::core::ffi::c_void, + start as *const ::core::ffi::c_void, + length, + ); + *out.offset(length as isize) = '\0' as ::core::ffi::c_char; + if !observation_classification_segment_is_valid(out) { + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + } +} +unsafe extern "C" fn parse_put_path( + mut path: *const ::core::ffi::c_char, + mut target: *mut put_target_t, +) -> bool { + memset( + target as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut prefix: size_t = strlen(OBS_PUT_PREFIX.as_ptr()); + let mut infix: size_t = strlen(OBS_PUT_INFIX.as_ptr()); + if path.is_null() || strncmp(path, OBS_PUT_PREFIX.as_ptr(), prefix) != 0 as ::core::ffi::c_int { + return r#false != 0; + } + let mut id: *const ::core::ffi::c_char = path.offset(prefix as isize); + let mut slash: *const ::core::ffi::c_char = strchr(id, '/' as ::core::ffi::c_int); + if slash.is_null() + || slash.offset_from(id) != (OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as isize + || strncmp(slash, OBS_PUT_INFIX.as_ptr(), infix) != 0 as ::core::ffi::c_int + { + return r#false != 0; + } + memcpy( + &raw mut (*target).id as *mut ::core::ffi::c_char as *mut ::core::ffi::c_void, + id as *const ::core::ffi::c_void, + (OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as size_t, + ); + (*target).id[(OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as usize] = '\0' as ::core::ffi::c_char; + lowercase(&raw mut (*target).id as *mut ::core::ffi::c_char); + if !lightnvr_uuid_is_valid(&raw mut (*target).id as *mut ::core::ffi::c_char) { + return r#false != 0; + } + let mut ns: *const ::core::ffi::c_char = slash.offset(infix as isize); + let mut separator: *const ::core::ffi::c_char = strchr(ns, '/' as ::core::ffi::c_int); + if separator.is_null() { + return r#false != 0; + } + copy_segment( + ns, + separator.offset_from(ns) as size_t, + &raw mut (*target).ns as *mut ::core::ffi::c_char, + ); + copy_segment( + separator.offset(1 as ::core::ffi::c_int as isize), + strlen(separator.offset(1 as ::core::ffi::c_int as isize)), + &raw mut (*target).name as *mut ::core::ffi::c_char, + ); + return r#true != 0; +} +unsafe extern "C" fn clean_utf8(mut text: *const ::core::ffi::c_char) -> bool { + let mut p: *const ::core::ffi::c_uchar = text as *const ::core::ffi::c_uchar; + while *p != 0 { + let mut c: ::core::ffi::c_uchar = *p; + let mut extra: ::core::ffi::c_int = 0; + let mut code: uint32_t = 0; + if (c as ::core::ffi::c_int) < 0x80 as ::core::ffi::c_int { + if (c as ::core::ffi::c_int) < 0x20 as ::core::ffi::c_int + || c as ::core::ffi::c_int == 0x7f as ::core::ffi::c_int + { + return r#false != 0; + } + p = p.offset(1); + } else { + if c as ::core::ffi::c_int & 0xe0 as ::core::ffi::c_int == 0xc0 as ::core::ffi::c_int { + extra = 1 as ::core::ffi::c_int; + code = (c as ::core::ffi::c_int & 0x1f as ::core::ffi::c_int) as uint32_t; + } else if c as ::core::ffi::c_int & 0xf0 as ::core::ffi::c_int + == 0xe0 as ::core::ffi::c_int + { + extra = 2 as ::core::ffi::c_int; + code = (c as ::core::ffi::c_int & 0xf as ::core::ffi::c_int) as uint32_t; + } else if c as ::core::ffi::c_int & 0xf8 as ::core::ffi::c_int + == 0xf0 as ::core::ffi::c_int + { + extra = 3 as ::core::ffi::c_int; + code = (c as ::core::ffi::c_int & 0x7 as ::core::ffi::c_int) as uint32_t; + } else { + return r#false != 0; + } + let mut i: ::core::ffi::c_int = 1 as ::core::ffi::c_int; + while i <= extra { + if *p.offset(i as isize) as ::core::ffi::c_int & 0xc0 as ::core::ffi::c_int + != 0x80 as ::core::ffi::c_int + { + return r#false != 0; + } + code = code << 6 as ::core::ffi::c_int + | (*p.offset(i as isize) as ::core::ffi::c_int & 0x3f as ::core::ffi::c_int) + as uint32_t; + i += 1; + } + static mut minimum: [uint32_t; 4] = [ + 0 as uint32_t, + 0x80 as uint32_t, + 0x800 as uint32_t, + 0x10000 as uint32_t, + ]; + if code < minimum[extra as usize] + || code > 0x10ffff as uint32_t + || code >= 0xd800 as uint32_t && code <= 0xdfff as uint32_t + { + return r#false != 0; + } + p = p.offset((extra + 1 as ::core::ffi::c_int) as isize); + } + } + return r#true != 0; +} +unsafe extern "C" fn body_text( + mut body: *const cJSON, + mut name: *const ::core::ffi::c_char, + mut required: bool, + mut max: size_t, + mut out: *mut ::core::ffi::c_char, + mut size: size_t, +) -> bool { + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + let mut item: *const cJSON = cJSON_GetObjectItemCaseSensitive(body, name); + if item.is_null() { + return !required; + } + if !required && cJSON_IsNull(item) != 0 { + return r#true != 0; + } + if cJSON_IsString(item) == 0 || (*item).valuestring.is_null() { + return r#false != 0; + } + let mut length: size_t = strlen((*item).valuestring); + if length > max + || required as ::core::ffi::c_int != 0 && length == 0 as size_t + || length >= size + { + return r#false != 0; + } + if !clean_utf8((*item).valuestring) { + return r#false != 0; + } + memcpy( + out as *mut ::core::ffi::c_void, + (*item).valuestring as *const ::core::ffi::c_void, + length.wrapping_add(1 as size_t), + ); + return r#true != 0; +} +unsafe extern "C" fn body_integer( + mut body: *const cJSON, + mut name: *const ::core::ffi::c_char, + mut minimum: ::core::ffi::c_int, + mut out: *mut ::core::ffi::c_int, +) -> bool { + let mut item: *const cJSON = cJSON_GetObjectItemCaseSensitive(body, name); + if cJSON_IsNumber(item) == 0 { + return r#false != 0; + } + let mut value: ::core::ffi::c_double = (*item).valuedouble; + if value.is_finite() as ::core::ffi::c_int == 0 + || value != floor(value) + || value < minimum as ::core::ffi::c_double + || value > INT_MAX as ::core::ffi::c_double + { + return r#false != 0; + } + *out = value as ::core::ffi::c_int; + return r#true != 0; +} +unsafe extern "C" fn parse_put_body( + mut body: *const cJSON, + mut out: *mut put_body_t, +) -> *const ::core::ffi::c_char { + static mut allowed: [*const ::core::ffi::c_char; 12] = [ + b"classifier\0".as_ptr() as *const ::core::ffi::c_char, + b"model_version\0".as_ptr() as *const ::core::ffi::c_char, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + b"taxonomy_id\0".as_ptr() as *const ::core::ffi::c_char, + b"confidence\0".as_ptr() as *const ::core::ffi::c_char, + b"classified_at\0".as_ptr() as *const ::core::ffi::c_char, + b"input_asset_id\0".as_ptr() as *const ::core::ffi::c_char, + b"input_revision\0".as_ptr() as *const ::core::ffi::c_char, + b"kind\0".as_ptr() as *const ::core::ffi::c_char, + b"expected_revision\0".as_ptr() as *const ::core::ffi::c_char, + b"writer_namespace\0".as_ptr() as *const ::core::ffi::c_char, + b"writer\0".as_ptr() as *const ::core::ffi::c_char, + ]; + memset( + out as *mut ::core::ffi::c_void, + 0 as ::core::ffi::c_int, + ::core::mem::size_of::(), + ); + let mut item: *const cJSON = ::core::ptr::null::(); + item = if !body.is_null() { + (*body).child + } else { + ::core::ptr::null_mut::() + }; + while !item.is_null() { + let mut known: bool = r#false != 0; + let mut i: size_t = 0 as size_t; + while !known + && i < ::core::mem::size_of::<[*const ::core::ffi::c_char; 12]>() + .wrapping_div(::core::mem::size_of::<*const ::core::ffi::c_char>()) + { + known = !(*item).string.is_null() + && strcmp((*item).string, allowed[i]) == 0 as ::core::ffi::c_int; + i = i.wrapping_add(1); + } + if !known { + return if !(*item).string.is_null() { + (*item).string + } else { + b"body\0".as_ptr() as *const ::core::ffi::c_char + }; + } + item = (*item).next; + } + if !body_text( + body, + b"classifier\0".as_ptr() as *const ::core::ffi::c_char, + r#true != 0, + (OBS_CLS_CLASSIFIER_SIZE - 1 as ::core::ffi::c_int) as size_t, + &raw mut (*out).classifier as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ) { + return b"classifier\0".as_ptr() as *const ::core::ffi::c_char; + } + if !body_text( + body, + b"model_version\0".as_ptr() as *const ::core::ffi::c_char, + r#false != 0, + (OBS_CLS_MODEL_VERSION_SIZE - 1 as ::core::ffi::c_int) as size_t, + &raw mut (*out).model_version as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ) { + return b"model_version\0".as_ptr() as *const ::core::ffi::c_char; + } + if !body_text( + body, + b"label\0".as_ptr() as *const ::core::ffi::c_char, + r#true != 0, + (OBS_CLS_LABEL_SIZE - 1 as ::core::ffi::c_int) as size_t, + &raw mut (*out).label as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 129]>(), + ) { + return b"label\0".as_ptr() as *const ::core::ffi::c_char; + } + if !body_text( + body, + b"taxonomy_id\0".as_ptr() as *const ::core::ffi::c_char, + r#false != 0, + (OBS_CLS_TAXONOMY_SIZE - 1 as ::core::ffi::c_int) as size_t, + &raw mut (*out).taxonomy_id as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 65]>(), + ) { + return b"taxonomy_id\0".as_ptr() as *const ::core::ffi::c_char; + } + item = cJSON_GetObjectItemCaseSensitive( + body, + b"confidence\0".as_ptr() as *const ::core::ffi::c_char, + ); + if cJSON_IsNumber(item) == 0 + || (*item).valuedouble.is_finite() as ::core::ffi::c_int == 0 + || (*item).valuedouble < 0.0f64 + || (*item).valuedouble > 1.0f64 + { + return b"confidence\0".as_ptr() as *const ::core::ffi::c_char; + } + (*out).confidence = (*item).valuedouble; + item = cJSON_GetObjectItemCaseSensitive( + body, + b"classified_at\0".as_ptr() as *const ::core::ffi::c_char, + ); + if cJSON_IsString(item) == 0 + || observation_parse_time((*item).valuestring, &raw mut (*out).classified_at_ms) + != 0 as ::core::ffi::c_int + { + return b"classified_at\0".as_ptr() as *const ::core::ffi::c_char; + } + item = cJSON_GetObjectItemCaseSensitive( + body, + b"input_asset_id\0".as_ptr() as *const ::core::ffi::c_char, + ); + if cJSON_IsString(item) == 0 + || strlen((*item).valuestring) != (OBS_UUID_SIZE - 1 as ::core::ffi::c_int) as size_t + { + return b"input_asset_id\0".as_ptr() as *const ::core::ffi::c_char; + } + memcpy( + &raw mut (*out).input_asset_uuid as *mut ::core::ffi::c_char as *mut ::core::ffi::c_void, + (*item).valuestring as *const ::core::ffi::c_void, + OBS_UUID_SIZE as size_t, + ); + lowercase(&raw mut (*out).input_asset_uuid as *mut ::core::ffi::c_char); + if !lightnvr_uuid_is_valid(&raw mut (*out).input_asset_uuid as *mut ::core::ffi::c_char) { + return b"input_asset_id\0".as_ptr() as *const ::core::ffi::c_char; + } + if !body_integer( + body, + b"input_revision\0".as_ptr() as *const ::core::ffi::c_char, + 1 as ::core::ffi::c_int, + &raw mut (*out).input_revision, + ) { + return b"input_revision\0".as_ptr() as *const ::core::ffi::c_char; + } + item = cJSON_GetObjectItemCaseSensitive(body, b"kind\0".as_ptr() as *const ::core::ffi::c_char); + if cJSON_IsString(item) == 0 + || observation_classification_kind_parse((*item).valuestring, &raw mut (*out).kind) + != 0 as ::core::ffi::c_int + { + return b"kind\0".as_ptr() as *const ::core::ffi::c_char; + } + (*out).has_kind = r#true != 0; + if !cJSON_GetObjectItemCaseSensitive( + body, + b"expected_revision\0".as_ptr() as *const ::core::ffi::c_char, + ) + .is_null() + && !body_integer( + body, + b"expected_revision\0".as_ptr() as *const ::core::ffi::c_char, + 0 as ::core::ffi::c_int, + &raw mut (*out).expected_revision, + ) + { + return b"expected_revision\0".as_ptr() as *const ::core::ffi::c_char; + } + return ::core::ptr::null::<::core::ffi::c_char>(); +} +unsafe extern "C" fn audit_annotation( + mut audit: *const annotate_audit_t, + mut outcome: *const ::core::ffi::c_char, + mut result: *const ::core::ffi::c_char, + mut before: ::core::ffi::c_int, + mut after: ::core::ffi::c_int, +) { + let mut context: *mut cJSON = cJSON_CreateObject(); + if !context.is_null() { + cJSON_AddItemToObject( + context, + b"camera_uuid\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null((*audit).camera_uuid), + ); + cJSON_AddItemToObject( + context, + b"key\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null(&raw const (*audit).key as *const ::core::ffi::c_char), + ); + cJSON_AddItemToObject( + context, + b"kind\0".as_ptr() as *const ::core::ffi::c_char, + text_or_null((*audit).kind), + ); + cJSON_AddStringToObject( + context, + b"result\0".as_ptr() as *const ::core::ffi::c_char, + result, + ); + if before >= 0 as ::core::ffi::c_int { + cJSON_AddNumberToObject( + context, + b"annotation_revision_before\0".as_ptr() as *const ::core::ffi::c_char, + before as ::core::ffi::c_double, + ); + } else { + cJSON_AddNullToObject( + context, + b"annotation_revision_before\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + if after >= 0 as ::core::ffi::c_int { + cJSON_AddNumberToObject( + context, + b"annotation_revision_after\0".as_ptr() as *const ::core::ffi::c_char, + after as ::core::ffi::c_double, + ); + } else { + cJSON_AddNullToObject( + context, + b"annotation_revision_after\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + } + audit_log_operation( + (*audit).req, + (*audit).user, + OBS_ANNOTATE_ACTION.as_ptr(), + b"observation\0".as_ptr() as *const ::core::ffi::c_char, + (*audit).observation_uuid, + b"annotate\0".as_ptr() as *const ::core::ffi::c_char, + outcome, + context, + ); + cJSON_Delete(context); +} +unsafe extern "C" fn send_annotation_error( + mut res: *mut http_response_t, + mut status: ::core::ffi::c_int, + mut code: *const ::core::ffi::c_char, + mut message: *const ::core::ffi::c_char, + mut result: *const observation_classification_result_t, +) { + let mut body: *mut cJSON = error_body(message, code); + if !body.is_null() && !result.is_null() && (*result).has_annotation as ::core::ffi::c_int != 0 { + let mut current: *mut cJSON = + observation_classification_to_json(&raw const (*result).annotation); + if !current.is_null() { + cJSON_AddItemToObject( + body, + b"current\0".as_ptr() as *const ::core::ffi::c_char, + current, + ); + } + } + send_json(res, status, body); +} +unsafe extern "C" fn respond_to_put( + mut audit: *const annotate_audit_t, + mut res: *mut http_response_t, + mut rc: ::core::ffi::c_int, + mut result: *const observation_classification_result_t, +) { + let mut stored: ::core::ffi::c_int = if (*result).has_annotation as ::core::ffi::c_int != 0 { + (*result).annotation.annotation_revision + } else { + -1 as ::core::ffi::c_int + }; + match rc { + 0 | 1 | 2 => { + let mut changed: bool = rc != C2Rust_Unnamed_0::DB_OBS_CLS_NOOP.0; + let mut body: *mut cJSON = cJSON_CreateObject(); + let mut annotation: *mut cJSON = + observation_classification_to_json(&raw const (*result).annotation); + if body.is_null() || annotation.is_null() { + cJSON_Delete(body); + cJSON_Delete(annotation); + audit_annotation( + audit, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_internal(res); + return; + } + cJSON_AddBoolToObject( + body, + b"changed\0".as_ptr() as *const ::core::ffi::c_char, + changed as cJSON_bool, + ); + cJSON_AddNumberToObject( + body, + b"observation_revision\0".as_ptr() as *const ::core::ffi::c_char, + (*result).observation_revision as ::core::ffi::c_double, + ); + cJSON_AddItemToObject( + body, + b"annotation\0".as_ptr() as *const ::core::ffi::c_char, + annotation, + ); + let mut name: *const ::core::ffi::c_char = + if rc == C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 { + b"created\0".as_ptr() as *const ::core::ffi::c_char + } else if rc == C2Rust_Unnamed_0::DB_OBS_CLS_UPDATED.0 { + b"updated\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"noop\0".as_ptr() as *const ::core::ffi::c_char + }; + let mut before: ::core::ffi::c_int = if rc == C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 { + -1 as ::core::ffi::c_int + } else if rc == C2Rust_Unnamed_0::DB_OBS_CLS_UPDATED.0 { + stored - 1 as ::core::ffi::c_int + } else { + stored + }; + audit_annotation( + audit, + b"success\0".as_ptr() as *const ::core::ffi::c_char, + name, + before, + stored, + ); + send_json( + res, + if rc == C2Rust_Unnamed_0::DB_OBS_CLS_CREATED.0 { + 201 as ::core::ffi::c_int + } else { + 200 as ::core::ffi::c_int + }, + body, + ); + return; + } + 3 => { + audit_annotation( + audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"revision_conflict\0".as_ptr() as *const ::core::ffi::c_char, + stored, + stored, + ); + send_annotation_error( + res, + 409 as ::core::ffi::c_int, + b"revision_conflict\0".as_ptr() as *const ::core::ffi::c_char, + b"Annotation revision does not match\0".as_ptr() as *const ::core::ffi::c_char, + result, + ); + return; + } + 4 => { + audit_annotation( + audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"owner_decision_locked\0".as_ptr() as *const ::core::ffi::c_char, + stored, + stored, + ); + send_annotation_error( + res, + 409 as ::core::ffi::c_int, + b"owner_decision_locked\0".as_ptr() as *const ::core::ffi::c_char, + b"An owner-confirmed annotation cannot be replaced by an automated one\0".as_ptr() + as *const ::core::ffi::c_char, + result, + ); + return; + } + 5 => { + audit_annotation( + audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"evidence_mismatch\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + let mut body_0: *mut cJSON = error_body( + b"Annotation input does not match the observation evidence\0".as_ptr() + as *const ::core::ffi::c_char, + b"evidence_mismatch\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body_0.is_null() { + cJSON_AddStringToObject( + body_0, + b"observation_asset_id\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*result).observation_asset_uuid as *const ::core::ffi::c_char, + ); + cJSON_AddStringToObject( + body_0, + b"evidence_state\0".as_ptr() as *const ::core::ffi::c_char, + observation_asset_state_name((*result).evidence_state), + ); + } + send_json(res, 422 as ::core::ffi::c_int, body_0); + return; + } + 6 => { + audit_annotation( + audit, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + b"namespace_forbidden\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_error( + res, + 403 as ::core::ffi::c_int, + b"namespace_forbidden\0".as_ptr() as *const ::core::ffi::c_char, + b"Writer namespace not permitted\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + 7 => { + audit_annotation( + audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"annotation_limit\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + let mut body_1: *mut cJSON = error_body( + b"Annotation limit reached for this observation\0".as_ptr() + as *const ::core::ffi::c_char, + b"annotation_limit\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !body_1.is_null() { + cJSON_AddNumberToObject( + body_1, + b"limit\0".as_ptr() as *const ::core::ffi::c_char, + (*result).limit as ::core::ffi::c_double, + ); + } + send_json(res, 409 as ::core::ffi::c_int, body_1); + return; + } + -2 => { + audit_annotation( + audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_invalid( + res, + ::core::ptr::null::<::core::ffi::c_char>(), + b"Invalid annotation\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + -3 => { + audit_annotation( + audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"observation_not_found\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_not_found(res); + return; + } + _ => { + audit_annotation( + audit, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_internal(res); + return; + } + }; +} +unsafe extern "C" fn authorize_annotation( + mut audit: *const annotate_audit_t, + mut res: *mut http_response_t, + mut user: *const user_t, + mut camera_uuid: *const ::core::ffi::c_char, +) -> bool { + let mut evaluation: authorization_evaluation_t = authorization_evaluation_t { + decision: authorization_decision_t::AUTHZ_DECISION_DENY, + source: authorization_decision_source_t::AUTHZ_SOURCE_NONE, + policy_version: 0, + grant_uuid: [0; 37], + token_uuid: [0; 37], + role_uuid: [0; 37], + role_name: [0; 128], + explanation: [0; 256], + }; + let mut rc: ::core::ffi::c_int = httpd_evaluate_camera_identity_action( + user, + authorization_action_t::AUTHZ_OBSERVATIONS_ANNOTATE, + camera_uuid, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut evaluation, + ); + if rc == 0 as ::core::ffi::c_int + && evaluation.decision.0 == authorization_decision_t::AUTHZ_DECISION_ALLOW.0 + { + return r#true != 0; + } + if rc < 0 as ::core::ffi::c_int { + audit_annotation( + audit, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_internal(res); + return r#false != 0; + } + if rc > 0 as ::core::ffi::c_int { + audit_annotation( + audit, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + b"observation_not_found\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_not_found(res); + return r#false != 0; + } + let mut view: authorization_evaluation_t = authorization_evaluation_t { + decision: authorization_decision_t::AUTHZ_DECISION_DENY, + source: authorization_decision_source_t::AUTHZ_SOURCE_NONE, + policy_version: 0, + grant_uuid: [0; 37], + token_uuid: [0; 37], + role_uuid: [0; 37], + role_name: [0; 128], + explanation: [0; 256], + }; + let mut view_rc: ::core::ffi::c_int = httpd_evaluate_camera_identity_action( + user, + authorization_action_t::AUTHZ_LIVE_VIEW, + camera_uuid, + ::core::ptr::null::<::core::ffi::c_char>(), + &raw mut view, + ); + if view_rc < 0 as ::core::ffi::c_int { + audit_annotation( + audit, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_internal(res); + } else if view_rc == 0 as ::core::ffi::c_int + && view.decision.0 == authorization_decision_t::AUTHZ_DECISION_ALLOW.0 + { + audit_annotation( + audit, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + b"forbidden\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_error( + res, + 403 as ::core::ffi::c_int, + b"forbidden\0".as_ptr() as *const ::core::ffi::c_char, + b"Forbidden\0".as_ptr() as *const ::core::ffi::c_char, + ); + } else { + audit_annotation( + audit, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + b"observation_not_found\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_not_found(res); + } + return r#false != 0; +} +unsafe extern "C" fn body_names_other_writer( + mut body: *const cJSON, + mut field: *const ::core::ffi::c_char, + mut writer: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + let mut item: *const cJSON = cJSON_GetObjectItemCaseSensitive(body, field); + if item.is_null() { + return 0 as ::core::ffi::c_int; + } + if cJSON_IsString(item) == 0 || (*item).valuestring.is_null() { + return -1 as ::core::ffi::c_int; + } + return if strcmp((*item).valuestring, writer) == 0 as ::core::ffi::c_int { + 0 as ::core::ffi::c_int + } else { + 1 as ::core::ffi::c_int + }; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn handle_put_observation_classification( + mut req: *const http_request_t, + mut res: *mut http_response_t, +) { + let mut user: user_t = user_t { + id: 0, + username: [0; 64], + email: [0; 128], + role: user_role_t::USER_ROLE_ADMIN, + api_key: [0; 64], + created_at: 0, + updated_at: 0, + last_login: 0, + is_active: false, + password_change_locked: false, + must_change_password: false, + totp_enabled: false, + allowed_login_cidrs: [0; 1024], + has_login_cidr_restriction: false, + authorization_mode: [0; 16], + authenticated_via_scoped_token: false, + api_token_uuid: [0; 37], + authentication_method: [0; 24], + }; + if !authenticate(req, res, &raw mut user) { + return; + } + let mut source: [::core::ffi::c_char; 128] = [0; 128]; + if !installation_source(&raw mut source as *mut ::core::ffi::c_char) { + send_unavailable(res); + return; + } + let mut target: put_target_t = put_target_t { + id: [0; 37], + ns: [0; 33], + name: [0; 33], + }; + if !parse_put_path( + &raw const (*req).path as *const ::core::ffi::c_char, + &raw mut target, + ) { + send_invalid( + res, + b"id\0".as_ptr() as *const ::core::ffi::c_char, + b"Observation ID must be a UUID\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut audit: annotate_audit_t = annotate_audit_t { + req: req, + user: &raw mut user, + observation_uuid: &raw mut target.id as *mut ::core::ffi::c_char, + camera_uuid: ::core::ptr::null::<::core::ffi::c_char>(), + key: [0; 66], + kind: ::core::ptr::null::<::core::ffi::c_char>(), + }; + if target.ns[0usize] as ::core::ffi::c_int != 0 + && target.name[0usize] as ::core::ffi::c_int != 0 + { + snprintf( + &raw mut audit.key as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 66]>(), + b"%s/%s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut target.ns as *mut ::core::ffi::c_char, + &raw mut target.name as *mut ::core::ffi::c_char, + ); + } + let mut record: *mut observation_record_t = + calloc(1 as size_t, ::core::mem::size_of::()) + as *mut observation_record_t; + if record.is_null() { + send_internal(res); + return; + } + let mut rc: ::core::ffi::c_int = + db_observation_get(&raw mut target.id as *mut ::core::ffi::c_char, record); + if rc != C2Rust_Unnamed::DB_OBS_OK.0 { + if rc == C2Rust_Unnamed::DB_OBS_NOT_FOUND.0 { + audit_annotation( + &raw mut audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"observation_not_found\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_not_found(res); + } else { + audit_annotation( + &raw mut audit, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_internal(res); + } + free(record as *mut ::core::ffi::c_void); + return; + } + let mut camera_uuid: [::core::ffi::c_char; 37] = [0; 37]; + safe_strcpy( + &raw mut camera_uuid as *mut ::core::ffi::c_char, + &raw mut (*record).camera_uuid as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 37]>(), + 0 as size_t, + ); + free(record as *mut ::core::ffi::c_void); + audit.camera_uuid = &raw mut camera_uuid as *mut ::core::ffi::c_char; + if !authorize_annotation( + &raw mut audit, + res, + &raw mut user, + &raw mut camera_uuid as *mut ::core::ffi::c_char, + ) { + return; + } + let mut writer: [::core::ffi::c_char; 33] = [0; 33]; + if observation_writer_namespace( + &raw mut user.username as *mut ::core::ffi::c_char, + &raw mut writer as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + || target.ns[0usize] == 0 + || strcmp( + &raw mut target.ns as *mut ::core::ffi::c_char, + &raw mut writer as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int + { + audit_annotation( + &raw mut audit, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + b"namespace_forbidden\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_error( + res, + 403 as ::core::ffi::c_int, + b"namespace_forbidden\0".as_ptr() as *const ::core::ffi::c_char, + b"Writer namespace not permitted\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + if (*req).body.is_null() + || (*req).body_len == 0 as size_t + || (*req).body_len > OBS_PUT_BODY_MAX as size_t + { + audit_annotation( + &raw mut audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_invalid( + res, + b"body\0".as_ptr() as *const ::core::ffi::c_char, + if (*req).body_len > OBS_PUT_BODY_MAX as size_t { + b"Request body is too large\0".as_ptr() as *const ::core::ffi::c_char + } else { + b"Request body is required\0".as_ptr() as *const ::core::ffi::c_char + }, + ); + return; + } + let mut body: *mut cJSON = httpd_parse_json_body(req); + if cJSON_IsObject(body) == 0 { + cJSON_Delete(body); + audit_annotation( + &raw mut audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_invalid( + res, + b"body\0".as_ptr() as *const ::core::ffi::c_char, + b"Request body must be a JSON object\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + let mut writer_fields: [*const ::core::ffi::c_char; 2] = [ + b"writer_namespace\0".as_ptr() as *const ::core::ffi::c_char, + b"writer\0".as_ptr() as *const ::core::ffi::c_char, + ]; + let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + while i < 2 as ::core::ffi::c_int { + let mut other: ::core::ffi::c_int = body_names_other_writer( + body, + writer_fields[i as usize], + &raw mut writer as *mut ::core::ffi::c_char, + ); + if other == 1 as ::core::ffi::c_int { + cJSON_Delete(body); + audit_annotation( + &raw mut audit, + b"denied\0".as_ptr() as *const ::core::ffi::c_char, + b"namespace_forbidden\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_error( + res, + 403 as ::core::ffi::c_int, + b"namespace_forbidden\0".as_ptr() as *const ::core::ffi::c_char, + b"Writer namespace not permitted\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + if other < 0 as ::core::ffi::c_int { + cJSON_Delete(body); + audit_annotation( + &raw mut audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_invalid( + res, + writer_fields[i as usize], + b"Writer fields must be strings\0".as_ptr() as *const ::core::ffi::c_char, + ); + return; + } + i += 1; + } + if target.name[0usize] == 0 { + cJSON_Delete(body); + audit_annotation( + &raw mut audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_invalid( + res, + b"name\0".as_ptr() as *const ::core::ffi::c_char, + b"Annotation name must match ^[a-z0-9][a-z0-9_.-]{0,31}$\0".as_ptr() + as *const ::core::ffi::c_char, + ); + return; + } + let mut fields: put_body_t = put_body_t { + classifier: [0; 65], + model_version: [0; 65], + label: [0; 129], + taxonomy_id: [0; 65], + confidence: 0., + classified_at_ms: 0, + input_asset_uuid: [0; 37], + input_revision: 0, + kind: observation_classification_kind_t::OBS_CLS_KIND_AUTOMATED, + has_kind: false, + expected_revision: 0, + }; + let mut invalid: *const ::core::ffi::c_char = parse_put_body(body, &raw mut fields); + if fields.has_kind { + audit.kind = observation_classification_kind_name(fields.kind); + } + if !invalid.is_null() { + let mut field: [::core::ffi::c_char; 64] = [0; 64]; + let mut message: [::core::ffi::c_char; 128] = [0; 128]; + safe_strcpy( + &raw mut field as *mut ::core::ffi::c_char, + invalid, + ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + 0 as size_t, + ); + cJSON_Delete(body); + snprintf( + &raw mut message as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 128]>(), + b"Invalid or unknown field: %s\0".as_ptr() as *const ::core::ffi::c_char, + &raw mut field as *mut ::core::ffi::c_char, + ); + audit_annotation( + &raw mut audit, + b"failure\0".as_ptr() as *const ::core::ffi::c_char, + b"invalid_request\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_invalid( + res, + &raw mut field as *mut ::core::ffi::c_char, + &raw mut message as *mut ::core::ffi::c_char, + ); + return; + } + cJSON_Delete(body); + let mut input: observation_classification_input_t = observation_classification_input_t { + writer_namespace: &raw mut writer as *mut ::core::ffi::c_char, + writer_user_id: user.id, + name: &raw mut target.name as *mut ::core::ffi::c_char, + classifier: &raw mut fields.classifier as *mut ::core::ffi::c_char, + model_version: &raw mut fields.model_version as *mut ::core::ffi::c_char, + label: &raw mut fields.label as *mut ::core::ffi::c_char, + taxonomy_id: &raw mut fields.taxonomy_id as *mut ::core::ffi::c_char, + confidence: fields.confidence, + classified_at_ms: fields.classified_at_ms, + input_asset_uuid: &raw mut fields.input_asset_uuid as *mut ::core::ffi::c_char, + input_revision: fields.input_revision, + kind: fields.kind, + expected_revision: fields.expected_revision, + }; + let mut result: *mut observation_classification_result_t = calloc( + 1 as size_t, + ::core::mem::size_of::(), + ) + as *mut observation_classification_result_t; + if result.is_null() { + audit_annotation( + &raw mut audit, + b"error\0".as_ptr() as *const ::core::ffi::c_char, + b"internal_error\0".as_ptr() as *const ::core::ffi::c_char, + -1 as ::core::ffi::c_int, + -1 as ::core::ffi::c_int, + ); + send_internal(res); + return; + } + rc = db_observation_classification_put( + &raw mut target.id as *mut ::core::ffi::c_char, + &raw mut input, + result, + ); + respond_to_put(&raw mut audit, res, rc, result); + free(result as *mut ::core::ffi::c_void); +} +pub const LIGHTNVR_MAX_STREAMS: ::core::ffi::c_int = 1024 as ::core::ffi::c_int; +pub const __INT_MAX__: ::core::ffi::c_int = 2147483647 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_onvif_backend_agnostic.rs b/rust/generated/src__web__api_handlers_onvif_backend_agnostic.rs index 7fb70ff32..79a901f17 100644 --- a/rust/generated/src__web__api_handlers_onvif_backend_agnostic.rs +++ b/rust/generated/src__web__api_handlers_onvif_backend_agnostic.rs @@ -51,23 +51,6 @@ unsafe extern "C" { status_code: ::core::ffi::c_int, error_message: *const ::core::ffi::c_char, ) -> ::core::ffi::c_int; - unsafe fn httpd_authorize_global_action( - req: *const http_request_t, - res: *mut http_response_t, - action: authorization_action_t, - ) -> ::core::ffi::c_int; - unsafe fn _log_message_ctx( - level: log_level_t, - component: *const ::core::ffi::c_char, - stream: *const ::core::ffi::c_char, - format: *const ::core::ffi::c_char, - ... - ); - unsafe fn url_strip_credentials( - url: *const ::core::ffi::c_char, - out_url: *mut ::core::ffi::c_char, - out_size: size_t, - ) -> ::core::ffi::c_int; unsafe fn get_discovered_onvif_devices( devices: *mut onvif_device_info_t, max_devices: ::core::ffi::c_int, @@ -91,10 +74,29 @@ unsafe extern "C" { devices: *mut onvif_device_info_t, max_devices: ::core::ffi::c_int, ) -> ::core::ffi::c_int; - unsafe fn test_onvif_connection( + unsafe fn test_onvif_connection_detail( url: *const ::core::ffi::c_char, username: *const ::core::ffi::c_char, password: *const ::core::ffi::c_char, + detail: *mut ::core::ffi::c_char, + detail_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn httpd_authorize_global_action( + req: *const http_request_t, + res: *mut http_response_t, + action: authorization_action_t, + ) -> ::core::ffi::c_int; + unsafe fn _log_message_ctx( + level: log_level_t, + component: *const ::core::ffi::c_char, + stream: *const ::core::ffi::c_char, + format: *const ::core::ffi::c_char, + ... + ); + unsafe fn url_strip_credentials( + url: *const ::core::ffi::c_char, + out_url: *mut ::core::ffi::c_char, + out_size: size_t, ) -> ::core::ffi::c_int; unsafe fn db_onvif_inventory_list_addresses( inventory_uuid: *const ::core::ffi::c_char, @@ -130,6 +132,7 @@ unsafe extern "C" { pub type size_t = usize; pub type __time_t = ::core::ffi::c_long; pub type time_t = __time_t; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct cJSON { @@ -228,6 +231,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -333,7 +340,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -408,41 +415,6 @@ pub struct http_response_t { pub num_headers: ::core::ffi::c_int, pub user_data: *mut ::core::ffi::c_void, } -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct log_level_t(pub ::core::ffi::c_uint); -impl log_level_t { - pub const LOG_LEVEL_ERROR: Self = Self(0); - pub const LOG_LEVEL_WARN: Self = Self(1); - pub const LOG_LEVEL_INFO: Self = Self(2); - pub const LOG_LEVEL_DEBUG: Self = Self(3); -} -#[derive(Clone, Copy, PartialEq, Eq)] -#[repr(transparent)] -pub struct authorization_action_t(pub ::core::ffi::c_int); -impl authorization_action_t { - pub const AUTHZ_ACTION_INVALID: Self = Self(-1); - pub const AUTHZ_LIVE_VIEW: Self = Self(0); - pub const AUTHZ_AUDIO_LISTEN: Self = Self(1); - pub const AUTHZ_AUDIO_TALK: Self = Self(2); - pub const AUTHZ_RECORDINGS_REPLAY: Self = Self(3); - pub const AUTHZ_RECORDINGS_EXPORT: Self = Self(4); - pub const AUTHZ_SNAPSHOT_CREATE: Self = Self(5); - pub const AUTHZ_PTZ_CONTROL: Self = Self(6); - pub const AUTHZ_EVIDENCE_PROTECT: Self = Self(7); - pub const AUTHZ_RECORDING_DELETE: Self = Self(8); - pub const AUTHZ_CAMERA_CONFIGURE: Self = Self(9); - pub const AUTHZ_FLEET_EXECUTE_JOB: Self = Self(10); - pub const AUTHZ_STORAGE_CONFIGURE: Self = Self(11); - pub const AUTHZ_EVENTS_CONFIGURE: Self = Self(12); - pub const AUTHZ_USERS_MANAGE: Self = Self(13); - pub const AUTHZ_SYSTEM_ADMIN: Self = Self(14); - pub const AUTHZ_LPR_READ: Self = Self(15); - pub const AUTHZ_LPR_SEARCH: Self = Self(16); - pub const AUTHZ_LPR_EXPORT: Self = Self(17); - pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); -} #[derive(Copy, Clone)] #[repr(C)] pub struct onvif_device_info_t { @@ -485,6 +457,52 @@ pub struct onvif_profile_t { } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] +pub struct C2Rust_Unnamed(pub ::core::ffi::c_int); +impl C2Rust_Unnamed { + pub const ONVIF_TEST_OK: Self = Self(0); + pub const ONVIF_TEST_DEVICE_ERROR: Self = Self(-1); + pub const ONVIF_TEST_UNREACHABLE: Self = Self(-2); + pub const ONVIF_TEST_AUTH_FAILED: Self = Self(-3); + pub const ONVIF_TEST_STREAM_FAILED: Self = Self(-4); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct log_level_t(pub ::core::ffi::c_uint); +impl log_level_t { + pub const LOG_LEVEL_ERROR: Self = Self(0); + pub const LOG_LEVEL_WARN: Self = Self(1); + pub const LOG_LEVEL_INFO: Self = Self(2); + pub const LOG_LEVEL_DEBUG: Self = Self(3); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct authorization_action_t(pub ::core::ffi::c_int); +impl authorization_action_t { + pub const AUTHZ_ACTION_INVALID: Self = Self(-1); + pub const AUTHZ_LIVE_VIEW: Self = Self(0); + pub const AUTHZ_AUDIO_LISTEN: Self = Self(1); + pub const AUTHZ_AUDIO_TALK: Self = Self(2); + pub const AUTHZ_RECORDINGS_REPLAY: Self = Self(3); + pub const AUTHZ_RECORDINGS_EXPORT: Self = Self(4); + pub const AUTHZ_SNAPSHOT_CREATE: Self = Self(5); + pub const AUTHZ_PTZ_CONTROL: Self = Self(6); + pub const AUTHZ_EVIDENCE_PROTECT: Self = Self(7); + pub const AUTHZ_RECORDING_DELETE: Self = Self(8); + pub const AUTHZ_CAMERA_CONFIGURE: Self = Self(9); + pub const AUTHZ_FLEET_EXECUTE_JOB: Self = Self(10); + pub const AUTHZ_STORAGE_CONFIGURE: Self = Self(11); + pub const AUTHZ_EVENTS_CONFIGURE: Self = Self(12); + pub const AUTHZ_USERS_MANAGE: Self = Self(13); + pub const AUTHZ_SYSTEM_ADMIN: Self = Self(14); + pub const AUTHZ_LPR_READ: Self = Self(15); + pub const AUTHZ_LPR_SEARCH: Self = Self(16); + pub const AUTHZ_LPR_EXPORT: Self = Self(17); + pub const AUTHZ_LPR_DELETE: Self = Self(18); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); +} +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] pub struct db_onvif_inventory_result_t(pub ::core::ffi::c_int); impl db_onvif_inventory_result_t { pub const DB_ONVIF_INVENTORY_OK: Self = Self(0); @@ -2153,19 +2171,33 @@ pub unsafe extern "C" fn handle_post_test_onvif_connection( ); return; } - let mut result: ::core::ffi::c_int = test_onvif_connection(url, username, password); + let mut detail: [::core::ffi::c_char; 1024] = [0; 1024]; + let mut result: ::core::ffi::c_int = test_onvif_connection_detail( + url, + username, + password, + &raw mut detail as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 1024]>(), + ); cJSON_Delete(root); - if result != 0 as ::core::ffi::c_int { + if result != C2Rust_Unnamed::ONVIF_TEST_OK.0 { _log_message_ctx( log_level_t::LOG_LEVEL_ERROR, LOG_COMPONENT.as_ptr(), _log_stream_name, - b"Failed to connect to ONVIF device\0".as_ptr() as *const ::core::ffi::c_char, + b"[ONVIFAPI] ONVIF connection test failed (%d): %s\0".as_ptr() + as *const ::core::ffi::c_char, + result, + &raw mut detail as *mut ::core::ffi::c_char, ); http_response_set_json_error( res, - 500 as ::core::ffi::c_int, - b"Failed to connect to ONVIF device\0".as_ptr() as *const ::core::ffi::c_char, + 502 as ::core::ffi::c_int, + if detail[0usize] as ::core::ffi::c_int != 0 { + &raw mut detail as *mut ::core::ffi::c_char + } else { + b"Failed to connect to ONVIF device\0".as_ptr() as *const ::core::ffi::c_char + }, ); return; } @@ -2192,7 +2224,11 @@ pub unsafe extern "C" fn handle_post_test_onvif_connection( cJSON_AddStringToObject( response_json, b"message\0".as_ptr() as *const ::core::ffi::c_char, - b"Successfully connected to ONVIF device\0".as_ptr() as *const ::core::ffi::c_char, + if detail[0usize] as ::core::ffi::c_int != 0 { + &raw mut detail as *mut ::core::ffi::c_char + } else { + b"Successfully connected to ONVIF device\0".as_ptr() as *const ::core::ffi::c_char + }, ); let mut json_str: *mut ::core::ffi::c_char = cJSON_PrintUnformatted(response_json); cJSON_Delete(response_json); diff --git a/rust/generated/src__web__api_handlers_openapi.rs b/rust/generated/src__web__api_handlers_openapi.rs index ed0e1b7b9..8d01c674b 100644 --- a/rust/generated/src__web__api_handlers_openapi.rs +++ b/rust/generated/src__web__api_handlers_openapi.rs @@ -181,18 +181,18 @@ impl user_role_t { pub const USER_ROLE_VIEWER: Self = Self(2); pub const USER_ROLE_API: Self = Self(3); } -static mut lightnvr_openapi_document: [::core::ffi::c_uchar; 701977] = unsafe { +static mut lightnvr_openapi_document: [::core::ffi::c_uchar; 742642] = unsafe { ::core::mem::transmute::< - [u8; 701977], - [::core::ffi::c_uchar; 701977], + [u8; 742642], + [::core::ffi::c_uchar; 742642], >( - *b"{\"components\":{\"schemas\":{\"AdminActionCatalog\":{\"additionalProperties\":false,\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AdminActionCatalogEntry\"},\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"actions\",\"count\"],\"type\":\"object\"},\"AdminActionCatalogEntry\":{\"additionalProperties\":false,\"properties\":{\"camera_scoped\":{\"type\":\"boolean\"},\"category\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"destructive\":{\"type\":\"boolean\"},\"enforced\":{\"type\":\"boolean\"},\"key\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"mask_bit\":{\"type\":\"integer\"}},\"required\":[\"key\",\"category\",\"description\",\"camera_scoped\",\"destructive\",\"enforced\",\"mask_bit\"],\"type\":\"object\"},\"AdminAuthorizationRole\":{\"additionalProperties\":false,\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"array\"},\"builtin\":{\"type\":\"boolean\"},\"created_at\":{\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"builtin\",\"actions\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"AdminPolicyGrant\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"role\":{\"type\":\"string\"},\"role_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenStoredScope\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"role_uuid\",\"role\",\"enabled\",\"created_at\",\"updated_at\",\"scope\"],\"type\":\"object\"},\"AdminPolicyWrite\":{\"additionalProperties\":true,\"properties\":{\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"grants\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"role_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenScope\"}},\"required\":[\"role_uuid\",\"scope\"],\"type\":\"object\"},\"maxItems\":256,\"type\":\"array\"},\"mode\":{\"const\":\"policy\"}},\"required\":[\"mode\",\"expected_policy_version\",\"grants\"],\"type\":\"object\"},\"AdminRoleDeleted\":{\"additionalProperties\":false,\"properties\":{\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"policy_version\"],\"type\":\"object\"},\"AdminRoleWrite\":{\"additionalProperties\":true,\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"description\":{\"maxLength\":255,\"type\":\"string\"},\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"pattern\":\"\\\\S\",\"type\":\"string\"}},\"required\":[\"expected_policy_version\",\"name\",\"actions\"],\"type\":\"object\"},\"AdminRoleWritten\":{\"additionalProperties\":false,\"properties\":{\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"role\":{\"$ref\":\"#/components/schemas/AdminAuthorizationRole\"}},\"required\":[\"policy_version\",\"role\"],\"type\":\"object\"},\"AdminRoles\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"roles\":{\"items\":{\"$ref\":\"#/components/schemas/AdminAuthorizationRole\"},\"type\":\"array\"}},\"required\":[\"policy_version\",\"count\",\"roles\"],\"type\":\"object\"},\"AdminSimulation\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"allowed\":{\"type\":\"boolean\"},\"explanation\":{\"type\":\"string\"},\"grant_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"principal\":{\"additionalProperties\":false,\"properties\":{\"authorization_mode\":{\"type\":\"string\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"username\":{\"type\":\"string\"}},\"required\":[\"id\",\"username\",\"authorization_mode\"],\"type\":\"object\"},\"resource\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"role\":{\"type\":\"string\"},\"role_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"source\":{\"type\":\"string\"}},\"required\":[\"allowed\",\"action\",\"source\",\"policy_version\",\"explanation\",\"grant_uuid\",\"role_uuid\",\"role\",\"principal\",\"resource\"],\"type\":\"object\"},\"AdminSimulationRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"live.view\",\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"recordings.export\",\"snapshot.create\",\"ptz.control\",\"evidence.protect\",\"recording.delete\",\"camera.configure\",\"fleet.execute_job\",\"lpr.read\",\"lpr.search\",\"lpr.export\",\"lpr.delete\"],\"type\":\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"user_id\":{\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"action\",\"camera_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin\"],\"type\":\"string\"},\"camera_uuid\":false,\"user_id\":{\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"action\"],\"type\":\"object\"}]},\"AdminUser\":{\"additionalProperties\":false,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"authorization_mode\":{\"type\":\"string\"},\"created_at\":{\"type\":\"integer\"},\"email\":{\"type\":\"string\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"is_active\":{\"type\":\"boolean\"},\"last_login\":{\"type\":\"integer\"},\"must_change_password\":{\"type\":\"boolean\"},\"password_change_locked\":{\"type\":\"boolean\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"role_name\":{\"enum\":[\"admin\",\"user\",\"viewer\",\"api\",\"unknown\"],\"type\":\"string\"},\"totp_enabled\":{\"type\":\"boolean\"},\"updated_at\":{\"type\":\"integer\"},\"username\":{\"type\":\"string\"}},\"required\":[\"id\",\"username\",\"email\",\"role\",\"role_name\",\"created_at\",\"updated_at\",\"last_login\",\"is_active\",\"password_change_locked\",\"must_change_password\",\"totp_enabled\",\"authorization_mode\",\"allowed_login_cidrs\"],\"type\":\"object\"},\"AdminUserCreate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"email\":{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password\":{\"minLength\":8,\"type\":\"string\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"username\":{\"maxLength\":32,\"minLength\":3,\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"AdminUserDeleted\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"AdminUserPolicy\":{\"additionalProperties\":false,\"properties\":{\"grant_count\":{\"minimum\":0,\"type\":\"integer\"},\"grants\":{\"items\":{\"$ref\":\"#/components/schemas/AdminPolicyGrant\"},\"type\":\"array\"},\"mode\":{\"enum\":[\"legacy\",\"policy\"],\"type\":\"string\"},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"user_id\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"mode\",\"policy_version\",\"grant_count\",\"grants\"],\"type\":\"object\"},\"AdminUserUpdate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"allowed_tags\":false,\"email\":{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password\":{\"anyOf\":[{\"const\":\"\"},{\"minLength\":8,\"type\":\"string\"}]},\"role\":false,\"username\":{\"maxLength\":32,\"minLength\":3,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"AdminUsers\":{\"additionalProperties\":false,\"properties\":{\"users\":{\"items\":{\"$ref\":\"#/components/schemas/AdminUser\"},\"type\":\"array\"}},\"required\":[\"users\"],\"type\":\"object\"},\"AdminVersionRequest\":{\"additionalProperties\":true,\"properties\":{\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"expected_policy_version\"],\"type\":\"object\"},\"ApiError\":{\"additionalProperties\":true,\"description\":\"Most errors use a string error. YAML validation may use valid=false and error.message. Extra diagnostic fields may be added; clients must not log privileged request or response data.\",\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"string\"},{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type\":\"object\"}]},\"valid\":{\"type\":\"boolean\"}},\"required\":[\"error\"],\"type\":\"object\"},\"ApiTokenCreateRequest\":{\"additionalProperties\":true,\"description\":\"Requested action and camera scope must be contained in BOTH token owner and requester authority. At most256 active tokens per owner. Additional request keys do not rewrite user policy.\",\"properties\":{\"actions\":{\"description\":\"Duplicates collapse into an action mask.\",\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"minItems\":1,\"type\":\"array\"},\"description\":{\"description\":\"Trimmed by server; must contain non-whitespace text, no control characters, and fewer than128 UTF-8 bytes.\",\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"expires_at\":{\"description\":\"Unix seconds strictly after server time and at most366 days ahead.\",\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenScope\"}},\"required\":[\"description\",\"expires_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenCreated\":{\"additionalProperties\":true,\"description\":\"Only successful token creation returns a secret. No secret value is included in examples.\",\"properties\":{\"secret\":{\"description\":\"One-time bearer secret; store securely after creation. Ordinary token-list/identity responses never include it.\",\"pattern\":\"^lnvr_[0-9a-f]{64}$\",\"readOnly\":true,\"type\":\"string\"},\"token\":{\"$ref\":\"#/components/schemas/ApiTokenMetadata\"}},\"required\":[\"secret\",\"token\"],\"type\":\"object\"},\"ApiTokenList\":{\"additionalProperties\":true,\"description\":\"Lists token metadata, including revoked/expired records. Secret is never returned.\",\"properties\":{\"api_key\":false,\"count\":{\"minimum\":0,\"type\":\"integer\"},\"password\":false,\"password_hash\":false,\"secret\":false,\"token_hash\":false,\"tokens\":{\"items\":{\"$ref\":\"#/components/schemas/ApiTokenMetadata\"},\"type\":\"array\"},\"totp_secret\":false,\"totp_token\":false,\"user_id\":{\"type\":\"integer\"}},\"required\":[\"user_id\",\"count\",\"tokens\"],\"type\":\"object\"},\"ApiTokenMetadata\":{\"additionalProperties\":true,\"description\":\"Non-secret token record; prefix is an identifier, never a reusable credential. Secret/hash fields are explicitly forbidden.\",\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"array\"},\"api_key\":false,\"created_at\":{\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"expires_at\":{\"type\":\"integer\"},\"last_used_at\":{\"type\":[\"integer\",\"null\"]},\"password\":false,\"password_hash\":false,\"prefix\":{\"type\":\"string\"},\"revoked_at\":{\"type\":[\"integer\",\"null\"]},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenStoredScope\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"description\",\"prefix\",\"expires_at\",\"revoked_at\",\"last_used_at\",\"created_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenScope\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"collection\"}},\"required\":[\"type\",\"collection_uuid\"],\"type\":\"object\"}],\"description\":\"All, selector or collection camera scope. Inapplicable selector/collection fields may be absent or null. A narrow owner cannot delegate all scope simply because current matches happen to be identical.\"},\"ApiTokenStoredScope\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"collection\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"}]},\"AuditDecisionMode\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"category\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"mode\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"description\",\"mode\"],\"type\":\"object\"},\"AuditEvent\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"api_token_uuid\":{\"type\":[\"string\",\"null\"]},\"auth_method\":{\"type\":\"string\"},\"details\":{\"additionalProperties\":true,\"type\":[\"object\",\"null\"]},\"occurred_at\":{\"type\":\"integer\"},\"outcome\":{\"type\":\"string\"},\"principal_user_id\":{\"type\":[\"integer\",\"null\"]},\"principal_username\":{\"type\":\"string\"},\"remote_address\":{\"type\":\"string\"},\"request_id\":{\"type\":\"string\"},\"target_type\":{\"type\":[\"string\",\"null\"]},\"target_uuid\":{\"type\":[\"string\",\"null\"]},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"occurred_at\",\"request_id\",\"principal_user_id\",\"principal_username\",\"auth_method\",\"api_token_uuid\",\"action\",\"target_type\",\"target_uuid\",\"outcome\",\"remote_address\",\"details\"],\"type\":\"object\"},\"AuditEventsPage\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"events\":{\"items\":{\"$ref\":\"#/components/schemas/AuditEvent\"},\"type\":\"array\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"minimum\":1,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"page\",\"page_size\",\"count\",\"total\",\"events\"],\"type\":\"object\"},\"AuditSettings\":{\"additionalProperties\":false,\"properties\":{\"allowed_decision_modes\":{\"items\":{\"$ref\":\"#/components/schemas/AuditDecisionMode\"},\"type\":\"array\"},\"pruned_events\":{\"minimum\":0,\"type\":\"integer\"},\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"},\"summary_window_seconds\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\",\"summary_window_seconds\",\"allowed_decision_modes\"],\"type\":\"object\"},\"AuditSettingsWrite\":{\"additionalProperties\":true,\"anyOf\":[{\"properties\":{\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\"]},{\"properties\":{\"allowed_decision_modes\":{\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"object\"}},\"required\":[\"allowed_decision_modes\"]}],\"properties\":{\"allowed_decision_modes\":{\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"object\"},\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"AuthDemoIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"authenticated\":{\"const\":false},\"demo_mode\":{\"const\":true},\"must_change_password\":{\"const\":false},\"password\":false,\"password_hash\":false,\"role\":{\"const\":\"viewer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"username\":{\"const\":\"demo\"}},\"required\":[\"authenticated\",\"demo_mode\",\"username\",\"role\",\"must_change_password\"],\"type\":\"object\"},\"AuthDisabledIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":false},\"auth_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"must_change_password\":{\"const\":false},\"password\":false,\"password_hash\":false,\"role\":{\"const\":\"admin\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"},\"username\":{\"const\":\"admin\"}},\"required\":[\"authenticated\",\"username\",\"role\",\"must_change_password\",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"AuthSession\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"idle_expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_activity_at\":{\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[\"id\",\"created_at\",\"expires_at\",\"ip_address\",\"user_agent\",\"current\",\"last_activity_at\",\"idle_expires_at\"],\"type\":\"object\"},\"AuthSessionsResponse\":{\"additionalProperties\":false,\"properties\":{\"sessions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthSession\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"sessions\"],\"type\":\"object\"},\"AuthVerifiedIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":true},\"auth_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"email\":{\"type\":\"string\"},\"id\":{\"type\":\"integer\"},\"is_active\":{\"type\":\"boolean\"},\"must_change_password\":{\"type\":\"boolean\"},\"password\":false,\"password_change_locked\":{\"type\":\"boolean\"},\"password_hash\":false,\"role\":{\"type\":\"string\"},\"role_id\":{\"type\":\"integer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"},\"username\":{\"type\":\"string\"}},\"required\":[\"authenticated\",\"id\",\"username\",\"email\",\"role\",\"role_id\",\"is_active\",\"password_change_locked\",\"must_change_password\",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"AuthVerifyResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/AuthVerifiedIdentity\"},{\"$ref\":\"#/components/schemas/AuthDisabledIdentity\"},{\"$ref\":\"#/components/schemas/AuthDemoIdentity\"}],\"description\":\"Real credential identity, auth-disabled synthetic admin, or unauthenticated demo viewer. Synthetic modes do not authorize personalized discovery.\"},\"AuthorizationAction\":{\"enum\":[\"live.view\",\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"recordings.export\",\"snapshot.create\",\"ptz.control\",\"evidence.protect\",\"recording.delete\",\"camera.configure\",\"fleet.execute_job\",\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin\",\"lpr.read\",\"lpr.search\",\"lpr.export\",\"lpr.delete\"],\"type\":\"string\"},\"CameraCollection\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"effective_count\":{\"minimum\":0,\"type\":\"integer\"},\"member_count\":{\"minimum\":0,\"type\":\"integer\"},\"name\":{\"type\":\"string\"},\"owner_user_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"selector\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FleetSelector\"},{\"type\":\"null\"}]},\"selector_redacted\":{\"type\":\"boolean\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"type\",\"shared\",\"owner_user_id\",\"member_count\",\"effective_count\",\"created_at\",\"updated_at\",\"selector\",\"selector_redacted\"],\"type\":\"object\"},\"CameraCollectionCreate\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"type\":\"string\"}},\"required\":[\"name\",\"type\"],\"type\":\"object\"},\"CameraCollectionMembersRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"maxItems\":4096,\"type\":\"array\"}},\"required\":[\"camera_uuids\"],\"type\":\"object\"},\"CameraCollectionMembersResponse\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"collection_uuid\",\"camera_uuids\",\"count\"],\"type\":\"object\"},\"CameraCollectionPreview\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"matched_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\",\"location_path\"],\"type\":\"object\"},\"maxItems\":50,\"type\":\"array\"}},\"required\":[\"collection_uuid\",\"matched_count\",\"sample\"],\"type\":\"object\"},\"CameraCollectionUpdate\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"CameraCollectionsResponse\":{\"additionalProperties\":false,\"properties\":{\"collections\":{\"items\":{\"$ref\":\"#/components/schemas/CameraCollection\"},\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"collections\",\"count\"],\"type\":\"object\"},\"CameraFavorite\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"created_at\"],\"type\":\"object\"},\"CameraFavoriteRemoval\":{\"additionalProperties\":false,\"properties\":{\"removed\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"removed\"],\"type\":\"object\"},\"CameraFavoritesResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"favorites\":{\"items\":{\"$ref\":\"#/components/schemas/CameraFavorite\"},\"type\":\"array\"}},\"required\":[\"favorites\",\"can_modify\"],\"type\":\"object\"},\"CameraLocationAssignment\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"location_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\",\"location_uuid\"],\"type\":\"object\"},\"CameraLocationAssignmentRequest\":{\"additionalProperties\":false,\"properties\":{\"location_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"location_uuid\"],\"type\":\"object\"},\"CameraTagAssignmentsRequest\":{\"additionalProperties\":false,\"properties\":{\"tag_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"tag_uuids\"],\"type\":\"object\"},\"CameraTagAssignmentsResponse\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"array\"}},\"required\":[\"camera_uuid\",\"tags\",\"count\"],\"type\":\"object\"},\"CameraUuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"ClientConfig\":{\"additionalProperties\":false,\"description\":\"Safe browser playback configuration only. Deliberately closed shape: no storage paths, integrations, private settings or credentials. go2rtc_available may stay true while its lifecycle guard is busy.\",\"properties\":{\"api_key\":false,\"audio_disabled\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"default_playback_transport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hls_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_available\":{\"type\":\"boolean\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"mse_disabled\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"secret\":false,\"thumbnails_per_recording\":{\"type\":\"number\"},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"web_auth_enabled\":{\"type\":\"boolean\"},\"webrtc_connection_timeout_ms\":{\"type\":\"number\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"number\"}},\"required\":[\"go2rtc_enabled\",\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_disabled\",\"go2rtc_available\",\"webrtc_disabled\",\"hls_disabled\",\"mse_disabled\",\"go2rtc_force_native_hls\",\"generate_thumbnails\",\"go2rtc_api_port\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\",\"thumbnails_per_recording\",\"go2rtc_base_path\",\"default_playback_transport\"],\"type\":\"object\"},\"CredentialRevoked\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"DaynightRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"mode\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"mode\"]},{\"properties\":{\"daynight\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"daynight\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"properties\":{\"daynight\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"force_persistence\":{\"type\":\"boolean\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"mode\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"DaynightResponse\":{\"additionalProperties\":false,\"properties\":{\"ir_cut_filter\":{\"type\":\"string\"},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\",\"unknown\"]},\"options_available\":{\"type\":\"boolean\"},\"source\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\":true},\"supported_modes\":{\"items\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"type\":\"array\"},\"video_source_token\":{\"type\":\"string\"}},\"required\":[\"success\",\"source\",\"video_source_token\",\"mode\",\"supported_modes\",\"options_available\"],\"type\":\"object\"},\"DaynightUpdated\":{\"additionalProperties\":false,\"properties\":{\"ir_cut_filter\":{\"enum\":[\"On\",\"Off\",\"Auto\"]},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"source\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"source\",\"mode\",\"ir_cut_filter\"],\"type\":\"object\"},\"DetectionZone\":{\"additionalProperties\":true,\"properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polygon\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"array\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"id\",\"name\",\"enabled\",\"color\",\"polygon\",\"filter_classes\",\"min_confidence\",\"stream_name\"],\"type\":\"object\"},\"DetectionZoneWrite\":{\"additionalProperties\":true,\"properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polygon\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"array\"}},\"required\":[\"id\",\"name\",\"polygon\"],\"type\":\"object\"},\"DeviceCommandResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"DeviceEmptyRequest\":{\"additionalProperties\":false,\"properties\":{},\"required\":[],\"type\":\"object\"},\"DeviceFloatRange\":{\"additionalProperties\":false,\"properties\":{\"max\":{\"type\":\"number\"},\"min\":{\"type\":\"number\"}},\"required\":[\"min\",\"max\"],\"type\":\"object\"},\"DeviceSuccess\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true}},\"required\":[\"success\"],\"type\":\"object\"},\"EventCameraScope\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"type\":{\"const\":\"all\",\"type\":\"string\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"type\":{\"const\":\"selector\",\"type\":\"string\"}},\"required\":[\"type\",\"selector\"],\"type\":\"object\"}]},\"EventCatalog\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"default_expiry_seconds\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"dynamic_severity\":{\"type\":\"boolean\"},\"expected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"family\":{\"type\":\"string\"},\"maximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"media_policy\":{\"type\":\"string\"},\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"sensitivity\":{\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"storage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"severity\",\"dynamic_severity\",\"minimum_severity\",\"maximum_severity\",\"subject_kind\",\"family\",\"description\",\"sensitivity\",\"media_policy\",\"default_expiry_seconds\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"schema_version\",\"count\",\"event_types\"],\"type\":\"object\"},\"EventDestination\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password_configured\":{\"type\":\"boolean\"},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[\"username\",\"password_configured\"],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"host\",\"port\",\"client_id\",\"topic_template\",\"status_topic_template\",\"keepalive_seconds\",\"qos\"],\"type\":\"object\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"managed\":{\"const\":true,\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[\"mode\",\"ca_file\",\"cert_file\",\"key_file\"],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"required\":[\"uuid\",\"key\",\"name\",\"description\",\"enabled\",\"type\",\"broker\",\"authentication\",\"tls\",\"managed\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"EventDestinationCreate\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password\":{\"maxLength\":255,\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"host\"],\"type\":\"object\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[\"name\",\"broker\"],\"type\":\"object\"},\"EventDestinationUpdate\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password\":{\"maxLength\":255,\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EventDestinations\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"maximum\":64,\"minimum\":0,\"type\":\"integer\"},\"default_destination\":{\"additionalProperties\":false,\"properties\":{\"configuration_source\":{\"const\":\"settings\",\"type\":\"string\"},\"key\":{\"const\":\"mqtt:default\",\"type\":\"string\"},\"managed\":{\"const\":false,\"type\":\"boolean\"},\"name\":{\"type\":\"string\"}},\"required\":[\"key\",\"name\",\"configuration_source\",\"managed\"],\"type\":\"object\"},\"destinations\":{\"items\":{\"$ref\":\"#/components/schemas/EventDestination\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"default_destination\",\"count\",\"destinations\"],\"type\":\"object\"},\"EventMutationAck\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"EventPredicate\":{\"additionalProperties\":false,\"properties\":{\"detection\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"labels_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"zone_ids_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"health\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"condition_codes_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"severities_any\":{\"items\":{\"enum\":[\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"maxItems\":3,\"minItems\":1,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\"],\"type\":\"object\"},\"EventRoute\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"debounce_seconds\",\"cooldown_seconds\",\"grouping_window_seconds\",\"max_events_per_minute\"],\"type\":\"object\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"enabled\",\"destination\",\"event_types\",\"camera_scope\",\"predicate\",\"schedule\",\"suppression\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"EventRouteCreate\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"name\",\"event_types\"],\"type\":\"object\"},\"EventRoutePreview\":{\"additionalProperties\":false,\"properties\":{\"camera_sample\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\",\"location_path\"],\"type\":\"object\"},\"maxItems\":20,\"type\":\"array\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"dynamic_severity\":{\"type\":\"boolean\"},\"expected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"maximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"storage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"severity\",\"dynamic_severity\",\"minimum_severity\",\"maximum_severity\",\"subject_kind\"],\"type\":\"object\"},\"type\":\"array\"},\"matched_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_limit\":{\"const\":20,\"type\":\"integer\"},\"would_publish\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"matched_camera_count\",\"sample_limit\",\"camera_sample\",\"event_types\",\"would_publish\"],\"type\":\"object\"},\"EventRoutePreviewRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"name\",\"event_types\"],\"type\":\"object\"},\"EventRouteUpdate\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EventRoutes\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"maximum\":512,\"minimum\":0,\"type\":\"integer\"},\"routes\":{\"items\":{\"$ref\":\"#/components/schemas/EventRoute\"},\"maxItems\":512,\"type\":\"array\"}},\"required\":[\"routes\",\"count\"],\"type\":\"object\"},\"EventSchedule\":{\"additionalProperties\":false,\"properties\":{\"timezone\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"version\":{\"const\":1,\"type\":\"integer\"},\"windows\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"days\":{\"items\":{\"maximum\":6,\"minimum\":0,\"type\":\"integer\"},\"maxItems\":7,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"end\":{\"pattern\":\"^(?:[01][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"},\"start\":{\"pattern\":\"^(?:[01][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"}},\"required\":[\"days\",\"start\",\"end\"],\"type\":\"object\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"version\",\"timezone\",\"windows\"],\"type\":\"object\"},\"EvidenceBookmark\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"created_at\":{\"type\":\"integer\"},\"cursor_time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkFilters\"},\"holds_recordings\":{\"const\":false},\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"representative_result\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkResult\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"title\",\"note\",\"start_time\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\",\"camera_uuids\",\"filters\",\"representative_result\",\"uuid\",\"revision\",\"created_at\",\"updated_at\",\"holds_recordings\"],\"type\":\"object\"},\"EvidenceBookmarkCreate\":{\"additionalProperties\":true,\"description\":\"Camera list, primary camera, region and saved result must agree. Cursor must lie inside the interval. The bookmark is owned by the current user (also for scoped tokens), never grants access, and does not protect media from retention.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"cursor_time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkFilters\"},\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"representative_result\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkResult\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"title\",\"start_time\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\",\"camera_uuids\",\"filters\"],\"type\":\"object\"},\"EvidenceBookmarkDelete\":{\"additionalProperties\":true,\"properties\":{\"revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EvidenceBookmarkDeleted\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true}},\"required\":[\"success\"],\"type\":\"object\"},\"EvidenceBookmarkFilters\":{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"maxLength\":127,\"type\":\"string\"},\"event_type\":{\"maxLength\":127,\"type\":\"string\"},\"label\":{\"maxLength\":127,\"type\":\"string\"},\"location\":{\"maxLength\":127,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protection\":{\"maxLength\":127,\"type\":\"string\"},\"recording_tag\":{\"maxLength\":127,\"type\":\"string\"},\"region\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"source\":{\"maxLength\":127,\"type\":\"string\"},\"zone\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"EvidenceBookmarkResult\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection_id\":{\"type\":[\"string\",\"number\"]},\"end_time\":{\"type\":[\"string\",\"number\"]},\"event_type\":{\"type\":[\"string\",\"number\"]},\"label\":{\"type\":[\"string\",\"number\"]},\"recording_id\":{\"type\":[\"string\",\"number\"]},\"result_id\":{\"type\":[\"string\",\"number\"]},\"start_time\":{\"type\":[\"string\",\"number\"]}},\"required\":[],\"type\":\"object\"},{\"type\":\"null\"}]},\"EvidenceBookmarkUpdate\":{\"additionalProperties\":true,\"properties\":{\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"title\",\"revision\"],\"type\":\"object\"},\"EvidenceBookmarks\":{\"additionalProperties\":false,\"properties\":{\"bookmarks\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"},\"maxItems\":256,\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"bookmarks\",\"count\"],\"type\":\"object\"},\"EvidenceDetection\":{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"type\":\"number\"},\"end_timestamp\":{\"type\":\"integer\"},\"height\":{\"type\":\"number\"},\"label\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"integer\"},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"confidence\",\"x\",\"y\",\"width\",\"height\",\"label\",\"timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"EvidenceDetections\":{\"additionalProperties\":false,\"properties\":{\"detections\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceDetection\"},\"type\":\"array\"},\"stream_status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"timestamp\":{\"type\":\"string\"}},\"required\":[\"detections\",\"timestamp\",\"stream_status\"],\"type\":\"object\"},\"EvidenceFileCheck\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":false}},\"required\":[\"exists\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":true},\"mtime\":{\"type\":\"integer\"},\"size\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"exists\",\"size\",\"mtime\"],\"type\":\"object\"}]},\"EvidenceFileDeleted\":{\"additionalProperties\":false,\"properties\":{\"existed\":{\"type\":\"boolean\"},\"status\":{\"enum\":[\"deleted\",\"deletion_pending\"],\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"existed\",\"status\"],\"type\":\"object\"},\"EvidenceLprDeleted\":{\"additionalProperties\":false,\"properties\":{\"deleted\":{\"const\":true}},\"required\":[\"deleted\"],\"type\":\"object\"},\"EvidenceLprExportRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"end_at\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"},\"match\":{\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"EvidenceLprRead\":{\"additionalProperties\":false,\"properties\":{\"attributes\":{\"additionalProperties\":false,\"properties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"bottom\":{\"type\":\"number\"},\"left\":{\"type\":\"number\"},\"right\":{\"type\":\"number\"},\"top\":{\"type\":\"number\"}},\"required\":[\"left\",\"top\",\"right\",\"bottom\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"correlation_id\":{\"type\":[\"string\",\"null\"]},\"country\":{\"type\":[\"string\",\"null\"]},\"direction\":{\"type\":[\"string\",\"null\"]},\"lane\":{\"type\":[\"string\",\"null\"]},\"object_id\":{\"type\":[\"string\",\"null\"]},\"plate_type\":{\"type\":[\"string\",\"null\"]},\"region\":{\"type\":[\"string\",\"null\"]},\"vehicle_color\":{\"type\":[\"string\",\"null\"]},\"vehicle_type\":{\"type\":[\"string\",\"null\"]},\"vendor_topic\":{\"type\":\"string\"}},\"required\":[\"country\",\"region\",\"plate_type\",\"direction\",\"lane\",\"vehicle_type\",\"vehicle_color\",\"object_id\",\"correlation_id\",\"vendor_topic\",\"bounding_box\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"confidence\":{\"anyOf\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"id\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"observed_at\":{\"minimum\":1,\"type\":\"integer\"},\"plate\":{\"type\":\"string\"},\"received_at\":{\"minimum\":1,\"type\":\"integer\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"source\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"type\":{\"const\":\"license_plate\"}},\"required\":[\"id\",\"type\",\"camera_uuid\",\"stream_name\",\"observed_at\",\"received_at\",\"source\",\"plate\",\"confidence\",\"recording_id\",\"attributes\"],\"type\":\"object\"},\"EvidenceLprReads\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"reads\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceLprRead\"},\"maxItems\":1000,\"type\":\"array\"}},\"required\":[\"reads\",\"count\"],\"type\":\"object\"},\"EvidenceLprSearchRequest\":{\"additionalProperties\":true,\"description\":\"Epoch milliseconds, end at/after start within 366 days; match requires a canonicalizable plate (partial at least 3 canonical characters). Search returns at most 100 rows.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"end_at\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"match\":{\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"EvidenceModel\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"size\":{\"minimum\":0,\"type\":\"integer\"},\"supported\":{\"type\":\"boolean\"},\"type\":{\"type\":\"string\"},\"unsupported_reason\":{\"type\":\"string\"}},\"required\":[\"id\",\"name\",\"path\",\"type\",\"supported\"],\"type\":\"object\"},\"EvidenceModels\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"litert_available\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"models\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceModel\"},\"type\":\"array\"},\"sod_available\":{\"type\":\"boolean\"}},\"required\":[\"models\",\"count\",\"litert_available\",\"sod_available\"],\"type\":\"object\"},\"EvidencePreview\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"permissions\":{\"additionalProperties\":false,\"properties\":{\"export\":{\"additionalProperties\":false,\"properties\":{\"all_allowed\":{\"type\":\"boolean\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"denied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"all_allowed\"],\"type\":\"object\"},\"protect\":{\"additionalProperties\":false,\"properties\":{\"actionable_count\":{\"minimum\":0,\"type\":\"integer\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"denied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"actionable_count\"],\"type\":\"object\"}},\"required\":[\"protect\",\"export\"],\"type\":\"object\"},\"protected_count\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"recordings\":{\"items\":{\"$ref\":\"#/components/schemas/EvidencePreviewRecording\"},\"maxItems\":200,\"type\":\"array\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"total_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"unprotected_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_count\",\"recording_count\",\"total_bytes\",\"protected_count\",\"unprotected_count\",\"recordings\",\"permissions\"],\"type\":\"object\"},\"EvidencePreviewRecording\":{\"additionalProperties\":false,\"properties\":{\"camera_name\":{\"type\":\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"can_export\":{\"type\":\"boolean\"},\"can_protect\":{\"type\":\"boolean\"},\"end_time\":{\"type\":\"integer\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"id\",\"camera_uuid\",\"camera_name\",\"stream_name\",\"start_time\",\"end_time\",\"size_bytes\",\"protected\",\"can_protect\",\"can_export\"],\"type\":\"object\"},\"EvidenceProtectedCount\":{\"additionalProperties\":false,\"properties\":{\"protected_count\":{\"minimum\":0,\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"protected_count\"],\"type\":\"object\"},\"EvidenceRangeRequest\":{\"additionalProperties\":true,\"description\":\"End must exceed start by no more than 31 days. All explicit cameras must exist and permit recordings.replay; mixed unauthorized input is rejected.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceSearch\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"complete\":{\"type\":\"boolean\"},\"incomplete_reasons\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"requested_end\":{\"minimum\":1,\"type\":\"integer\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"},\"searched_end\":{\"minimum\":1,\"type\":\"integer\"},\"searched_start\":{\"minimum\":1,\"type\":\"integer\"},\"spatial_metadata\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"requested\":{\"const\":false}},\"required\":[\"requested\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"complete\":{\"type\":\"boolean\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"rectangle\":{\"additionalProperties\":false,\"properties\":{\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"requested\":{\"const\":true},\"rows_with_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"rows_without_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"search_type\":{\"const\":\"metadata_search\"}},\"required\":[\"requested\",\"search_type\",\"camera_uuid\",\"match\",\"rows_with_boxes\",\"rows_without_boxes\",\"complete\",\"rectangle\"],\"type\":\"object\"}]},\"unresolved_legacy_rows\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"requested_start\",\"requested_end\",\"searched_start\",\"searched_end\",\"unresolved_legacy_rows\",\"spatial_metadata\",\"incomplete_reasons\",\"complete\"],\"type\":\"object\"},\"facets\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"capture_methods\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"labels\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"protection\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"recording_tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"sources\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"zones\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"cameras\",\"labels\",\"zones\",\"sources\",\"event_types\",\"locations\",\"capture_methods\",\"recording_tags\",\"protection\"],\"type\":\"object\"},\"histogram\":{\"additionalProperties\":false,\"properties\":{\"bucket_seconds\":{\"minimum\":0,\"type\":\"integer\"},\"buckets\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"event_time\":{\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"event_time\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"bucket_seconds\",\"buckets\"],\"type\":\"object\"},\"page\":{\"additionalProperties\":false,\"properties\":{\"has_more\":{\"type\":\"boolean\"},\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"next_cursor\":{\"type\":[\"string\",\"null\"]},\"returned\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"limit\",\"returned\",\"total\",\"has_more\",\"next_cursor\"],\"type\":\"object\"},\"results\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceSearchResult\"},\"type\":\"array\"},\"selector_applied\":{\"type\":\"boolean\"},\"summary_complete\":{\"type\":\"boolean\"}},\"required\":[\"camera_count\",\"selector_applied\",\"summary_complete\",\"results\",\"page\",\"facets\",\"histogram\",\"coverage\"],\"type\":\"object\"},\"EvidenceSearchRequest\":{\"oneOf\":[{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true,\"properties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_tags\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"sources\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"zones\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimum\":1,\"type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":false,\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":false,\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true,\"properties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_tags\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"sources\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"zones\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimum\":1,\"type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\",\"end_time\",\"selector\"],\"type\":\"object\"}]},\"EvidenceSearchResult\":{\"additionalProperties\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"location_name\":{\"type\":[\"string\",\"null\"]},\"location_path\":{\"type\":[\"string\",\"null\"]},\"location_uuid\":{\"type\":[\"string\",\"null\"]},\"name\":{\"type\":\"string\"}},\"required\":[\"name\",\"location_uuid\",\"location_name\",\"location_path\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection\":{\"additionalProperties\":false,\"properties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"height\":{\"type\":\"number\"},\"normalized\":{\"const\":true},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\",\"normalized\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"confidence\":{\"type\":\"number\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"source\":{\"type\":\"string\"},\"track_id\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"zone_uuid\":{\"type\":[\"string\",\"null\"]}},\"required\":[\"id\",\"label\",\"confidence\",\"source\",\"zone_uuid\",\"track_id\",\"bounding_box\"],\"type\":\"object\"},\"end_time\":{\"type\":\"integer\"},\"event_type\":{\"enum\":[\"motion\",\"detection\"],\"type\":\"string\"},\"known_gap\":{\"type\":\"boolean\"},\"media_available\":{\"type\":\"boolean\"},\"recording\":{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"type\":[\"string\",\"null\"]},\"protected\":{\"anyOf\":[{\"type\":\"boolean\"},{\"type\":\"null\"}]}},\"required\":[\"capture_method\",\"protected\"],\"type\":\"object\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"result_id\":{\"type\":\"string\"},\"start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"string\"}},\"required\":[\"status\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"status\":{\"enum\":[\"unavailable\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"}]}},\"required\":[\"result_id\",\"event_type\",\"camera_uuid\",\"start_time\",\"end_time\",\"media_available\",\"known_gap\",\"recording_id\",\"camera\",\"detection\",\"thumbnail\",\"recording\"],\"type\":\"object\"},\"EvidenceSegment\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"type\":\"string\"},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"id\",\"start_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_available\",\"capture_method\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"coverage_only\":{\"const\":true},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"source_segment_count\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"id\",\"start_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_available\",\"source_segment_count\",\"coverage_only\"],\"type\":\"object\"}]},\"EvidenceSegmentAt\":{\"additionalProperties\":false,\"properties\":{\"segment\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/EvidenceSegment\"},{\"type\":\"null\"}]}},\"required\":[\"segment\"],\"type\":\"object\"},\"EvidenceSegmentAtRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceSyncComplete\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true},\"updated\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"success\",\"message\",\"updated\"],\"type\":\"object\"},\"EvidenceThumbnailRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"sample_count\":{\"maximum\":12,\"minimum\":3,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceThumbnailSample\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"media_status\":{\"const\":\"gap\"},\"thumbnail\":{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"unavailable\"}},\"required\":[\"status\"],\"type\":\"object\"},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"media_status\":{\"const\":\"available\"},\"offset_ms\":{\"minimum\":0,\"type\":\"integer\"},\"recording_end_time\":{\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"recording_start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"string\"}},\"required\":[\"status\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"status\":{\"enum\":[\"unavailable\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"}]},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\",\"recording_id\",\"recording_start_time\",\"recording_end_time\",\"offset_ms\"],\"type\":\"object\"}]},\"EvidenceThumbnails\":{\"additionalProperties\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"available_samples\":{\"minimum\":0,\"type\":\"integer\"},\"gap_samples\":{\"minimum\":0,\"type\":\"integer\"},\"segments_truncated\":{\"const\":false}},\"required\":[\"segments_truncated\",\"available_samples\",\"gap_samples\"],\"type\":\"object\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"sample_count\":{\"minimum\":1,\"type\":\"integer\"},\"samples\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceThumbnailSample\"},\"maxItems\":12,\"type\":\"array\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"sample_count\",\"camera\",\"samples\",\"coverage\"],\"type\":\"object\"},\"EvidenceTimeline\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"max_active_decoders\":{\"const\":4},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"tracks\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceTimelineTrack\"},\"type\":\"array\"}},\"required\":[\"start_time\",\"end_time\",\"camera_count\",\"max_active_decoders\",\"tracks\"],\"type\":\"object\"},\"EvidenceTimelineTrack\":{\"additionalProperties\":false,\"properties\":{\"aggregated\":{\"type\":\"boolean\"},\"aggregation_bucket_seconds\":{\"minimum\":1,\"type\":\"integer\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"identity_resolved\":{\"const\":true},\"requested_end\":{\"minimum\":1,\"type\":\"integer\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"identity_resolved\",\"requested_start\",\"requested_end\"],\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"returned_interval_count\":{\"minimum\":0,\"type\":\"integer\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceSegment\"},\"type\":\"array\"},\"stream_name\":{\"type\":\"string\"},\"truncated\":{\"const\":false}},\"required\":[\"camera_uuid\",\"name\",\"stream_name\",\"segment_count\",\"returned_interval_count\",\"aggregated\",\"aggregation_bucket_seconds\",\"truncated\",\"coverage\",\"segments\"],\"type\":\"object\"},\"FleetCamera\":{\"additionalProperties\":false,\"properties\":{\"address\":{\"type\":\"string\"},\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"can_configure\":{\"type\":\"boolean\"},\"capabilities\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchannel\"]},\"type\":\"array\"},\"current_fps\":{\"type\":\"number\"},\"enabled\":{\"type\":\"boolean\"},\"first_video_at\":{\"minimum\":0,\"type\":\"integer\"},\"health\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]},\"health_changed_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_frame_ts\":{\"minimum\":0,\"type\":\"integer\"},\"last_recording_at\":{\"minimum\":0,\"type\":\"integer\"},\"location\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"path\"],\"type\":\"object\"},\"manufacturer\":{\"type\":\"string\"},\"matched_clauses\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"model\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"recording_active\":{\"type\":\"boolean\"},\"recording_mode\":{\"enum\":[\"off\",\"continuous\",\"detection\"]},\"tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"label\":{\"type\":\"string\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"label\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"camera_uuid\",\"name\",\"address\",\"enabled\",\"recording_mode\",\"health\",\"availability\",\"health_changed_at\",\"last_frame_ts\",\"first_video_at\",\"last_recording_at\",\"current_fps\",\"recording_active\",\"can_configure\",\"manufacturer\",\"model\",\"capabilities\",\"location\",\"tags\"],\"type\":\"object\"},\"FleetFacetEntity\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"count\"],\"type\":\"object\"},\"FleetFacets\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"enabled\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"type\":\"boolean\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"health\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetEntity\"},\"type\":\"array\"},\"recording_mode\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"off\",\"continuous\",\"detection\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetEntity\"},\"type\":\"array\"}},\"required\":[\"health\",\"enabled\",\"recording_mode\",\"availability\",\"tags\",\"locations\"],\"type\":\"object\"},\"FleetLocation\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"child_count\":{\"minimum\":0,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_system\":{\"type\":\"boolean\"},\"metadata\":{\"additionalProperties\":true,\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"integer\"},\"type\":{\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"parent_uuid\",\"name\",\"type\",\"sort_order\",\"is_system\",\"metadata\",\"child_count\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetLocationCreate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additionalProperties\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"number\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\"],\"type\":\"object\"},\"FleetLocationUpdate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additionalProperties\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"number\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"FleetLocationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetLocation\"},\"type\":\"array\"},\"unassigned_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"locations\",\"count\"],\"type\":\"object\"},\"FleetMutationAck\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"FleetOperationalQueue\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"key\":{\"type\":\"string\"},\"oldest_unresolved_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"remediation\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"severity\":{\"type\":\"string\"}},\"required\":[\"key\",\"severity\",\"count\",\"remediation\",\"oldest_unresolved_at\",\"selector\"],\"type\":\"object\"},\"FleetPreviewRequest\":{\"additionalProperties\":false,\"description\":\"Read-only POST. Defaults: all selector, page 1, page_size 50, name/asc, facets true, explain false, availability all. Server enforces UTF-8 byte length for search and bounded selector parsing; an invalid or stale selector never grants visibility.\",\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"facets\":{\"type\":\"boolean\"},\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[],\"type\":\"object\"},\"FleetPreviewResponse\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FleetCamera\"},\"maxItems\":50,\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"facets\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"const\":true},\"queue_version\":{\"const\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"},\"type\":\"array\"},\"selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"selector_version\",\"queue_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_order\",\"cameras\",\"queues\"],\"type\":\"object\"},\"FleetQueryRequest\":{\"additionalProperties\":false,\"description\":\"Read-only POST. Defaults: all selector, page 1, page_size 50, name/asc, facets true, explain false, availability all. Server enforces UTF-8 byte length for search and bounded selector parsing; an invalid or stale selector never grants visibility.\",\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"facets\":{\"type\":\"boolean\"},\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200,\"minimum\":1,\"type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[],\"type\":\"object\"},\"FleetQueryResponse\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FleetCamera\"},\"maxItems\":200,\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"facets\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"queue_version\":{\"const\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"},\"type\":\"array\"},\"selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"selector_version\",\"queue_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_order\",\"cameras\",\"queues\"],\"type\":\"object\"},\"FleetRevisionRequest\":{\"additionalProperties\":false,\"properties\":{\"revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"FleetSavedView\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"is_shared\",\"owned\",\"selector\",\"search\",\"collection_uuid\",\"columns\",\"sort_by\",\"sort_order\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetSavedViewsResponse\":{\"additionalProperties\":false,\"properties\":{\"can_share\":{\"type\":\"boolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"views\":{\"items\":{\"$ref\":\"#/components/schemas/FleetSavedView\"},\"maxItems\":256,\"type\":\"array\"}},\"required\":[\"views\",\"count\",\"can_share\"],\"type\":\"object\"},\"FleetSelector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"FleetTag\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"color\":{\"type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"label\",\"color\",\"description\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetTagCreate\":{\"additionalProperties\":false,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\":\"string\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"},\"FleetTagMergeRequest\":{\"additionalProperties\":false,\"properties\":{\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"FleetTagMergeResult\":{\"additionalProperties\":false,\"properties\":{\"source_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":true,\"type\":\"boolean\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"success\",\"source_uuid\",\"target_uuid\"],\"type\":\"object\"},\"FleetTagUpdate\":{\"additionalProperties\":false,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\":\"string\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"FleetTagsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"array\"}},\"required\":[\"tags\",\"count\"],\"type\":\"object\"},\"FleetViewCreate\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[\"name\",\"selector\"],\"type\":\"object\"},\"FleetViewUpdate\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[\"name\",\"selector\",\"revision\"],\"type\":\"object\"},\"FloorPlan\":{\"additionalProperties\":false,\"properties\":{\"background_mime\":{\"enum\":[\"image/png\",\"image/jpeg\",null],\"type\":[\"string\",\"null\"]},\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCamera\"},\"maxItems\":1000,\"type\":\"array\"},\"can_modify\":{\"type\":\"boolean\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"name\",\"location_uuid\",\"parent_plan_uuid\",\"canvas_width\",\"canvas_height\",\"sketch\",\"cameras\",\"uuid\",\"background_mime\",\"partially_authorized\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FloorPlanCamera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotation\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"rotation\",\"fov\"],\"type\":\"object\"},\"FloorPlanCameraRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotation\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\"],\"type\":\"object\"},\"FloorPlanCreate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]}},\"required\":[\"name\"],\"type\":\"object\"},\"FloorPlanDeletion\":{\"additionalProperties\":false,\"properties\":{\"deleted\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"deleted\"],\"type\":\"object\"},\"FloorPlanUpdate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]}},\"required\":[\"name\",\"cameras\",\"revision\"],\"type\":\"object\"},\"FloorPlansResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"plans\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlan\"},\"type\":\"array\"}},\"required\":[\"plans\",\"can_modify\"],\"type\":\"object\"},\"FloorSketch\":{\"additionalProperties\":false,\"properties\":{\"shapes\":{\"items\":{\"oneOf\":[{\"$ref\":\"#/components/schemas/FloorSketchRect\"},{\"$ref\":\"#/components/schemas/FloorSketchWall\"},{\"$ref\":\"#/components/schemas/FloorSketchArea\"},{\"$ref\":\"#/components/schemas/FloorSketchLabel\"}]},\"maxItems\":400,\"type\":\"array\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\",\"shapes\"],\"type\":\"object\"},\"FloorSketchArea\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"points\":{\"items\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"maxItems\":64,\"minItems\":3,\"type\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"area\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"points\"],\"type\":\"object\"},\"FloorSketchLabel\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"size\":{\"enum\":[\"sm\",\"md\",\"lg\"],\"type\":\"string\"},\"text\":{\"maxLength\":95,\"minLength\":1,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"label\",\"type\":\"string\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"text\"],\"type\":\"object\"},\"FloorSketchRect\":{\"additionalProperties\":false,\"properties\":{\"filled\":{\"type\":\"boolean\"},\"h\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"rect\",\"type\":\"string\"},\"w\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"w\",\"h\"],\"type\":\"object\"},\"FloorSketchWall\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"points\":{\"items\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"maxItems\":64,\"minItems\":2,\"type\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"wall\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"points\"],\"type\":\"object\"},\"Go2rtcConfigurationInput\":{\"additionalProperties\":true,\"properties\":{\"override\":{\"maxLength\":65535,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"Go2rtcConfigurationValidation\":{\"additionalProperties\":true,\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"column\":{\"type\":\"number\"},\"line\":{\"type\":\"number\"},\"message\":{\"type\":\"string\"}},\"type\":\"object\"}]},\"libyaml_available\":{\"type\":\"boolean\"},\"skip_reason\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type\":\"object\"},\"skipped\":{\"type\":\"boolean\"},\"valid\":{\"type\":\"boolean\"},\"warnings\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"valid\",\"libyaml_available\",\"skipped\",\"warnings\"],\"type\":\"object\"},\"Go2rtcEffectiveConfiguration\":{\"additionalProperties\":true,\"properties\":{\"base\":{\"type\":\"string\"},\"go2rtc_initialized\":{\"type\":\"boolean\"},\"managed_listener_policy\":{\"type\":\"string\"},\"merged_source_order\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"override\":{\"type\":\"string\"},\"override_in_use\":{\"type\":\"boolean\"},\"redaction_available\":{\"type\":\"boolean\"},\"warnings\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"go2rtc_initialized\",\"redaction_available\",\"base\",\"override\",\"managed_listener_policy\",\"merged_source_order\",\"override_in_use\",\"warnings\"],\"type\":\"object\"},\"Go2rtcOverrideDiagnostics\":{\"additionalProperties\":true,\"properties\":{\"content_matches\":{\"type\":\"boolean\"},\"db_bytes\":{\"type\":\"number\"},\"expected_file_path\":{\"type\":\"string\"},\"file_bytes\":{\"type\":\"number\"},\"file_present\":{\"type\":\"boolean\"},\"file_read_error\":{\"type\":\"string\"},\"process_pid\":{\"type\":\"number\"},\"process_running\":{\"type\":\"boolean\"},\"process_state_known\":{\"type\":\"boolean\"},\"quarantine_active\":{\"type\":\"boolean\"},\"quarantine_reason\":{\"type\":\"string\"},\"quarantined_copy_bytes\":{\"type\":\"integer\"},\"validated_version\":{\"type\":\"string\"}},\"required\":[\"expected_file_path\",\"db_bytes\",\"file_present\",\"file_bytes\",\"content_matches\",\"quarantine_active\",\"process_state_known\",\"process_running\",\"process_pid\",\"quarantined_copy_bytes\",\"validated_version\"],\"type\":\"object\"},\"HealthResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamHealth\"},{\"$ref\":\"#/components/schemas/ProcessLiveness\"},{\"$ref\":\"#/components/schemas/StreamHealthHistory\"}]},\"HlsHealth\":{\"additionalProperties\":true,\"properties\":{\"healthy\":{\"type\":\"boolean\"},\"status\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"},\"watchdogRestartCount\":{\"type\":\"integer\"}},\"required\":[\"healthy\",\"status\",\"watchdogRestartCount\",\"timestamp\"],\"type\":\"object\"},\"HostHealth\":{\"additionalProperties\":true,\"properties\":{\"active_incidents\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"incident_id\":{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"type\":\"string\"},\"sampled_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"persistence_pending\":{\"type\":\"boolean\"},\"scope\":{\"type\":\"string\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"thresholds\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"critical_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seconds\":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\",\"overridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"warning_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"}},\"required\":[\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_observed_at_ms\",\"persistence_pending\",\"observation\"],\"type\":\"object\"},\"type\":\"array\"},\"coverage\":{\"additionalProperties\":true,\"properties\":{\"abandoned_helpers\":{\"type\":\"number\"},\"collection_errors\":{\"type\":\"number\"},\"collection_timeouts\":{\"type\":\"number\"},\"complete\":{\"type\":\"boolean\"},\"coverage_overflows\":{\"type\":\"number\"},\"incomplete_count\":{\"type\":\"number\"},\"observations_dropped\":{\"type\":\"number\"},\"unavailable_observations\":{\"type\":\"number\"}},\"required\":[\"complete\",\"incomplete_count\",\"unavailable_observations\",\"observations_dropped\",\"collection_errors\",\"collection_timeouts\",\"abandoned_helpers\",\"coverage_overflows\"],\"type\":\"object\"},\"evaluator\":{\"additionalProperties\":true,\"properties\":{\"active_incidents\":{\"type\":\"integer\"},\"pending_persistence\":{\"type\":\"integer\"},\"tracked_conditions\":{\"type\":\"integer\"}},\"required\":[\"tracked_conditions\",\"active_incidents\",\"pending_persistence\"],\"type\":\"object\"},\"monitoring\":{\"additionalProperties\":true,\"properties\":{\"configured_enabled\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"initialized\":{\"type\":\"boolean\"},\"restart_required\":{\"type\":\"boolean\"},\"worker_threads\":{\"type\":\"number\"}},\"required\":[\"configured_enabled\",\"initialized\",\"enabled\",\"worker_threads\",\"restart_required\"],\"type\":\"object\"},\"observations\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"type\":\"string\"},\"sampled_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"overall_state\":{\"type\":\"string\"},\"recent_samples\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"completed_at_ms\":{\"type\":\"number\"},\"observation_count\":{\"type\":\"number\"},\"observations_dropped\":{\"type\":\"number\"},\"sequence\":{\"type\":\"number\"}},\"required\":[\"sequence\",\"completed_at_ms\",\"observation_count\",\"observations_dropped\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":{\"type\":\"number\"},\"self_observability\":{\"additionalProperties\":true,\"properties\":{\"collectors\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"attempts\":{\"type\":\"number\"},\"busy\":{\"type\":\"boolean\"},\"collector\":{\"type\":\"string\"},\"completions\":{\"type\":\"number\"},\"failures\":{\"type\":\"number\"},\"last_duration_ms\":{\"type\":\"number\"},\"maximum_duration_ms\":{\"type\":\"number\"},\"overlap_skips\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"stale\":{\"type\":\"boolean\"},\"tier\":{\"type\":\"number\"},\"timeouts\":{\"type\":\"number\"}},\"required\":[\"collector\",\"scope\",\"tier\",\"attempts\",\"completions\",\"failures\",\"timeouts\",\"overlap_skips\",\"last_duration_ms\",\"maximum_duration_ms\",\"busy\",\"stale\"],\"type\":\"object\"},\"type\":\"array\"},\"coverage_overflows\":{\"type\":\"number\"},\"event_delivery\":{\"additionalProperties\":true,\"properties\":{\"all_destinations_unavailable\":{\"type\":\"boolean\"},\"circular_report_path\":{\"type\":\"boolean\"},\"degraded\":{\"type\":\"boolean\"},\"outbox\":{\"additionalProperties\":true,\"properties\":{\"available\":{\"type\":\"boolean\"}},\"type\":\"object\"},\"outbox_full\":{\"type\":\"boolean\"}},\"type\":\"object\"},\"incident_persistence\":{\"additionalProperties\":true,\"properties\":{\"failures\":{\"type\":\"number\"},\"pending\":{\"type\":\"number\"},\"retries\":{\"type\":\"number\"}},\"type\":\"object\"}},\"required\":[\"collectors\",\"coverage_overflows\",\"incident_persistence\",\"event_delivery\"],\"type\":\"object\"},\"snapshot\":{\"additionalProperties\":true,\"properties\":{\"age_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"available\":{\"type\":\"boolean\"},\"completed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"freshness\":{\"type\":\"string\"},\"sequence\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"available\",\"sequence\",\"completed_at_ms\",\"age_ms\",\"freshness\"],\"type\":\"object\"},\"thresholds\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"critical_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seconds\":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\",\"overridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"warning_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"},\"type\":\"array\"},\"visibility\":{\"additionalProperties\":true,\"properties\":{\"coverage_boundary\":{\"type\":\"string\"},\"effective_scope\":{\"type\":\"string\"},\"host_hardware_visible\":{\"type\":\"boolean\"}},\"required\":[\"effective_scope\",\"host_hardware_visible\",\"coverage_boundary\"],\"type\":\"object\"}},\"required\":[\"schema_version\",\"monitoring\",\"overall_state\",\"visibility\",\"snapshot\",\"coverage\",\"observations\",\"thresholds\",\"active_incidents\",\"recent_samples\",\"self_observability\"],\"type\":\"object\"},\"HostHealthIncidents\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"incidents\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"closed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"incident_id\":{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"resource\":{\"type\":\"string\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"unit\",\"value\"],\"type\":\"object\"},\"reconciliation\":{\"type\":\"string\"},\"revision\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"}},\"required\":[\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_observed_at_ms\",\"closed_at_ms\",\"reconciliation\",\"revision\",\"observation\"],\"type\":\"object\"},\"type\":\"array\"},\"next_cursor\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"schema_version\":{\"type\":\"number\"}},\"required\":[\"schema_version\",\"count\",\"incidents\",\"next_cursor\"],\"type\":\"object\"},\"IceServersResponse\":{\"additionalProperties\":true,\"properties\":{\"ice_servers\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"credential\":{\"type\":\"string\"},\"urls\":{\"items\":{\"type\":\"string\"},\"minItems\":1,\"type\":\"array\"},\"username\":{\"type\":\"string\"}},\"required\":[\"urls\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"ice_servers\"],\"type\":\"object\"},\"ImagingModeLevel\":{\"additionalProperties\":false,\"properties\":{\"level\":{\"type\":\"number\"},\"mode\":{\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"ImagingModeLevelUpdate\":{\"oneOf\":[{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"level\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"mode\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"}]},\"ImagingModeOptions\":{\"additionalProperties\":false,\"properties\":{\"level\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"modes\"],\"type\":\"object\"},\"ImagingOptions\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"brightness\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"color_saturation\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"contrast\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"ir_cut_filter_modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"noise_reduction\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"sharpness\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"video_source_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"}},\"required\":[\"video_source_token\",\"ir_cut_filter_modes\"],\"type\":\"object\"},\"ImagingOptionsResponse\":{\"additionalProperties\":false,\"properties\":{\"options\":{\"$ref\":\"#/components/schemas/ImagingOptions\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"options\"],\"type\":\"object\"},\"ImagingSettings\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"brightness\":{\"type\":\"number\"},\"color_saturation\":{\"type\":\"number\"},\"contrast\":{\"type\":\"number\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"ir_cut_filter\":{\"type\":\"string\"},\"noise_reduction\":{\"type\":\"number\"},\"sharpness\":{\"type\":\"number\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"video_source_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"}},\"required\":[\"video_source_token\"],\"type\":\"object\"},\"ImagingSettingsRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"brightness\"]},{\"properties\":{\"color_saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"color_saturation\"]},{\"properties\":{\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"contrast\"]},{\"properties\":{\"sharpness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"sharpness\"]},{\"properties\":{\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"noise_reduction\"]},{\"properties\":{\"saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"saturation\"]},{\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"backlight_compensation\"]},{\"properties\":{\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"wide_dynamic_range\"]},{\"properties\":{\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"tone_compensation\"]},{\"properties\":{\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"defogging\"]},{\"properties\":{\"backlight\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"backlight\"]},{\"properties\":{\"wdr\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"wdr\"]},{\"properties\":{\"tone\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"tone\"]},{\"properties\":{\"defog\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"defog\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"description\":\"Known aliases remain accepted; force_persistence alone is rejected. Numeric limits come from the device options rather than an invented global range.\",\"minProperties\":1,\"properties\":{\"backlight\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"color_saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"defog\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"force_persistence\":{\"type\":\"boolean\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"sharpness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tone\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wdr\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[],\"type\":\"object\"},\"ImagingSettingsResponse\":{\"additionalProperties\":false,\"properties\":{\"settings\":{\"$ref\":\"#/components/schemas/ImagingSettings\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"settings\"],\"type\":\"object\"},\"LegacyApiKeyCreated\":{\"additionalProperties\":false,\"properties\":{\"api_key\":{\"minLength\":1,\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"api_key\"],\"type\":\"object\"},\"LiveCameraSlot\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"camera_uuid\"],\"type\":\"object\"},\"LiveLayout\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"name\",\"is_shared\",\"location_uuid\",\"availability\",\"columns\",\"rows\",\"camera_slots\",\"uuid\",\"owned\",\"partially_authorized\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"LiveLayoutCreate\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"name\",\"availability\",\"columns\",\"rows\",\"camera_slots\"],\"type\":\"object\"},\"LiveLayoutUpdate\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"name\",\"availability\",\"columns\",\"rows\",\"camera_slots\",\"revision\"],\"type\":\"object\"},\"LiveLayoutsResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"can_share\":{\"type\":\"boolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"layouts\":{\"items\":{\"$ref\":\"#/components/schemas/LiveLayout\"},\"type\":\"array\"}},\"required\":[\"layouts\",\"count\",\"can_share\",\"can_modify\"],\"type\":\"object\"},\"LiveStreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=live. Uses the same filtered pagination envelope, with playback-specific row fields.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/LiveStreamSummaryRedacted\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"LiveStreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"type\":\"boolean\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":true,\"type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"privacy_mode\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_transport\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"}],\"description\":\"surface=live includes playback and capability hints; admin_url is never emitted by this surface.\"},\"LiveStreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"type\":\"boolean\"},\"can_configure\":{\"const\":false,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":false,\"type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"privacy_mode\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_transport\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"}],\"description\":\"surface=live includes playback and capability hints; admin_url is never emitted by this surface.\"},\"LoginConfig\":{\"additionalProperties\":false,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"remember_device_enabled\":{\"type\":\"boolean\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"}},\"required\":[\"force_mfa_on_login\",\"remember_device_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"LoginLockoutCleared\":{\"additionalProperties\":false,\"properties\":{\"cleared\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"cleared\",\"message\"],\"type\":\"object\"},\"LoginMfaChallenge\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_password\":false,\"password\":false,\"password_hash\":false,\"redirect\":false,\"secret\":false,\"success\":false,\"token_hash\":false,\"totp_required\":{\"const\":true},\"totp_secret\":false,\"totp_token\":{\"description\":\"Ephemeral MFA challenge credential. Returned only by login; never an ordinary identity/token-list field or documentation example.\",\"minLength\":1,\"readOnly\":true,\"type\":\"string\"}},\"required\":[\"totp_required\",\"totp_token\"],\"type\":\"object\"},\"LoginRequest\":{\"additionalProperties\":true,\"description\":\"JSON credentials. The C handler copies username/password into 64-byte buffers (63-byte payload); it does not reject oversized strings before truncating. Invalid credentials return401. Optional fields with wrong types are ignored by current C; this schema describes typed client input.\",\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"remember_device\":{\"type\":\"boolean\"},\"totp_code\":{\"description\":\"Optional inline MFA code; enrollment and challenge completion endpoints remain outside this slice.\",\"type\":\"string\",\"writeOnly\":true},\"username\":{\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"LoginResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/LoginSuccess\"},{\"$ref\":\"#/components/schemas/LoginMfaChallenge\"}]},\"LoginSuccess\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_password\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"redirect\":{\"const\":\"/index.html\"},\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_required\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\",\"redirect\",\"must_change_password\"],\"type\":\"object\"},\"LogoutResponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false,\"password_hash\":false,\"redirect\":{\"const\":\"/login.html?logout=true\"},\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\",\"redirect\"],\"type\":\"object\"},\"LprStreamSummary\":{\"additionalProperties\":true,\"description\":\"surface=lpr exposes only camera identity and requires both lpr.search and lpr.read for each returned camera. It does not imply live.view or configuration access.\",\"properties\":{\"camera_uuid\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"LprStreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=lpr. Empty results remain a valid response even with no LPR grants.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"$ref\":\"#/components/schemas/LprStreamSummary\"},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"MotionTrigger\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"},\"label\":{\"minLength\":1,\"type\":\"string\"},\"objects\":{\"items\":{\"oneOf\":[{\"minLength\":1,\"type\":\"string\"},{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"label\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"}]},\"type\":\"array\"},\"stream\":{\"minLength\":1,\"type\":\"string\"},\"tags\":{\"items\":{\"minLength\":1,\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"stream\",\"action\"],\"type\":\"object\"},\"MotionTriggered\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"detections_stored\":{\"minimum\":0,\"type\":\"integer\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"},\"recording_triggered\":{\"type\":\"boolean\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"},\"tags_applied\":{\"minimum\":0,\"type\":\"integer\"},\"warning\":{\"type\":\"string\"}},\"required\":[\"success\",\"stream\",\"action\",\"recording_triggered\",\"detections_stored\",\"tags_applied\"],\"type\":\"object\"},\"OnvifAddRequest\":{\"additionalProperties\":false,\"properties\":{\"device_url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"password\":{\"maxLength\":63,\"type\":\"string\"},\"profile_token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"username\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[\"device_url\",\"profile_token\",\"stream_name\"],\"type\":\"object\"},\"OnvifAddResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\",\"stream_name\"],\"type\":\"object\"},\"OnvifClaimRequest\":{\"additionalProperties\":false,\"properties\":{\"inventory_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"inventory_uuid\",\"stream_name\"],\"type\":\"object\"},\"OnvifDevice\":{\"additionalProperties\":false,\"properties\":{\"addresses\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"claim_state\":{\"type\":\"string\"},\"claimed_camera_uuid\":{\"type\":\"string\"},\"device_service\":{\"type\":\"string\"},\"discovery_time\":{\"type\":\"integer\"},\"duplicate_suspected\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"firmware_version\":{\"type\":\"string\"},\"first_seen_at\":{\"type\":\"integer\"},\"hardware_id\":{\"type\":\"string\"},\"imaging_service\":{\"type\":\"string\"},\"inventory_uuid\":{\"type\":\"string\"},\"ip_address\":{\"type\":\"string\"},\"last_scan_network\":{\"type\":\"string\"},\"last_seen_at\":{\"type\":\"integer\"},\"mac_address\":{\"type\":\"string\"},\"manufacturer\":{\"type\":\"string\"},\"media_service\":{\"type\":\"string\"},\"model\":{\"type\":\"string\"},\"online\":{\"type\":\"boolean\"},\"ptz_service\":{\"type\":\"string\"},\"serial_number\":{\"type\":\"string\"}},\"required\":[\"endpoint\",\"device_service\",\"media_service\",\"ptz_service\",\"imaging_service\",\"manufacturer\",\"model\",\"firmware_version\",\"serial_number\",\"hardware_id\",\"ip_address\",\"mac_address\",\"inventory_uuid\",\"last_scan_network\",\"claim_state\",\"claimed_camera_uuid\",\"discovery_time\",\"first_seen_at\",\"last_seen_at\",\"online\",\"duplicate_suspected\",\"addresses\"],\"type\":\"object\"},\"OnvifDevicesResponse\":{\"additionalProperties\":false,\"properties\":{\"devices\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifDevice\"},\"type\":\"array\"}},\"required\":[\"devices\"],\"type\":\"object\"},\"OnvifDiscoveryRequest\":{\"additionalProperties\":false,\"properties\":{\"network\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"OnvifDiscoveryStatus\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"interval\":{\"type\":\"integer\"},\"network\":{\"type\":\"string\"}},\"required\":[\"enabled\",\"network\",\"interval\"],\"type\":\"object\"},\"OnvifProfile\":{\"additionalProperties\":false,\"properties\":{\"bitrate\":{\"type\":\"integer\"},\"encoding\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"},\"ptz_configuration_token\":{\"type\":\"string\"},\"ptz_supported\":{\"type\":\"boolean\"},\"snapshot_uri\":{\"type\":\"string\"},\"stream_uri\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"},\"video_source_token\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"token\",\"name\",\"video_source_token\",\"ptz_configuration_token\",\"snapshot_uri\",\"stream_uri\",\"encoding\",\"width\",\"height\",\"fps\",\"bitrate\",\"ptz_supported\"],\"type\":\"object\"},\"OnvifProfilesResponse\":{\"additionalProperties\":false,\"properties\":{\"profiles\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifProfile\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"profiles\"],\"type\":\"object\"},\"OnvifTestRequest\":{\"additionalProperties\":false,\"properties\":{\"insecure\":{\"type\":\"boolean\"},\"password\":{\"maxLength\":63,\"type\":\"string\"},\"url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"username\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[\"url\"],\"type\":\"object\"},\"OpenApiCoverage\":{\"additionalProperties\":true,\"description\":\"Describes the canonical pilot coverage, not user or camera identities. Omission of an unspecified API is not a denial of access.\",\"properties\":{\"canonical_revision\":{\"type\":\"string\"},\"covered_operations\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"description\":{\"type\":\"string\"},\"partial\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"partial\",\"canonical_revision\",\"covered_operations\"],\"type\":\"object\"},\"OpenApiDocument\":{\"additionalProperties\":true,\"description\":\"A valid OpenAPI 3.1.1 document projected for the actual authenticated principal. This response schema checks its envelope; independent OpenAPI lint checks document validity. It can contain no camera-specific eligible operations while retaining authenticated discovery/list operations.\",\"properties\":{\"components\":{\"additionalProperties\":true,\"type\":\"object\"},\"info\":{\"additionalProperties\":true,\"properties\":{\"title\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"required\":[\"title\",\"version\"],\"type\":\"object\"},\"openapi\":{\"const\":\"3.1.1\",\"type\":\"string\"},\"paths\":{\"additionalProperties\":true,\"type\":\"object\"},\"x-lightnvr-coverage\":{\"$ref\":\"#/components/schemas/OpenApiCoverage\"}},\"required\":[\"openapi\",\"info\",\"paths\",\"x-lightnvr-coverage\"],\"type\":\"object\"},\"PasswordAdminReset\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"string\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"new_password\"],\"type\":\"object\"},\"PasswordChangeRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/PasswordSelfChange\"},{\"$ref\":\"#/components/schemas/PasswordAdminReset\",\"x-lightnvr-visibility\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}],\"description\":\"New password must contain at least eight UTF-8 bytes. Self-service requires the current password; effective users.manage can reset passwords except its own forced first-login change. Locked/default-password rules are enforced by the server.\"},\"PasswordChangedResponse\":{\"additionalProperties\":false,\"properties\":{\"must_change_password\":{\"const\":false},\"success\":{\"const\":true}},\"required\":[\"success\",\"must_change_password\"],\"type\":\"object\"},\"PasswordLockRequest\":{\"additionalProperties\":true,\"properties\":{\"locked\":{\"type\":\"boolean\"}},\"required\":[\"locked\"],\"type\":\"object\"},\"PasswordSelfChange\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"string\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"old_password\",\"new_password\"],\"type\":\"object\"},\"PlaybackTransport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hls_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"PlayerTelemetry\":{\"additionalProperties\":true,\"properties\":{\"rebuffer_count\":{\"type\":\"integer\"},\"rebuffer_duration_ms\":{\"type\":\"number\"},\"resolution_switches\":{\"type\":\"integer\"},\"session_id\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"transport\":{\"type\":\"string\"},\"ttff_ms\":{\"type\":\"number\"},\"webrtc_rtt_ms\":{\"type\":\"number\"}},\"required\":[\"stream_name\"],\"type\":\"object\"},\"ProcessLiveness\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"liveness\":{\"const\":true,\"type\":\"boolean\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"integer\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"liveness\"],\"type\":\"object\"},\"PtzAxesRequest\":{\"additionalProperties\":false,\"description\":\"Finite float-representable coordinates in the camera default space. Camera-advertised ranges vary; the server does not assume normalized position units. Omitted axes retain existing native zero/axis-presence semantics.\",\"minProperties\":1,\"properties\":{\"pan\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tilt\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"zoom\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[],\"type\":\"object\"},\"PtzCapabilities\":{\"additionalProperties\":false,\"properties\":{\"has_absolute_move\":{\"type\":\"boolean\"},\"has_continuous_move\":{\"type\":\"boolean\"},\"has_home\":{\"type\":\"boolean\"},\"has_home_position\":{\"type\":\"boolean\"},\"has_pan_tilt\":{\"type\":\"boolean\"},\"has_presets\":{\"type\":\"boolean\"},\"has_relative_move\":{\"type\":\"boolean\"},\"has_zoom\":{\"type\":\"boolean\"},\"max_presets\":{\"type\":\"integer\"},\"max_x\":{\"type\":\"number\"},\"max_y\":{\"type\":\"number\"},\"max_z\":{\"type\":\"number\"},\"onvif_queried\":{\"type\":\"boolean\"},\"pan_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"ptz_service_url\":{\"type\":\"string\"},\"tilt_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"zoom_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"}},\"required\":[\"ptz_enabled\",\"onvif_queried\",\"has_pan_tilt\",\"has_zoom\",\"has_continuous_move\",\"has_absolute_move\",\"has_relative_move\",\"has_home_position\",\"has_presets\",\"has_home\",\"pan_range\",\"tilt_range\",\"zoom_range\",\"max_x\",\"max_y\",\"max_z\",\"max_presets\",\"ptz_service_url\"],\"type\":\"object\"},\"PtzGotoPresetRequest\":{\"additionalProperties\":false,\"properties\":{\"token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"token\"],\"type\":\"object\"},\"PtzPresetRequest\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"PtzPresetResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true},\"token\":{\"type\":\"string\"}},\"required\":[\"success\",\"message\",\"token\"],\"type\":\"object\"},\"PtzPresets\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"type\":\"integer\"},\"presets\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"}},\"required\":[\"token\",\"name\"],\"type\":\"object\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"presets\",\"count\"],\"type\":\"object\"},\"RecordingBatchProtectionRequest\":{\"additionalProperties\":true,\"description\":\"Empty arrays are accepted. Invalid or missing IDs count as failures; an existing forbidden member rejects the entire request before writes.\",\"properties\":{\"ids\":{\"items\":{},\"type\":\"array\"},\"protected\":{\"type\":\"boolean\"}},\"required\":[\"ids\",\"protected\"],\"type\":\"object\"},\"RecordingBatchProtectionResult\":{\"additionalProperties\":true,\"properties\":{\"fail_count\":{\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"success_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"success_count\",\"fail_count\",\"protected\",\"message\"],\"type\":\"object\"},\"RecordingBatchTagsRequest\":{\"additionalProperties\":true,\"description\":\"Add/remove are optional; wrong-typed arrays and non-string or empty elements are ignored by the handler. All IDs are authorized before writes. Database failures may follow earlier successful tag writes.\",\"properties\":{\"add\":{\"items\":{},\"type\":\"array\"},\"ids\":{\"items\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"maxItems\":10000,\"minItems\":1,\"type\":\"array\"},\"remove\":{\"items\":{},\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},\"RecordingBatchTagsResult\":{\"additionalProperties\":true,\"properties\":{\"recordings_count\":{\"description\":\"Number of authorized input IDs processed, including duplicate IDs.\",\"minimum\":0,\"type\":\"integer\"},\"tags_added\":{\"description\":\"Successful per-recording add statement executions, including INSERT OR IGNORE no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"},\"tags_removed\":{\"description\":\"Successful per-recording remove statement executions, including absent-tag no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"recordings_count\",\"tags_added\",\"tags_removed\"],\"type\":\"object\"},\"RecordingDeletionFilter\":{\"additionalProperties\":false,\"description\":\"stream_name (or stream fallback) selects comma-separated current camera names. Without a nonempty stream filter, all-fleet recording.delete authority is required. Authorized camera UUIDs and collection membership are captured before dispatch; caller-supplied camera UUID fields do not grant scope. Supplied start/end must be valid UTC YYYY-MM-DDTHH:mm:ss strings, optionally followed by fractional seconds and/or Z; fractions are truncated to whole seconds. Bounds must be after the Unix epoch and start must not exceed end. Invalid types, calendar dates, suffixes, or ranges return400 before job creation. Omitted bounds are unbounded. detection/protected must be -1, 0, or 1. Unknown keys, duplicate keys (including date-case aliases), simultaneous stream and stream_name, invalid types, and empty selectors return400. Date key spelling is case-insensitive; other keys are case-sensitive. Caller-supplied camera/stream UUID selectors are unsupported and rejected. In-progress rows are excluded.\",\"patternProperties\":{\"^(?!start$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"}},\"properties\":{\"capture_method\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"collection_uuid\":{\"maxLength\":36,\"minLength\":1,\"type\":\"string\"},\"detection\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"detection_label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"end\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"protected\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"start\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"stream\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"tag\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"RecordingDeletionJobAccepted\":{\"additionalProperties\":true,\"properties\":{\"job_id\":{\"type\":\"string\"},\"status\":{\"const\":\"started\"}},\"required\":[\"job_id\",\"status\"],\"type\":\"object\"},\"RecordingDeletionProgress\":{\"additionalProperties\":true,\"description\":\"complete is true for both complete and error terminal states. A complete job can have failed members or queued pending deletions; it does not imply every file is already removed. SQL failure is error; an empty successful filter completes with zero counts. No cancellation endpoint/state exists.\",\"properties\":{\"complete\":{\"type\":\"boolean\"},\"current\":{\"minimum\":0,\"type\":\"integer\"},\"error_message\":{\"type\":\"string\"},\"failed\":{\"minimum\":0,\"type\":\"integer\"},\"job_id\":{\"type\":\"string\"},\"pending_deletions\":{\"minimum\":0,\"type\":\"integer\"},\"status\":{\"enum\":[\"pending\",\"running\",\"complete\",\"error\",\"unknown\"],\"type\":\"string\"},\"status_message\":{\"type\":\"string\"},\"succeeded\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"job_id\",\"status\",\"total\",\"current\",\"succeeded\",\"pending_deletions\",\"failed\",\"status_message\",\"complete\"],\"type\":\"object\"},\"RecordingDeletionRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"description\":\"IDs take precedence over filter. IDs accept safe positive integer JSON numbers or exact positive decimal uint64 strings. Invalid, unsafe numeric, or missing members become failed counts; a forbidden existing member rejects the whole request before job creation.\",\"properties\":{\"ids\":{\"items\":{},\"minItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"filter\":{\"$ref\":\"#/components/schemas/RecordingDeletionFilter\"},\"ids\":{\"anyOf\":[{\"items\":{},\"minItems\":1,\"type\":\"array\"},{\"type\":[\"null\",\"boolean\",\"object\",\"number\",\"string\"]}],\"description\":\"Array-valued ids take precedence over filter and must be nonempty. Other ids values fall through to the filter handler.\"}},\"required\":[\"filter\"],\"type\":\"object\"}]},\"RecordingDeletionResult\":{\"additionalProperties\":true,\"description\":\"Immediate200 has success/message. Queued202 also carries status deletion_pending; neither outcome promises deletion of unrelated recordings.\",\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"deletion_pending\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"RecordingDetail\":{\"additionalProperties\":true,\"description\":\"Authorized recording metadata. id is durable database identity, not list position. Times are UTC display strings with separate Unix seconds. In-progress rows have null end_time/end_time_unix and changing effective duration. playback_issue is empty for no saved issue; invalid_media/no_video/decode_error cause playback422. Other diagnostic values do not conclusively prohibit playback. Optional storage fields are omitted if their lookup fails. file_path is existing authorized metadata, not a client-selected playback path. A finalized row or timeline presence does not guarantee playable media.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pending\":{\"type\":\"boolean\"},\"detection_labels\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"durable_copy_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"in_progress\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"retention_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_time_unix\":{\"type\":\"integer\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_version\":{\"type\":\"integer\"},\"storage_state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable\",\"archiving\",\"archived\",\"hot_and_archive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"end_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"protected\"],\"type\":\"object\"},\"RecordingDetectionLabels\":{\"additionalProperties\":true,\"properties\":{\"labels\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"labels\"],\"type\":\"object\"},\"RecordingExportAccepted\":{\"additionalProperties\":true,\"properties\":{\"token\":{\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"token\",\"total\"],\"type\":\"object\"},\"RecordingExportRequest\":{\"additionalProperties\":true,\"description\":\"Positive numeric IDs are converted to uint64. Filename is sanitized for attachment delivery. Every member is authorized before a job is created; existing operational missing/incomplete errors can abort earlier in input order.\",\"properties\":{\"filename\":{\"type\":\"string\"},\"ids\":{\"items\":{\"description\":\"Recording ID: a number through 9007199254740991 (JavaScript maximum safe integer), otherwise an exact decimal string. Treat IDs as opaque values; do not convert strings to Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"maxItems\":200,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},\"RecordingExportStatus\":{\"additionalProperties\":true,\"description\":\"error is always present, usually empty until failure. Detected unavailable sources or ZIP32 member/size-limit failures put the job in error and remove its temporary result. The current writer does not consistently check filesystem read/write/close errors: complete is not a guarantee against undetected I/O failure or a valid intact archive. No cancellation endpoint/state exists.\",\"properties\":{\"current\":{\"minimum\":0,\"type\":\"integer\"},\"error\":{\"type\":\"string\"},\"status\":{\"enum\":[\"pending\",\"running\",\"complete\",\"error\"],\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"status\",\"current\",\"total\",\"error\"],\"type\":\"object\"},\"RecordingListItem\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pending\":{\"type\":\"boolean\"},\"detection_labels\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"durable_copy_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"in_progress\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"retention_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_time_unix\":{\"type\":\"integer\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_version\":{\"type\":\"integer\"},\"storage_state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable\",\"archiving\",\"archived\",\"hot_and_archive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"tags\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"end_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"protected\",\"tags\"],\"type\":\"object\"},\"RecordingPagination\":{\"additionalProperties\":true,\"description\":\"Counts use the same authorized predicate as rows. Ordinary totals may use a ten-second cache invalidated by supported database mutations; limit=all recounts and resets page1. No top-level total is emitted. When replay scope is empty, the early empty envelope retains the normalized requested page and ordinary limit (all is treated as20).\",\"properties\":{\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"pages\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"page\",\"pages\",\"total\",\"limit\"],\"type\":\"object\"},\"RecordingPlaybackPreparing\":{\"additionalProperties\":true,\"properties\":{\"error\":{\"type\":\"string\"},\"source\":{\"const\":\"archive\"},\"status\":{\"const\":\"preparing\"}},\"required\":[\"status\"],\"type\":\"object\"},\"RecordingPlaybackReady\":{\"additionalProperties\":true,\"properties\":{\"source\":{\"const\":\"archive\"},\"status\":{\"const\":\"ready\"}},\"required\":[\"status\"],\"type\":\"object\"},\"RecordingProtectionRequest\":{\"additionalProperties\":true,\"properties\":{\"protected\":{\"type\":\"boolean\"}},\"required\":[\"protected\"],\"type\":\"object\"},\"RecordingProtectionResult\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"protected\",\"message\"],\"type\":\"object\"},\"RecordingRetentionRequest\":{\"additionalProperties\":true,\"description\":\"Numeric input is truncated toward zero through the C JSON integer accessor. Any negative value clears the override; zero means indefinite; positive values are days.\",\"properties\":{\"retention_days\":{\"type\":\"number\"}},\"required\":[\"retention_days\"],\"type\":\"object\"},\"RecordingRetentionResult\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"retention_days\":{\"type\":\"integer\"}},\"required\":[\"id\",\"retention_days\",\"message\"],\"type\":\"object\"},\"RecordingTags\":{\"additionalProperties\":true,\"properties\":{\"tags\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTagsById\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"tags\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"id\",\"tags\"],\"type\":\"object\"},\"RecordingTagsRequest\":{\"additionalProperties\":true,\"description\":\"Only nonempty string elements are used; other elements are ignored. The database trims tag text, truncates to its fixed UTF-8 byte buffer, and deduplicates. This is replacement, including clearing with an empty array.\",\"properties\":{\"tags\":{\"items\":{},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTimeline\":{\"additionalProperties\":true,\"description\":\"One authorized camera and time window, capped at8640 segments. Optional exact external-motion intervals may be absent on allocation failure.\",\"properties\":{\"detection_interval_count\":{\"minimum\":0,\"type\":\"integer\"},\"detection_intervals\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"end_timestamp\":{\"type\":\"integer\"},\"start_timestamp\":{\"type\":\"integer\"}},\"required\":[\"start_timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"type\":\"array\"},\"end_time\":{\"type\":\"string\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\",\"segment_count\"],\"type\":\"object\"},\"RecordingTimelineByIds\":{\"additionalProperties\":true,\"description\":\"Requested IDs are processed in request order. Missing, deleted-camera and denied recordings are omitted. Counts and overall range reflect retained segments only; an empty result has empty start_time/end_time. Duplicate input IDs can produce duplicate segments.\",\"properties\":{\"end_time\":{\"type\":\"string\"},\"multi_stream\":{\"const\":true},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"string\"},\"stream\":{\"const\":\"(selected)\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\",\"segment_count\",\"multi_stream\"],\"type\":\"object\"},\"RecordingTimelineSegment\":{\"additionalProperties\":true,\"description\":\"Timeline timestamps are Unix seconds; display strings use server local YYYY-MM-DD HH:MM:SS. local_* fields currently equal the Unix timestamp fields. In-progress segments use an effective end for display. Timeline segments omit playback_issue and protected/storage metadata: use detail/playback before treating a finalized segment as playable.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":\"string\"},\"end_timestamp\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"description\":\"Recording ID: a number through 9007199254740991 (JavaScript maximum safe integer), otherwise an exact decimal string. Treat IDs as opaque values; do not convert strings to Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"in_progress\":{\"type\":\"boolean\"},\"local_end_timestamp\":{\"type\":\"integer\"},\"local_start_timestamp\":{\"type\":\"integer\"},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_timestamp\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"start_time\",\"in_progress\",\"duration\",\"size\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"end_time\",\"start_timestamp\",\"end_timestamp\",\"local_start_timestamp\",\"local_end_timestamp\"],\"type\":\"object\"},\"RecordingsPage\":{\"additionalProperties\":true,\"properties\":{\"pagination\":{\"$ref\":\"#/components/schemas/RecordingPagination\"},\"recordings\":{\"items\":{\"$ref\":\"#/components/schemas/RecordingListItem\"},\"type\":\"array\"}},\"required\":[\"recordings\",\"pagination\"],\"type\":\"object\"},\"SetupStatus\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\":\"boolean\"},\"setup_completed_at\":{\"type\":\"number\"}},\"required\":[\"complete\",\"setup_completed_at\"],\"type\":\"object\"},\"SetupUpdate\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"SetupUpdated\":{\"additionalProperties\":true,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"StorageArchiveDeletion\":{\"additionalProperties\":false,\"properties\":{\"attempts\":{\"minimum\":0,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"next_attempt_at\":{\"minimum\":0,\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"remaining_objects\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"recording_id\",\"created_at\",\"remaining_objects\",\"attempts\",\"next_attempt_at\",\"last_error\"],\"type\":\"object\"},\"StorageArchiveResponse\":{\"additionalProperties\":false,\"properties\":{\"archive_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"pending_deletion_count\":{\"minimum\":0,\"type\":\"integer\"},\"pending_deletions\":{\"items\":{\"$ref\":\"#/components/schemas/StorageArchiveDeletion\"},\"maxItems\":100,\"type\":\"array\"},\"recovery_read_only\":{\"type\":\"boolean\"},\"retrieval_cache_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"retrieval_jobs\":{\"minimum\":0,\"type\":\"integer\"},\"s3_enabled\":{\"type\":\"boolean\"},\"transfer_backlog_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"s3_enabled\",\"recovery_read_only\",\"archive_bytes\",\"transfer_backlog_bytes\",\"retrieval_cache_bytes\",\"retrieval_jobs\",\"pending_deletion_count\",\"pending_deletions\"],\"type\":\"object\"},\"StorageArchiveRetryRequest\":{\"additionalProperties\":false,\"properties\":{\"deletion_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"deletion_uuid\"],\"type\":\"object\"},\"StorageArchiveRetryResponse\":{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"retry_queued\",\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"},\"StorageCleanupRequest\":{\"additionalProperties\":false,\"properties\":{\"aggressive\":{\"type\":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"StorageCleanupResponse\":{\"additionalProperties\":false,\"properties\":{\"aggressive\":{\"type\":\"boolean\"},\"message\":{\"const\":\"Cleanup triggered successfully\",\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"aggressive\",\"message\"],\"type\":\"object\"},\"StorageCompliancePolicy\":{\"additionalProperties\":false,\"properties\":{\"achieved_retention_days\":{\"type\":\"number\"},\"active_violation_count\":{\"minimum\":0,\"type\":\"integer\"},\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"copy_deficit_recordings\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"expected_retention_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"maximum_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"minimum_forecast_met\":{\"type\":\"boolean\"},\"minimum_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"observed_daily_bytes\":{\"type\":\"number\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"required_copy_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_window_days\":{\"type\":\"number\"}},\"required\":[\"policy_uuid\",\"policy_name\",\"recording_count\",\"recording_bytes\",\"minimum_retention_days\",\"desired_retention_days\",\"maximum_retention_days\",\"required_copy_count\",\"copy_deficit_recordings\",\"active_violation_count\",\"sample_window_days\",\"observed_daily_bytes\",\"achieved_retention_days\",\"confidence\",\"expected_retention_days\",\"minimum_forecast_met\"],\"type\":\"object\"},\"StorageComplianceResponse\":{\"additionalProperties\":false,\"properties\":{\"active_violations\":{\"items\":{\"$ref\":\"#/components/schemas/StorageComplianceViolation\"},\"type\":\"array\"},\"forecast_note\":{\"type\":\"string\"},\"forecast_window_days\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/schemas/StorageCompliancePolicy\"},\"type\":\"array\"},\"targets\":{\"items\":{\"$ref\":\"#/components/schemas/StorageComplianceTarget\"},\"type\":\"array\"}},\"required\":[\"forecast_window_days\",\"forecast_note\",\"targets\",\"policies\",\"active_violations\"],\"type\":\"object\"},\"StorageComplianceTarget\":{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"days_to_high_watermark\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"expected_retention_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"observed_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"observed_daily_bytes\":{\"minimum\":0,\"type\":\"number\"},\"sample_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_window_days\":{\"minimum\":0,\"type\":\"number\"},\"target_name\":{\"type\":\"string\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"target_uuid\",\"target_name\",\"sample_window_days\",\"observed_daily_bytes\",\"sample_count\",\"observed_bytes\",\"confidence\",\"days_to_high_watermark\",\"expected_retention_days\"],\"type\":\"object\"},\"StorageComplianceViolation\":{\"additionalProperties\":false,\"properties\":{\"details\":{\"type\":\"string\"},\"first_seen_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_seen_at\":{\"minimum\":0,\"type\":\"integer\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"type\":{\"type\":\"string\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"policy_uuid\",\"policy_name\",\"recording_id\",\"type\",\"details\",\"first_seen_at\",\"last_seen_at\"],\"type\":\"object\"},\"StorageHealthResponse\":{\"additionalProperties\":false,\"properties\":{\"capacity_target_free_pct\":{\"minimum\":0,\"type\":\"integer\"},\"fill_rate_bytes_per_day\":{\"type\":\"number\"},\"free_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"free_space_pct\":{\"type\":\"number\"},\"last_check_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_deleted\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_freed\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_deep_time\":{\"minimum\":0,\"type\":\"integer\"},\"pressure_level\":{\"type\":\"string\"},\"pressure_level_num\":{\"minimum\":0,\"type\":\"integer\"},\"projected_seconds_to_target\":{\"type\":\"number\"},\"total_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_space_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"pressure_level\",\"pressure_level_num\",\"free_space_pct\",\"fill_rate_bytes_per_day\",\"projected_seconds_to_target\",\"free_space_bytes\",\"total_space_bytes\",\"used_space_bytes\",\"last_check_time\",\"last_cleanup_time\",\"last_deep_time\",\"last_cleanup_deleted\",\"last_cleanup_freed\",\"capacity_target_free_pct\"],\"type\":\"object\"},\"StorageMigration\":{\"additionalProperties\":false,\"properties\":{\"attempt_count\":{\"minimum\":0,\"type\":\"integer\"},\"bandwidth_limit_bps\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_copied\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_total\":{\"minimum\":0,\"type\":\"integer\"},\"cancel_requested\":{\"type\":\"boolean\"},\"checksum\":{\"anyOf\":[{\"pattern\":\"^[0-9a-f]{64}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"checksum_mode\":{\"const\":\"sha256\",\"type\":\"string\"},\"completed_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"destination_object_key\":{\"type\":\"string\"},\"destination_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"last_error\":{\"type\":\"string\"},\"max_attempts\":{\"minimum\":0,\"type\":\"integer\"},\"next_attempt_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"progress\":{\"minimum\":0,\"type\":\"number\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"source_object_key\":{\"type\":\"string\"},\"source_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"started_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"state\":{\"enum\":[\"queued\",\"copying\",\"verifying\",\"committing\",\"retry_wait\",\"cleanup_pending\",\"completed\",\"failed\",\"cancelled\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"uuid\",\"source_target_uuid\",\"destination_target_uuid\",\"recording_id\",\"operation\",\"source_object_key\",\"destination_object_key\",\"last_error\",\"state\",\"checksum_mode\",\"checksum\",\"bytes_total\",\"bytes_copied\",\"attempt_count\",\"max_attempts\",\"bandwidth_limit_bps\",\"progress\",\"next_attempt_at\",\"cancel_requested\",\"window_start_minute\",\"window_end_minute\",\"revision\",\"created_at\",\"updated_at\",\"started_at\",\"completed_at\"],\"type\":\"object\"},\"StorageMigrationCreate\":{\"additionalProperties\":false,\"properties\":{\"destination_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"recording_id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"recording_id\",\"destination_target_uuid\"],\"type\":\"object\"},\"StorageMigrationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"jobs\":{\"items\":{\"$ref\":\"#/components/schemas/StorageMigration\"},\"type\":\"array\"},\"worker_concurrency\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"jobs\",\"count\",\"worker_concurrency\"],\"type\":\"object\"},\"StoragePoliciesResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicy\"},\"type\":\"array\"}},\"required\":[\"policies\",\"count\"],\"type\":\"object\"},\"StoragePolicy\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"enabled\",\"priority\",\"selector\",\"primary_target_uuid\",\"primary_pool_uuid\",\"fallback_target_uuid\",\"replication_pool_uuid\",\"migration_target_uuid\",\"fallback_mode\",\"minimum_retention_days\",\"desired_retention_days\",\"maximum_retention_days\",\"migration_after_days\",\"required_copy_count\",\"pressure_priority\",\"archive_after_seconds\",\"hot_residency_seconds\",\"archive_protected\",\"archive_on_pressure\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"StoragePolicyApplyRequest\":{\"additionalProperties\":false,\"properties\":{\"preview\":{\"type\":\"boolean\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\",\"preview\"],\"type\":\"object\"},\"StoragePolicyApplyResponse\":{\"additionalProperties\":false,\"properties\":{\"bytes\":{\"minimum\":0,\"type\":\"integer\"},\"past_new_retention_limit\":{\"minimum\":0,\"type\":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"recordings\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"unfinished_transfers\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"revision\",\"preview\",\"recordings\",\"bytes\",\"past_new_retention_limit\",\"unfinished_transfers\"],\"type\":\"object\"},\"StoragePolicyConflict\":{\"additionalProperties\":false,\"properties\":{\"draft_precedes\":{\"type\":\"boolean\"},\"overlap_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"policy_uuid\",\"policy_name\",\"priority\",\"overlap_camera_count\",\"draft_precedes\"],\"type\":\"object\"},\"StoragePolicyCreate\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"primary_target_uuid\"]},{\"properties\":{\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]}},\"required\":[\"primary_pool_uuid\"]}],\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\"],\"type\":\"object\"},\"StoragePolicyPreview\":{\"additionalProperties\":false,\"properties\":{\"conflict_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"conflict_policy_count\":{\"minimum\":0,\"type\":\"integer\"},\"conflicts\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyConflict\"},\"type\":\"array\"},\"draft_enabled\":{\"type\":\"boolean\"},\"effective_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"matched_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyPreviewSample\"},\"maxItems\":50,\"type\":\"array\"},\"sample_truncated\":{\"type\":\"boolean\"},\"shadowed_camera_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"draft_enabled\",\"sample_truncated\",\"matched_camera_count\",\"effective_camera_count\",\"shadowed_camera_count\",\"conflict_camera_count\",\"conflict_policy_count\",\"sample\",\"conflicts\"],\"type\":\"object\"},\"StoragePolicyPreviewRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"revision\"]},{\"properties\":{\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\",\"primary_target_uuid\"]},{\"properties\":{\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\",\"primary_pool_uuid\"]}],\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[],\"type\":\"object\"},\"StoragePolicyPreviewSample\":{\"additionalProperties\":false,\"properties\":{\"camera_name\":{\"type\":\"string\"},\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"effective_policy\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StoragePolicyWinner\"},{\"type\":\"null\"}]},\"overlapping_policy_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"camera_name\",\"overlapping_policy_count\",\"effective_policy\"],\"type\":\"object\"},\"StoragePolicyUpdate\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StoragePolicyWinner\":{\"additionalProperties\":false,\"properties\":{\"draft\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"draft\",\"policy_uuid\",\"policy_name\",\"priority\",\"primary_target_uuid\",\"primary_pool_uuid\",\"fallback_mode\",\"fallback_target_uuid\"],\"type\":\"object\"},\"StoragePool\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePoolMember\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"strategy\",\"enabled\",\"revision\",\"created_at\",\"updated_at\",\"members\"],\"type\":\"object\"},\"StoragePoolCreate\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"}},\"required\":[\"name\",\"members\"],\"type\":\"object\"},\"StoragePoolMember\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"integer\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"target_uuid\",\"position\",\"weight\"],\"type\":\"object\"},\"StoragePoolMemberRequest\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"integer\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"StoragePoolUpdate\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StoragePoolsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"pools\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePool\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"pools\",\"count\"],\"type\":\"object\"},\"StorageTarget\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"archive_only\":{\"type\":\"boolean\"},\"bucket\":{\"type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"health\":{\"$ref\":\"#/components/schemas/StorageTargetHealth\"},\"high_watermark_pct\":{\"type\":\"number\"},\"is_default\":{\"type\":\"boolean\"},\"low_watermark_pct\":{\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"integer\"},\"mount_guard_path\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"null\"}]},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"recording_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"region\":{\"type\":\"string\"},\"replica_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"replica_count\":{\"minimum\":0,\"type\":\"integer\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"root_path\",\"endpoint\",\"region\",\"bucket\",\"credential_ref\",\"target_type\",\"archive_only\",\"enabled\",\"is_default\",\"mount_required\",\"mount_guard_path\",\"storage_class\",\"archive_budget_bytes\",\"reserve_bytes\",\"recording_count\",\"recording_bytes\",\"replica_count\",\"replica_bytes\",\"migration_bandwidth_bps\",\"high_watermark_pct\",\"low_watermark_pct\",\"archival_window_start_minute\",\"archival_window_end_minute\",\"revision\",\"created_at\",\"updated_at\",\"health\"],\"type\":\"object\"},\"StorageTargetCreate\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_watermark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\":{\"exclusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"}},\"required\":[\"name\",\"root_path\"],\"type\":\"object\"},\"StorageTargetHealth\":{\"additionalProperties\":false,\"properties\":{\"available_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"capacity_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"capacity_known\":{\"type\":\"boolean\"},\"cleanup_target_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"duplicate_filesystem\":{\"type\":\"boolean\"},\"filesystem_device\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"last_probe_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"last_success_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"pressure\":{\"type\":\"string\"},\"status\":{\"type\":\"string\"},\"used_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_pct\":{\"minimum\":0,\"type\":\"number\"}},\"required\":[\"status\",\"pressure\",\"last_error\",\"capacity_known\",\"duplicate_filesystem\",\"capacity_bytes\",\"available_bytes\",\"used_bytes\",\"cleanup_target_bytes\",\"filesystem_device\",\"used_pct\",\"last_probe_at\",\"last_success_at\"],\"type\":\"object\"},\"StorageTargetUpdate\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_watermark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\":{\"exclusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StorageTargetsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"targets\":{\"items\":{\"$ref\":\"#/components/schemas/StorageTarget\"},\"maxItems\":128,\"type\":\"array\"}},\"required\":[\"targets\",\"count\"],\"type\":\"object\"},\"StreamCompatibilityList\":{\"description\":\"Default GET /api/streams response. An empty array is valid. Each row is filtered and redacted using runtime plus go2rtc health observations.\",\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/components/schemas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"StreamConfigurationUpdate\":{\"additionalProperties\":true,\"description\":\"Partial ordinary update. All fields are optional and omission preserves existing state. Unknown fields (including detected width/height/fps) are currently ignored. This typed contract describes intended values; existing handlers can ignore wrong types instead of returning 400. It does not promise every malformed value is rejected by C.\",\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabled\":{\"const\":false}},\"required\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"admin_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_based_recording\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_interval\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"detection_model\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter_list\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"detection_retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"detection_threshold\":{\"description\":\"Percentage confidence, including zero; divided by 100 for storage. Omission/null preserves the current value. The handler does not clamp this numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values must start with http://, https://, rtsp:// or rtsps:// (case-insensitive), at most 511 UTF-8 bytes.\",\"type\":[\"string\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty string disables; otherwise at most 1023 UTF-8 bytes containing version=1, projection=equidistant, mount=ceiling and bounded centerX/centerY [0,1], radius [.05,1], fov [160,360], rotation/defaultYaw [-360,360], defaultTilt [-90,30], defaultViewFov [20,120]. Null is rejected. Unknown document fields are tolerated.\",\"maxLength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Whitespace-only also clears. Maximum 8191 UTF-8 bytes. Nonblank multiline values are YAML-validated when YAML support is enabled; malformed YAML can return a structured error. Single-line values are not YAML-checked.\",\"type\":[\"string\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"motion_trigger_source\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"Optional identifying metadata; ignored. This operation does not rename the stream.\",\"type\":\"string\"},\"onvif_password\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_port\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"pre_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_y\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values are a supported RTMP/RTMPS destination list, at most 511 UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\":[\"string\",\"null\"]},\"record\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_audio\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tags\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]}},\"required\":[],\"type\":\"object\"},\"StreamCreateRequest\":{\"additionalProperties\":true,\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabled\":{\"const\":false}},\"required\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"admin_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_based_recording\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_interval\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"detection_model\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter_list\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"detection_retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"detection_threshold\":{\"description\":\"Percentage confidence, including zero; divided by 100 for storage. Omission/null preserves the current value. The handler does not clamp this numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values must start with http://, https://, rtsp:// or rtsps:// (case-insensitive), at most 511 UTF-8 bytes.\",\"type\":[\"string\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty string disables; otherwise at most 1023 UTF-8 bytes containing version=1, projection=equidistant, mount=ceiling and bounded centerX/centerY [0,1], radius [.05,1], fov [160,360], rotation/defaultYaw [-360,360], defaultTilt [-90,30], defaultViewFov [20,120]. Null is rejected. Unknown document fields are tolerated.\",\"maxLength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Whitespace-only also clears. Maximum 8191 UTF-8 bytes. Nonblank multiline values are YAML-validated when YAML support is enabled; malformed YAML can return a structured error. Single-line values are not YAML-checked.\",\"type\":[\"string\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"motion_trigger_source\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"maxLength\":255,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_.-]+$\",\"type\":\"string\"},\"onvif_password\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_port\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"pre_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_y\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values are a supported RTMP/RTMPS destination list, at most 511 UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\":[\"string\",\"null\"]},\"record\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_audio\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tags\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\",\"url\"],\"type\":\"object\"},\"StreamCreated\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"type\":\"string\"},\"location_uuid\":{\"type\":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"],\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\",\"location_uuid\"],\"type\":\"object\"},\"StreamDeleteAccepted\":{\"additionalProperties\":true,\"properties\":{\"permanent\":{\"type\":\"boolean\"},\"status\":{\"const\":\"accepted\",\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"permanent\",\"status\"],\"type\":\"object\"},\"StreamDetailsBase\":{\"additionalProperties\":true,\"description\":\"Fields shared by compatibility-list rows and full detail. Strings use empty values rather than null; additional fields are preserved for additive compatibility.\",\"properties\":{\"audio_voice_enhancement\":{\"type\":\"boolean\"},\"backchannel_enabled\":{\"type\":\"boolean\"},\"camera_uuid\":{\"description\":\"Stable camera UUID. Older migrated rows may have an empty string; no instance identifiers are embedded in this document.\",\"type\":\"string\"},\"codec\":{\"type\":\"string\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"detection_interval\":{\"type\":\"integer\"},\"detection_model\":{\"type\":\"string\"},\"detection_object_filter\":{\"type\":\"string\"},\"detection_object_filter_list\":{\"type\":\"string\"},\"detection_record_on_schedule\":{\"type\":\"boolean\"},\"detection_recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"detection_retention_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive values are days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"detection_threshold\":{\"description\":\"Integer percentage emitted from stored float confidence, including zero. The existing update handler does not clamp this to 0\xE2\x80\x93100.\",\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"eptz_config\":{\"description\":\"Empty string disables browser dewarping; otherwise a serialized version-1 JSON document. It is a string on the wire, not an object.\",\"maxLength\":1023,\"type\":\"string\"},\"error_message\":{\"description\":\"Present only when an Error state has a recorded reason.\",\"type\":\"string\"},\"fps\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"integer\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"height\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"integer\"},\"isOnvif\":{\"type\":\"boolean\"},\"location_uuid\":{\"description\":\"Primary location UUID, or an empty string when unset.\",\"type\":\"string\"},\"max_storage_mb\":{\"description\":\"Storage quota in MB; zero means unlimited.\",\"type\":\"integer\"},\"motion_trigger_source\":{\"type\":\"string\"},\"name\":{\"description\":\"Current stream name; lookup is by name, not camera_uuid.\",\"type\":\"string\"},\"onvif_port\":{\"description\":\"Zero requests automatic port detection.\",\"type\":\"integer\"},\"onvif_profile\":{\"type\":\"string\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"type\":\"integer\"},\"pre_detection_buffer\":{\"type\":\"integer\"},\"priority\":{\"type\":\"integer\"},\"privacy_mode\":{\"type\":\"boolean\"},\"protocol\":{\"description\":\"Stored transport code: 0 TCP, 1 UDP. Existing configuration paths may retain other numeric values.\",\"type\":\"integer\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"ptz_has_home\":{\"type\":\"boolean\"},\"ptz_max_x\":{\"type\":\"integer\"},\"ptz_max_y\":{\"type\":\"integer\"},\"ptz_max_z\":{\"type\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record_audio\":{\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boolean\"},\"recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"retention_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive values are days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"segment_duration\":{\"type\":\"integer\"},\"status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"storage_priority\":{\"type\":\"integer\"},\"streaming_enabled\":{\"type\":\"boolean\"},\"tags\":{\"type\":\"string\"},\"tier_critical_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"tier_ephemeral_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"tier_important_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"url\":{\"description\":\"Configured input URL. For readers without camera.configure, URL userinfo is stripped; unsupported URL formats may become an empty string. For configurators the legacy/custom form may remain, while ONVIF/separate-credential configurations still strip URL userinfo.\",\"type\":\"string\"},\"width\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"location_uuid\",\"name\",\"url\",\"eptz_config\",\"codec\",\"detection_model\",\"detection_object_filter\",\"detection_object_filter_list\",\"onvif_profile\",\"tags\",\"motion_trigger_source\",\"enabled\",\"streaming_enabled\",\"record\",\"detection_based_recording\",\"record_audio\",\"audio_voice_enhancement\",\"isOnvif\",\"backchannel_enabled\",\"ptz_enabled\",\"ptz_has_home\",\"record_on_schedule\",\"detection_record_on_schedule\",\"privacy_mode\",\"has_sub_stream\",\"go2rtc_hls_available\",\"width\",\"height\",\"fps\",\"priority\",\"segment_duration\",\"detection_threshold\",\"detection_interval\",\"pre_detection_buffer\",\"post_detection_buffer\",\"protocol\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\",\"storage_priority\",\"ptz_max_x\",\"ptz_max_y\",\"ptz_max_z\",\"onvif_port\",\"tier_critical_multiplier\",\"tier_important_multiplier\",\"tier_ephemeral_multiplier\",\"playback_transport\",\"recording_schedule\",\"detection_recording_schedule\",\"status\"],\"type\":\"object\"},\"StreamDetectionEngine\":{\"additionalProperties\":true,\"properties\":{\"config\":{\"additionalProperties\":true,\"description\":\"Engine-specific configuration; current engine plugins own its keys. Serialized JSON must fit the native configuration buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"minimum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string\"},\"managed_by_legacy_stream_fields\":{\"type\":\"boolean\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"sort_order\":{\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"type\":\"string\"}},\"required\":[\"key\",\"type\",\"model_path\",\"enabled\",\"threshold\",\"interval_seconds\",\"sort_order\",\"config\",\"managed_by_legacy_stream_fields\"],\"type\":\"object\"},\"StreamDetectionEngineWrite\":{\"additionalProperties\":true,\"properties\":{\"config\":{\"additionalProperties\":true,\"description\":\"Engine-specific configuration; current engine plugins own its keys. Serialized JSON must fit the native configuration buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"minimum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"sort_order\":{\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"type\":\"string\"}},\"required\":[\"key\",\"type\",\"threshold\",\"interval_seconds\"],\"type\":\"object\"},\"StreamDetectionEngines\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"integer\"},\"engines\":{\"items\":{\"$ref\":\"#/components/schemas/StreamDetectionEngine\"},\"type\":\"array\"},\"restart_required\":{\"type\":\"boolean\"},\"stream_name\":{\"type\":\"string\"},\"trigger_policy\":{\"const\":\"any_of\",\"type\":\"string\"}},\"required\":[\"stream_name\",\"engines\",\"count\",\"trigger_policy\",\"restart_required\"],\"type\":\"object\"},\"StreamDetectionEnginesRequest\":{\"additionalProperties\":true,\"properties\":{\"engines\":{\"items\":{\"$ref\":\"#/components/schemas/StreamDetectionEngineWrite\"},\"maxItems\":7,\"type\":\"array\"}},\"required\":[\"engines\"],\"type\":\"object\"},\"StreamFullPrivilegedResponse\":{\"additionalProperties\":true,\"properties\":{\"stream\":{\"$ref\":\"#/components/schemas/StreamPrivileged\"}},\"required\":[\"stream\"],\"type\":\"object\"},\"StreamFullRedactedResponse\":{\"additionalProperties\":true,\"properties\":{\"stream\":{\"$ref\":\"#/components/schemas/StreamRedacted\"}},\"required\":[\"stream\"],\"type\":\"object\"},\"StreamFullResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamFullRedactedResponse\"},{\"$ref\":\"#/components/schemas/StreamFullPrivilegedResponse\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"runtime\"}],\"version\":1}}],\"description\":\"Full details are wrapped in stream. Read authorization uses raw camera facts; privileged fields additionally use runtime-enriched facts on the same camera.\"},\"StreamHealth\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"number\"},\"healthy\":{\"type\":\"boolean\"},\"status\":{\"type\":\"string\"},\"storage\":{\"additionalProperties\":true,\"properties\":{\"available_bytes\":{\"type\":\"number\"},\"used_bytes\":{\"type\":\"number\"}},\"required\":[\"used_bytes\",\"available_bytes\"],\"type\":\"object\"},\"streams\":{\"additionalProperties\":true,\"properties\":{\"degraded\":{\"type\":\"number\"},\"down\":{\"type\":\"number\"},\"total\":{\"type\":\"number\"},\"up\":{\"type\":\"number\"}},\"required\":[\"total\",\"up\",\"degraded\",\"down\"],\"type\":\"object\"},\"streams_detail\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"bitrate_bps\":{\"type\":\"number\"},\"configured_fps\":{\"type\":\"number\"},\"connection_latency_ms\":{\"type\":\"number\"},\"current_fps\":{\"type\":\"number\"},\"detector_operating\":{\"type\":\"boolean\"},\"expected_fps\":{\"type\":\"number\"},\"fps\":{\"type\":\"number\"},\"frames_dropped\":{\"type\":\"number\"},\"frames_total\":{\"type\":\"number\"},\"last_completed_segment_ts\":{\"type\":\"number\"},\"last_frame_ts\":{\"type\":\"number\"},\"last_packet_ts\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"observed_fps\":{\"type\":\"number\"},\"reconnects\":{\"type\":\"number\"},\"recorder_session_active\":{\"type\":\"boolean\"},\"recorder_writing\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"boolean\"},\"recording_gaps\":{\"type\":\"number\"},\"source_connected\":{\"type\":\"boolean\"},\"sparkline_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"status\":{\"type\":\"string\"},\"uptime_seconds\":{\"type\":\"number\"},\"video_advancing\":{\"type\":\"boolean\"}},\"required\":[\"name\",\"status\",\"observed_fps\",\"current_fps\",\"expected_fps\",\"fps\",\"configured_fps\",\"bitrate_bps\",\"uptime_seconds\",\"reconnects\",\"last_packet_ts\",\"last_frame_ts\",\"last_completed_segment_ts\",\"frames_total\",\"frames_dropped\",\"connection_latency_ms\",\"source_connected\",\"video_advancing\",\"recorder_writing\",\"detector_operating\",\"recorder_session_active\",\"recording_active\",\"recording_gaps\"],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"number\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"status\",\"streams\",\"storage\",\"streams_detail\"],\"type\":\"object\"},\"StreamHealthHistory\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"streams_detail\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"name\":{\"type\":\"string\"},\"sparkline_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"integer\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"streams_detail\"],\"type\":\"object\"},\"StreamListResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamCompatibilityList\"},{\"$ref\":\"#/components/schemas/StreamSummaryPage\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPage\"},{\"$ref\":\"#/components/schemas/LprStreamSummaryPage\"}],\"description\":\"Select the response with the request query. Consumers validating an admin or live summary must use its specific page schema: the identity-only LPR variant is deliberately minimal.\"},\"StreamPrivacyUpdate\":{\"additionalProperties\":false,\"description\":\"Synchronous lightweight privacy toggle. No other update fields may accompany it; unknown fields are rejected in this form.\",\"properties\":{\"name\":{\"description\":\"Optional ignored identifying metadata.\",\"type\":\"string\"},\"set_privacy_mode\":{\"type\":\"boolean\"}},\"required\":[\"set_privacy_mode\"],\"type\":\"object\"},\"StreamPrivacyUpdated\":{\"additionalProperties\":true,\"description\":\"HTTP 200 privacy result. A disabled stream can succeed with stream_registered=false; this is not evidence of a live stream.\",\"properties\":{\"privacy_mode\":{\"type\":\"boolean\"},\"stream_registered\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"privacy_mode\",\"stream_registered\"],\"type\":\"object\"},\"StreamPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"type\":\"string\"},\"can_control_privacy\":{\"const\":true,\"type\":\"boolean\"},\"detection_url\":{\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Single source string or multiline YAML source list; empty means generated configuration.\",\"type\":\"string\"},\"onvif_password\":{\"description\":\"Configuration credential, conditionally visible for this resource. Empty is valid when unset.\",\"type\":\"string\"},\"onvif_username\":{\"type\":\"string\"},\"publish_url\":{\"type\":\"string\"},\"sub_stream_url\":{\"type\":\"string\"}},\"required\":[\"onvif_username\",\"onvif_password\",\"admin_url\",\"go2rtc_source_override\",\"sub_stream_url\",\"detection_url\",\"publish_url\",\"can_control_privacy\"],\"type\":\"object\"}],\"description\":\"This camera is both readable and configurable for the current principal. Privileged values may still be empty; authority over another camera is insufficient.\"},\"StreamProbeRequest\":{\"additionalProperties\":true,\"properties\":{\"onvif_password\":{\"type\":\"string\"},\"onvif_username\":{\"type\":\"string\"},\"protocol\":{\"type\":\"integer\"},\"url\":{\"type\":\"string\"}},\"required\":[\"url\",\"protocol\"],\"type\":\"object\"},\"StreamProbeResponse\":{\"oneOf\":[{\"additionalProperties\":true,\"properties\":{\"info\":{\"additionalProperties\":true,\"properties\":{\"codec\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"width\",\"height\",\"fps\",\"codec\"],\"type\":\"object\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"info\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"}]},\"StreamRecordingChanged\":{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"type\":\"string\"},\"state\":{\"enum\":[\"starting\",\"idle\"],\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"stream\",\"action\",\"state\"],\"type\":\"object\"},\"StreamRecordingRequest\":{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"type\":\"string\"}},\"required\":[\"action\"],\"type\":\"object\"},\"StreamRecordingState\":{\"additionalProperties\":true,\"properties\":{\"capture_method\":{\"type\":[\"string\",\"null\"]},\"manual_control_allowed\":{\"type\":\"boolean\"},\"manual_control_reason\":{\"enum\":[\"read_only\",\"continuous_recording\",null],\"type\":[\"string\",\"null\"]},\"recording_active\":{\"type\":\"boolean\"},\"recording_id\":{\"type\":[\"integer\",\"null\"]},\"state\":{\"enum\":[\"starting\",\"recording\",\"idle\"],\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"stream\",\"state\",\"recording_active\",\"capture_method\",\"recording_id\",\"manual_control_allowed\",\"manual_control_reason\"],\"type\":\"object\"},\"StreamRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"const\":\"\",\"type\":\"string\"},\"can_control_privacy\":{\"const\":false,\"type\":\"boolean\"},\"detection_url\":{\"const\":\"\",\"type\":\"string\"},\"go2rtc_source_override\":{\"const\":\"\",\"type\":\"string\"},\"onvif_password\":{\"const\":\"\",\"type\":\"string\"},\"onvif_username\":{\"const\":\"\",\"type\":\"string\"},\"publish_url\":{\"const\":\"\",\"type\":\"string\"},\"sub_stream_url\":{\"const\":\"\",\"type\":\"string\"}},\"required\":[\"onvif_username\",\"onvif_password\",\"admin_url\",\"go2rtc_source_override\",\"sub_stream_url\",\"detection_url\",\"publish_url\",\"can_control_privacy\"],\"type\":\"object\"}],\"description\":\"This camera is readable but not configurable. Sensitive fields remain present as empty strings; has_sub_stream can still be true.\"},\"StreamRefreshAccepted\":{\"additionalProperties\":true,\"properties\":{\"coalesced\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"retry_after_seconds\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"stream\"],\"type\":\"object\"},\"StreamRetention\":{\"additionalProperties\":true,\"properties\":{\"detection_retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"stream_name\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\"],\"type\":\"object\"},\"StreamRetentionRequest\":{\"additionalProperties\":true,\"properties\":{\"detection_retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"StreamSingleResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/components/schemas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"runtime\"}],\"version\":1}}],\"description\":\"Single legacy stream object. URL and credential fields are redacted unless this same camera is configurable.\"},\"StreamStatus\":{\"enum\":[\"Stopped\",\"Starting\",\"Running\",\"Stopping\",\"Error\",\"Reconnecting\",\"Degraded\",\"Unknown\"],\"type\":\"string\"},\"StreamSummaryBase\":{\"additionalProperties\":true,\"description\":\"Administrative and live summary fields. Unlike detail, summary rows contain no input URL, credentials or error_message.\",\"properties\":{\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_uuid\":{\"type\":\"string\"},\"codec\":{\"type\":\"string\"},\"current_fps\":{\"type\":\"number\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"first_video_at\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"isOnvif\":{\"type\":\"boolean\"},\"last_frame_ts\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"last_recording_at\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"location_name\":{\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"location_uuid\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"protocol\":{\"type\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"boolean\"},\"status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"tags\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"name\",\"location_uuid\",\"location_name\",\"location_path\",\"codec\",\"tags\",\"enabled\",\"record\",\"record_on_schedule\",\"detection_based_recording\",\"isOnvif\",\"recording_active\",\"width\",\"height\",\"fps\",\"protocol\",\"first_video_at\",\"last_frame_ts\",\"last_recording_at\",\"current_fps\",\"status\",\"availability\"],\"type\":\"object\"},\"StreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=admin (default). Totals count only authorized, searched and availability-filtered rows before pagination. A page beyond the end has an empty streams array; total_pages is zero when total is zero.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryRedacted\"},{\"$ref\":\"#/components/schemas/StreamSummaryPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"StreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"description\":\"Only emitted for surface=admin with include_admin_url=true or 1. Empty when not configurable.\",\"type\":\"string\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"object\"}]},\"StreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"const\":\"\",\"description\":\"Only emitted for surface=admin with include_admin_url=true or 1. Empty when not configurable.\",\"type\":\"string\"},\"can_configure\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"object\"}]},\"StreamUpdateAccepted\":{\"additionalProperties\":true,\"description\":\"HTTP 202 acknowledges a queued update, not successful durable application. ONVIF results are optional. Re-fetch detail to observe eventual state; a request requiring ONVIF work may perform that probe before acceptance.\",\"properties\":{\"message\":{\"type\":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"],\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"StreamUpdateRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamConfigurationUpdate\"},{\"$ref\":\"#/components/schemas/StreamPrivacyUpdate\"}]},\"StreamZones\":{\"additionalProperties\":true,\"properties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZone\"},\"type\":\"array\"}},\"required\":[\"zones\"],\"type\":\"object\"},\"StreamZonesDeleted\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"StreamZonesRequest\":{\"additionalProperties\":true,\"properties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZoneWrite\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"zones\"],\"type\":\"object\"},\"StreamZonesSaved\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"maximum\":16,\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"count\"],\"type\":\"object\"},\"SuccessResponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false,\"password_hash\":false,\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\"],\"type\":\"object\"},\"SystemActionAccepted\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"SystemBackupCreated\":{\"additionalProperties\":true,\"properties\":{\"backupUrl\":{\"type\":\"string\"},\"filename\":{\"type\":\"string\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"backupUrl\",\"filename\"],\"type\":\"object\"},\"SystemInformation\":{\"additionalProperties\":true,\"properties\":{\"cpu\":{\"additionalProperties\":true,\"properties\":{\"cores\":{\"type\":\"number\"},\"model\":{\"type\":\"string\"},\"usage\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"usageCapability\":{\"type\":\"string\"}},\"type\":\"object\"},\"detectorMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"disk\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"git_commit\":{\"type\":\"string\"},\"global_retention_days\":{\"type\":\"number\"},\"go2rtcMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"memory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"network\":{\"additionalProperties\":true,\"properties\":{\"interfaces\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"address\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"capability\":{\"type\":\"string\"},\"ipv6\":{\"type\":\"string\"},\"mac\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"up\":{\"type\":\"boolean\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"array\"}},\"type\":\"object\"},\"recordings\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"count\",\"size\"],\"type\":\"object\"},\"streamStorage\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"detection_retention_days\":{\"type\":\"number\"},\"max_storage_mb\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"retention_days\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"name\",\"size\",\"count\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\"],\"type\":\"object\"},\"type\":\"array\"},\"streams\":{\"additionalProperties\":true,\"properties\":{\"active\":{\"type\":\"number\"},\"total\":{\"type\":\"number\"}},\"required\":[\"active\",\"total\"],\"type\":\"object\"},\"systemDisk\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"systemMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"threads\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"uptime\":{\"type\":\"number\"},\"uptimeCapability\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"},\"versions\":{\"additionalProperties\":true,\"properties\":{\"items\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"category\":{\"type\":\"string\"},\"details\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"required\":[\"name\",\"category\",\"version\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"items\"],\"type\":\"object\"},\"webThreadPoolSize\":{\"type\":\"number\"}},\"required\":[\"version\",\"git_commit\",\"cpu\",\"memory\",\"threads\",\"webThreadPoolSize\",\"uptime\",\"disk\",\"systemDisk\",\"global_retention_days\",\"versions\",\"network\",\"streams\",\"recordings\",\"streamStorage\"],\"type\":\"object\"},\"SystemLogs\":{\"additionalProperties\":true,\"properties\":{\"file\":{\"type\":\"string\"},\"level\":{\"type\":\"string\"},\"logs\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"level\":{\"type\":\"string\"},\"message\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"logs\",\"file\",\"level\"],\"type\":\"object\"},\"SystemSettings\":{\"additionalProperties\":true,\"properties\":{\"api_detection_backend\":{\"type\":\"string\"},\"api_detection_detector_name\":{\"type\":\"string\"},\"api_detection_format\":{\"type\":\"string\"},\"api_detection_url\":{\"type\":\"string\"},\"audio_disabled\":{\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"buffer_size\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_backup_retention_count\":{\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"string\"},\"db_post_backup_script\":{\"type\":\"string\"},\"default_detection_threshold\":{\"type\":\"number\"},\"default_playback_transport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engine_delegate\":{\"type\":\"string\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_threads\":{\"type\":\"number\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go2rtc_binary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc_config_override\":{\"type\":\"string\"},\"go2rtc_config_override_disabled_reason\":{\"type\":\"string\"},\"go2rtc_config_override_quarantined\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"type\":\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_server\":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{\"type\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"type\":\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_device_interval_seconds\":{\"type\":\"number\"},\"health_effective_policy\":{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"}},\"type\":\"object\"},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_interval_seconds\":{\"type\":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"},\"health_incident_retention_days\":{\"type\":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"number\"},\"health_presence_interval_seconds\":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"health_slow_interval_seconds\":{\"type\":\"number\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"log_file\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabled\":{\"type\":\"boolean\"},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"login_rate_limit_window_seconds\":{\"type\":\"number\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":\"number\"},\"max_streams\":{\"type\":\"number\"},\"max_streams_ceiling\":{\"type\":\"number\"},\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{\"type\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqtt_client_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"boolean\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean\"},\"mqtt_topic_prefix\":{\"type\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type\":\"boolean\"},\"onvif_discovery_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"number\"},\"onvif_discovery_network\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_buffer\":{\"type\":\"number\"},\"pre_detection_buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"number\"},\"storage_min_free_pct\":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hls\":{\"type\":\"string\"},\"storage_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pressure_emergency_pct\":{\"type\":\"number\"},\"storage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"number\"},\"syslog_enabled\":{\"type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":{\"type\":\"string\"},\"thumbnails_per_recording\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"number\"},\"trusted_proxy_cidrs\":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"string\"},\"use_swap\":{\"type\":\"boolean\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":\"string\"},\"web_port\":{\"type\":\"number\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"type\":\"number\"},\"webrtc_connection_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"integer\"}},\"required\":[\"log_level\",\"syslog_enabled\",\"syslog_ident\",\"syslog_facility\",\"storage_path\",\"storage_path_hls\",\"mp4_directory_format\",\"max_storage_size\",\"retention_days\",\"auto_delete_oldest\",\"storage_min_free_pct\",\"storage_pressure_warning_pct\",\"storage_pressure_critical_pct\",\"storage_pressure_emergency_pct\",\"generate_thumbnails\",\"thumbnails_per_recording\",\"db_path\",\"db_backup_interval_minutes\",\"db_backup_retention_count\",\"db_backup_verify\",\"db_post_backup_script\",\"web_port\",\"web_bind_ip\",\"web_thread_pool_size\",\"max_streams\",\"max_streams_ceiling\",\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_disabled\",\"webrtc_disabled\",\"hls_disabled\",\"mse_disabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\",\"trusted_proxy_cidrs\",\"force_mfa_on_login\",\"login_rate_limit_enabled\",\"login_rate_limit_max_attempts\",\"login_rate_limit_window_seconds\",\"buffer_size\",\"use_swap\",\"swap_size\",\"models_path\",\"api_detection_url\",\"api_detection_backend\",\"api_detection_format\",\"api_detection_detector_name\",\"default_detection_threshold\",\"pre_detection_buffer\",\"post_detection_buffer\",\"default_playback_transport\",\"detection_grace_period\",\"detection_engine_enabled\",\"detection_engine_threads\",\"detection_engine_delegate\",\"go2rtc_enabled\",\"go2rtc_binary_path\",\"go2rtc_config_dir\",\"go2rtc_api_port\",\"go2rtc_rtsp_port\",\"go2rtc_webrtc_enabled\",\"go2rtc_webrtc_listen_port\",\"go2rtc_stun_enabled\",\"go2rtc_stun_server\",\"go2rtc_external_ip\",\"go2rtc_ice_servers\",\"go2rtc_force_native_hls\",\"go2rtc_config_override\",\"go2rtc_config_override_disabled_reason\",\"go2rtc_config_override_quarantined\",\"mqtt_enabled\",\"mqtt_broker_host\",\"mqtt_broker_port\",\"mqtt_username\",\"mqtt_password\",\"mqtt_client_id\",\"mqtt_topic_prefix\",\"mqtt_tls_enabled\",\"mqtt_keepalive\",\"mqtt_qos\",\"mqtt_retain\",\"mqtt_ha_discovery\",\"mqtt_ha_discovery_prefix\",\"mqtt_ha_snapshot_interval\",\"turn_enabled\",\"turn_server_url\",\"turn_username\",\"turn_password\",\"onvif_discovery_enabled\",\"onvif_discovery_interval\",\"onvif_discovery_network\",\"health_enabled\",\"health_profile\",\"health_fast_interval_seconds\",\"health_normal_interval_seconds\",\"health_slow_interval_seconds\",\"health_device_interval_seconds\",\"health_write_probe_enabled\",\"health_hardware_provider\",\"health_presence_interval_seconds\",\"health_incident_retention_days\",\"health_condition_overrides\",\"web_root\",\"log_file\",\"pid_file\",\"go2rtc_base_path\",\"lpr_retention_days\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\",\"mp4_faststart\"],\"type\":\"object\"},\"SystemSettingsSaved\":{\"additionalProperties\":true,\"properties\":{\"restart_required\":{\"type\":\"boolean\"},\"restart_required_message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"restart_required\"],\"type\":\"object\"},\"SystemSettingsUpdate\":{\"additionalProperties\":true,\"description\":\"Partial settings update; absent fields are retained. Secret mask values preserve the stored password.\",\"properties\":{\"api_detection_backend\":{\"type\":\"string\"},\"api_detection_detector_name\":{\"type\":\"string\"},\"api_detection_format\":{\"enum\":[\"light-object-detect\",\"doods2\"],\"type\":\"string\"},\"api_detection_url\":{\"type\":\"string\"},\"audio_disabled\":{\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"buffer_size\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_backup_retention_count\":{\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"string\"},\"db_post_backup_script\":{\"type\":\"string\"},\"default_detection_threshold\":{\"type\":\"number\"},\"default_playback_transport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engine_delegate\":{\"type\":\"string\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_threads\":{\"type\":\"number\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_binary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc_config_override\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"type\":\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_server\":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{\"type\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"type\":\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_device_interval_seconds\":{\"type\":\"number\"},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_interval_seconds\":{\"type\":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"},\"health_incident_retention_days\":{\"type\":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"number\"},\"health_presence_interval_seconds\":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"health_slow_interval_seconds\":{\"type\":\"number\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"log_file\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabled\":{\"type\":\"boolean\"},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"login_rate_limit_window_seconds\":{\"type\":\"number\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":\"number\"},\"max_streams\":{\"type\":\"number\"},\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{\"type\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqtt_client_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"boolean\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"type\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean\"},\"mqtt_topic_prefix\":{\"type\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type\":\"boolean\"},\"onvif_discovery_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"number\"},\"onvif_discovery_network\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_buffer\":{\"type\":\"number\"},\"pre_detection_buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"number\"},\"storage_min_free_pct\":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hls\":{\"type\":\"string\"},\"storage_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pressure_emergency_pct\":{\"type\":\"number\"},\"storage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"number\"},\"syslog_enabled\":{\"type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":{\"type\":\"string\"},\"thumbnails_per_recording\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"number\"},\"trusted_proxy_cidrs\":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\":{\"type\":\"string\"},\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"string\"},\"use_swap\":{\"type\":\"boolean\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":\"string\"},\"web_port\":{\"type\":\"number\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"type\":\"number\"},\"webrtc_connection_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"integer\"}},\"type\":\"object\"},\"SystemStatus\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"ok\",\"type\":\"string\"}},\"required\":[\"status\",\"message\"],\"type\":\"object\"},\"TokenSelector\":{\"additionalProperties\":true,\"description\":\"Existing fleet selector grammar. Server additionally enforces depth\xE2\x89\xA48, total nodes\xE2\x89\xA464, UTF-8 byte limits and serialized selector<8192 bytes. Evaluation and delegation containment remain server decisions.\",\"properties\":{\"expression\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"version\":{\"const\":1}},\"required\":[\"version\",\"expression\"],\"type\":\"object\"},\"TokenSelectorExpression\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"all\"}},\"required\":[\"op\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"children\":{\"items\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"op\":{\"enum\":[\"and\",\"or\"]}},\"required\":[\"op\",\"children\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"child\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"op\":{\"const\":\"not\"}},\"required\":[\"op\",\"child\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"location_subtree\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"op\",\"uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"enabled\"},\"value\":{\"type\":\"boolean\"}},\"required\":[\"op\",\"value\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"camera_uuid\"]},\"values\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"tag_any\",\"tag_all\",\"tag_none\"]},\"uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"recording_mode\"]},\"values\":{\"items\":{\"enum\":[\"off\",\"continuous\",\"detection\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"vendor\",\"model\"]},\"values\":{\"items\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"capability_any\",\"capability_all\"]},\"values\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchannel\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"health\"]},\"values\":{\"items\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"}]},\"TotpChangedResponse\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"TotpCodeRequest\":{\"additionalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"code\"],\"type\":\"object\"},\"TotpLoginRequest\":{\"additionalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":true},\"remember_device\":{\"type\":\"boolean\"},\"totp_token\":{\"minLength\":1,\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"totp_token\",\"code\"],\"type\":\"object\"},\"TotpLoginResponse\":{\"additionalProperties\":false,\"properties\":{\"redirect\":{\"const\":\"/index.html\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"redirect\"],\"type\":\"object\"},\"TotpSetupResponse\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"otpauth_uri\":{\"format\":\"uri\",\"pattern\":\"^otpauth://totp/\",\"readOnly\":true,\"type\":\"string\"},\"secret\":{\"pattern\":\"^[A-Z2-7]{32}$\",\"readOnly\":true,\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"secret\",\"otpauth_uri\",\"message\"],\"type\":\"object\"},\"TotpStatusResponse\":{\"additionalProperties\":false,\"properties\":{\"totp_configured\":{\"type\":\"boolean\"},\"totp_enabled\":{\"type\":\"boolean\"}},\"required\":[\"totp_enabled\",\"totp_configured\"],\"type\":\"object\"},\"TrustedDevice\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_used_at\":{\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[\"id\",\"created_at\",\"expires_at\",\"ip_address\",\"user_agent\",\"current\",\"last_used_at\"],\"type\":\"object\"},\"TrustedDevicesResponse\":{\"additionalProperties\":false,\"properties\":{\"trusted_devices\":{\"items\":{\"$ref\":\"#/components/schemas/TrustedDevice\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"trusted_devices\"],\"type\":\"object\"},\"WeeklySchedule\":{\"description\":\"Seven days \xC3\x97 24 hours, indexed day*24+hour. true permits recording in that slot; interpreted using the configured recorder schedule timezone.\",\"items\":{\"type\":\"boolean\"},\"maxItems\":168,\"minItems\":168,\"type\":\"array\"},\"WorkspacesResponse\":{\"additionalProperties\":true,\"properties\":{\"configurable\":{\"type\":\"boolean\"},\"scope\":{\"enum\":[\"user\",\"installation\"],\"type\":\"string\"},\"workspaces\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"authorization_unchanged\":{\"const\":true,\"type\":\"boolean\"},\"configurable\":{\"type\":\"boolean\"},\"description\":{\"type\":\"string\"},\"key\":{\"enum\":[\"live.navigator\",\"investigation\"],\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"visible\":{\"type\":\"boolean\"}},\"required\":[\"key\",\"label\",\"description\",\"visible\",\"configurable\",\"authorization_unchanged\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"workspaces\",\"configurable\",\"scope\"],\"type\":\"object\"},\"WorkspacesUpdate\":{\"additionalProperties\":true,\"properties\":{\"workspaces\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"investigation\":{\"type\":\"boolean\"},\"live.navigator\":{\"type\":\"boolean\"}},\"type\":\"object\"}},\"required\":[\"workspaces\"],\"type\":\"object\"}},\"securitySchemes\":{\"apiKeyHeader\":{\"description\":\"Legacy API key or scoped token. Scope intersects the active owner\xE2\x80\x99s authority.\",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"basicAuth\":{\"description\":\"Existing database username/password authentication; restricted when a required password change is pending.\",\"scheme\":\"basic\",\"type\":\"http\"},\"bearerToken\":{\"description\":\"Legacy API key or scoped token through Authorization. Use a cookie-free client when discovering token-specific permissions.\",\"scheme\":\"bearer\",\"type\":\"http\"},\"legacyApiKeyHeader\":{\"description\":\"Legacy API key only. This operation rejects scoped API tokens even when their owner could call it.\",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"legacyBearer\":{\"description\":\"Legacy API key through Authorization: Bearer. Scoped tokens are not accepted on this operation. A valid session cookie still takes precedence.\",\"scheme\":\"bearer\",\"type\":\"http\"},\"sessionCookie\":{\"description\":\"Existing authenticated session cookie; checked before other credentials. The explorer uses this method only.\",\"in\":\"cookie\",\"name\":\"session\",\"type\":\"apiKey\"}}},\"info\":{\"description\":\"Canonical contract for the current C backend: authentication and administration, fleet and workspaces, streams and devices, recordings and investigations, storage, events, system settings and bootstrap. The authenticated discovery endpoint projects operations and response variants for the current principal. Eight media-protocol surfaces and one internal callback are documented separately in the coverage inventory. Discovery is descriptive and does not grant access.\",\"title\":\"LightNVR API \xE2\x80\x94 permission-aware contracts\",\"version\":\"0.46.3-contracts.3\"},\"openapi\":\"3.1.1\",\"paths\":{\"/api/audit/events\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuditEvents\",\"parameters\":[{\"description\":\"One-based page.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Page size, including CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"schema\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditEventsPage\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List audit events\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/audit/events/export\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"exportAuditEvents\",\"parameters\":[{\"description\":\"One-based page.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Page size, including CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"schema\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"One filtered page as UTF-8 CSV, quoted cells and neutralized spreadsheet formula prefixes. This is not an export of all pages.\",\"headers\":{\"Content-Disposition\":{\"schema\":{\"const\":\"attachment; filename=lightnvr-audit.csv\",\"type\":\"string\"}},\"X-Total-Count\":{\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export audit events\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/audit/settings\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getAuditSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettings\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get audit settings\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires system.admin. All submitted fields are validated before writes. Retention saves and pruning occur before decision modes: on internal failure some work may have applied. Read settings before deciding to retry. Successful retention changes include pruned_events.\",\"operationId\":\"updateAuditSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettingsWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettings\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update audit settings\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/login\":{\"post\":{\"description\":\"Public credential submission. Successful JSON login sets the HttpOnly session cookie; MFA challenge returns totp_required/totp_token without a cookie. Optional inline TOTP is described; MFA completion and enrollment have separate identity contracts. Form submissions redirect for success, credential rejection and rate limit. Form parsing uses username/password only; JSON optional fields are not form inputs.\",\"operationId\":\"login\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginRequest\"}},\"application/x-www-form-urlencoded\":{\"schema\":{\"additionalProperties\":true,\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"username\":{\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginResponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Set-Cookie\":{\"description\":\"Session cookie on full login success; absent for MFA challenge. Additional trusted-device cookie may be set.\",\"schema\":{\"type\":\"string\"}}}},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Login rate limited.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[],\"summary\":\"Start an authenticated session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/login/config\":{\"get\":{\"description\":\"Public booleans and timeout values needed before sign-in. No personalized discovery is exposed.\",\"operationId\":\"getLoginConfig\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginConfig\"}}},\"description\":\"Successful response\"}},\"security\":[],\"summary\":\"Get public login configuration\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/login/totp\":{\"post\":{\"description\":\"Consumes an unexpired password-verified challenge bound to the issuing IP/user agent. Six-digit codes are single-use per account time step; account throttling applies. Success sets an HttpOnly session cookie, and optionally a trusted-device cookie. Never automatically retry a completion after an ambiguous response.\",\"operationId\":\"completeTotpLogin\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpLoginRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpLoginResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Authenticated session; optional trusted device if enabled and requested.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[],\"summary\":\"Complete an MFA login challenge\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/logout\":{\"post\":{\"description\":\"Public and idempotent. Deletes the session from the supplied cookie and clears session/trusted-device cookies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 requires Accept containing application/json or X-Requested-With; otherwise302 redirects to login.\",\"operationId\":\"logout\",\"parameters\":[{\"description\":\"Use application/json for the JSON success envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Presence also selects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[],\"summary\":\"End the current browser session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/sessions\":{\"get\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. Returns at most 32 unexpired entries ordered by most recent activity, then descending ID. There is no pagination in this bounded contract. Pending MFA challenges are excluded from sessions.\",\"operationId\":\"listAuthSessions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthSessionsResponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List own sessions\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/sessions/{id}\":{\"delete\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. The ID must be a canonical positive decimal safe integer without signs, leading zeros, escapes or trailing path segments. Revocation is independent of the 32-entry list limit, can remove expired records, and excludes pending MFA sessions. Current-cookie clearing uses the exact removed credential. Missing or another owner\xE2\x80\x99s ID returns the same 404; database failure is 500. A successful revoke invalidates future use, but does not cancel already-authorized in-flight requests.\",\"operationId\":\"revokeAuthSession\",\"parameters\":[{\"description\":\"Canonical positive decimal safe integer; no leading zeros or encoded aliases.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CredentialRevoked\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Clears session or trusted_device only when the exact current credential was removed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential ID.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No matching owned credential.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke own session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/trusted-devices\":{\"get\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. Returns at most 32 unexpired entries ordered by most recent activity, then descending ID. There is no pagination in this bounded contract. Pending MFA challenges are excluded from sessions.\",\"operationId\":\"listTrustedDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TrustedDevicesResponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List own trusted devices\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/trusted-devices/{id}\":{\"delete\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. The ID must be a canonical positive decimal safe integer without signs, leading zeros, escapes or trailing path segments. Revocation is independent of the 32-entry list limit, can remove expired records, and excludes pending MFA sessions. Current-cookie clearing uses the exact removed credential. Missing or another owner\xE2\x80\x99s ID returns the same 404; database failure is 500. A successful revoke invalidates future use, but does not cancel already-authorized in-flight requests.\",\"operationId\":\"revokeTrustedDevice\",\"parameters\":[{\"description\":\"Canonical positive decimal safe integer; no leading zeros or encoded aliases.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CredentialRevoked\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Clears session or trusted_device only when the exact current credential was removed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential ID.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No matching owned credential.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke own trusted device\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/users\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAdminUsers\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUsers\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List admin users\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires a non-scoped users.manage principal. Initial legacy role is converted to built-in policy grants; the caller may only delegate actions it holds. Returns 200. New users are provisioned inactive until policy and CIDR rules have been applied.\",\"operationId\":\"createAdminUser\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserCreate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}\":{\"delete\":{\"description\":\"Requires users.manage. Cannot delete yourself or the last legacy administrator; deletion and credential revocation are durable.\",\"operationId\":\"deleteAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"A non-scoped owner may edit its username/email. Other fields and other users require effective users.manage. Role is rejected; use versioned policy management. CIDR input is validated before saving. Password changes invalidate existing sessions/challenges/trusted devices; locked passwords cannot change.\",\"operationId\":\"updateAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/api-key\":{\"post\":{\"description\":\"Only a non-scoped owner or non-scoped users.manage principal may rotate the legacy key. Scoped tokens are always forbidden, including tokens with users.manage. The unrestricted key is returned once; lists never expose it. Discovery does not grant access to another user.\",\"operationId\":\"createLegacyApiKey\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LegacyApiKeyCreated\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create legacy api key\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/users/{id}/login-lockout/clear\":{\"post\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"clearUserLoginLockout\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginLockoutCleared\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear user login lockout\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}/password\":{\"put\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Requires old_password for self-service without users.manage and for a forced own password change. Success atomically changes the password and revokes sessions, pending challenges and trusted devices. Changing your own password clears browser cookies. Basic/key compatibility remains supported.\",\"operationId\":\"changePassword\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordChangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Change or reset a password\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/password-lock\":{\"put\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"setUserPasswordLock\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordLockRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set user password lock\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}/totp/disable\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Disables MFA, erases the secret and revokes other sessions/challenges/trusted devices. The initiating authenticated session is retained.\",\"operationId\":\"disableTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Disable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/setup\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Returns enrollment secret only here; setup expires after ten minutes. Replacing pending enrollment invalidates the previous secret. An enabled account returns 409.\",\"operationId\":\"setupTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpSetupResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start MFA enrollment\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/status\":{\"get\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Returns enabled/configured booleans without secrets. Unavailability is not evidence that MFA is disabled.\",\"operationId\":\"getTotpStatus\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpStatusResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read MFA enrollment status\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/verify\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Consumes a fresh enrollment code, enables MFA and revokes other sessions/challenges/trusted devices. The initiating authenticated session is retained.\",\"operationId\":\"verifyTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpCodeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Verify and enable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/verify\":{\"get\":{\"description\":\"Session, Basic or legacy key only; scoped tokens do not resolve here. Required-password-change accounts may use verify. Auth-disabled mode returns synthetic admin and anonymous demo returns synthetic viewer; neither permits personalized discovery. Missing authentication clears stale session cookie.\",\"operationId\":\"verifyIdentity\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthVerifyResponse\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Verify current account identity\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/authorization/actions\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuthorizationActions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminActionCatalog\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorization actions\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuthorizationRoles\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoles\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List authorization roles\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"createAuthorizationRole\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWritten\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles/{uuid}\":{\"delete\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"deleteAuthorizationRole\",\"parameters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminVersionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Delete authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"updateAuthorizationRole\",\"parameters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWritten\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Update authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/simulate\":{\"post\":{\"description\":\"Requires users.manage. Evaluates the target user, not a token, against a concrete existing camera for camera-scoped actions or no camera for global actions. Does not modify grants; an audit event is appended.\",\"operationId\":\"simulateAuthorization\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminSimulationRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminSimulation\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Simulate authorization\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{id}\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getUserAuthorizationPolicy\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserPolicy\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Get user authorization policy\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires a non-scoped users.manage principal, current expected_policy_version, and mode policy. Replaces the grants atomically. Delegated actions and camera scope cannot exceed caller authority. Self-lockout is rejected. Effective permissions and scoped-token intersections are re-evaluated on subsequent requests.\",\"operationId\":\"setUserAuthorizationPolicy\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminPolicyWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserPolicy\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Set user authorization policy\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{user_id}/tokens\":{\"get\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 before authentication. Current target and policy checks still run for each request.\",\"operationId\":\"listUserTokens\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenList\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List token metadata for an allowed owner\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"parameters\":[{\"description\":\"Positive user ID. Non-scoped caller may manage their own tokens; other owners require users.manage. Visibility never grants authority over arbitrary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"post\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 before authentication. Current target and policy checks still run for each request. Requested actions and scope must be contained by both owner and requester. Only201 returns the one-time secret. Denied/invalid issuance leaves tokens and policy unchanged; audit entries may be written.\",\"operationId\":\"createUserToken\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenCreated\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Active token limit reached.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create a scoped token\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/authorization/users/{user_id}/tokens/{token_uuid}\":{\"delete\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 before authentication. Current target and policy checks still run for each request. Revocation invalidates subsequent credential requests; it does not rewrite owner policy.\",\"operationId\":\"revokeUserToken\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SuccessResponse\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke an allowed owner token\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"parameters\":[{\"description\":\"Positive user ID. Non-scoped caller may manage their own tokens; other owners require users.manage. Visibility never grants authority over arbitrary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Token UUID belonging to this owner.\",\"in\":\"path\",\"name\":\"token_uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}]},\"/api/camera-collections\":{\"get\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"listCameraCollections\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera collections\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"createCameraCollection\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"getCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera collection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}/members\":{\"get\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"getCameraCollectionMembers\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Only static collections have explicit members.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized static collection members\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"setCameraCollectionMembers\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraCollectionMembers\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}/preview\":{\"post\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"previewCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionPreview\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview authorized collection matches\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/camera-favorites\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listCameraFavorites\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavoritesResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListCameraFavorites\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/camera-favorites/{uuid}\":{\"delete\":{\"description\":\"Remove only the current user favorite, including cameras no longer visible. Idempotent; scoped tokens and demo identities cannot mutate.\",\"operationId\":\"deleteCameraFavorite\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavoriteRemoval\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"addCameraFavorite\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavorite\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"AddCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"listFleetTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera tags\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"createFleetTag\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera tag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags/{uuid}/merge\":{\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"mergeFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagMergeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagMergeResult\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"MergeFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/cameras/{uuid}/location\":{\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"setCameraLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraLocationAssignmentRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraLocationAssignment\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/cameras/{uuid}/tags\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getCameraTagAssignments\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read tags for an authorized camera\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"setCameraTags\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraTags\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"},{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/client-config\":{\"get\":{\"description\":\"Any usable principal, including scoped tokens, can read this intentionally limited shape. Existing auth-disabled/demo behavior remains; this is not public in normal authenticated mode.\",\"operationId\":\"getClientConfig\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ClientConfig\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get safe browser playback configuration\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/detection/models\":{\"get\":{\"description\":\"Requires global camera.configure or that permission on an existing camera. Built-in models remain present if the configured model directory is absent. File models include paths and support/build metadata only for an authorized configurator.\",\"operationId\":\"listEvidenceModels\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceModels\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence models\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"global_or_camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/detection/results/{name}\":{\"get\":{\"description\":\"Current camera live.view authorizes the query. Captured detection UUIDs take precedence over reused stream names; only legacy NULL/empty UUID rows fall back to name. Optional start/end are epoch-second filters; absent bounds use a recent interval of at least 30 seconds.\",\"operationId\":\"getEvidenceDetections\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"start\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"end\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceDetections\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence detections\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-destinations\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"listEventDestinations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinations\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List event destinations\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"createEventDestination\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-destinations/{uuid}\":{\"delete\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"deleteEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutationAck\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"updateEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinationUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"listEventRoutes\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutes\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List event routes\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"createEventRoute\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRouteCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/preview\":{\"post\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"previewEventRoute\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutePreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutePreview\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/{uuid}\":{\"delete\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"deleteEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutationAck\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"updateEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRouteUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/events/catalog\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventCatalog\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventCatalog\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event catalog\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/fleet/cameras/query\":{\"post\":{\"description\":\"Read-only query over live.view-filtered, runtime-enriched inventory. Rows, totals and facets use matching authorized cameras; operational queues use all authorized cameras before query filters. can_configure is evaluated separately for each returned camera. Unknown collection is 404; out-of-range pages may be empty with nonzero totals. No stream credentials are returned.\",\"operationId\":\"queryFleetCameras\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQueryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQueryResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Query authorized fleet cameras\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/selectors/preview\":{\"post\":{\"description\":\"Read-only query over live.view-filtered, runtime-enriched inventory. Rows, totals and facets use matching authorized cameras; operational queues use all authorized cameras before query filters. can_configure is evaluated separately for each returned camera. Unknown collection is 404; out-of-range pages may be empty with nonzero totals. No stream credentials are returned.\",\"operationId\":\"previewFleetSelector\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetPreviewResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview a selector over authorized fleet cameras\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/views\":{\"get\":{\"description\":\"Owned and shared saved filter configurations; up to 256 list entries. Stored selector references do not grant camera access and must be reauthorized on query. Existing can_share reflects the legacy admin role, not arbitrary action grants. Mutation contracts remain outside this read slice.\",\"operationId\":\"listFleetViews\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedViewsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List owned and shared fleet views\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shared visibility does not grant modification.\",\"operationId\":\"createFleetView\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetViewCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/views/{uuid}\":{\"delete\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shared visibility does not grant modification.\",\"operationId\":\"deleteFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"get\":{\"description\":\"Owned and shared saved filter configurations; up to 256 list entries. Stored selector references do not grant camera access and must be reauthorized on query. Existing can_share reflects the legacy admin role, not arbitrary action grants. Mutation contracts remain outside this read slice.\",\"operationId\":\"getFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an owned or shared fleet view\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shared visibility does not grant modification.\",\"operationId\":\"updateFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetViewUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/health\":{\"get\":{\"operationId\":\"getStreamHealth\",\"parameters\":[{\"description\":\"Return only process liveness.\",\"in\":\"query\",\"name\":\"liveness\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Return authorized stream histories.\",\"in\":\"query\",\"name\":\"history_only\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Include stream history arrays.\",\"in\":\"query\",\"name\":\"sparklines\",\"schema\":{\"type\":\"boolean\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HealthResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get stream health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/health/hls\":{\"get\":{\"operationId\":\"getHlsHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HlsHealth\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get hls health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/ice-servers\":{\"get\":{\"description\":\"Return configured ICE servers for a principal permitted to view at least one camera. TURN credentials are intentionally provided to that authorized browser.\",\"operationId\":\"getIceServers\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/IceServersResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Return configured ICE servers for a principal permitted to view at least one camera. TURN credentials are intentionally provided to that authorized browser.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigation-bookmarks\":{\"get\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"listEvidenceBookmarks\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarks\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence bookmarks\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"createEvidenceBookmark\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigation-bookmarks/{uuid}\":{\"delete\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"deleteEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkDelete\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"get\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"getEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"updateEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/recordings/preview\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"previewEvidenceRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidencePreview\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/search\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"searchEvidence\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSearchRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSearch\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Search evidence\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/segment-at\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"resolveEvidenceSegment\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSegmentAtRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSegmentAt\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Resolve evidence segment\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/thumbnail-samples\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"sampleEvidenceThumbnails\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThumbnailRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThumbnails\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Sample evidence thumbnails\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/thumbnail/{recording_id}/{offset_ms}\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"getEvidenceThumbnail\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}},{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"offset_ms\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence thumbnail\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/timeline\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"queryEvidenceTimeline\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceTimeline\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Query evidence timeline\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/layouts\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listLiveLayouts\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListLiveLayouts\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"createLiveLayout\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayout\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/live/layouts/{uuid}\":{\"delete\":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"deleteLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"put\":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"updateLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayout\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/live/plans\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listFloorPlans\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlansResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListFloorPlans\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"createFloorPlan\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlanCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/plans/{uuid}\":{\"delete\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"deleteFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlanDeletion\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"getFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"updateFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlanUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/plans/{uuid}/background\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"getFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Exact uploaded PNG/JPEG bytes, accessible only when the plan is visible.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Raw structurally validated PNG/JPEG, at most 786432 bytes. Atomic publication preserves previous bytes on invalid upload. Returns the new plan revision; no request revision is used.\",\"operationId\":\"setFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"maxLength\":786432,\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format\":\"binary\",\"maxLength\":786432,\"type\":\"string\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Image too large or too short.\"},\"415\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Unsupported image content type.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/locations\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"listFleetLocations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera locations\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"createFleetLocation\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/locations/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera location\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/export\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"exportEvidenceLpr\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprExportRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprReads\"}}},\"description\":\"Validated success.\",\"headers\":{\"Content-Disposition\":{\"schema\":{\"const\":\"attachment; filename=\\\"lightnvr-lpr-export.json\\\"\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.export\",\"resource\":\"raw\"},{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\":\"lpr.read\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/reads/{read_uuid}\":{\"delete\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"deleteEvidenceLpr\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"read_uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/search\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"searchEvidenceLpr\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprSearchRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprReads\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Search evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\":\"lpr.read\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/metrics\":{\"get\":{\"operationId\":\"getMetrics\",\"responses\":{\"200\":{\"content\":{\"text/plain\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get metrics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/motion/trigger\":{\"post\":{\"description\":\"Requires camera.configure on the named stream. Start/stop/pulse report annotation, tag and recording effects independently; tags require an active recording and detection-recording schedules may suppress recording. Pulse is bounded to 600000 ms. Invalid requests may return 400 before principal evaluation.\",\"operationId\":\"triggerMotion\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MotionTrigger\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MotionTriggered\"}}},\"description\":\"Accepted motion effects are enumerated separately.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Trigger motion\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/add\":{\"post\":{\"operationId\":\"addOnvifDevice\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAddRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAddResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"addOnvifDevice\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/claim\":{\"post\":{\"operationId\":\"claimOnvifDevice\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifClaimRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceSuccess\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"claimOnvifDevice\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/profiles\":{\"get\":{\"operationId\":\"getOnvifProfiles\",\"parameters\":[{\"in\":\"header\",\"name\":\"X-Device-URL\",\"required\":true,\"schema\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Username\",\"required\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Password\",\"required\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifProfilesResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifProfiles\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/test\":{\"post\":{\"operationId\":\"testOnvifDevice\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifTestRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"testOnvifDevice\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/devices\":{\"get\":{\"operationId\":\"getOnvifDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDevicesResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDevices\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/discovery/discover\":{\"post\":{\"operationId\":\"discoverOnvifDevices\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDiscoveryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDevicesResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"discoverOnvifDevices\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/discovery/status\":{\"get\":{\"operationId\":\"getOnvifDiscoveryStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDiscoveryStatus\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDiscoveryStatus\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/openapi.json\":{\"get\":{\"description\":\"Uses actual session, Basic, legacy API-key or scoped-token identity with existing credential precedence and restrictions. Synthetic auth_disabled/anonymous demo principals are rejected with403. Real authenticated demo users retain their actual action permissions. No requested-user/role override. Every response is private and non-cacheable; downloaded documentation never grants authority.\",\"operationId\":\"getOpenApi\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OpenApiDocument\"}}},\"description\":\"Permission-aware partial document. Never a fallback to the canonical unfiltered source.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth_disabled or anonymous demo identity is excluded from personalized discovery.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Discover the current principal\xE2\x80\x99s API contract\",\"tags\":[\"Discovery\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings\":{\"get\":{\"description\":\"Replay-authorized rows and counts; empty scope without an explicit stream returns an empty page. Every explicitly named stream must be authorized or the request returns generic403. Completed rows and sufficiently recent in-progress rows are included; stale incomplete rows age out. Filters and collection membership intersect authorization. Current handlers preserve auth-disabled/demo deployment behavior.\",\"operationId\":\"listRecordings\",\"parameters\":[{\"description\":\"Comma-separated current stream names; every requested name must be replay authorized.\",\"in\":\"query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Optional collection UUID, resolved on the server and intersected with replay scope.\",\"in\":\"query\",\"name\":\"collection_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 start bound; invalid strings currently behave as no bound.\",\"in\":\"query\",\"name\":\"start\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 end bound; invalid strings currently behave as no bound.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Default1; nonpositive/invalid numeric values normalize to1.\",\"in\":\"query\",\"name\":\"page\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Default20, numeric maximum1000; nonpositive/invalid values normalize to20. Case-insensitive all returns all matching authorized rows, sets page1 and recounts.\",\"in\":\"query\",\"name\":\"limit\",\"required\":false,\"schema\":{\"anyOf\":[{\"type\":\"integer\"},{\"const\":\"all\"}]}},{\"description\":\"Unsupported fields fall back to start_time.\",\"in\":\"query\",\"name\":\"sort\",\"required\":false,\"schema\":{\"enum\":[\"id\",\"stream_name\",\"start_time\",\"end_time\",\"size_bytes\"]}},{\"description\":\"asc or desc; unsupported values fall back to desc.\",\"in\":\"query\",\"name\":\"order\",\"required\":false,\"schema\":{\"enum\":[\"asc\",\"desc\"]}},{\"description\":\"0 all,1 with detection,-1 without; numeric values clamp to this interval.\",\"in\":\"query\",\"name\":\"has_detection\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Comma-separated labels; implies detection-only filter.\",\"in\":\"query\",\"name\":\"detection_label\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Leading0 means unprotected, leading1 protected; otherwise all.\",\"in\":\"query\",\"name\":\"protected\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Comma-separated recording tags.\",\"in\":\"query\",\"name\":\"tag\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Comma-separated capture methods, including continuous,scheduled,detection,motion,manual.\",\"in\":\"query\",\"name\":\"capture_method\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingsPage\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized recordings\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-delete\":{\"post\":{\"description\":\"Fixed IDs preflight every existing member before dispatch; forbidden members reject the whole batch. Filter jobs capture authorized UUID scope before dispatch. Operational missing/protected failures are counted separately and can coexist with successes. Poll the owned job; cancellation is unsupported.\",\"operationId\":\"deleteRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionJobAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create a recording deletion job\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/batch-delete/progress/{job_id}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Terminal complete includes partial operational failures; inspect all counters. There is no cancel operation.\",\"operationId\":\"getRecordingDeletionProgress\",\"parameters\":[{\"description\":\"Opaque deletion job identifier; possession alone grants no access.\",\"in\":\"path\",\"name\":\"job_id\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionProgress\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned recording deletion progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-download\":{\"post\":{\"description\":\"Preflights every requested recording before creating the job. Missing404/in-progress409 may abort in input order before a later forbidden member; these early errors create no job or archive. ZIP32 uses original media, including recovery-download files with saved playback issues. Detected unavailable-source/member-count and ZIP32-limit failures reject the result. The current writer does not consistently detect filesystem read/write/close failures; complete alone does not prove archive integrity. Cancellation is unsupported.\",\"operationId\":\"createRecordingExport\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Busy job slots, unavailable source, or shutdown.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create an authorized ZIP export job\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/batch-download/result/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Export ownership also matches username. Pending/running jobs return409 with error Job is not_ready; failed jobs return409 with error Job is error. Only complete archives are downloadable. Results can be read repeatedly while retained; result access refreshes job retention. This is not a one-time token. No cancellation endpoint exists.\",\"operationId\":\"downloadRecordingExport\",\"parameters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"path\",\"name\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single byte range using the common file-serving parser.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Complete ZIP archive.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"206\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Single ZIP byte range.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Download an owned completed ZIP export\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-download/status/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Export ownership also matches the stored username. Jobs can be reclaimed after their retention interval; polling does not make a job permanent.\",\"operationId\":\"getRecordingExportStatus\",\"parameters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"path\",\"name\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportStatus\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned recording export progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-protect\":{\"post\":{\"description\":\"An otherwise-valid forbidden existing member yields403 before any protection writes. Invalid/missing IDs and subsequent database write failures are counted, so operational failures may produce partial success.\",\"operationId\":\"protectRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchProtectionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchProtectionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set protection on recording IDs\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/batch-tags\":{\"post\":{\"description\":\"Preflights all IDs and rejects missing/forbidden members before writes. Invalid IDs return400. Operational database failures do not promise an all-or-nothing transaction.\",\"operationId\":\"updateRecordingTagsBatch\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchTagsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchTagsResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Add or remove tags on recording IDs\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/detection-labels\":{\"get\":{\"description\":\"Unique detection labels within replay-authorized camera scope. An empty scope returns labels:[].\",\"operationId\":\"listRecordingDetectionLabels\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDetectionLabels\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized detection label facets\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/download/{recording_id}\":{\"get\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Requires recordings.export before source probes. In-progress media returns409. Saved playback failures do not block original-byte recovery download. prepare=1 returns ready/preparing JSON; ordinary success is an attachment. No compatibility transcoding is requested by this route. Byte ranges use the same permissive local parser as playback: prefix/suffix/open-ended accepted and clamped, malformed missing-dash/multiple ranges may parse as a single range. Local416 omits Content-Range.\",\"operationId\":\"downloadRecording\",\"parameters\":[{\"description\":\"Exactly1 selects JSON readiness without media bytes.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"UI hint for externally stored recordings. Actual source selection follows stored metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=start-, or bytes=-suffix range. Actual permissive parser limitations are documented above.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"prepare=1 JSON ready, or complete media body.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Attachment with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"description\":\"Requested media is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"schema\":{\"type\":\"string\"}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A single satisfiable media byte range. Validate bytes/protocol separately from JSON.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Attachment with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\",\"headers\":{\"Retry-After\":{\"description\":\"Present when preparation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Download or prepare original recording media\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/files\":{\"delete\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"deleteEvidenceRecordingFile\",\"parameters\":[{\"description\":\"Exact registered recording file path. Unknown paths return404; this is not an arbitrary filesystem operation. Captured recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileDeleted\"}}},\"description\":\"Validated success.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileDeleted\"}}},\"description\":\"Deletion scheduled; not proof of file removal.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence recording file\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/files/check\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"checkEvidenceRecordingFile\",\"parameters\":[{\"description\":\"Exact registered recording file path. Unknown paths return404; this is not an arbitrary filesystem operation. Captured recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileCheck\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Check evidence recording file\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/play/{recording_id}\":{\"get\":{\"description\":\"Requires recordings.replay for capture identity before inspecting source availability. In-progress409; saved invalid_media/no_video/decode_error422 with recovery explanation. prepare=1 returns JSON ready/preparing; ordinary success serves media bytes. Archive/HEVC preparation can return202 (prepare) or503 (ordinary) with Retry-After2. Authorization uses the logical recording, never a caller-supplied filesystem path. Local byte ranges accept prefix, suffix and open-ended forms; excessive end/suffix clamp to EOF. Invalid unit, start\xE2\x89\xA5size, reversed bounds and zero suffix yield416 JSON. Local416 currently omits Content-Range; archive416 supplies bytes */size. Existing parser is permissive: bytes=0 (missing dash) becomes full206 and multi-ranges are treated as their first numeric range, not multipart. Do not infer strict RFC rejection of malformed ranges.\",\"operationId\":\"playRecording\",\"parameters\":[{\"description\":\"Exactly1 selects JSON readiness without media bytes.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"Exactly1 requests compatibility cache;0 requests original. Absent defaults to prepare=1 compatibility behavior.\",\"in\":\"query\",\"name\":\"transcode\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"UI hint for externally stored recordings. Actual source selection follows stored metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=start-, or bytes=-suffix range. Actual permissive parser limitations are documented above.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"prepare=1 JSON ready, or complete media body.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"description\":\"Requested media is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"schema\":{\"type\":\"string\"}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A single satisfiable media byte range. Validate bytes/protocol separately from JSON.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Conclusive saved interrupted-media failure; playback is unavailable. Download recovery is a separately authorized export operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\",\"headers\":{\"Retry-After\":{\"description\":\"Present when preparation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Prepare or play an authorized recording\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/protected\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"countEvidenceProtected\",\"parameters\":[{\"description\":\"Optional current camera name; otherwise count only recordings visible by replay scope.\",\"in\":\"query\",\"name\":\"stream\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceProtectedCount\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Count evidence protected\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/sync\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"syncEvidenceRecordings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSyncComplete\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Sync evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/tags\":{\"get\":{\"description\":\"Unique recording tags within replay-authorized camera scope. An empty scope returns tags:[]; no hidden-camera facet values are published.\",\"operationId\":\"listRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTags\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized recording tag facets\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/thumbnail/{recording_id}/{index}\":{\"get\":{\"description\":\"JPEG cache/generation requires capture-camera recordings.replay. Global disabled thumbnails403 and disabled hover frames403 are checked before auth. Otherwise authorization precedes cache/file access. Index0,1,2 selects start/middle/end samples; index>0 may be disabled by thumbnails_per_recording=1. Cache uses private max-age86400; source preparation/deletion can return503/409. Thumbnails do not assert original media is playable.\",\"operationId\":\"getRecordingThumbnail\",\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"JPEG thumbnail bytes.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Single cached/generated JPEG byte range.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized recording thumbnail\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Thumbnail sample index.\",\"in\":\"path\",\"name\":\"index\",\"required\":true,\"schema\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"}}]},\"/api/recordings/{recording_id}\":{\"delete\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Protected/in-progress/active-transfer media returns409. A202 response means deletion is queued, not that bytes have disappeared.\",\"operationId\":\"deleteRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionResult\"}}},\"description\":\"Successful response\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionResult\"}}},\"description\":\"Deletion accepted and pending; status is deletion_pending.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete a recording\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorizes recordings.replay using current raw facts for stored capture identity before emitting metadata. This response does not include tags; use the separate tags endpoint.\",\"operationId\":\"getRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDetail\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read recording detail\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/{recording_id}/protect\":{\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Protected media cannot be deleted by ordinary recording deletion.\",\"operationId\":\"setRecordingProtection\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingProtectionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingProtectionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set recording protection\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recording_id}/retention\":{\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Negative input clears the override, zero means indefinite. The returned integer reflects the converted input; clearing does not normalize it to -1.\",\"operationId\":\"setRecordingRetention\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingRetentionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingRetentionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set or clear a recording retention override\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recording_id}/tags\":{\"get\":{\"description\":\"Uses stored capture identity and recordings.replay before returning tags.\",\"operationId\":\"getRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized recording\xE2\x80\x99s tags\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Authorization precedes tag mutation.\",\"operationId\":\"setRecordingTags\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Replace recording tags\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/settings\":{\"get\":{\"operationId\":\"getSystemSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettings\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system settings\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"operationId\":\"updateSystemSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettingsUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettingsSaved\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update system settings\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/settings/go2rtc/validate\":{\"post\":{\"operationId\":\"validateGo2rtcConfiguration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcConfigurationInput\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcConfigurationValidation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Validate go2rtc configuration\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/setup/status\":{\"get\":{\"operationId\":\"getSetupStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupStatus\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get setup status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}},\"post\":{\"description\":\"Before setup completion this bootstrap operation is public. Once complete it requires system.admin; resetting requires that authority. Malformed/absent optional fields retain the native default of completing setup.\",\"operationId\":\"updateSetupStatus\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupUpdate\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupUpdated\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update setup status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"bootstrap_setup\",\"version\":1}}},\"/api/snapshots/{camera}/{filename}\":{\"get\":{\"description\":\"New MQTT snapshot URLs use the captured immutable camera UUID namespace. UUID URLs authorize that UUID and never fall back to a display-name directory. Existing name-only snapshot URLs remain compatible and authorize the current named camera; old files do not contain recoverable capture identity. Camera snapshot.create is required.\",\"operationId\":\"getEvidenceSnapshot\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"camera\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}},{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"filename\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence snapshot\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"snapshot.create\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-archive\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageArchive\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-archive/retry\":{\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"retryStorageArchive\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveRetryRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveRetryResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-compliance\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageCompliance\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageComplianceResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageCompliance\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStorageMigrations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigrationsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageMigrations\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Queues move (default) or copy of a complete recording. A 202 job is not completion. Poll the job state; verify checksum/publication and cleanup independently. Permission is global storage.configure, not creator ownership.\",\"operationId\":\"createStorageMigration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigrationCreate\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{uuid}\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{uuid}/cancel\":{\"post\":{\"description\":\"Global storage.configure permits acting on any job. Cancel may request cancellation of an active job; retry requeues eligible failed/cancelled work. Poll state to observe completion and cleanup; the acknowledgement is not a completed transfer.\",\"operationId\":\"cancelStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CancelStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{uuid}/retry\":{\"post\":{\"description\":\"Global storage.configure permits acting on any job. Cancel may request cancellation of an active job; retry requeues eligible failed/cancelled work. Poll state to observe completion and cleanup; the acknowledgement is not a completed transfer.\",\"operationId\":\"retryStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStoragePolicies\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoliciesResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStoragePolicies\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"createStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/preview\":{\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"previewStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyPreview\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"PreviewStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/{uuid}\":{\"delete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"deleteStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/{uuid}/apply\":{\"post\":{\"description\":\"Preview counts records whose captured policy revision differs. Apply updates those revision references transactionally and requires unfinished transfers to finish first. It does not itself delete files or complete asynchronous archive/migration work.\",\"operationId\":\"applyStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyApplyRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyApplyResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ApplyStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStoragePools\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStoragePools\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"createStoragePool\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools/{uuid}\":{\"delete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"deleteStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStorageTargets\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageTargets\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"createStorageTarget\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}\":{\"delete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"deleteStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}/probe\":{\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"probeStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ProbeStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage/cleanup\":{\"post\":{\"description\":\"Signals the asynchronous cleanup controller. Success acknowledges scheduling, not deletion or reclaimed bytes. Invalid bodies are rejected before scheduling. Protected media and retention constraints still apply.\",\"operationId\":\"triggerStorageCleanup\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCleanupRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCleanupResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"TriggerStorageCleanup\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage/health\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageHealthResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageHealth\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams\":{\"get\":{\"description\":\"Authenticated list discovery remains available even with no matching camera grants. Actual rows and totals are filtered per camera. Permission checks use runtime plus bounded go2rtc health observations; this can differ from full-read raw-health authorization. This pilot describes the existing auth-disabled/demo behavior only as a deployment caveat; personalized discovery itself requires a real identity.\",\"operationId\":\"listStreams\",\"parameters\":[{\"description\":\"Exactly true or 1 selects a paginated summary envelope. All other values, including absence, select the compatibility array. Other list query options only apply to summary mode.\",\"in\":\"query\",\"name\":\"summary\",\"required\":false,\"schema\":{\"default\":\"false\",\"type\":\"string\"}},{\"description\":\"Summary row shape: admin/default or live filters live.view; lpr filters the conjunction lpr.search and lpr.read and emits only identity.\",\"in\":\"query\",\"name\":\"surface\",\"required\":false,\"schema\":{\"default\":\"admin\",\"enum\":[\"admin\",\"live\",\"lpr\"],\"type\":\"string\"}},{\"description\":\"Summary page number.\",\"in\":\"query\",\"name\":\"page\",\"required\":false,\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Summary page size.\",\"in\":\"query\",\"name\":\"page_size\",\"required\":false,\"schema\":{\"default\":50,\"maximum\":100,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Case-insensitive substring over authorized name, UUID, tags, location path and safe address. Handler buffer retains up to 255 bytes.\",\"in\":\"query\",\"name\":\"search\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Summary sort key; equal values break ties by name.\",\"in\":\"query\",\"name\":\"sort_by\",\"required\":false,\"schema\":{\"default\":\"name\",\"enum\":[\"name\",\"status\",\"resolution\",\"fps\",\"recording\"],\"type\":\"string\"}},{\"description\":\"Summary sort direction.\",\"in\":\"query\",\"name\":\"sort_order\",\"required\":false,\"schema\":{\"default\":\"asc\",\"enum\":[\"asc\",\"desc\"],\"type\":\"string\"}},{\"description\":\"Summary availability filter, applied before total.\",\"in\":\"query\",\"name\":\"availability\",\"required\":false,\"schema\":{\"default\":\"all\",\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"}},{\"description\":\"Exactly true or 1 requests admin_url for the admin summary surface. Other values omit it; live/LPR surfaces never include it. Values are redacted unless that same camera is configurable.\",\"in\":\"query\",\"name\":\"include_admin_url\",\"required\":false,\"schema\":{\"default\":\"false\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamListResponse\"}}},\"description\":\"Compatibility array or requested summary page; empty authorized results are valid.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized streams\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Create a camera using global camera.configure. Creation intent is distinct from editing an existing camera.\",\"operationId\":\"createStream\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamCreated\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create a camera using global camera.configure. Creation intent is distinct from editing an existing camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/test\":{\"post\":{\"description\":\"Probe an administrator-supplied media URL. A failed media probe returns 200 with success=false; it does not create a camera.\",\"operationId\":\"probeStream\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamProbeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamProbeResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Probe an administrator-supplied media URL. A failed media probe returns 200 with success=false; it does not create a camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}\":{\"delete\":{\"description\":\"Queue camera deletion using captured identity. Acknowledgment does not prove completion. Default disables the camera; permanent=true removes its configuration.\",\"operationId\":\"deleteStream\",\"parameters\":[{\"description\":\"Exactly true requests permanent deletion; absence or another value disables.\",\"in\":\"query\",\"name\":\"permanent\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDeleteAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Queue camera deletion using captured identity. Acknowledgment does not prove completion. Default disables the camera; permanent=true removes its configuration.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"operationId\":\"getStream\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamSingleResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStream\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"description\":\"Requires camera.configure on the named camera using raw DB facts; no read permission is implied. Boolean set_privacy_mode selects a privacy-only request and rejects accompanying configuration fields. Ordinary updates are partial; enable_disabled=true with enabled=false is rejected. Unknown ordinary fields are ignored, and 202 is not a durable-success guarantee. A 502 privacy response can follow a saved flag when go2rtc removal fails; retry is required and the stream may remain viewable in the meantime.\",\"operationId\":\"updateStream\",\"requestBody\":{\"content\":{\"application/json\":{\"examples\":{\"privacy\":{\"value\":{\"set_privacy_mode\":true}},\"zeroRetention\":{\"value\":{\"detection_threshold\":0,\"retention_days\":0}}},\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamPrivacyUpdated\"}}},\"description\":\"Privacy-only update completed.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateAccepted\"}}},\"description\":\"Ordinary update accepted for asynchronous application.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"The authenticated principal cannot perform the action on this camera.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No runtime stream with this name exists. Existing stream handlers may resolve this before checking credentials.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"An explicitly bounded override exceeds its supported byte size.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Privacy flag was saved, but removing the stream from go2rtc failed. The camera may still be viewable; retry the privacy-only request. This error is not a rollback of the saved privacy flag.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update stream configuration or privacy\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/daynight\":{\"get\":{\"operationId\":\"getDaynight\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getDaynight\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setDaynight\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightUpdated\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setDaynight\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/detection-engines\":{\"get\":{\"operationId\":\"getStreamDetectionEngines\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setStreamDetectionEngines\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEnginesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/full\":{\"get\":{\"description\":\"Requires live.view on this camera using raw DB facts. camera.configure on the same camera using runtime facts determines sensitive fields. A configure-only grant does not imply read access.\",\"operationId\":\"getStreamFull\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamFullResponse\"}}},\"description\":\"Wrapped details with per-camera conditional redaction.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"The authenticated principal cannot perform the action on this camera.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No runtime stream with this name exists. Existing stream handlers may resolve this before checking credentials.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read full stream configuration\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/imaging/options\":{\"get\":{\"operationId\":\"getImagingOptions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingOptionsResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getImagingOptions\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/imaging/settings\":{\"get\":{\"operationId\":\"getImagingSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getImagingSettings\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setImagingSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setImagingSettings\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/absolute\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"absolutePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"absolutePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/capabilities\":{\"get\":{\"operationId\":\"getPtzCapabilities\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzCapabilities\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getPtzCapabilities\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/ptz/goto-preset\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"gotoPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzGotoPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"gotoPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/home\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"homePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"homePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/move\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"movePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"movePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/preset\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresetResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/presets\":{\"get\":{\"operationId\":\"getPtzPresets\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresets\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getPtzPresets\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/ptz/relative\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"relativePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"relativePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/set-home\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"setPtzHome\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setPtzHome\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/stop\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"stopPtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"stopPtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/recording\":{\"get\":{\"operationId\":\"getStreamRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingState\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamRecording\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Start manual recording (202 starting) or stop it (200 idle). Continuous scheduled recording cannot be manually stopped. Follow the recording state to observe completion.\",\"operationId\":\"controlStreamRecording\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingChanged\"}}},\"description\":\"Successful response.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingChanged\"}}},\"description\":\"Recording start accepted; not a durable recording guarantee.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start manual recording (202 starting) or stop it (200 idle). Continuous scheduled recording cannot be manually stopped. Follow the recording state to observe completion.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/refresh\":{\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Queue refresh of the captured camera identity. A response may coalesce with an existing refresh; it does not prove a successful camera connection.\",\"operationId\":\"refreshStream\",\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRefreshAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Queue refresh of the captured camera identity. A response may coalesce with an existing refresh; it does not prove a successful camera connection.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/retention\":{\"get\":{\"operationId\":\"getStreamRetention\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamRetention\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setStreamRetention\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetentionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setStreamRetention\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/zones\":{\"delete\":{\"operationId\":\"deleteStreamZones\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesDeleted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"deleteStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"operationId\":\"getStreamZones\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZones\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"saveStreamZones\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesSaved\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"saveStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system\":{\"get\":{\"operationId\":\"getSystemInformationLegacy\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system information legacy\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/backup\":{\"post\":{\"operationId\":\"createSystemBackup\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemBackupCreated\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create system backup\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/go2rtc/effective-config\":{\"get\":{\"operationId\":\"getGo2rtcEffectiveConfiguration\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcEffectiveConfiguration\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc effective configuration\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/go2rtc/override-status\":{\"get\":{\"operationId\":\"getGo2rtcOverrideDiagnostics\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcOverrideDiagnostics\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc override diagnostics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/health\":{\"get\":{\"operationId\":\"getHostHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HostHealth\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get host health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/health/incidents\":{\"get\":{\"operationId\":\"getHostHealthIncidents\",\"parameters\":[{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"include_closed\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Opaque versioned cursor from the preceding page; malformed cursors return 400.\",\"in\":\"query\",\"name\":\"cursor\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HostHealthIncidents\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get host health incidents\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/info\":{\"get\":{\"operationId\":\"getSystemInformation\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system information\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/logs\":{\"get\":{\"operationId\":\"getSystemLogs\",\"parameters\":[{\"in\":\"query\",\"name\":\"level\",\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"count\",\"schema\":{\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"last_ts\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemLogs\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system logs\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/logs/clear\":{\"post\":{\"operationId\":\"clearSystemLogs\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear system logs\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/restart\":{\"post\":{\"operationId\":\"restartSystem\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Restart system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/shutdown\":{\"post\":{\"operationId\":\"shutdownSystem\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Shutdown system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/status\":{\"get\":{\"operationId\":\"getSystemStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemStatus\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/telemetry/player\":{\"post\":{\"description\":\"A named stream requires live.view on that stream. Empty/malformed unnamed telemetry is ignored with 204; an acknowledgment does not imply storage of an event.\",\"operationId\":\"recordPlayerTelemetry\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PlayerTelemetry\"}}},\"required\":false},\"responses\":{\"204\":{\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Record player telemetry\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/manifest\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"getEvidenceManifest\",\"parameters\":[{\"in\":\"query\",\"name\":\"stream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601 or date-only; defaults to recent24hours.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601 or date-only; defaults to recent24hours.\",\"in\":\"query\",\"name\":\"end\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/vnd.apple.mpegurl\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"application/vnd.apple.mpegurl\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence manifest\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/play\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"redirectEvidencePlayback\",\"parameters\":[{\"in\":\"query\",\"name\":\"stream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Positive epoch seconds or ISO8601; defaults to now.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"302\":{\"description\":\"Empty redirect to the chosen recording playback endpoint; playback independently reauthorizes the recording.\",\"headers\":{\"Location\":{\"schema\":{\"pattern\":\"^/api/recordings/play/[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Redirect evidence playback\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments\":{\"get\":{\"description\":\"Requires stream or camera_uuid. Explicit camera_uuid is authoritative even with a stale stream name. Raw capture-camera replay authorization precedes timeline lookup; deleted cameras return404. Timeline presence does not imply playable media.\",\"operationId\":\"getTimelineSegments\",\"parameters\":[{\"description\":\"Current stream name, required unless camera_uuid is present.\",\"in\":\"query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Capture camera UUID; takes precedence over stream, invalid UUID400.\",\"in\":\"query\",\"name\":\"camera_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defaults24hours ago. Invalid nonempty time parses as epoch rather than400.\",\"in\":\"query\",\"name\":\"start\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defaultsnow. Date-only end expands to end of day.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTimeline\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized camera timeline\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments-by-ids\":{\"get\":{\"description\":\"Filters each recording through stored capture identity and raw replay permission. Hidden/missing/orphaned entries do not contribute counts or overall time bounds. Does not reveal which requested IDs were denied.\",\"operationId\":\"getTimelineSegmentsByIds\",\"parameters\":[{\"description\":\"Required comma-separated positive decimal uint64 recording IDs. Surrounding whitespace and leading zeros are accepted; empty comma-separated fields are skipped. Signed, zero, overflowing, or partially numeric tokens reject the entire request with HTTP 400. Order and duplicates are preserved; missing or unauthorized IDs are omitted. The HTTP query string is limited to 1023 bytes; an additional handler bound rejects more than 8640 IDs.\",\"in\":\"query\",\"name\":\"ids\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTimelineByIds\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized timeline segments by stable IDs\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/ui/workspaces\":{\"get\":{\"operationId\":\"getWorkspaces\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get workspaces\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"operationId\":\"updateWorkspaces\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update workspaces\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/logout\":{\"get\":{\"description\":\"Public and idempotent. Deletes the session from the supplied cookie and clears session/trusted-device cookies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 requires Accept containing application/json or X-Requested-With; otherwise302 redirects to login.\",\"operationId\":\"legacyLogout\",\"parameters\":[{\"description\":\"Use application/json for the JSON success envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Presence also selects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[],\"summary\":\"End a browser session using the legacy route\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}}},\"servers\":[{\"description\":\"Application root relative to the actual served /api/openapi.json URL, including an existing proxy path prefix.\",\"url\":\"../\"}],\"tags\":[{\"description\":\"Camera inventory and configuration.\",\"name\":\"Streams\"},{\"description\":\"Current-principal contract visibility.\",\"name\":\"Discovery\"},{\"description\":\"Existing login, session verification and safe browser bootstrap.\",\"name\":\"Identity\"},{\"description\":\"Self-service or users.manage token lifecycle; scoped credentials cannot manage tokens.\",\"name\":\"Tokens\"},{\"description\":\"Replay-authorized recording inventory, metadata, timeline and media protocols.\",\"name\":\"Recording reads\"},{\"description\":\"Capture-identity protection, tags, deletion and export jobs. Job access remains creator/token-bound; cancellation is unsupported.\",\"name\":\"Recording actions\"},{\"description\":\"User, policy and audit administration.\",\"name\":\"Identity administration\"},{\"description\":\"User, policy and audit administration.\",\"name\":\"Audit\"},{\"description\":\"System administration, health, settings and bootstrap.\",\"name\":\"System\"},{\"description\":\"Event routing, destinations and external motion.\",\"name\":\"Events\"},{\"description\":\"Storage configuration, local lifecycle and archive operations.\",\"name\":\"Storage\"},{\"description\":\"Investigations, detections, bookmarks, LPR and recording media protocols.\",\"name\":\"Evidence\"},{\"description\":\"PTZ, imaging and ONVIF inventory and device commands.\",\"name\":\"Device controls\"}],\"x-lightnvr-coverage\":{\"canonical_revision\":\"eng-api-four-pr-v1\",\"covered_operations\":[\"listStreams\",\"getStreamFull\",\"updateStream\",\"getOpenApi\",\"getLoginConfig\",\"login\",\"logout\",\"verifyIdentity\",\"getClientConfig\",\"listUserTokens\",\"createUserToken\",\"revokeUserToken\",\"listRecordings\",\"getRecording\",\"listRecordingTags\",\"listRecordingDetectionLabels\",\"getRecordingTags\",\"getTimelineSegments\",\"getTimelineSegmentsByIds\",\"playRecording\",\"getRecordingThumbnail\",\"setRecordingProtection\",\"setRecordingRetention\",\"protectRecordings\",\"setRecordingTags\",\"updateRecordingTagsBatch\",\"deleteRecording\",\"deleteRecordings\",\"getRecordingDeletionProgress\",\"createRecordingExport\",\"getRecordingExportStatus\",\"downloadRecording\",\"downloadRecordingExport\",\"listAuthSessions\",\"revokeAuthSession\",\"listTrustedDevices\",\"revokeTrustedDevice\",\"completeTotpLogin\",\"legacyLogout\",\"changePassword\",\"disableTotp\",\"getTotpStatus\",\"setupTotp\",\"verifyTotp\",\"queryFleetCameras\",\"previewFleetSelector\",\"listFleetViews\",\"getFleetView\",\"listCameraCollections\",\"getCameraCollection\",\"getCameraCollectionMembers\",\"previewCameraCollection\",\"listFleetLocations\",\"getFleetLocation\",\"listFleetTags\",\"getFleetTag\",\"getCameraTagAssignments\",\"getSystemSettings\",\"updateSystemSettings\",\"validateGo2rtcConfiguration\",\"getSystemInformationLegacy\",\"getSystemInformation\",\"getSystemStatus\",\"getHostHealth\",\"getHostHealthIncidents\",\"getSystemLogs\",\"clearSystemLogs\",\"createSystemBackup\",\"restartSystem\",\"shutdownSystem\",\"getGo2rtcEffectiveConfiguration\",\"getGo2rtcOverrideDiagnostics\",\"getWorkspaces\",\"updateWorkspaces\",\"getSetupStatus\",\"updateSetupStatus\",\"getStreamHealth\",\"getHlsHealth\",\"getMetrics\",\"recordPlayerTelemetry\",\"getEventCatalog\",\"listEventDestinations\",\"createEventDestination\",\"getEventDestination\",\"updateEventDestination\",\"deleteEventDestination\",\"listEventRoutes\",\"createEventRoute\",\"getEventRoute\",\"updateEventRoute\",\"deleteEventRoute\",\"previewEventRoute\",\"triggerMotion\",\"createFleetLocation\",\"updateFleetLocation\",\"deleteFleetLocation\",\"createFleetTag\",\"updateFleetTag\",\"deleteFleetTag\",\"createCameraCollection\",\"updateCameraCollection\",\"deleteCameraCollection\",\"setCameraLocation\",\"setCameraTags\",\"mergeFleetTag\",\"setCameraCollectionMembers\",\"createFleetView\",\"updateFleetView\",\"deleteFleetView\",\"listLiveLayouts\",\"createLiveLayout\",\"updateLiveLayout\",\"deleteLiveLayout\",\"listCameraFavorites\",\"addCameraFavorite\",\"deleteCameraFavorite\",\"listFloorPlans\",\"getFloorPlan\",\"createFloorPlan\",\"updateFloorPlan\",\"deleteFloorPlan\",\"getFloorPlanBackground\",\"setFloorPlanBackground\",\"deleteFloorPlanBackground\",\"listAdminUsers\",\"createAdminUser\",\"getAdminUser\",\"updateAdminUser\",\"deleteAdminUser\",\"createLegacyApiKey\",\"setUserPasswordLock\",\"clearUserLoginLockout\",\"listAuthorizationActions\",\"simulateAuthorization\",\"listAuthorizationRoles\",\"createAuthorizationRole\",\"updateAuthorizationRole\",\"deleteAuthorizationRole\",\"getUserAuthorizationPolicy\",\"setUserAuthorizationPolicy\",\"listAuditEvents\",\"exportAuditEvents\",\"getAuditSettings\",\"updateAuditSettings\",\"listStorageTargets\",\"createStorageTarget\",\"getStorageTarget\",\"updateStorageTarget\",\"deleteStorageTarget\",\"listStoragePools\",\"createStoragePool\",\"getStoragePool\",\"updateStoragePool\",\"deleteStoragePool\",\"listStoragePolicies\",\"createStoragePolicy\",\"getStoragePolicy\",\"updateStoragePolicy\",\"deleteStoragePolicy\",\"probeStorageTarget\",\"previewStoragePolicy\",\"applyStoragePolicy\",\"listStorageMigrations\",\"createStorageMigration\",\"getStorageMigration\",\"cancelStorageMigration\",\"retryStorageMigration\",\"getStorageArchive\",\"retryStorageArchive\",\"getStorageCompliance\",\"getStorageHealth\",\"triggerStorageCleanup\",\"getEvidenceDetections\",\"listEvidenceModels\",\"searchEvidenceLpr\",\"exportEvidenceLpr\",\"deleteEvidenceLpr\",\"checkEvidenceRecordingFile\",\"deleteEvidenceRecordingFile\",\"countEvidenceProtected\",\"syncEvidenceRecordings\",\"queryEvidenceTimeline\",\"resolveEvidenceSegment\",\"searchEvidence\",\"previewEvidenceRecordings\",\"sampleEvidenceThumbnails\",\"listEvidenceBookmarks\",\"createEvidenceBookmark\",\"getEvidenceBookmark\",\"updateEvidenceBookmark\",\"deleteEvidenceBookmark\",\"getEvidenceSnapshot\",\"getEvidenceThumbnail\",\"getEvidenceManifest\",\"redirectEvidencePlayback\",\"getPtzCapabilities\",\"getPtzPresets\",\"movePtz\",\"absolutePtz\",\"relativePtz\",\"stopPtz\",\"homePtz\",\"setPtzHome\",\"gotoPtzPreset\",\"setPtzPreset\",\"getImagingSettings\",\"setImagingSettings\",\"getImagingOptions\",\"getDaynight\",\"setDaynight\",\"getOnvifDiscoveryStatus\",\"getOnvifDevices\",\"discoverOnvifDevices\",\"getOnvifProfiles\",\"addOnvifDevice\",\"claimOnvifDevice\",\"testOnvifDevice\",\"createStream\",\"probeStream\",\"getStream\",\"deleteStream\",\"refreshStream\",\"getStreamRecording\",\"controlStreamRecording\",\"getStreamZones\",\"saveStreamZones\",\"deleteStreamZones\",\"getStreamRetention\",\"setStreamRetention\",\"getStreamDetectionEngines\",\"setStreamDetectionEngines\",\"getIceServers\"],\"description\":\"232 reviewed OpenAPI operations; eight registered media-protocol surfaces and one internal callback have explicit inventory dispositions outside this document. Discovery is filtered by current authority and never grants access.\",\"partial\":true},\"x-lightnvr-validators\":[\"StreamSummaryPage\",\"LiveStreamSummaryPage\",\"LprStreamSummaryPage\",\"StreamCompatibilityList\",\"StreamFullResponse\",\"StreamUpdateRequest\",\"StreamUpdateAccepted\",\"StreamPrivacyUpdated\",\"ApiError\",\"OpenApiDocument\",\"LoginRequest\",\"LoginResponse\",\"LoginConfig\",\"AuthVerifyResponse\",\"LogoutResponse\",\"ClientConfig\",\"ApiTokenCreateRequest\",\"ApiTokenMetadata\",\"ApiTokenList\",\"ApiTokenCreated\",\"SuccessResponse\",\"RecordingDetail\",\"RecordingsPage\",\"RecordingTags\",\"RecordingTagsById\",\"RecordingDetectionLabels\",\"RecordingTimeline\",\"RecordingTimelineByIds\",\"RecordingPlaybackReady\",\"RecordingPlaybackPreparing\",\"RecordingProtectionRequest\",\"RecordingProtectionResult\",\"RecordingRetentionRequest\",\"RecordingRetentionResult\",\"RecordingBatchProtectionRequest\",\"RecordingBatchProtectionResult\",\"RecordingTagsRequest\",\"RecordingBatchTagsRequest\",\"RecordingBatchTagsResult\",\"RecordingDeletionResult\",\"RecordingDeletionFilter\",\"RecordingDeletionRequest\",\"RecordingDeletionJobAccepted\",\"RecordingDeletionProgress\",\"RecordingExportRequest\",\"RecordingExportAccepted\",\"RecordingExportStatus\",\"AuthSessionsResponse\",\"TrustedDevicesResponse\",\"CredentialRevoked\",\"TotpLoginRequest\",\"TotpLoginResponse\",\"TotpCodeRequest\",\"TotpSetupResponse\",\"TotpStatusResponse\",\"TotpChangedResponse\",\"PasswordSelfChange\",\"PasswordAdminReset\",\"PasswordChangeRequest\",\"PasswordChangedResponse\",\"LoginMfaChallenge\",\"LoginSuccess\",\"FleetQueryRequest\",\"FleetPreviewRequest\",\"FleetQueryResponse\",\"FleetPreviewResponse\",\"FleetSavedView\",\"FleetSavedViewsResponse\",\"FleetLocation\",\"FleetLocationsResponse\",\"FleetTag\",\"FleetTagsResponse\",\"CameraTagAssignmentsResponse\",\"CameraCollection\",\"CameraCollectionsResponse\",\"CameraCollectionMembersResponse\",\"CameraCollectionPreview\",\"SystemSettings\",\"SystemSettingsUpdate\",\"SystemSettingsSaved\",\"SystemInformation\",\"SystemLogs\",\"HostHealth\",\"HostHealthIncidents\",\"Go2rtcConfigurationValidation\",\"Go2rtcConfigurationInput\",\"Go2rtcEffectiveConfiguration\",\"Go2rtcOverrideDiagnostics\",\"SetupStatus\",\"SetupUpdate\",\"SetupUpdated\",\"SystemStatus\",\"SystemActionAccepted\",\"SystemBackupCreated\",\"WorkspacesResponse\",\"WorkspacesUpdate\",\"StreamHealth\",\"ProcessLiveness\",\"StreamHealthHistory\",\"HlsHealth\",\"PlayerTelemetry\",\"EventDestination\",\"EventDestinations\",\"EventDestinationCreate\",\"EventDestinationUpdate\",\"EventPredicate\",\"EventCameraScope\",\"EventSchedule\",\"EventRoute\",\"EventRoutes\",\"EventRouteCreate\",\"EventRouteUpdate\",\"EventRoutePreviewRequest\",\"EventCatalog\",\"EventRoutePreview\",\"EventMutationAck\",\"MotionTrigger\",\"MotionTriggered\",\"FleetMutationAck\",\"FleetLocationCreate\",\"FleetLocationUpdate\",\"CameraLocationAssignmentRequest\",\"CameraLocationAssignment\",\"FleetTagCreate\",\"FleetTagUpdate\",\"FleetTagMergeRequest\",\"FleetTagMergeResult\",\"CameraTagAssignmentsRequest\",\"CameraCollectionCreate\",\"CameraCollectionUpdate\",\"CameraCollectionMembersRequest\",\"FleetViewCreate\",\"FleetViewUpdate\",\"FleetRevisionRequest\",\"LiveCameraSlot\",\"LiveLayoutCreate\",\"LiveLayoutUpdate\",\"LiveLayout\",\"LiveLayoutsResponse\",\"CameraFavorite\",\"CameraFavoritesResponse\",\"CameraFavoriteRemoval\",\"FloorSketchRect\",\"FloorSketchWall\",\"FloorSketchArea\",\"FloorSketchLabel\",\"FloorSketch\",\"FloorPlanCamera\",\"FloorPlanCameraRequest\",\"FloorPlanCreate\",\"FloorPlanUpdate\",\"FloorPlan\",\"FloorPlansResponse\",\"FloorPlanDeletion\",\"CameraUuid\",\"AdminUser\",\"AdminUsers\",\"AdminUserCreate\",\"AdminUserUpdate\",\"AdminUserDeleted\",\"LegacyApiKeyCreated\",\"PasswordLockRequest\",\"LoginLockoutCleared\",\"AdminActionCatalogEntry\",\"AdminActionCatalog\",\"AdminAuthorizationRole\",\"AdminRoles\",\"AdminRoleWrite\",\"AdminRoleWritten\",\"AdminVersionRequest\",\"AdminRoleDeleted\",\"AdminPolicyGrant\",\"AdminUserPolicy\",\"AdminPolicyWrite\",\"AdminSimulationRequest\",\"AdminSimulation\",\"AuditEvent\",\"AuditEventsPage\",\"AuditDecisionMode\",\"AuditSettings\",\"AuditSettingsWrite\",\"StorageTargetHealth\",\"StorageTarget\",\"StorageTargetsResponse\",\"StorageTargetCreate\",\"StorageTargetUpdate\",\"StoragePoolMember\",\"StoragePoolMemberRequest\",\"StoragePool\",\"StoragePoolsResponse\",\"StoragePoolCreate\",\"StoragePoolUpdate\",\"StoragePolicy\",\"StoragePoliciesResponse\",\"StoragePolicyCreate\",\"StoragePolicyUpdate\",\"StoragePolicyPreviewRequest\",\"StoragePolicyWinner\",\"StoragePolicyPreviewSample\",\"StoragePolicyConflict\",\"StoragePolicyPreview\",\"StoragePolicyApplyRequest\",\"StoragePolicyApplyResponse\",\"StorageMigration\",\"StorageMigrationsResponse\",\"StorageMigrationCreate\",\"StorageArchiveDeletion\",\"StorageArchiveResponse\",\"StorageArchiveRetryRequest\",\"StorageArchiveRetryResponse\",\"StorageHealthResponse\",\"StorageCleanupRequest\",\"StorageCleanupResponse\",\"StorageComplianceTarget\",\"StorageCompliancePolicy\",\"StorageComplianceViolation\",\"StorageComplianceResponse\",\"EvidenceDetection\",\"EvidenceDetections\",\"EvidenceModel\",\"EvidenceModels\",\"EvidenceFileCheck\",\"EvidenceFileDeleted\",\"EvidenceProtectedCount\",\"EvidenceSyncComplete\",\"EvidenceSegment\",\"EvidenceTimelineTrack\",\"EvidenceTimeline\",\"EvidenceSegmentAt\",\"EvidenceRangeRequest\",\"EvidenceSegmentAtRequest\",\"EvidenceThumbnailRequest\",\"EvidencePreviewRecording\",\"EvidencePreview\",\"EvidenceThumbnailSample\",\"EvidenceThumbnails\",\"EvidenceSearchResult\",\"EvidenceSearch\",\"EvidenceSearchRequest\",\"EvidenceBookmarkFilters\",\"EvidenceBookmarkResult\",\"EvidenceBookmark\",\"EvidenceBookmarks\",\"EvidenceBookmarkCreate\",\"EvidenceBookmarkUpdate\",\"EvidenceBookmarkDelete\",\"EvidenceLprRead\",\"EvidenceLprReads\",\"EvidenceLprSearchRequest\",\"EvidenceLprExportRequest\",\"EvidenceLprDeleted\",\"EvidenceBookmarkDeleted\",\"DeviceFloatRange\",\"DeviceCommandResult\",\"DeviceSuccess\",\"DeviceEmptyRequest\",\"PtzAxesRequest\",\"PtzPresetRequest\",\"PtzGotoPresetRequest\",\"PtzPresetResult\",\"PtzPresets\",\"PtzCapabilities\",\"ImagingModeLevel\",\"ImagingModeLevelUpdate\",\"ImagingSettings\",\"ImagingSettingsResponse\",\"ImagingModeOptions\",\"ImagingOptions\",\"ImagingOptionsResponse\",\"ImagingSettingsRequest\",\"DaynightRequest\",\"DaynightResponse\",\"DaynightUpdated\",\"OnvifDiscoveryStatus\",\"OnvifDevice\",\"OnvifDevicesResponse\",\"OnvifDiscoveryRequest\",\"OnvifProfile\",\"OnvifProfilesResponse\",\"OnvifTestRequest\",\"OnvifAddRequest\",\"OnvifAddResult\",\"OnvifClaimRequest\",\"StreamSingleResponse\",\"StreamCreateRequest\",\"StreamCreated\",\"StreamProbeRequest\",\"StreamProbeResponse\",\"StreamDeleteAccepted\",\"StreamRefreshAccepted\",\"StreamRecordingState\",\"StreamRecordingRequest\",\"StreamRecordingChanged\",\"DetectionZoneWrite\",\"DetectionZone\",\"StreamZones\",\"StreamZonesRequest\",\"StreamZonesSaved\",\"StreamZonesDeleted\",\"StreamRetentionRequest\",\"StreamRetention\",\"StreamDetectionEngineWrite\",\"StreamDetectionEngine\",\"StreamDetectionEnginesRequest\",\"StreamDetectionEngines\",\"IceServersResponse\"]}\n\0", + *b"{\"components\":{\"schemas\":{\"AdminActionCatalog\":{\"additionalProperties\":false,\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AdminActionCatalogEntry\"},\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"actions\",\"count\"],\"type\":\"object\"},\"AdminActionCatalogEntry\":{\"additionalProperties\":false,\"properties\":{\"camera_scoped\":{\"type\":\"boolean\"},\"category\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"destructive\":{\"type\":\"boolean\"},\"enforced\":{\"type\":\"boolean\"},\"key\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"mask_bit\":{\"type\":\"integer\"}},\"required\":[\"key\",\"category\",\"description\",\"camera_scoped\",\"destructive\",\"enforced\",\"mask_bit\"],\"type\":\"object\"},\"AdminAuthorizationRole\":{\"additionalProperties\":false,\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"array\"},\"builtin\":{\"type\":\"boolean\"},\"created_at\":{\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"builtin\",\"actions\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"AdminPolicyGrant\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"role\":{\"type\":\"string\"},\"role_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenStoredScope\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"role_uuid\",\"role\",\"enabled\",\"created_at\",\"updated_at\",\"scope\"],\"type\":\"object\"},\"AdminPolicyWrite\":{\"additionalProperties\":true,\"properties\":{\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"grants\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"role_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenScope\"}},\"required\":[\"role_uuid\",\"scope\"],\"type\":\"object\"},\"maxItems\":256,\"type\":\"array\"},\"mode\":{\"const\":\"policy\"}},\"required\":[\"mode\",\"expected_policy_version\",\"grants\"],\"type\":\"object\"},\"AdminRoleDeleted\":{\"additionalProperties\":false,\"properties\":{\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"policy_version\"],\"type\":\"object\"},\"AdminRoleWrite\":{\"additionalProperties\":true,\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"description\":{\"maxLength\":255,\"type\":\"string\"},\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"pattern\":\"\\\\S\",\"type\":\"string\"}},\"required\":[\"expected_policy_version\",\"name\",\"actions\"],\"type\":\"object\"},\"AdminRoleWritten\":{\"additionalProperties\":false,\"properties\":{\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"role\":{\"$ref\":\"#/components/schemas/AdminAuthorizationRole\"}},\"required\":[\"policy_version\",\"role\"],\"type\":\"object\"},\"AdminRoles\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"roles\":{\"items\":{\"$ref\":\"#/components/schemas/AdminAuthorizationRole\"},\"type\":\"array\"}},\"required\":[\"policy_version\",\"count\",\"roles\"],\"type\":\"object\"},\"AdminSimulation\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"allowed\":{\"type\":\"boolean\"},\"explanation\":{\"type\":\"string\"},\"grant_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"principal\":{\"additionalProperties\":false,\"properties\":{\"authorization_mode\":{\"type\":\"string\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"username\":{\"type\":\"string\"}},\"required\":[\"id\",\"username\",\"authorization_mode\"],\"type\":\"object\"},\"resource\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"role\":{\"type\":\"string\"},\"role_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"source\":{\"type\":\"string\"}},\"required\":[\"allowed\",\"action\",\"source\",\"policy_version\",\"explanation\",\"grant_uuid\",\"role_uuid\",\"role\",\"principal\",\"resource\"],\"type\":\"object\"},\"AdminSimulationRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"live.view\",\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"recordings.export\",\"snapshot.create\",\"ptz.control\",\"evidence.protect\",\"recording.delete\",\"camera.configure\",\"fleet.execute_job\",\"lpr.read\",\"lpr.search\",\"lpr.export\",\"lpr.delete\",\"observations.annotate\"],\"type\":\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"user_id\":{\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"action\",\"camera_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin\"],\"type\":\"string\"},\"camera_uuid\":false,\"user_id\":{\"maximum\":2147483647,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"action\"],\"type\":\"object\"}]},\"AdminUser\":{\"additionalProperties\":false,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"authorization_mode\":{\"type\":\"string\"},\"created_at\":{\"type\":\"integer\"},\"email\":{\"type\":\"string\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"is_active\":{\"type\":\"boolean\"},\"last_login\":{\"type\":\"integer\"},\"must_change_password\":{\"type\":\"boolean\"},\"password_change_locked\":{\"type\":\"boolean\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"role_name\":{\"enum\":[\"admin\",\"user\",\"viewer\",\"api\",\"unknown\"],\"type\":\"string\"},\"totp_enabled\":{\"type\":\"boolean\"},\"updated_at\":{\"type\":\"integer\"},\"username\":{\"type\":\"string\"}},\"required\":[\"id\",\"username\",\"email\",\"role\",\"role_name\",\"created_at\",\"updated_at\",\"last_login\",\"is_active\",\"password_change_locked\",\"must_change_password\",\"totp_enabled\",\"authorization_mode\",\"allowed_login_cidrs\"],\"type\":\"object\"},\"AdminUserCreate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"email\":{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password\":{\"minLength\":8,\"type\":\"string\"},\"role\":{\"enum\":[0,1,2,3],\"type\":\"integer\"},\"username\":{\"maxLength\":32,\"minLength\":3,\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"AdminUserDeleted\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"AdminUserPolicy\":{\"additionalProperties\":false,\"properties\":{\"grant_count\":{\"minimum\":0,\"type\":\"integer\"},\"grants\":{\"items\":{\"$ref\":\"#/components/schemas/AdminPolicyGrant\"},\"type\":\"array\"},\"mode\":{\"enum\":[\"legacy\",\"policy\"],\"type\":\"string\"},\"policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"user_id\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"user_id\",\"mode\",\"policy_version\",\"grant_count\",\"grants\"],\"type\":\"object\"},\"AdminUserUpdate\":{\"additionalProperties\":true,\"properties\":{\"allowed_login_cidrs\":{\"type\":[\"string\",\"null\"]},\"allowed_tags\":false,\"email\":{\"type\":\"string\"},\"is_active\":{\"type\":\"boolean\"},\"password\":{\"anyOf\":[{\"const\":\"\"},{\"minLength\":8,\"type\":\"string\"}]},\"role\":false,\"username\":{\"maxLength\":32,\"minLength\":3,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"AdminUsers\":{\"additionalProperties\":false,\"properties\":{\"users\":{\"items\":{\"$ref\":\"#/components/schemas/AdminUser\"},\"type\":\"array\"}},\"required\":[\"users\"],\"type\":\"object\"},\"AdminVersionRequest\":{\"additionalProperties\":true,\"properties\":{\"expected_policy_version\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"expected_policy_version\"],\"type\":\"object\"},\"ApiError\":{\"additionalProperties\":true,\"description\":\"Most errors use a string error. YAML validation may use valid=false and error.message. Extra diagnostic fields may be added; clients must not log privileged request or response data.\",\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"string\"},{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type\":\"object\"}]},\"valid\":{\"type\":\"boolean\"}},\"required\":[\"error\"],\"type\":\"object\"},\"ApiTokenCreateRequest\":{\"additionalProperties\":true,\"description\":\"Requested action and camera scope must be contained in BOTH token owner and requester authority. At most256 active tokens per owner. Additional request keys do not rewrite user policy.\",\"properties\":{\"actions\":{\"description\":\"Duplicates collapse into an action mask.\",\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"minItems\":1,\"type\":\"array\"},\"description\":{\"description\":\"Trimmed by server; must contain non-whitespace text, no control characters, and fewer than128 UTF-8 bytes.\",\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"expires_at\":{\"description\":\"Unix seconds strictly after server time and at most366 days ahead.\",\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenScope\"}},\"required\":[\"description\",\"expires_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenCreated\":{\"additionalProperties\":true,\"description\":\"Only successful token creation returns a secret. No secret value is included in examples.\",\"properties\":{\"secret\":{\"description\":\"One-time bearer secret; store securely after creation. Ordinary token-list/identity responses never include it.\",\"pattern\":\"^lnvr_[0-9a-f]{64}$\",\"readOnly\":true,\"type\":\"string\"},\"token\":{\"$ref\":\"#/components/schemas/ApiTokenMetadata\"}},\"required\":[\"secret\",\"token\"],\"type\":\"object\"},\"ApiTokenList\":{\"additionalProperties\":true,\"description\":\"Lists token metadata, including revoked/expired records. Secret is never returned.\",\"properties\":{\"api_key\":false,\"count\":{\"minimum\":0,\"type\":\"integer\"},\"password\":false,\"password_hash\":false,\"secret\":false,\"token_hash\":false,\"tokens\":{\"items\":{\"$ref\":\"#/components/schemas/ApiTokenMetadata\"},\"type\":\"array\"},\"totp_secret\":false,\"totp_token\":false,\"user_id\":{\"type\":\"integer\"}},\"required\":[\"user_id\",\"count\",\"tokens\"],\"type\":\"object\"},\"ApiTokenMetadata\":{\"additionalProperties\":true,\"description\":\"Non-secret token record; prefix is an identifier, never a reusable credential. Secret/hash fields are explicitly forbidden.\",\"properties\":{\"actions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"array\"},\"api_key\":false,\"created_at\":{\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"expires_at\":{\"type\":\"integer\"},\"last_used_at\":{\"type\":[\"integer\",\"null\"]},\"password\":false,\"password_hash\":false,\"prefix\":{\"type\":\"string\"},\"revoked_at\":{\"type\":[\"integer\",\"null\"]},\"scope\":{\"$ref\":\"#/components/schemas/ApiTokenStoredScope\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"description\",\"prefix\",\"expires_at\",\"revoked_at\",\"last_used_at\",\"created_at\",\"actions\",\"scope\"],\"type\":\"object\"},\"ApiTokenScope\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"collection\"}},\"required\":[\"type\",\"collection_uuid\"],\"type\":\"object\"}],\"description\":\"All, selector or collection camera scope. Inapplicable selector/collection fields may be absent or null. A narrow owner cannot delegate all scope simply because current matches happen to be identical.\"},\"ApiTokenStoredScope\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"all\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"type\":\"null\"},\"selector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"type\":{\"const\":\"selector\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"selector\":{\"type\":\"null\"},\"type\":{\"const\":\"collection\"}},\"required\":[\"type\",\"selector\",\"collection_uuid\"],\"type\":\"object\"}]},\"AuditDecisionMode\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"category\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"mode\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"}},\"required\":[\"action\",\"category\",\"description\",\"mode\"],\"type\":\"object\"},\"AuditEvent\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"type\":\"string\"},\"api_token_uuid\":{\"type\":[\"string\",\"null\"]},\"auth_method\":{\"type\":\"string\"},\"details\":{\"additionalProperties\":true,\"type\":[\"object\",\"null\"]},\"occurred_at\":{\"type\":\"integer\"},\"outcome\":{\"type\":\"string\"},\"principal_user_id\":{\"type\":[\"integer\",\"null\"]},\"principal_username\":{\"type\":\"string\"},\"remote_address\":{\"type\":\"string\"},\"request_id\":{\"type\":\"string\"},\"target_type\":{\"type\":[\"string\",\"null\"]},\"target_uuid\":{\"type\":[\"string\",\"null\"]},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"occurred_at\",\"request_id\",\"principal_user_id\",\"principal_username\",\"auth_method\",\"api_token_uuid\",\"action\",\"target_type\",\"target_uuid\",\"outcome\",\"remote_address\",\"details\"],\"type\":\"object\"},\"AuditEventsPage\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"events\":{\"items\":{\"$ref\":\"#/components/schemas/AuditEvent\"},\"type\":\"array\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"minimum\":1,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"page\",\"page_size\",\"count\",\"total\",\"events\"],\"type\":\"object\"},\"AuditSettings\":{\"additionalProperties\":false,\"properties\":{\"allowed_decision_modes\":{\"items\":{\"$ref\":\"#/components/schemas/AuditDecisionMode\"},\"type\":\"array\"},\"pruned_events\":{\"minimum\":0,\"type\":\"integer\"},\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"},\"summary_window_seconds\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\",\"summary_window_seconds\",\"allowed_decision_modes\"],\"type\":\"object\"},\"AuditSettingsWrite\":{\"additionalProperties\":true,\"anyOf\":[{\"properties\":{\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\"]},{\"properties\":{\"allowed_decision_modes\":{\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"object\"}},\"required\":[\"allowed_decision_modes\"]}],\"properties\":{\"allowed_decision_modes\":{\"additionalProperties\":{\"enum\":[\"off\",\"summarize\",\"record\"],\"type\":\"string\"},\"propertyNames\":{\"$ref\":\"#/components/schemas/AuthorizationAction\"},\"type\":\"object\"},\"retention_days\":{\"maximum\":3650,\"minimum\":1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"AuthDemoIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"authenticated\":{\"const\":false},\"demo_mode\":{\"const\":true},\"must_change_password\":{\"const\":false},\"password\":false,\"password_hash\":false,\"role\":{\"const\":\"viewer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"username\":{\"const\":\"demo\"}},\"required\":[\"authenticated\",\"demo_mode\",\"username\",\"role\",\"must_change_password\"],\"type\":\"object\"},\"AuthDisabledIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":false},\"auth_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"must_change_password\":{\"const\":false},\"password\":false,\"password_hash\":false,\"role\":{\"const\":\"admin\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"},\"username\":{\"const\":\"admin\"}},\"required\":[\"authenticated\",\"username\",\"role\",\"must_change_password\",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"AuthSession\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"idle_expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_activity_at\":{\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[\"id\",\"created_at\",\"expires_at\",\"ip_address\",\"user_agent\",\"current\",\"last_activity_at\",\"idle_expires_at\"],\"type\":\"object\"},\"AuthSessionsResponse\":{\"additionalProperties\":false,\"properties\":{\"sessions\":{\"items\":{\"$ref\":\"#/components/schemas/AuthSession\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"sessions\"],\"type\":\"object\"},\"AuthVerifiedIdentity\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_enabled\":{\"const\":true},\"auth_timeout_hours\":{\"type\":\"number\"},\"authenticated\":{\"const\":true},\"email\":{\"type\":\"string\"},\"id\":{\"type\":\"integer\"},\"is_active\":{\"type\":\"boolean\"},\"must_change_password\":{\"type\":\"boolean\"},\"password\":false,\"password_change_locked\":{\"type\":\"boolean\"},\"password_hash\":false,\"role\":{\"type\":\"string\"},\"role_id\":{\"type\":\"integer\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"},\"username\":{\"type\":\"string\"}},\"required\":[\"authenticated\",\"id\",\"username\",\"email\",\"role\",\"role_id\",\"is_active\",\"password_change_locked\",\"must_change_password\",\"auth_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"AuthVerifyResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/AuthVerifiedIdentity\"},{\"$ref\":\"#/components/schemas/AuthDisabledIdentity\"},{\"$ref\":\"#/components/schemas/AuthDemoIdentity\"}],\"description\":\"Real credential identity, auth-disabled synthetic admin, or unauthenticated demo viewer. Synthetic modes do not authorize personalized discovery.\"},\"AuthorizationAction\":{\"enum\":[\"live.view\",\"audio.listen\",\"audio.talk\",\"recordings.replay\",\"recordings.export\",\"snapshot.create\",\"ptz.control\",\"evidence.protect\",\"recording.delete\",\"camera.configure\",\"fleet.execute_job\",\"storage.configure\",\"events.configure\",\"users.manage\",\"system.admin\",\"lpr.read\",\"lpr.search\",\"lpr.export\",\"lpr.delete\",\"observations.annotate\"],\"type\":\"string\"},\"CameraCollection\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"effective_count\":{\"minimum\":0,\"type\":\"integer\"},\"member_count\":{\"minimum\":0,\"type\":\"integer\"},\"name\":{\"type\":\"string\"},\"owner_user_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"selector\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FleetSelector\"},{\"type\":\"null\"}]},\"selector_redacted\":{\"type\":\"boolean\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"type\",\"shared\",\"owner_user_id\",\"member_count\",\"effective_count\",\"created_at\",\"updated_at\",\"selector\",\"selector_redacted\"],\"type\":\"object\"},\"CameraCollectionCreate\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"type\":\"string\"}},\"required\":[\"name\",\"type\"],\"type\":\"object\"},\"CameraCollectionMembersRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"maxItems\":4096,\"type\":\"array\"}},\"required\":[\"camera_uuids\"],\"type\":\"object\"},\"CameraCollectionMembersResponse\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"collection_uuid\",\"camera_uuids\",\"count\"],\"type\":\"object\"},\"CameraCollectionPreview\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"matched_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\",\"location_path\"],\"type\":\"object\"},\"maxItems\":50,\"type\":\"array\"}},\"required\":[\"collection_uuid\",\"matched_count\",\"sample\"],\"type\":\"object\"},\"CameraCollectionUpdate\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"maxLength\":511,\"type\":\"string\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"shared\":{\"type\":\"boolean\"},\"type\":{\"enum\":[\"static\",\"smart\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"CameraCollectionsResponse\":{\"additionalProperties\":false,\"properties\":{\"collections\":{\"items\":{\"$ref\":\"#/components/schemas/CameraCollection\"},\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"collections\",\"count\"],\"type\":\"object\"},\"CameraFavorite\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"created_at\"],\"type\":\"object\"},\"CameraFavoriteRemoval\":{\"additionalProperties\":false,\"properties\":{\"removed\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"removed\"],\"type\":\"object\"},\"CameraFavoritesResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"favorites\":{\"items\":{\"$ref\":\"#/components/schemas/CameraFavorite\"},\"type\":\"array\"}},\"required\":[\"favorites\",\"can_modify\"],\"type\":\"object\"},\"CameraLocationAssignment\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"location_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\",\"location_uuid\"],\"type\":\"object\"},\"CameraLocationAssignmentRequest\":{\"additionalProperties\":false,\"properties\":{\"location_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"location_uuid\"],\"type\":\"object\"},\"CameraTagAssignmentsRequest\":{\"additionalProperties\":false,\"properties\":{\"tag_uuids\":{\"items\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"tag_uuids\"],\"type\":\"object\"},\"CameraTagAssignmentsResponse\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"array\"}},\"required\":[\"camera_uuid\",\"tags\",\"count\"],\"type\":\"object\"},\"CameraUuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"ClientConfig\":{\"additionalProperties\":false,\"description\":\"Safe browser playback configuration only. Deliberately closed shape: no storage paths, integrations, private settings or credentials. go2rtc_available may stay true while its lifecycle guard is busy.\",\"properties\":{\"api_key\":false,\"audio_disabled\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"default_playback_transport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hls_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_available\":{\"type\":\"boolean\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"mse_disabled\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"secret\":false,\"thumbnails_per_recording\":{\"type\":\"number\"},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"web_auth_enabled\":{\"type\":\"boolean\"},\"webrtc_connection_timeout_ms\":{\"type\":\"number\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"number\"}},\"required\":[\"go2rtc_enabled\",\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_disabled\",\"go2rtc_available\",\"webrtc_disabled\",\"hls_disabled\",\"mse_disabled\",\"go2rtc_force_native_hls\",\"generate_thumbnails\",\"go2rtc_api_port\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\",\"thumbnails_per_recording\",\"go2rtc_base_path\",\"default_playback_transport\"],\"type\":\"object\"},\"CredentialRevoked\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"DaynightRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"mode\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"mode\"]},{\"properties\":{\"daynight\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"daynight\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"properties\":{\"daynight\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"force_persistence\":{\"type\":\"boolean\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"mode\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"DaynightResponse\":{\"additionalProperties\":false,\"properties\":{\"ir_cut_filter\":{\"type\":\"string\"},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\",\"unknown\"]},\"options_available\":{\"type\":\"boolean\"},\"source\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\":true},\"supported_modes\":{\"items\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"type\":\"array\"},\"video_source_token\":{\"type\":\"string\"}},\"required\":[\"success\",\"source\",\"video_source_token\",\"mode\",\"supported_modes\",\"options_available\"],\"type\":\"object\"},\"DaynightUpdated\":{\"additionalProperties\":false,\"properties\":{\"ir_cut_filter\":{\"enum\":[\"On\",\"Off\",\"Auto\"]},\"mode\":{\"enum\":[\"day\",\"night\",\"auto\"]},\"source\":{\"const\":\"onvif-imaging\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"source\",\"mode\",\"ir_cut_filter\"],\"type\":\"object\"},\"DetectionZone\":{\"additionalProperties\":true,\"properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polygon\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"array\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"id\",\"name\",\"enabled\",\"color\",\"polygon\",\"filter_classes\",\"min_confidence\",\"stream_name\"],\"type\":\"object\"},\"DetectionZoneWrite\":{\"additionalProperties\":true,\"properties\":{\"color\":{\"pattern\":\"^#[0-9a-fA-F]{6}$\",\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"filter_classes\":{\"maxLength\":255,\"type\":\"string\"},\"id\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"name\":{\"maxLength\":63,\"type\":\"string\"},\"polygon\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\"],\"type\":\"object\"},\"maxItems\":32,\"minItems\":3,\"type\":\"array\"}},\"required\":[\"id\",\"name\",\"polygon\"],\"type\":\"object\"},\"DeviceCommandResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"DeviceEmptyRequest\":{\"additionalProperties\":false,\"properties\":{},\"required\":[],\"type\":\"object\"},\"DeviceFloatRange\":{\"additionalProperties\":false,\"properties\":{\"max\":{\"type\":\"number\"},\"min\":{\"type\":\"number\"}},\"required\":[\"min\",\"max\"],\"type\":\"object\"},\"DeviceSuccess\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true}},\"required\":[\"success\"],\"type\":\"object\"},\"EventCameraScope\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"type\":{\"const\":\"all\",\"type\":\"string\"}},\"required\":[\"type\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"type\":{\"const\":\"selector\",\"type\":\"string\"}},\"required\":[\"type\",\"selector\"],\"type\":\"object\"}]},\"EventCatalog\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"default_expiry_seconds\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"dynamic_severity\":{\"type\":\"boolean\"},\"expected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"family\":{\"type\":\"string\"},\"maximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"media_policy\":{\"type\":\"string\"},\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"sensitivity\":{\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"storage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"severity\",\"dynamic_severity\",\"minimum_severity\",\"maximum_severity\",\"subject_kind\",\"family\",\"description\",\"sensitivity\",\"media_policy\",\"default_expiry_seconds\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"schema_version\",\"count\",\"event_types\"],\"type\":\"object\"},\"EventDestination\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password_configured\":{\"type\":\"boolean\"},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[\"username\",\"password_configured\"],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"host\",\"port\",\"client_id\",\"topic_template\",\"status_topic_template\",\"keepalive_seconds\",\"qos\"],\"type\":\"object\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"managed\":{\"const\":true,\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[\"mode\",\"ca_file\",\"cert_file\",\"key_file\"],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"required\":[\"uuid\",\"key\",\"name\",\"description\",\"enabled\",\"type\",\"broker\",\"authentication\",\"tls\",\"managed\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"EventDestinationCreate\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password\":{\"maxLength\":255,\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"host\"],\"type\":\"object\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[\"name\",\"broker\"],\"type\":\"object\"},\"EventDestinationUpdate\":{\"additionalProperties\":false,\"properties\":{\"authentication\":{\"additionalProperties\":false,\"properties\":{\"password\":{\"maxLength\":255,\"type\":[\"string\",\"null\"]},\"username\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"broker\":{\"additionalProperties\":false,\"properties\":{\"client_id\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"host\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"keepalive_seconds\":{\"maximum\":3600,\"minimum\":5,\"type\":\"integer\"},\"port\":{\"maximum\":65535,\"minimum\":1,\"type\":\"integer\"},\"qos\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"},\"status_topic_template\":{\"maxLength\":511,\"type\":\"string\"},\"topic_template\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"tls\":{\"additionalProperties\":false,\"properties\":{\"ca_file\":{\"maxLength\":511,\"type\":\"string\"},\"cert_file\":{\"maxLength\":511,\"type\":\"string\"},\"key_file\":{\"maxLength\":511,\"type\":\"string\"},\"mode\":{\"enum\":[\"disabled\",\"system\",\"custom_ca\",\"mutual\"],\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"type\":{\"const\":\"mqtt\",\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EventDestinations\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"maximum\":64,\"minimum\":0,\"type\":\"integer\"},\"default_destination\":{\"additionalProperties\":false,\"properties\":{\"configuration_source\":{\"const\":\"settings\",\"type\":\"string\"},\"key\":{\"const\":\"mqtt:default\",\"type\":\"string\"},\"managed\":{\"const\":false,\"type\":\"boolean\"},\"name\":{\"type\":\"string\"}},\"required\":[\"key\",\"name\",\"configuration_source\",\"managed\"],\"type\":\"object\"},\"destinations\":{\"items\":{\"$ref\":\"#/components/schemas/EventDestination\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"default_destination\",\"count\",\"destinations\"],\"type\":\"object\"},\"EventMutationAck\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"EventPredicate\":{\"additionalProperties\":false,\"properties\":{\"detection\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"labels_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"zone_ids_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"health\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"condition_codes_any\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"severities_any\":{\"items\":{\"enum\":[\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"maxItems\":3,\"minItems\":1,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\"],\"type\":\"object\"},\"EventRoute\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"debounce_seconds\",\"cooldown_seconds\",\"grouping_window_seconds\",\"max_events_per_minute\"],\"type\":\"object\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"format\":\"uuid\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"description\",\"enabled\",\"destination\",\"event_types\",\"camera_scope\",\"predicate\",\"schedule\",\"suppression\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"EventRouteCreate\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"name\",\"event_types\"],\"type\":\"object\"},\"EventRoutePreview\":{\"additionalProperties\":false,\"properties\":{\"camera_sample\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\",\"location_path\"],\"type\":\"object\"},\"maxItems\":20,\"type\":\"array\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"dynamic_severity\":{\"type\":\"boolean\"},\"expected_rate\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"maximum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"minimum_severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"severity\":{\"enum\":[\"debug\",\"info\",\"notice\",\"warning\",\"error\",\"critical\"],\"type\":\"string\"},\"subject_kind\":{\"enum\":[\"camera\",\"storage\",\"system\"],\"type\":\"string\"},\"type\":{\"type\":\"string\"}},\"required\":[\"type\",\"expected_rate\",\"severity\",\"dynamic_severity\",\"minimum_severity\",\"maximum_severity\",\"subject_kind\"],\"type\":\"object\"},\"type\":\"array\"},\"matched_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_limit\":{\"const\":20,\"type\":\"integer\"},\"would_publish\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"matched_camera_count\",\"sample_limit\",\"camera_sample\",\"event_types\",\"would_publish\"],\"type\":\"object\"},\"EventRoutePreviewRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"name\",\"event_types\"],\"type\":\"object\"},\"EventRouteUpdate\":{\"additionalProperties\":false,\"properties\":{\"camera_scope\":{\"$ref\":\"#/components/schemas/EventCameraScope\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"destination\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":32,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"predicate\":{\"$ref\":\"#/components/schemas/EventPredicate\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"schedule\":{\"$ref\":\"#/components/schemas/EventSchedule\"},\"suppression\":{\"additionalProperties\":false,\"properties\":{\"cooldown_seconds\":{\"maximum\":604800,\"minimum\":0,\"type\":\"integer\"},\"debounce_seconds\":{\"maximum\":86400,\"minimum\":0,\"type\":\"integer\"},\"grouping_window_seconds\":{\"maximum\":3600,\"minimum\":0,\"type\":\"integer\"},\"max_events_per_minute\":{\"maximum\":60000,\"minimum\":0,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EventRoutes\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"maximum\":512,\"minimum\":0,\"type\":\"integer\"},\"routes\":{\"items\":{\"$ref\":\"#/components/schemas/EventRoute\"},\"maxItems\":512,\"type\":\"array\"}},\"required\":[\"routes\",\"count\"],\"type\":\"object\"},\"EventSchedule\":{\"additionalProperties\":false,\"properties\":{\"timezone\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"version\":{\"const\":1,\"type\":\"integer\"},\"windows\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"days\":{\"items\":{\"maximum\":6,\"minimum\":0,\"type\":\"integer\"},\"maxItems\":7,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"end\":{\"pattern\":\"^(?:[01][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"},\"start\":{\"pattern\":\"^(?:[01][0-9]|2[0-3]):[0-5][0-9]$\",\"type\":\"string\"}},\"required\":[\"days\",\"start\",\"end\"],\"type\":\"object\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"version\",\"timezone\",\"windows\"],\"type\":\"object\"},\"EvidenceBookmark\":{\"additionalProperties\":false,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"created_at\":{\"type\":\"integer\"},\"cursor_time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkFilters\"},\"holds_recordings\":{\"const\":false},\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"representative_result\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkResult\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"updated_at\":{\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"title\",\"note\",\"start_time\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\",\"camera_uuids\",\"filters\",\"representative_result\",\"uuid\",\"revision\",\"created_at\",\"updated_at\",\"holds_recordings\"],\"type\":\"object\"},\"EvidenceBookmarkCreate\":{\"additionalProperties\":true,\"description\":\"Camera list, primary camera, region and saved result must agree. Cursor must lie inside the interval. The bookmark is owned by the current user (also for scoped tokens), never grants access, and does not protect media from retention.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"cursor_time\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkFilters\"},\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"primary_camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"representative_result\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkResult\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"title\",\"start_time\",\"end_time\",\"cursor_time\",\"primary_camera_uuid\",\"camera_uuids\",\"filters\"],\"type\":\"object\"},\"EvidenceBookmarkDelete\":{\"additionalProperties\":true,\"properties\":{\"revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"EvidenceBookmarkDeleted\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true}},\"required\":[\"success\"],\"type\":\"object\"},\"EvidenceBookmarkFilters\":{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"maxLength\":127,\"type\":\"string\"},\"event_type\":{\"maxLength\":127,\"type\":\"string\"},\"label\":{\"maxLength\":127,\"type\":\"string\"},\"location\":{\"maxLength\":127,\"type\":\"string\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protection\":{\"maxLength\":127,\"type\":\"string\"},\"recording_tag\":{\"maxLength\":127,\"type\":\"string\"},\"region\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"source\":{\"maxLength\":127,\"type\":\"string\"},\"zone\":{\"maxLength\":127,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"EvidenceBookmarkResult\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection_id\":{\"type\":[\"string\",\"number\"]},\"end_time\":{\"type\":[\"string\",\"number\"]},\"event_type\":{\"type\":[\"string\",\"number\"]},\"label\":{\"type\":[\"string\",\"number\"]},\"recording_id\":{\"type\":[\"string\",\"number\"]},\"result_id\":{\"type\":[\"string\",\"number\"]},\"start_time\":{\"type\":[\"string\",\"number\"]}},\"required\":[],\"type\":\"object\"},{\"type\":\"null\"}]},\"EvidenceBookmarkUpdate\":{\"additionalProperties\":true,\"properties\":{\"note\":{\"maxLength\":2047,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"title\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"title\",\"revision\"],\"type\":\"object\"},\"EvidenceBookmarks\":{\"additionalProperties\":false,\"properties\":{\"bookmarks\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"},\"maxItems\":256,\"type\":\"array\"},\"count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"bookmarks\",\"count\"],\"type\":\"object\"},\"EvidenceDetection\":{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"type\":\"number\"},\"end_timestamp\":{\"type\":\"integer\"},\"height\":{\"type\":\"number\"},\"label\":{\"type\":\"string\"},\"observation\":{\"$ref\":\"#/components/schemas/EvidenceDetectionObservation\"},\"timestamp\":{\"type\":\"integer\"},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"confidence\",\"x\",\"y\",\"width\",\"height\",\"label\",\"timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"EvidenceDetectionObservation\":{\"additionalProperties\":false,\"description\":\"INT_01 P2 (L15). Present only when the detection links to an observation of the same camera: observed in the same second \xC2\xB11 with the same label (ASCII case-insensitive), nearest second first. classification is the annotation to display \xE2\x80\x94 owner_confirmed[0] before automated[0] of GET /api/observations/{id} \xE2\x80\x94 or null; the detection's own label and confidence are the detector's and never change.\",\"properties\":{\"classification\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationClassification\"},{\"type\":\"null\"}]},\"id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"owner_confirmed\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"owner_confirmed\",\"classification\"],\"type\":\"object\"},\"EvidenceDetections\":{\"additionalProperties\":false,\"properties\":{\"detections\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceDetection\"},\"type\":\"array\"},\"stream_status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"timestamp\":{\"type\":\"string\"}},\"required\":[\"detections\",\"timestamp\",\"stream_status\"],\"type\":\"object\"},\"EvidenceFileCheck\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":false}},\"required\":[\"exists\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"exists\":{\"const\":true},\"mtime\":{\"type\":\"integer\"},\"size\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"exists\",\"size\",\"mtime\"],\"type\":\"object\"}]},\"EvidenceFileDeleted\":{\"additionalProperties\":false,\"properties\":{\"existed\":{\"type\":\"boolean\"},\"status\":{\"enum\":[\"deleted\",\"deletion_pending\"],\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"existed\",\"status\"],\"type\":\"object\"},\"EvidenceLprDeleted\":{\"additionalProperties\":false,\"properties\":{\"deleted\":{\"const\":true}},\"required\":[\"deleted\"],\"type\":\"object\"},\"EvidenceLprExportRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"end_at\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"},\"match\":{\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"EvidenceLprRead\":{\"additionalProperties\":false,\"properties\":{\"attributes\":{\"additionalProperties\":false,\"properties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"bottom\":{\"type\":\"number\"},\"left\":{\"type\":\"number\"},\"right\":{\"type\":\"number\"},\"top\":{\"type\":\"number\"}},\"required\":[\"left\",\"top\",\"right\",\"bottom\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"correlation_id\":{\"type\":[\"string\",\"null\"]},\"country\":{\"type\":[\"string\",\"null\"]},\"direction\":{\"type\":[\"string\",\"null\"]},\"lane\":{\"type\":[\"string\",\"null\"]},\"object_id\":{\"type\":[\"string\",\"null\"]},\"plate_type\":{\"type\":[\"string\",\"null\"]},\"region\":{\"type\":[\"string\",\"null\"]},\"vehicle_color\":{\"type\":[\"string\",\"null\"]},\"vehicle_type\":{\"type\":[\"string\",\"null\"]},\"vendor_topic\":{\"type\":\"string\"}},\"required\":[\"country\",\"region\",\"plate_type\",\"direction\",\"lane\",\"vehicle_type\",\"vehicle_color\",\"object_id\",\"correlation_id\",\"vendor_topic\",\"bounding_box\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"confidence\":{\"anyOf\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"id\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"observed_at\":{\"minimum\":1,\"type\":\"integer\"},\"plate\":{\"type\":\"string\"},\"received_at\":{\"minimum\":1,\"type\":\"integer\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"source\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"type\":{\"const\":\"license_plate\"}},\"required\":[\"id\",\"type\",\"camera_uuid\",\"stream_name\",\"observed_at\",\"received_at\",\"source\",\"plate\",\"confidence\",\"recording_id\",\"attributes\"],\"type\":\"object\"},\"EvidenceLprReads\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"reads\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceLprRead\"},\"maxItems\":1000,\"type\":\"array\"}},\"required\":[\"reads\",\"count\"],\"type\":\"object\"},\"EvidenceLprSearchRequest\":{\"additionalProperties\":true,\"description\":\"Epoch milliseconds, end at/after start within 366 days; match requires a canonicalizable plate (partial at least 3 canonical characters). Search returns at most 100 rows.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"end_at\":{\"minimum\":1,\"type\":\"integer\"},\"limit\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"match\":{\"enum\":[\"exact\",\"partial\"],\"type\":\"string\"},\"plate\":{\"type\":\"string\"},\"start_at\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"start_at\",\"end_at\"],\"type\":\"object\"},\"EvidenceModel\":{\"additionalProperties\":false,\"properties\":{\"description\":{\"type\":\"string\"},\"id\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"size\":{\"minimum\":0,\"type\":\"integer\"},\"supported\":{\"type\":\"boolean\"},\"type\":{\"type\":\"string\"},\"unsupported_reason\":{\"type\":\"string\"}},\"required\":[\"id\",\"name\",\"path\",\"type\",\"supported\"],\"type\":\"object\"},\"EvidenceModels\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"litert_available\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"models\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceModel\"},\"type\":\"array\"},\"sod_available\":{\"type\":\"boolean\"}},\"required\":[\"models\",\"count\",\"litert_available\",\"sod_available\"],\"type\":\"object\"},\"EvidencePreview\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"permissions\":{\"additionalProperties\":false,\"properties\":{\"export\":{\"additionalProperties\":false,\"properties\":{\"all_allowed\":{\"type\":\"boolean\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"denied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"all_allowed\"],\"type\":\"object\"},\"protect\":{\"additionalProperties\":false,\"properties\":{\"actionable_count\":{\"minimum\":0,\"type\":\"integer\"},\"allowed_count\":{\"minimum\":0,\"type\":\"integer\"},\"denied_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"allowed_count\",\"denied_count\",\"actionable_count\"],\"type\":\"object\"}},\"required\":[\"protect\",\"export\"],\"type\":\"object\"},\"protected_count\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"recordings\":{\"items\":{\"$ref\":\"#/components/schemas/EvidencePreviewRecording\"},\"maxItems\":200,\"type\":\"array\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"total_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"unprotected_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_count\",\"recording_count\",\"total_bytes\",\"protected_count\",\"unprotected_count\",\"recordings\",\"permissions\"],\"type\":\"object\"},\"EvidencePreviewRecording\":{\"additionalProperties\":false,\"properties\":{\"camera_name\":{\"type\":\"string\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"can_export\":{\"type\":\"boolean\"},\"can_protect\":{\"type\":\"boolean\"},\"end_time\":{\"type\":\"integer\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"id\",\"camera_uuid\",\"camera_name\",\"stream_name\",\"start_time\",\"end_time\",\"size_bytes\",\"protected\",\"can_protect\",\"can_export\"],\"type\":\"object\"},\"EvidenceProtectedCount\":{\"additionalProperties\":false,\"properties\":{\"protected_count\":{\"minimum\":0,\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"protected_count\"],\"type\":\"object\"},\"EvidenceRangeRequest\":{\"additionalProperties\":true,\"description\":\"End must exceed start by no more than 31 days. All explicit cameras must exist and permit recordings.replay; mixed unauthorized input is rejected.\",\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceSearch\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"complete\":{\"type\":\"boolean\"},\"incomplete_reasons\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"requested_end\":{\"minimum\":1,\"type\":\"integer\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"},\"searched_end\":{\"minimum\":1,\"type\":\"integer\"},\"searched_start\":{\"minimum\":1,\"type\":\"integer\"},\"spatial_metadata\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"requested\":{\"const\":false}},\"required\":[\"requested\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"complete\":{\"type\":\"boolean\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"rectangle\":{\"additionalProperties\":false,\"properties\":{\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"requested\":{\"const\":true},\"rows_with_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"rows_without_boxes\":{\"minimum\":0,\"type\":\"integer\"},\"search_type\":{\"const\":\"metadata_search\"}},\"required\":[\"requested\",\"search_type\",\"camera_uuid\",\"match\",\"rows_with_boxes\",\"rows_without_boxes\",\"complete\",\"rectangle\"],\"type\":\"object\"}]},\"unresolved_legacy_rows\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"requested_start\",\"requested_end\",\"searched_start\",\"searched_end\",\"unresolved_legacy_rows\",\"spatial_metadata\",\"incomplete_reasons\",\"complete\"],\"type\":\"object\"},\"facets\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"capture_methods\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"event_types\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"labels\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"protection\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"recording_tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"sources\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"zones\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"value\":{\"type\":\"string\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"cameras\",\"labels\",\"zones\",\"sources\",\"event_types\",\"locations\",\"capture_methods\",\"recording_tags\",\"protection\"],\"type\":\"object\"},\"histogram\":{\"additionalProperties\":false,\"properties\":{\"bucket_seconds\":{\"minimum\":0,\"type\":\"integer\"},\"buckets\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"event_time\":{\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"event_time\",\"count\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"bucket_seconds\",\"buckets\"],\"type\":\"object\"},\"page\":{\"additionalProperties\":false,\"properties\":{\"has_more\":{\"type\":\"boolean\"},\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"next_cursor\":{\"type\":[\"string\",\"null\"]},\"returned\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"limit\",\"returned\",\"total\",\"has_more\",\"next_cursor\"],\"type\":\"object\"},\"results\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceSearchResult\"},\"type\":\"array\"},\"selector_applied\":{\"type\":\"boolean\"},\"summary_complete\":{\"type\":\"boolean\"}},\"required\":[\"camera_count\",\"selector_applied\",\"summary_complete\",\"results\",\"page\",\"facets\",\"histogram\",\"coverage\"],\"type\":\"object\"},\"EvidenceSearchRequest\":{\"oneOf\":[{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":16,\"minItems\":1,\"type\":\"array\",\"uniqueItems\":true},\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true,\"properties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_tags\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"sources\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"zones\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimum\":1,\"type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":false,\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":false,\"cursor\":{\"anyOf\":[{\"pattern\":\"^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"filters\":{\"additionalProperties\":true,\"properties\":{\"capture_methods\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"event_types\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"labels\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"locations\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"max_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"min_confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"protected\":{\"type\":\"boolean\"},\"recording_tags\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"sources\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"},\"zones\":{\"items\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[],\"type\":\"object\"},\"include_summary\":{\"type\":\"boolean\"},\"limit\":{\"maximum\":500,\"minimum\":1,\"type\":\"integer\"},\"region\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"match\":{\"enum\":[\"center\",\"intersects\",\"minimum_intersection\"],\"type\":\"string\"},\"min_intersection\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"summary_only\":{\"type\":\"boolean\"}},\"required\":[\"start_time\",\"end_time\",\"selector\"],\"type\":\"object\"}]},\"EvidenceSearchResult\":{\"additionalProperties\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"location_name\":{\"type\":[\"string\",\"null\"]},\"location_path\":{\"type\":[\"string\",\"null\"]},\"location_uuid\":{\"type\":[\"string\",\"null\"]},\"name\":{\"type\":\"string\"}},\"required\":[\"name\",\"location_uuid\",\"location_name\",\"location_path\"],\"type\":\"object\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"detection\":{\"additionalProperties\":false,\"properties\":{\"bounding_box\":{\"anyOf\":[{\"additionalProperties\":false,\"properties\":{\"height\":{\"type\":\"number\"},\"normalized\":{\"const\":true},\"width\":{\"type\":\"number\"},\"x\":{\"type\":\"number\"},\"y\":{\"type\":\"number\"}},\"required\":[\"x\",\"y\",\"width\",\"height\",\"normalized\"],\"type\":\"object\"},{\"type\":\"null\"}]},\"confidence\":{\"type\":\"number\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"source\":{\"type\":\"string\"},\"track_id\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"zone_uuid\":{\"type\":[\"string\",\"null\"]}},\"required\":[\"id\",\"label\",\"confidence\",\"source\",\"zone_uuid\",\"track_id\",\"bounding_box\"],\"type\":\"object\"},\"end_time\":{\"type\":\"integer\"},\"event_type\":{\"enum\":[\"motion\",\"detection\"],\"type\":\"string\"},\"known_gap\":{\"type\":\"boolean\"},\"media_available\":{\"type\":\"boolean\"},\"recording\":{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"type\":[\"string\",\"null\"]},\"protected\":{\"anyOf\":[{\"type\":\"boolean\"},{\"type\":\"null\"}]}},\"required\":[\"capture_method\",\"protected\"],\"type\":\"object\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"result_id\":{\"type\":\"string\"},\"start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"string\"}},\"required\":[\"status\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"status\":{\"enum\":[\"unavailable\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"}]}},\"required\":[\"result_id\",\"event_type\",\"camera_uuid\",\"start_time\",\"end_time\",\"media_available\",\"known_gap\",\"recording_id\",\"camera\",\"detection\",\"thumbnail\",\"recording\"],\"type\":\"object\"},\"EvidenceSegment\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"capture_method\":{\"type\":\"string\"},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"id\",\"start_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_available\",\"capture_method\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"coverage_only\":{\"const\":true},\"duration\":{\"type\":\"integer\"},\"end_time\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"media_available\":{\"const\":true},\"protected\":{\"type\":\"boolean\"},\"size_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"source_segment_count\":{\"minimum\":1,\"type\":\"integer\"},\"start_time\":{\"type\":\"integer\"}},\"required\":[\"id\",\"start_time\",\"end_time\",\"duration\",\"has_detection\",\"size_bytes\",\"protected\",\"media_available\",\"source_segment_count\",\"coverage_only\"],\"type\":\"object\"}]},\"EvidenceSegmentAt\":{\"additionalProperties\":false,\"properties\":{\"segment\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/EvidenceSegment\"},{\"type\":\"null\"}]}},\"required\":[\"segment\"],\"type\":\"object\"},\"EvidenceSegmentAtRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceSyncComplete\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true},\"updated\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"success\",\"message\",\"updated\"],\"type\":\"object\"},\"EvidenceThumbnailRequest\":{\"additionalProperties\":true,\"properties\":{\"camera_uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":1,\"minItems\":1,\"type\":\"array\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"sample_count\":{\"maximum\":12,\"minimum\":3,\"type\":\"integer\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"camera_uuids\"],\"type\":\"object\"},\"EvidenceThumbnailSample\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"media_status\":{\"const\":\"gap\"},\"thumbnail\":{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"unavailable\"}},\"required\":[\"status\"],\"type\":\"object\"},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"media_status\":{\"const\":\"available\"},\"offset_ms\":{\"minimum\":0,\"type\":\"integer\"},\"recording_end_time\":{\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"recording_start_time\":{\"type\":\"integer\"},\"thumbnail\":{\"oneOf\":[{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"available\"},\"url\":{\"type\":\"string\"}},\"required\":[\"status\",\"url\"],\"type\":\"object\"},{\"additionalProperties\":false,\"properties\":{\"status\":{\"enum\":[\"unavailable\",\"disabled\"],\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"}]},\"timestamp\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"timestamp\",\"media_status\",\"thumbnail\",\"recording_id\",\"recording_start_time\",\"recording_end_time\",\"offset_ms\"],\"type\":\"object\"}]},\"EvidenceThumbnails\":{\"additionalProperties\":false,\"properties\":{\"camera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"available_samples\":{\"minimum\":0,\"type\":\"integer\"},\"gap_samples\":{\"minimum\":0,\"type\":\"integer\"},\"segments_truncated\":{\"const\":false}},\"required\":[\"segments_truncated\",\"available_samples\",\"gap_samples\"],\"type\":\"object\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"sample_count\":{\"minimum\":1,\"type\":\"integer\"},\"samples\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceThumbnailSample\"},\"maxItems\":12,\"type\":\"array\"},\"start_time\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"start_time\",\"end_time\",\"sample_count\",\"camera\",\"samples\",\"coverage\"],\"type\":\"object\"},\"EvidenceTimeline\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":1,\"type\":\"integer\"},\"end_time\":{\"minimum\":1,\"type\":\"integer\"},\"max_active_decoders\":{\"const\":4},\"start_time\":{\"minimum\":1,\"type\":\"integer\"},\"tracks\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceTimelineTrack\"},\"type\":\"array\"}},\"required\":[\"start_time\",\"end_time\",\"camera_count\",\"max_active_decoders\",\"tracks\"],\"type\":\"object\"},\"EvidenceTimelineTrack\":{\"additionalProperties\":false,\"properties\":{\"aggregated\":{\"type\":\"boolean\"},\"aggregation_bucket_seconds\":{\"minimum\":1,\"type\":\"integer\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"coverage\":{\"additionalProperties\":false,\"properties\":{\"identity_resolved\":{\"const\":true},\"requested_end\":{\"minimum\":1,\"type\":\"integer\"},\"requested_start\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"identity_resolved\",\"requested_start\",\"requested_end\"],\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"returned_interval_count\":{\"minimum\":0,\"type\":\"integer\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components/schemas/EvidenceSegment\"},\"type\":\"array\"},\"stream_name\":{\"type\":\"string\"},\"truncated\":{\"const\":false}},\"required\":[\"camera_uuid\",\"name\",\"stream_name\",\"segment_count\",\"returned_interval_count\",\"aggregated\",\"aggregation_bucket_seconds\",\"truncated\",\"coverage\",\"segments\"],\"type\":\"object\"},\"FleetCamera\":{\"additionalProperties\":false,\"properties\":{\"address\":{\"type\":\"string\"},\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"can_configure\":{\"type\":\"boolean\"},\"capabilities\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchannel\"]},\"type\":\"array\"},\"current_fps\":{\"type\":\"number\"},\"enabled\":{\"type\":\"boolean\"},\"first_video_at\":{\"minimum\":0,\"type\":\"integer\"},\"health\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]},\"health_changed_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_frame_ts\":{\"minimum\":0,\"type\":\"integer\"},\"last_recording_at\":{\"minimum\":0,\"type\":\"integer\"},\"location\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"path\":{\"type\":\"string\"},\"uuid\":{\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"path\"],\"type\":\"object\"},\"manufacturer\":{\"type\":\"string\"},\"matched_clauses\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"model\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"recording_active\":{\"type\":\"boolean\"},\"recording_mode\":{\"enum\":[\"off\",\"continuous\",\"detection\"]},\"tags\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"label\":{\"type\":\"string\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"label\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"camera_uuid\",\"name\",\"address\",\"enabled\",\"recording_mode\",\"health\",\"availability\",\"health_changed_at\",\"last_frame_ts\",\"first_video_at\",\"last_recording_at\",\"current_fps\",\"recording_active\",\"can_configure\",\"manufacturer\",\"model\",\"capabilities\",\"location\",\"tags\"],\"type\":\"object\"},\"FleetFacetEntity\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"count\"],\"type\":\"object\"},\"FleetFacets\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"enabled\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"type\":\"boolean\"}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"health\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetEntity\"},\"type\":\"array\"},\"recording_mode\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"value\":{\"enum\":[\"off\",\"continuous\",\"detection\"]}},\"required\":[\"value\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetFacetEntity\"},\"type\":\"array\"}},\"required\":[\"health\",\"enabled\",\"recording_mode\",\"availability\",\"tags\",\"locations\"],\"type\":\"object\"},\"FleetLocation\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"child_count\":{\"minimum\":0,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_system\":{\"type\":\"boolean\"},\"metadata\":{\"additionalProperties\":true,\"type\":\"object\"},\"name\":{\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"integer\"},\"type\":{\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"parent_uuid\",\"name\",\"type\",\"sort_order\",\"is_system\",\"metadata\",\"child_count\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetLocationCreate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additionalProperties\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"number\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\"],\"type\":\"object\"},\"FleetLocationUpdate\":{\"additionalProperties\":false,\"properties\":{\"metadata\":{\"additionalProperties\":true,\"type\":\"object\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sort_order\":{\"type\":\"number\"},\"type\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"FleetLocationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"locations\":{\"items\":{\"$ref\":\"#/components/schemas/FleetLocation\"},\"type\":\"array\"},\"unassigned_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"locations\",\"count\"],\"type\":\"object\"},\"FleetMutationAck\":{\"additionalProperties\":false,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"FleetOperationalQueue\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"key\":{\"type\":\"string\"},\"oldest_unresolved_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"remediation\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"severity\":{\"type\":\"string\"}},\"required\":[\"key\",\"severity\",\"count\",\"remediation\",\"oldest_unresolved_at\",\"selector\"],\"type\":\"object\"},\"FleetPreviewRequest\":{\"additionalProperties\":false,\"description\":\"Read-only POST. Defaults: all selector, page 1, page_size 50, name/asc, facets true, explain false, availability all. Server enforces UTF-8 byte length for search and bounded selector parsing; an invalid or stale selector never grants visibility.\",\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"facets\":{\"type\":\"boolean\"},\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[],\"type\":\"object\"},\"FleetPreviewResponse\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FleetCamera\"},\"maxItems\":50,\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"facets\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":50,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"const\":true},\"queue_version\":{\"const\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"},\"type\":\"array\"},\"selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"selector_version\",\"queue_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_order\",\"cameras\",\"queues\"],\"type\":\"object\"},\"FleetQueryRequest\":{\"additionalProperties\":false,\"description\":\"Read-only POST. Defaults: all selector, page 1, page_size 50, name/asc, facets true, explain false, availability all. Server enforces UTF-8 byte length for search and bounded selector parsing; an invalid or stale selector never grants visibility.\",\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"]},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"explain\":{\"type\":\"boolean\"},\"facets\":{\"type\":\"boolean\"},\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200,\"minimum\":1,\"type\":\"integer\"},\"search\":{\"maxLength\":255,\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[],\"type\":\"object\"},\"FleetQueryResponse\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FleetCamera\"},\"maxItems\":200,\"type\":\"array\"},\"collection_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"facets\":{\"$ref\":\"#/components/schemas/FleetFacets\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":200,\"minimum\":1,\"type\":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"queue_version\":{\"const\":1},\"queues\":{\"items\":{\"$ref\":\"#/components/schemas/FleetOperationalQueue\"},\"type\":\"array\"},\"selector_version\":{\"const\":1},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"selector_version\",\"queue_version\",\"preview\",\"page\",\"page_size\",\"total\",\"total_pages\",\"sort_by\",\"sort_order\",\"cameras\",\"queues\"],\"type\":\"object\"},\"FleetRevisionRequest\":{\"additionalProperties\":false,\"properties\":{\"revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"FleetSavedView\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"anyOf\":[{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"name\",\"is_shared\",\"owned\",\"selector\",\"search\",\"collection_uuid\",\"columns\",\"sort_by\",\"sort_order\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetSavedViewsResponse\":{\"additionalProperties\":false,\"properties\":{\"can_share\":{\"type\":\"boolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"views\":{\"items\":{\"$ref\":\"#/components/schemas/FleetSavedView\"},\"maxItems\":256,\"type\":\"array\"}},\"required\":[\"views\",\"count\",\"can_share\"],\"type\":\"object\"},\"FleetSelector\":{\"$ref\":\"#/components/schemas/TokenSelector\"},\"FleetTag\":{\"additionalProperties\":false,\"properties\":{\"camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"color\":{\"type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"description\":{\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"uuid\",\"label\",\"color\",\"description\",\"camera_count\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FleetTagCreate\":{\"additionalProperties\":false,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\":\"string\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"},\"FleetTagMergeRequest\":{\"additionalProperties\":false,\"properties\":{\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"FleetTagMergeResult\":{\"additionalProperties\":false,\"properties\":{\"source_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"success\":{\"const\":true,\"type\":\"boolean\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"success\",\"source_uuid\",\"target_uuid\"],\"type\":\"object\"},\"FleetTagUpdate\":{\"additionalProperties\":false,\"properties\":{\"color\":{\"pattern\":\"^(|#[0-9a-fA-F]{6})$\",\"type\":\"string\"},\"description\":{\"maxLength\":511,\"type\":\"string\"},\"label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"FleetTagsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"tags\":{\"items\":{\"$ref\":\"#/components/schemas/FleetTag\"},\"type\":\"array\"}},\"required\":[\"tags\",\"count\"],\"type\":\"object\"},\"FleetViewCreate\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[\"name\",\"selector\"],\"type\":\"object\"},\"FleetViewUpdate\":{\"additionalProperties\":false,\"properties\":{\"collection_uuid\":{\"type\":\"string\"},\"columns\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"is_shared\":{\"type\":\"boolean\"},\"name\":{\"minLength\":1,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"search\":{\"type\":\"string\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"sort_by\":{\"enum\":[\"name\",\"camera_uuid\",\"location\",\"health\",\"enabled\",\"recording_mode\",\"address\"]},\"sort_order\":{\"enum\":[\"asc\",\"desc\"]}},\"required\":[\"name\",\"selector\",\"revision\"],\"type\":\"object\"},\"FloorPlan\":{\"additionalProperties\":false,\"properties\":{\"background_mime\":{\"enum\":[\"image/png\",\"image/jpeg\",null],\"type\":[\"string\",\"null\"]},\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCamera\"},\"maxItems\":1000,\"type\":\"array\"},\"can_modify\":{\"type\":\"boolean\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"name\",\"location_uuid\",\"parent_plan_uuid\",\"canvas_width\",\"canvas_height\",\"sketch\",\"cameras\",\"uuid\",\"background_mime\",\"partially_authorized\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"FloorPlanCamera\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotation\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\",\"x\",\"y\",\"rotation\",\"fov\"],\"type\":\"object\"},\"FloorPlanCameraRequest\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"fov\":{\"maximum\":180,\"minimum\":1,\"type\":\"number\"},\"rotation\":{\"maximum\":180,\"minimum\":-180,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"camera_uuid\"],\"type\":\"object\"},\"FloorPlanCreate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]}},\"required\":[\"name\"],\"type\":\"object\"},\"FloorPlanDeletion\":{\"additionalProperties\":false,\"properties\":{\"deleted\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"deleted\"],\"type\":\"object\"},\"FloorPlanUpdate\":{\"additionalProperties\":false,\"properties\":{\"cameras\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlanCameraRequest\"},\"maxItems\":1000,\"type\":\"array\"},\"canvas_height\":{\"maximum\":4000,\"minimum\":300,\"type\":\"integer\"},\"canvas_width\":{\"maximum\":4000,\"minimum\":400,\"type\":\"integer\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"parent_plan_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"sketch\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/FloorSketch\"},{\"type\":\"null\"}]}},\"required\":[\"name\",\"cameras\",\"revision\"],\"type\":\"object\"},\"FloorPlansResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"plans\":{\"items\":{\"$ref\":\"#/components/schemas/FloorPlan\"},\"type\":\"array\"}},\"required\":[\"plans\",\"can_modify\"],\"type\":\"object\"},\"FloorSketch\":{\"additionalProperties\":false,\"properties\":{\"shapes\":{\"items\":{\"oneOf\":[{\"$ref\":\"#/components/schemas/FloorSketchRect\"},{\"$ref\":\"#/components/schemas/FloorSketchWall\"},{\"$ref\":\"#/components/schemas/FloorSketchArea\"},{\"$ref\":\"#/components/schemas/FloorSketchLabel\"}]},\"maxItems\":400,\"type\":\"array\"},\"version\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"version\",\"shapes\"],\"type\":\"object\"},\"FloorSketchArea\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"points\":{\"items\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"maxItems\":64,\"minItems\":3,\"type\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"area\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"points\"],\"type\":\"object\"},\"FloorSketchLabel\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"size\":{\"enum\":[\"sm\",\"md\",\"lg\"],\"type\":\"string\"},\"text\":{\"maxLength\":95,\"minLength\":1,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"label\",\"type\":\"string\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"text\"],\"type\":\"object\"},\"FloorSketchRect\":{\"additionalProperties\":false,\"properties\":{\"filled\":{\"type\":\"boolean\"},\"h\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"rect\",\"type\":\"string\"},\"w\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"id\",\"type\",\"x\",\"y\",\"w\",\"h\"],\"type\":\"object\"},\"FloorSketchWall\":{\"additionalProperties\":false,\"properties\":{\"id\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"label\":{\"maxLength\":95,\"type\":\"string\"},\"points\":{\"items\":{\"items\":false,\"maxItems\":2,\"minItems\":2,\"prefixItems\":[{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}],\"type\":\"array\"},\"maxItems\":64,\"minItems\":2,\"type\":\"array\"},\"tone\":{\"enum\":[\"slate\",\"blue\",\"green\",\"amber\",\"red\",\"violet\"],\"type\":\"string\"},\"type\":{\"const\":\"wall\",\"type\":\"string\"}},\"required\":[\"id\",\"type\",\"points\"],\"type\":\"object\"},\"Go2rtcConfigurationInput\":{\"additionalProperties\":true,\"properties\":{\"override\":{\"maxLength\":65535,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"Go2rtcConfigurationValidation\":{\"additionalProperties\":true,\"properties\":{\"error\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"column\":{\"type\":\"number\"},\"line\":{\"type\":\"number\"},\"message\":{\"type\":\"string\"}},\"type\":\"object\"}]},\"libyaml_available\":{\"type\":\"boolean\"},\"skip_reason\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"}},\"required\":[\"message\"],\"type\":\"object\"},\"skipped\":{\"type\":\"boolean\"},\"valid\":{\"type\":\"boolean\"},\"warnings\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"valid\",\"libyaml_available\",\"skipped\",\"warnings\"],\"type\":\"object\"},\"Go2rtcEffectiveConfiguration\":{\"additionalProperties\":true,\"properties\":{\"base\":{\"type\":\"string\"},\"go2rtc_initialized\":{\"type\":\"boolean\"},\"managed_listener_policy\":{\"type\":\"string\"},\"merged_source_order\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"override\":{\"type\":\"string\"},\"override_in_use\":{\"type\":\"boolean\"},\"redaction_available\":{\"type\":\"boolean\"},\"warnings\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"go2rtc_initialized\",\"redaction_available\",\"base\",\"override\",\"managed_listener_policy\",\"merged_source_order\",\"override_in_use\",\"warnings\"],\"type\":\"object\"},\"Go2rtcOverrideDiagnostics\":{\"additionalProperties\":true,\"properties\":{\"content_matches\":{\"type\":\"boolean\"},\"db_bytes\":{\"type\":\"number\"},\"expected_file_path\":{\"type\":\"string\"},\"file_bytes\":{\"type\":\"number\"},\"file_present\":{\"type\":\"boolean\"},\"file_read_error\":{\"type\":\"string\"},\"process_pid\":{\"type\":\"number\"},\"process_running\":{\"type\":\"boolean\"},\"process_state_known\":{\"type\":\"boolean\"},\"quarantine_active\":{\"type\":\"boolean\"},\"quarantine_reason\":{\"type\":\"string\"},\"quarantined_copy_bytes\":{\"type\":\"integer\"},\"validated_version\":{\"type\":\"string\"}},\"required\":[\"expected_file_path\",\"db_bytes\",\"file_present\",\"file_bytes\",\"content_matches\",\"quarantine_active\",\"process_state_known\",\"process_running\",\"process_pid\",\"quarantined_copy_bytes\",\"validated_version\"],\"type\":\"object\"},\"HealthResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamHealth\"},{\"$ref\":\"#/components/schemas/ProcessLiveness\"},{\"$ref\":\"#/components/schemas/StreamHealthHistory\"}]},\"HlsHealth\":{\"additionalProperties\":true,\"properties\":{\"healthy\":{\"type\":\"boolean\"},\"status\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"},\"watchdogRestartCount\":{\"type\":\"integer\"}},\"required\":[\"healthy\",\"status\",\"watchdogRestartCount\",\"timestamp\"],\"type\":\"object\"},\"HostHealth\":{\"additionalProperties\":true,\"properties\":{\"active_incidents\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"incident_id\":{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"type\":\"string\"},\"sampled_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"persistence_pending\":{\"type\":\"boolean\"},\"scope\":{\"type\":\"string\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"},\"thresholds\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"critical_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seconds\":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\",\"overridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"warning_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"}},\"required\":[\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_observed_at_ms\",\"persistence_pending\",\"observation\"],\"type\":\"object\"},\"type\":\"array\"},\"coverage\":{\"additionalProperties\":true,\"properties\":{\"abandoned_helpers\":{\"type\":\"number\"},\"collection_errors\":{\"type\":\"number\"},\"collection_timeouts\":{\"type\":\"number\"},\"complete\":{\"type\":\"boolean\"},\"coverage_overflows\":{\"type\":\"number\"},\"incomplete_count\":{\"type\":\"number\"},\"observations_dropped\":{\"type\":\"number\"},\"unavailable_observations\":{\"type\":\"number\"}},\"required\":[\"complete\",\"incomplete_count\",\"unavailable_observations\",\"observations_dropped\",\"collection_errors\",\"collection_timeouts\",\"abandoned_helpers\",\"coverage_overflows\"],\"type\":\"object\"},\"evaluator\":{\"additionalProperties\":true,\"properties\":{\"active_incidents\":{\"type\":\"integer\"},\"pending_persistence\":{\"type\":\"integer\"},\"tracked_conditions\":{\"type\":\"integer\"}},\"required\":[\"tracked_conditions\",\"active_incidents\",\"pending_persistence\"],\"type\":\"object\"},\"monitoring\":{\"additionalProperties\":true,\"properties\":{\"configured_enabled\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"initialized\":{\"type\":\"boolean\"},\"restart_required\":{\"type\":\"boolean\"},\"worker_threads\":{\"type\":\"number\"}},\"required\":[\"configured_enabled\",\"initialized\",\"enabled\",\"worker_threads\",\"restart_required\"],\"type\":\"object\"},\"observations\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"freshness\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"observed_at_ms\":{\"type\":\"number\"},\"resource\":{\"type\":\"string\"},\"sampled_monotonic_ms\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"freshness\",\"unit\",\"value\"],\"type\":\"object\"},\"type\":\"array\"},\"overall_state\":{\"type\":\"string\"},\"recent_samples\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"completed_at_ms\":{\"type\":\"number\"},\"observation_count\":{\"type\":\"number\"},\"observations_dropped\":{\"type\":\"number\"},\"sequence\":{\"type\":\"number\"}},\"required\":[\"sequence\",\"completed_at_ms\",\"observation_count\",\"observations_dropped\"],\"type\":\"object\"},\"type\":\"array\"},\"schema_version\":{\"type\":\"number\"},\"self_observability\":{\"additionalProperties\":true,\"properties\":{\"collectors\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"attempts\":{\"type\":\"number\"},\"busy\":{\"type\":\"boolean\"},\"collector\":{\"type\":\"string\"},\"completions\":{\"type\":\"number\"},\"failures\":{\"type\":\"number\"},\"last_duration_ms\":{\"type\":\"number\"},\"maximum_duration_ms\":{\"type\":\"number\"},\"overlap_skips\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"stale\":{\"type\":\"boolean\"},\"tier\":{\"type\":\"number\"},\"timeouts\":{\"type\":\"number\"}},\"required\":[\"collector\",\"scope\",\"tier\",\"attempts\",\"completions\",\"failures\",\"timeouts\",\"overlap_skips\",\"last_duration_ms\",\"maximum_duration_ms\",\"busy\",\"stale\"],\"type\":\"object\"},\"type\":\"array\"},\"coverage_overflows\":{\"type\":\"number\"},\"event_delivery\":{\"additionalProperties\":true,\"properties\":{\"all_destinations_unavailable\":{\"type\":\"boolean\"},\"circular_report_path\":{\"type\":\"boolean\"},\"degraded\":{\"type\":\"boolean\"},\"outbox\":{\"additionalProperties\":true,\"properties\":{\"available\":{\"type\":\"boolean\"}},\"type\":\"object\"},\"outbox_full\":{\"type\":\"boolean\"}},\"type\":\"object\"},\"incident_persistence\":{\"additionalProperties\":true,\"properties\":{\"failures\":{\"type\":\"number\"},\"pending\":{\"type\":\"number\"},\"retries\":{\"type\":\"number\"}},\"type\":\"object\"}},\"required\":[\"collectors\",\"coverage_overflows\",\"incident_persistence\",\"event_delivery\"],\"type\":\"object\"},\"snapshot\":{\"additionalProperties\":true,\"properties\":{\"age_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"available\":{\"type\":\"boolean\"},\"completed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"freshness\":{\"type\":\"string\"},\"sequence\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"available\",\"sequence\",\"completed_at_ms\",\"age_ms\",\"freshness\"],\"type\":\"object\"},\"thresholds\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"condition\":{\"type\":\"string\"},\"critical_for_seconds\":{\"type\":\"number\"},\"critical_threshold\":{\"type\":\"number\"},\"direction\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"overridden\":{\"type\":\"boolean\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"recovery_threshold\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning_for_seconds\":{\"type\":\"number\"},\"warning_threshold\":{\"type\":\"number\"}},\"required\":[\"condition\",\"enabled\",\"overridden\",\"direction\",\"unit\",\"warning_threshold\",\"critical_threshold\",\"recovery_threshold\",\"warning_for_seconds\",\"critical_for_seconds\",\"recovery_for_seconds\"],\"type\":\"object\"},\"type\":\"array\"},\"visibility\":{\"additionalProperties\":true,\"properties\":{\"coverage_boundary\":{\"type\":\"string\"},\"effective_scope\":{\"type\":\"string\"},\"host_hardware_visible\":{\"type\":\"boolean\"}},\"required\":[\"effective_scope\",\"host_hardware_visible\",\"coverage_boundary\"],\"type\":\"object\"}},\"required\":[\"schema_version\",\"monitoring\",\"overall_state\",\"visibility\",\"snapshot\",\"coverage\",\"observations\",\"thresholds\",\"active_incidents\",\"recent_samples\",\"self_observability\"],\"type\":\"object\"},\"HostHealthIncidents\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"incidents\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"closed_at_ms\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"condition\":{\"type\":\"string\"},\"first_observed_at_ms\":{\"type\":\"number\"},\"incident_id\":{\"type\":\"string\"},\"last_observed_at_ms\":{\"type\":\"number\"},\"observation\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"metric\":{\"type\":\"string\"},\"resource\":{\"type\":\"string\"},\"scope\":{\"type\":\"string\"},\"unit\":{\"type\":\"string\"},\"value\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"required\":[\"metric\",\"resource\",\"scope\",\"capability\",\"unit\",\"value\"],\"type\":\"object\"},\"reconciliation\":{\"type\":\"string\"},\"revision\":{\"type\":\"number\"},\"scope\":{\"type\":\"string\"},\"severity\":{\"type\":\"string\"},\"state\":{\"type\":\"string\"},\"subject\":{\"type\":\"string\"}},\"required\":[\"incident_id\",\"condition\",\"subject\",\"scope\",\"state\",\"severity\",\"first_observed_at_ms\",\"last_observed_at_ms\",\"closed_at_ms\",\"reconciliation\",\"revision\",\"observation\"],\"type\":\"object\"},\"type\":\"array\"},\"next_cursor\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"schema_version\":{\"type\":\"number\"}},\"required\":[\"schema_version\",\"count\",\"incidents\",\"next_cursor\"],\"type\":\"object\"},\"IceServersResponse\":{\"additionalProperties\":true,\"properties\":{\"ice_servers\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"credential\":{\"type\":\"string\"},\"urls\":{\"items\":{\"type\":\"string\"},\"minItems\":1,\"type\":\"array\"},\"username\":{\"type\":\"string\"}},\"required\":[\"urls\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"ice_servers\"],\"type\":\"object\"},\"ImagingModeLevel\":{\"additionalProperties\":false,\"properties\":{\"level\":{\"type\":\"number\"},\"mode\":{\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"ImagingModeLevelUpdate\":{\"oneOf\":[{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"level\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"mode\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"}]},\"ImagingModeOptions\":{\"additionalProperties\":false,\"properties\":{\"level\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"modes\"],\"type\":\"object\"},\"ImagingOptions\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"brightness\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"color_saturation\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"contrast\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"ir_cut_filter_modes\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"noise_reduction\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"sharpness\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"},\"video_source_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeOptions\"}},\"required\":[\"video_source_token\",\"ir_cut_filter_modes\"],\"type\":\"object\"},\"ImagingOptionsResponse\":{\"additionalProperties\":false,\"properties\":{\"options\":{\"$ref\":\"#/components/schemas/ImagingOptions\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"options\"],\"type\":\"object\"},\"ImagingSettings\":{\"additionalProperties\":false,\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"brightness\":{\"type\":\"number\"},\"color_saturation\":{\"type\":\"number\"},\"contrast\":{\"type\":\"number\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"ir_cut_filter\":{\"type\":\"string\"},\"noise_reduction\":{\"type\":\"number\"},\"sharpness\":{\"type\":\"number\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"},\"video_source_token\":{\"type\":\"string\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevel\"}},\"required\":[\"video_source_token\"],\"type\":\"object\"},\"ImagingSettingsRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"brightness\"]},{\"properties\":{\"color_saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"color_saturation\"]},{\"properties\":{\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"contrast\"]},{\"properties\":{\"sharpness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"sharpness\"]},{\"properties\":{\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"noise_reduction\"]},{\"properties\":{\"saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[\"saturation\"]},{\"properties\":{\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"backlight_compensation\"]},{\"properties\":{\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"wide_dynamic_range\"]},{\"properties\":{\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"tone_compensation\"]},{\"properties\":{\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"defogging\"]},{\"properties\":{\"backlight\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"backlight\"]},{\"properties\":{\"wdr\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"wdr\"]},{\"properties\":{\"tone\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"tone\"]},{\"properties\":{\"defog\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[\"defog\"]},{\"properties\":{\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ir_cut_filter\"]},{\"properties\":{\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"ircut\"]}],\"description\":\"Known aliases remain accepted; force_persistence alone is rejected. Numeric limits come from the device options rather than an invented global range.\",\"minProperties\":1,\"properties\":{\"backlight\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"backlight_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"brightness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"color_saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"contrast\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"defog\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"defogging\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"force_persistence\":{\"type\":\"boolean\"},\"ir_cut_filter\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"ircut\":{\"maxLength\":15,\"minLength\":1,\"type\":\"string\"},\"noise_reduction\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"saturation\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"sharpness\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tone\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"tone_compensation\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wdr\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"},\"wide_dynamic_range\":{\"$ref\":\"#/components/schemas/ImagingModeLevelUpdate\"}},\"required\":[],\"type\":\"object\"},\"ImagingSettingsResponse\":{\"additionalProperties\":false,\"properties\":{\"settings\":{\"$ref\":\"#/components/schemas/ImagingSettings\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"settings\"],\"type\":\"object\"},\"IntegrationCapabilities\":{\"additionalProperties\":true,\"description\":\"Capability document scoped to the calling principal (contract \xC2\xA79).\",\"properties\":{\"cameras\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"actions\":{\"items\":{\"enum\":[\"live.view\",\"snapshot.create\",\"observations.annotate\"],\"type\":\"string\"},\"type\":\"array\",\"uniqueItems\":true},\"camera_uuid\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"evidence_enabled\":{\"type\":\"boolean\"}},\"required\":[\"camera_uuid\",\"evidence_enabled\",\"actions\"],\"type\":\"object\"},\"maxItems\":1024,\"type\":\"array\"},\"contract_versions\":{\"additionalProperties\":true,\"properties\":{\"classifications\":{\"minimum\":1,\"type\":\"integer\"},\"observation_event\":{\"minLength\":1,\"type\":\"string\"},\"observations\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"observations\",\"classifications\",\"observation_event\"],\"type\":\"object\"},\"delivery\":{\"additionalProperties\":true,\"properties\":{\"mqtt_enabled\":{\"type\":\"boolean\"},\"topic\":{\"description\":\"Configured topic with a literal {camera_uuid} placeholder.\",\"type\":[\"string\",\"null\"]}},\"required\":[\"mqtt_enabled\",\"topic\"],\"type\":\"object\"},\"history\":{\"additionalProperties\":true,\"properties\":{\"oldest_cursor\":{\"maxLength\":256,\"type\":[\"string\",\"null\"]},\"oldest_observed_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"retention_days\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"retention_days\",\"oldest_cursor\",\"oldest_observed_at\"],\"type\":\"object\"},\"limits\":{\"additionalProperties\":true,\"properties\":{\"annotations_per_observation_max\":{\"minimum\":1,\"type\":\"integer\"},\"history_filter_cameras_max\":{\"minimum\":1,\"type\":\"integer\"},\"history_filter_labels_max\":{\"minimum\":1,\"type\":\"integer\"},\"history_page_default\":{\"minimum\":1,\"type\":\"integer\"},\"history_page_max\":{\"minimum\":1,\"type\":\"integer\"},\"label_max_bytes\":{\"minimum\":1,\"type\":\"integer\"},\"max_objects_per_batch\":{\"minimum\":1,\"type\":\"integer\"},\"snapshot_retry_after_max_seconds\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"history_page_default\",\"history_page_max\",\"history_filter_cameras_max\",\"history_filter_labels_max\",\"max_objects_per_batch\",\"label_max_bytes\",\"snapshot_retry_after_max_seconds\",\"annotations_per_observation_max\"],\"type\":\"object\"},\"operations\":{\"additionalProperties\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"description\":\"Unsupported features are declared explicitly; never infer support from an error.\",\"properties\":{\"event_clip\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"observations.annotate\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"observations.history\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"observations.snapshot\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"range_clip\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"recorded_frame\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"},\"track_lifecycle\":{\"$ref\":\"#/components/schemas/IntegrationCapabilityOperation\"}},\"required\":[\"observations.history\",\"observations.snapshot\",\"observations.annotate\",\"track_lifecycle\",\"recorded_frame\",\"event_clip\",\"range_clip\"],\"type\":\"object\"},\"principal\":{\"additionalProperties\":true,\"properties\":{\"writer_namespace\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":[\"string\",\"null\"]}},\"required\":[\"writer_namespace\"],\"type\":\"object\"},\"source\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"pattern\":\"^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\",\"type\":\"string\"},\"installation_uuid\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"kind\":{\"const\":\"lightnvr\"},\"product_version\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"id\",\"kind\",\"installation_uuid\",\"product_version\"],\"type\":\"object\"}},\"required\":[\"contract_versions\",\"source\",\"principal\",\"operations\",\"limits\",\"history\",\"delivery\",\"cameras\"],\"type\":\"object\"},\"IntegrationCapabilityOperation\":{\"additionalProperties\":true,\"properties\":{\"authorized\":{\"type\":\"boolean\"},\"reason\":{\"enum\":[\"not_implemented\",\"not_available_in_this_version\",\"not_authorized\",\"invalid_writer_namespace\",\"reserved_writer_namespace\"],\"type\":\"string\"},\"supported\":{\"type\":\"boolean\"}},\"required\":[\"supported\",\"authorized\"],\"type\":\"object\"},\"LegacyApiKeyCreated\":{\"additionalProperties\":false,\"properties\":{\"api_key\":{\"minLength\":1,\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"api_key\"],\"type\":\"object\"},\"LiveCameraSlot\":{\"additionalProperties\":false,\"properties\":{\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"camera_uuid\"],\"type\":\"object\"},\"LiveLayout\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"owned\":{\"type\":\"boolean\"},\"partially_authorized\":{\"type\":\"boolean\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"name\",\"is_shared\",\"location_uuid\",\"availability\",\"columns\",\"rows\",\"camera_slots\",\"uuid\",\"owned\",\"partially_authorized\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"LiveLayoutCreate\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"name\",\"availability\",\"columns\",\"rows\",\"camera_slots\"],\"type\":\"object\"},\"LiveLayoutUpdate\":{\"additionalProperties\":false,\"properties\":{\"availability\":{\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_slots\":{\"items\":{\"$ref\":\"#/components/schemas/LiveCameraSlot\"},\"maxItems\":36,\"type\":\"array\"},\"columns\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"},\"is_shared\":{\"type\":\"boolean\"},\"location_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"rows\":{\"maximum\":9,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"name\",\"availability\",\"columns\",\"rows\",\"camera_slots\",\"revision\"],\"type\":\"object\"},\"LiveLayoutsResponse\":{\"additionalProperties\":false,\"properties\":{\"can_modify\":{\"type\":\"boolean\"},\"can_share\":{\"type\":\"boolean\"},\"count\":{\"minimum\":0,\"type\":\"integer\"},\"layouts\":{\"items\":{\"$ref\":\"#/components/schemas/LiveLayout\"},\"type\":\"array\"}},\"required\":[\"layouts\",\"count\",\"can_share\",\"can_modify\"],\"type\":\"object\"},\"LiveStreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=live. Uses the same filtered pagination envelope, with playback-specific row fields.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/LiveStreamSummaryRedacted\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"LiveStreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"type\":\"boolean\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":true,\"type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"privacy_mode\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_transport\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"}],\"description\":\"surface=live includes playback and capability hints; admin_url is never emitted by this surface.\"},\"LiveStreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"backchannel_enabled\":{\"type\":\"boolean\"},\"can_configure\":{\"const\":false,\"type\":\"boolean\"},\"can_control_privacy\":{\"const\":false,\"type\":\"boolean\"},\"detection_model\":{\"type\":\"string\"},\"eptz_config\":{\"type\":\"string\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"privacy_mode\":{\"type\":\"boolean\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"streaming_enabled\":{\"type\":\"boolean\"}},\"required\":[\"streaming_enabled\",\"privacy_mode\",\"has_sub_stream\",\"ptz_enabled\",\"backchannel_enabled\",\"go2rtc_hls_available\",\"playback_transport\",\"eptz_config\",\"detection_model\",\"can_control_privacy\",\"can_configure\"],\"type\":\"object\"}],\"description\":\"surface=live includes playback and capability hints; admin_url is never emitted by this surface.\"},\"LoginConfig\":{\"additionalProperties\":false,\"properties\":{\"api_key\":false,\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"remember_device_enabled\":{\"type\":\"boolean\"},\"secret\":false,\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false,\"trusted_device_days\":{\"type\":\"number\"}},\"required\":[\"force_mfa_on_login\",\"remember_device_enabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\"],\"type\":\"object\"},\"LoginLockoutCleared\":{\"additionalProperties\":false,\"properties\":{\"cleared\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"cleared\",\"message\"],\"type\":\"object\"},\"LoginMfaChallenge\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_password\":false,\"password\":false,\"password_hash\":false,\"redirect\":false,\"secret\":false,\"success\":false,\"token_hash\":false,\"totp_required\":{\"const\":true},\"totp_secret\":false,\"totp_token\":{\"description\":\"Ephemeral MFA challenge credential. Returned only by login; never an ordinary identity/token-list field or documentation example.\",\"minLength\":1,\"readOnly\":true,\"type\":\"string\"}},\"required\":[\"totp_required\",\"totp_token\"],\"type\":\"object\"},\"LoginRequest\":{\"additionalProperties\":true,\"description\":\"JSON credentials. The C handler copies username/password into 64-byte buffers (63-byte payload); it does not reject oversized strings before truncating. Invalid credentials return401. Optional fields with wrong types are ignored by current C; this schema describes typed client input.\",\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"remember_device\":{\"type\":\"boolean\"},\"totp_code\":{\"description\":\"Optional inline MFA code; enrollment and challenge completion endpoints remain outside this slice.\",\"type\":\"string\",\"writeOnly\":true},\"username\":{\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"},\"LoginResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/LoginSuccess\"},{\"$ref\":\"#/components/schemas/LoginMfaChallenge\"}]},\"LoginSuccess\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"must_change_password\":{\"type\":\"boolean\"},\"password\":false,\"password_hash\":false,\"redirect\":{\"const\":\"/index.html\"},\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_required\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\",\"redirect\",\"must_change_password\"],\"type\":\"object\"},\"LogoutResponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false,\"password_hash\":false,\"redirect\":{\"const\":\"/login.html?logout=true\"},\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\",\"redirect\"],\"type\":\"object\"},\"LprStreamSummary\":{\"additionalProperties\":true,\"description\":\"surface=lpr exposes only camera identity and requires both lpr.search and lpr.read for each returned camera. It does not imply live.view or configuration access.\",\"properties\":{\"camera_uuid\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"}},\"required\":[\"camera_uuid\",\"name\"],\"type\":\"object\"},\"LprStreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=lpr. Empty results remain a valid response even with no LPR grants.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"$ref\":\"#/components/schemas/LprStreamSummary\"},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"MotionTrigger\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"},\"label\":{\"minLength\":1,\"type\":\"string\"},\"objects\":{\"items\":{\"oneOf\":[{\"minLength\":1,\"type\":\"string\"},{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"label\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"label\"],\"type\":\"object\"}]},\"type\":\"array\"},\"stream\":{\"minLength\":1,\"type\":\"string\"},\"tags\":{\"items\":{\"minLength\":1,\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"stream\",\"action\"],\"type\":\"object\"},\"MotionTriggered\":{\"additionalProperties\":false,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\",\"pulse\"],\"type\":\"string\"},\"detections_stored\":{\"minimum\":0,\"type\":\"integer\"},\"duration_ms\":{\"maximum\":600000,\"minimum\":1,\"type\":\"integer\"},\"recording_triggered\":{\"type\":\"boolean\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"},\"tags_applied\":{\"minimum\":0,\"type\":\"integer\"},\"warning\":{\"type\":\"string\"}},\"required\":[\"success\",\"stream\",\"action\",\"recording_triggered\",\"detections_stored\",\"tags_applied\"],\"type\":\"object\"},\"Observation\":{\"additionalProperties\":true,\"description\":\"One detected object in one accepted inference result (contract \xC2\xA73). Readers ignore unknown fields.\",\"properties\":{\"batch_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"box\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationBox\"},{\"type\":\"null\"}]},\"camera_uuid\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"classifications\":{\"$ref\":\"#/components/schemas/ObservationClassifications\"},\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"created_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"detector\":{\"$ref\":\"#/components/schemas/ObservationDetector\"},\"evidence\":{\"$ref\":\"#/components/schemas/ObservationEvidence\"},\"id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"label\":{\"description\":\"Original detector label (1..31 bytes); never modified by classification.\",\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"object_index\":{\"maximum\":19,\"minimum\":0,\"type\":\"integer\"},\"observed_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"recording_reference\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationRecordingReference\"},{\"type\":\"null\"}]},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"source\":{\"pattern\":\"^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\",\"type\":\"string\"},\"stream_name_at_capture\":{\"description\":\"Display only.\",\"maxLength\":63,\"type\":\"string\"},\"track\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTrack\"},{\"type\":\"null\"}]}},\"required\":[\"id\",\"source\",\"camera_uuid\",\"stream_name_at_capture\",\"batch_id\",\"object_index\",\"observed_at\",\"created_at\",\"detector\",\"label\",\"confidence\",\"box\",\"evidence\",\"track\",\"recording_reference\",\"revision\",\"classifications\"],\"type\":\"object\"},\"ObservationBox\":{\"additionalProperties\":true,\"description\":\"Top-left corner and size normalized to the stored evidence image (contract \xC2\xA73.2). Consumers never re-apply transform.\",\"properties\":{\"format\":{\"const\":\"xywh_normalized\"},\"height\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"width\":{\"exclusiveMinimum\":0,\"maximum\":1,\"type\":\"number\"},\"x\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"y\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"}},\"required\":[\"format\",\"x\",\"y\",\"width\",\"height\"],\"type\":\"object\"},\"ObservationChangeEvent\":{\"additionalProperties\":true,\"allOf\":[{\"else\":{\"properties\":{\"evidence_reason\":{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"}},\"type\":\"object\"},\"if\":{\"properties\":{\"evidence_state\":{\"enum\":[\"pending\",\"ready\"]}},\"type\":\"object\"},\"then\":{\"properties\":{\"evidence_reason\":{\"type\":\"null\"}},\"type\":\"object\"}},{\"else\":{\"not\":{\"properties\":{\"snapshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"},\"type\":\"object\"},\"if\":{\"properties\":{\"evidence_state\":{\"const\":\"ready\"}},\"type\":\"object\"},\"then\":{\"properties\":{\"snapshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"}}],\"description\":\"Data payload of io.lightnvr.detection.observation_changed.v1 (contract \xC2\xA76.3). Consumers skip classification-only changes for re-classification.\",\"properties\":{\"asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"camera_uuid\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"change_cause\":{\"enum\":[\"created\",\"evidence\",\"classification\"],\"type\":\"string\"},\"evidence_reason\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"},{\"type\":\"null\"}]},\"evidence_state\":{\"$ref\":\"#/components/schemas/ObservationEvidenceState\"},\"label\":{\"maxLength\":31,\"minLength\":1,\"type\":\"string\"},\"observation_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"observed_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"revision\":{\"minimum\":1,\"type\":\"integer\"},\"snapshot_url\":{\"pattern\":\"^/api/observations/[0-9a-f-]{36}/snapshot$\",\"type\":\"string\"}},\"required\":[\"observation_id\",\"revision\",\"camera_uuid\",\"label\",\"observed_at\",\"change_cause\",\"evidence_state\",\"evidence_reason\",\"asset_id\"],\"type\":\"object\"},\"ObservationClassification\":{\"additionalProperties\":true,\"description\":\"Stored classification annotation (contract \xC2\xA710.3). P2.\",\"properties\":{\"annotation_revision\":{\"minimum\":1,\"type\":\"integer\"},\"classified_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"classifier\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"created_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"input_asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"input_revision\":{\"minimum\":1,\"type\":\"integer\"},\"key\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":\"string\"},\"kind\":{\"enum\":[\"automated\",\"owner_confirmed\"],\"type\":\"string\"},\"label\":{\"maxLength\":128,\"minLength\":1,\"type\":\"string\"},\"model_version\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"taxonomy_id\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"updated_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"writer_namespace\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":\"string\"}},\"required\":[\"key\",\"writer_namespace\",\"classifier\",\"model_version\",\"label\",\"taxonomy_id\",\"confidence\",\"classified_at\",\"input_asset_id\",\"input_revision\",\"kind\",\"annotation_revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"ObservationClassificationResult\":{\"additionalProperties\":true,\"properties\":{\"annotation\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"changed\":{\"type\":\"boolean\"},\"observation_revision\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"changed\",\"observation_revision\",\"annotation\"],\"type\":\"object\"},\"ObservationClassificationWrite\":{\"additionalProperties\":false,\"description\":\"PUT body (contract \xC2\xA710.3). Closed: unknown fields return 400. The writer namespace is derived by the server from the authenticated user, never from this body.\",\"properties\":{\"classified_at\":{\"$ref\":\"#/components/schemas/ObservationTime\"},\"classifier\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"confidence\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"expected_revision\":{\"description\":\"0 or absent means the key must not exist yet.\",\"minimum\":0,\"type\":\"integer\"},\"input_asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"input_revision\":{\"minimum\":1,\"type\":\"integer\"},\"kind\":{\"enum\":[\"automated\",\"owner_confirmed\"],\"type\":\"string\"},\"label\":{\"maxLength\":128,\"minLength\":1,\"type\":\"string\"},\"model_version\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"taxonomy_id\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]},\"writer\":{\"description\":\"Same rule as writer_namespace.\",\"type\":\"string\"},\"writer_namespace\":{\"description\":\"Optional. Accepted and ignored when it names the principal's derived namespace; any other value returns 403 namespace_forbidden.\",\"type\":\"string\"}},\"required\":[\"classifier\",\"label\",\"confidence\",\"classified_at\",\"input_asset_id\",\"input_revision\",\"kind\"],\"type\":\"object\"},\"ObservationClassifications\":{\"additionalProperties\":true,\"description\":\"Annotations split by kind, each ordered by updated_at descending. Both lists are empty in P1.\",\"properties\":{\"automated\":{\"items\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"maxItems\":8,\"type\":\"array\"},\"owner_confirmed\":{\"items\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"maxItems\":8,\"type\":\"array\"}},\"required\":[\"automated\",\"owner_confirmed\"],\"type\":\"object\"},\"ObservationDetector\":{\"additionalProperties\":true,\"properties\":{\"engine_key\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"engine_type\":{\"enum\":[\"object\",\"api\",\"onvif\",\"external\"],\"type\":\"string\"},\"model\":{\"description\":\"Model basename only, never a path.\",\"maxLength\":128,\"type\":[\"string\",\"null\"]},\"model_version\":{\"maxLength\":64,\"type\":[\"string\",\"null\"]}},\"required\":[\"engine_key\",\"engine_type\",\"model\",\"model_version\"],\"type\":\"object\"},\"ObservationError\":{\"additionalProperties\":true,\"description\":\"Coded error (contract \xC2\xA711, D2): a valid ApiError with a string error and a stable code. Clients branch on status and code, never on the message.\",\"properties\":{\"code\":{\"enum\":[\"invalid_request\",\"invalid_cursor\",\"forbidden\",\"namespace_forbidden\",\"observation_not_found\",\"evidence_unavailable\",\"evidence_pending\",\"evidence_expired\",\"cursor_expired\",\"revision_conflict\",\"owner_decision_locked\",\"annotation_limit\",\"evidence_mismatch\",\"not_implemented\",\"service_unavailable\",\"internal_error\"],\"type\":\"string\"},\"current\":{\"$ref\":\"#/components/schemas/ObservationClassification\"},\"error\":{\"type\":\"string\"},\"evidence_state\":{\"$ref\":\"#/components/schemas/ObservationEvidenceState\"},\"expired_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"field\":{\"type\":\"string\"},\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"observation_asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"oldest_cursor\":{\"maxLength\":256,\"minLength\":1,\"type\":\"string\"},\"oldest_observed_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"reason\":{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"},\"retry_after_seconds\":{\"maximum\":30,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"error\",\"code\"],\"type\":\"object\"},\"ObservationEvidence\":{\"additionalProperties\":true,\"allOf\":[{\"else\":{\"properties\":{\"reason\":{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"}},\"type\":\"object\"},\"if\":{\"properties\":{\"state\":{\"enum\":[\"pending\",\"ready\"]}},\"type\":\"object\"},\"then\":{\"properties\":{\"reason\":{\"type\":\"null\"}},\"type\":\"object\"}},{\"else\":{\"not\":{\"properties\":{\"snapshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"},\"type\":\"object\"},\"if\":{\"properties\":{\"state\":{\"const\":\"ready\"}},\"type\":\"object\"},\"then\":{\"properties\":{\"snapshot_url\":{\"type\":\"string\"}},\"required\":[\"snapshot_url\"],\"type\":\"object\"}}],\"description\":\"Evidence asset (contract \xC2\xA74). Every observation has one; siblings in a batch share asset_id.\",\"properties\":{\"asset_id\":{\"$ref\":\"#/components/schemas/ObservationUuid\"},\"bytes\":{\"minimum\":0,\"type\":[\"integer\",\"null\"]},\"captured_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"expires_at\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTime\"},{\"type\":\"null\"}]},\"height\":{\"minimum\":1,\"type\":[\"integer\",\"null\"]},\"media_type\":{\"enum\":[\"image/jpeg\",null],\"type\":[\"string\",\"null\"]},\"origin\":{\"enum\":[\"inference_input\",\"submitted_image\",null],\"type\":[\"string\",\"null\"]},\"reason\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationEvidenceReason\"},{\"type\":\"null\"}]},\"snapshot_url\":{\"description\":\"Present only when state is ready. Relative and authenticated.\",\"pattern\":\"^/api/observations/[0-9a-f-]{36}/snapshot$\",\"type\":\"string\"},\"state\":{\"$ref\":\"#/components/schemas/ObservationEvidenceState\"},\"transform\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ObservationTransform\"},{\"type\":\"null\"}]},\"width\":{\"minimum\":1,\"type\":[\"integer\",\"null\"]}},\"required\":[\"asset_id\",\"state\",\"reason\",\"origin\",\"media_type\",\"width\",\"height\",\"bytes\",\"captured_at\",\"expires_at\",\"transform\"],\"type\":\"object\"},\"ObservationEvidenceReason\":{\"description\":\"Bounded reason (contract \xC2\xA74.3). Consumers treat an unknown reason by its state.\",\"enum\":[\"queue_full\",\"budget_exhausted\",\"encode_failed\",\"write_failed\",\"interrupted\",\"no_matching_media\",\"disabled\",\"retention_expired\",\"deleted\"],\"type\":\"string\"},\"ObservationEvidenceState\":{\"enum\":[\"pending\",\"ready\",\"unavailable\",\"expired\"],\"type\":\"string\"},\"ObservationPage\":{\"additionalProperties\":true,\"description\":\"History page (contract \xC2\xA77.2-7.3). Empty items with has_more=true is valid; continue from next_cursor.\",\"properties\":{\"has_more\":{\"type\":\"boolean\"},\"items\":{\"items\":{\"$ref\":\"#/components/schemas/Observation\"},\"maxItems\":500,\"type\":\"array\"},\"next_cursor\":{\"maxLength\":256,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"items\",\"next_cursor\",\"has_more\"],\"type\":\"object\"},\"ObservationPipelineDiagnostics\":{\"additionalProperties\":true,\"description\":\"INT_01 observation pipeline diagnostics (observation_diagnostics_to_json(), contract section 15). Counters are per process run unless noted; per-camera rows come from the last retention pass and may be up to 5 minutes old.\",\"properties\":{\"accepted\":{\"minimum\":0,\"type\":\"integer\"},\"active\":{\"description\":\"False when evidence is unused: no queue exists, counters are zero and no observations are retained.\",\"type\":\"boolean\"},\"budget_exhausted_cameras\":{\"minimum\":0,\"type\":\"integer\"},\"cameras\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"budget_exhausted\":{\"type\":\"boolean\"},\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"max_age_days\":{\"minimum\":0,\"type\":\"integer\"},\"max_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"retained_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"retained_bytes\",\"max_bytes\",\"max_age_days\",\"budget_exhausted\"],\"type\":\"object\"},\"type\":\"array\"},\"dropped\":{\"additionalProperties\":true,\"description\":\"Observations dropped at admission, by reason.\",\"properties\":{\"budget_exhausted\":{\"minimum\":0,\"type\":\"integer\"},\"disabled\":{\"minimum\":0,\"type\":\"integer\"},\"queue_full\":{\"minimum\":0,\"type\":\"integer\"},\"untracked\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"queue_full\",\"budget_exhausted\",\"disabled\",\"untracked\"],\"type\":\"object\"},\"evidence\":{\"additionalProperties\":true,\"properties\":{\"backlog_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"backlog_items\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_written\":{\"minimum\":0,\"type\":\"integer\"},\"drop_ring_depth\":{\"minimum\":0,\"type\":\"integer\"},\"failures\":{\"additionalProperties\":true,\"properties\":{\"db_error\":{\"minimum\":0,\"type\":\"integer\"},\"disabled\":{\"minimum\":0,\"type\":\"integer\"},\"encode_failed\":{\"minimum\":0,\"type\":\"integer\"},\"interrupted\":{\"minimum\":0,\"type\":\"integer\"},\"write_failed\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"encode_failed\",\"write_failed\",\"db_error\",\"disabled\",\"interrupted\"],\"type\":\"object\"},\"high_water_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"high_water_items\":{\"minimum\":0,\"type\":\"integer\"},\"queue_allocated\":{\"type\":\"boolean\"},\"ready\":{\"minimum\":0,\"type\":\"integer\"},\"unavailable_submitted\":{\"minimum\":0,\"type\":\"integer\"},\"worker_running\":{\"type\":\"boolean\"}},\"required\":[\"queue_allocated\",\"worker_running\",\"backlog_items\",\"backlog_bytes\",\"high_water_items\",\"high_water_bytes\",\"drop_ring_depth\",\"ready\",\"unavailable_submitted\",\"bytes_written\",\"failures\"],\"type\":\"object\"},\"history\":{\"additionalProperties\":true,\"properties\":{\"available\":{\"description\":\"False when the observation tables could not be read; the DB-derived values are then zero.\",\"type\":\"boolean\"},\"epoch_regenerations\":{\"minimum\":0,\"type\":\"integer\"},\"head_seq\":{\"minimum\":0,\"type\":\"integer\"},\"observations\":{\"minimum\":0,\"type\":\"integer\"},\"oldest_observed_at\":{\"anyOf\":[{\"type\":\"null\"},{\"$ref\":\"#/components/schemas/ObservationTime\"}]},\"oldest_seq\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"available\",\"observations\",\"oldest_seq\",\"head_seq\",\"oldest_observed_at\",\"epoch_regenerations\"],\"type\":\"object\"},\"publications\":{\"additionalProperties\":true,\"properties\":{\"expired\":{\"minimum\":0,\"type\":\"integer\"},\"failed\":{\"minimum\":0,\"type\":\"integer\"},\"pending\":{\"minimum\":0,\"type\":\"integer\"},\"relay_lag_seconds\":{\"description\":\"Age of the oldest pending publication; 0 when none is pending.\",\"minimum\":0,\"type\":\"number\"},\"relay_running\":{\"type\":\"boolean\"},\"relayed\":{\"minimum\":0,\"type\":\"integer\"},\"relayed_no_route\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"pending\",\"failed\",\"expired\",\"relay_lag_seconds\",\"relay_running\",\"relayed\",\"relayed_no_route\"],\"type\":\"object\"}},\"required\":[\"active\",\"accepted\",\"dropped\",\"evidence\",\"history\",\"publications\",\"budget_exhausted_cameras\",\"cameras\"],\"type\":\"object\"},\"ObservationRecordingReference\":{\"additionalProperties\":true,\"properties\":{\"recording_id\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"recording_id\"],\"type\":\"object\"},\"ObservationTime\":{\"description\":\"UTC RFC 3339 with millisecond precision and a Z suffix (contract \xC2\xA72).\",\"format\":\"date-time\",\"type\":\"string\"},\"ObservationTrack\":{\"additionalProperties\":true,\"description\":\"Track hint only; compare id, namespace and session together. No lifecycle is implied (contract \xC2\xA73.4).\",\"properties\":{\"id\":{\"pattern\":\"^[0-9]{1,64}$\",\"type\":\"string\"},\"namespace\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"session\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}},\"required\":[\"id\",\"namespace\",\"session\"],\"type\":\"object\"},\"ObservationTransform\":{\"additionalProperties\":true,\"description\":\"Audit record of the inference-to-stored-image mapping: x = (u - pad_x) / scale_x, y = (v - pad_y) / scale_y (contract \xC2\xA74.4).\",\"properties\":{\"inference_height\":{\"minimum\":1,\"type\":\"integer\"},\"inference_width\":{\"minimum\":1,\"type\":\"integer\"},\"mode\":{\"enum\":[\"none\",\"resize\",\"letterbox\"],\"type\":\"string\"},\"pad_x\":{\"minimum\":0,\"type\":\"number\"},\"pad_y\":{\"minimum\":0,\"type\":\"number\"},\"scale_x\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"scale_y\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"source_height\":{\"minimum\":1,\"type\":\"integer\"},\"source_width\":{\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"mode\",\"source_width\",\"source_height\",\"inference_width\",\"inference_height\",\"scale_x\",\"scale_y\",\"pad_x\",\"pad_y\"],\"type\":\"object\"},\"ObservationUuid\":{\"description\":\"Lowercase canonical UUID (contract \xC2\xA72).\",\"pattern\":\"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$\",\"type\":\"string\"},\"OnvifAddRequest\":{\"additionalProperties\":false,\"properties\":{\"device_url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"password\":{\"maxLength\":63,\"type\":\"string\"},\"profile_token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"username\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[\"device_url\",\"profile_token\",\"stream_name\"],\"type\":\"object\"},\"OnvifAddResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\",\"stream_name\"],\"type\":\"object\"},\"OnvifClaimRequest\":{\"additionalProperties\":false,\"properties\":{\"inventory_uuid\":{\"format\":\"uuid\",\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"inventory_uuid\",\"stream_name\"],\"type\":\"object\"},\"OnvifDevice\":{\"additionalProperties\":false,\"properties\":{\"addresses\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"claim_state\":{\"type\":\"string\"},\"claimed_camera_uuid\":{\"type\":\"string\"},\"device_service\":{\"type\":\"string\"},\"discovery_time\":{\"type\":\"integer\"},\"duplicate_suspected\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"firmware_version\":{\"type\":\"string\"},\"first_seen_at\":{\"type\":\"integer\"},\"hardware_id\":{\"type\":\"string\"},\"imaging_service\":{\"type\":\"string\"},\"inventory_uuid\":{\"type\":\"string\"},\"ip_address\":{\"type\":\"string\"},\"last_scan_network\":{\"type\":\"string\"},\"last_seen_at\":{\"type\":\"integer\"},\"mac_address\":{\"type\":\"string\"},\"manufacturer\":{\"type\":\"string\"},\"media_service\":{\"type\":\"string\"},\"model\":{\"type\":\"string\"},\"online\":{\"type\":\"boolean\"},\"ptz_service\":{\"type\":\"string\"},\"serial_number\":{\"type\":\"string\"}},\"required\":[\"endpoint\",\"device_service\",\"media_service\",\"ptz_service\",\"imaging_service\",\"manufacturer\",\"model\",\"firmware_version\",\"serial_number\",\"hardware_id\",\"ip_address\",\"mac_address\",\"inventory_uuid\",\"last_scan_network\",\"claim_state\",\"claimed_camera_uuid\",\"discovery_time\",\"first_seen_at\",\"last_seen_at\",\"online\",\"duplicate_suspected\",\"addresses\"],\"type\":\"object\"},\"OnvifDevicesResponse\":{\"additionalProperties\":false,\"properties\":{\"devices\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifDevice\"},\"type\":\"array\"}},\"required\":[\"devices\"],\"type\":\"object\"},\"OnvifDiscoveryRequest\":{\"additionalProperties\":false,\"properties\":{\"network\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"OnvifDiscoveryStatus\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"interval\":{\"type\":\"integer\"},\"network\":{\"type\":\"string\"}},\"required\":[\"enabled\",\"network\",\"interval\"],\"type\":\"object\"},\"OnvifProfile\":{\"additionalProperties\":false,\"properties\":{\"bitrate\":{\"type\":\"integer\"},\"encoding\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"name\":{\"type\":\"string\"},\"ptz_configuration_token\":{\"type\":\"string\"},\"ptz_supported\":{\"type\":\"boolean\"},\"snapshot_uri\":{\"type\":\"string\"},\"stream_uri\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"},\"video_source_token\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"token\",\"name\",\"video_source_token\",\"ptz_configuration_token\",\"snapshot_uri\",\"stream_uri\",\"encoding\",\"width\",\"height\",\"fps\",\"bitrate\",\"ptz_supported\"],\"type\":\"object\"},\"OnvifProfilesResponse\":{\"additionalProperties\":false,\"properties\":{\"profiles\":{\"items\":{\"$ref\":\"#/components/schemas/OnvifProfile\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"profiles\"],\"type\":\"object\"},\"OnvifTestRequest\":{\"additionalProperties\":false,\"properties\":{\"insecure\":{\"type\":\"boolean\"},\"password\":{\"maxLength\":63,\"type\":\"string\"},\"url\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"},\"username\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[\"url\"],\"type\":\"object\"},\"OpenApiCoverage\":{\"additionalProperties\":true,\"description\":\"Describes the canonical pilot coverage, not user or camera identities. Omission of an unspecified API is not a denial of access.\",\"properties\":{\"canonical_revision\":{\"type\":\"string\"},\"covered_operations\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"},\"description\":{\"type\":\"string\"},\"partial\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"partial\",\"canonical_revision\",\"covered_operations\"],\"type\":\"object\"},\"OpenApiDocument\":{\"additionalProperties\":true,\"description\":\"A valid OpenAPI 3.1.1 document projected for the actual authenticated principal. This response schema checks its envelope; independent OpenAPI lint checks document validity. It can contain no camera-specific eligible operations while retaining authenticated discovery/list operations.\",\"properties\":{\"components\":{\"additionalProperties\":true,\"type\":\"object\"},\"info\":{\"additionalProperties\":true,\"properties\":{\"title\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"required\":[\"title\",\"version\"],\"type\":\"object\"},\"openapi\":{\"const\":\"3.1.1\",\"type\":\"string\"},\"paths\":{\"additionalProperties\":true,\"type\":\"object\"},\"x-lightnvr-coverage\":{\"$ref\":\"#/components/schemas/OpenApiCoverage\"}},\"required\":[\"openapi\",\"info\",\"paths\",\"x-lightnvr-coverage\"],\"type\":\"object\"},\"PasswordAdminReset\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"string\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"new_password\"],\"type\":\"object\"},\"PasswordChangeRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/PasswordSelfChange\"},{\"$ref\":\"#/components/schemas/PasswordAdminReset\",\"x-lightnvr-visibility\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}],\"description\":\"New password must contain at least eight UTF-8 bytes. Self-service requires the current password; effective users.manage can reset passwords except its own forced first-login change. Locked/default-password rules are enforced by the server.\"},\"PasswordChangedResponse\":{\"additionalProperties\":false,\"properties\":{\"must_change_password\":{\"const\":false},\"success\":{\"const\":true}},\"required\":[\"success\",\"must_change_password\"],\"type\":\"object\"},\"PasswordLockRequest\":{\"additionalProperties\":true,\"properties\":{\"locked\":{\"type\":\"boolean\"}},\"required\":[\"locked\"],\"type\":\"object\"},\"PasswordSelfChange\":{\"additionalProperties\":false,\"properties\":{\"new_password\":{\"type\":\"string\",\"writeOnly\":true},\"old_password\":{\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"old_password\",\"new_password\"],\"type\":\"object\"},\"PlaybackTransport\":{\"enum\":[\"auto\",\"webrtc_only\",\"mse_only\",\"hls_only\",\"webrtc_then_mse\",\"mse_then_hls\"],\"type\":\"string\"},\"PlayerTelemetry\":{\"additionalProperties\":true,\"properties\":{\"rebuffer_count\":{\"type\":\"integer\"},\"rebuffer_duration_ms\":{\"type\":\"number\"},\"resolution_switches\":{\"type\":\"integer\"},\"session_id\":{\"type\":\"string\"},\"stream_name\":{\"type\":\"string\"},\"transport\":{\"type\":\"string\"},\"ttff_ms\":{\"type\":\"number\"},\"webrtc_rtt_ms\":{\"type\":\"number\"}},\"required\":[\"stream_name\"],\"type\":\"object\"},\"ProcessLiveness\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"liveness\":{\"const\":true,\"type\":\"boolean\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"integer\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"liveness\"],\"type\":\"object\"},\"PtzAxesRequest\":{\"additionalProperties\":false,\"description\":\"Finite float-representable coordinates in the camera default space. Camera-advertised ranges vary; the server does not assume normalized position units. Omitted axes retain existing native zero/axis-presence semantics.\",\"minProperties\":1,\"properties\":{\"pan\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"tilt\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"},\"zoom\":{\"maximum\":3.4028234663852886e+38,\"minimum\":-3.4028234663852886e+38,\"type\":\"number\"}},\"required\":[],\"type\":\"object\"},\"PtzCapabilities\":{\"additionalProperties\":false,\"properties\":{\"has_absolute_move\":{\"type\":\"boolean\"},\"has_continuous_move\":{\"type\":\"boolean\"},\"has_home\":{\"type\":\"boolean\"},\"has_home_position\":{\"type\":\"boolean\"},\"has_pan_tilt\":{\"type\":\"boolean\"},\"has_presets\":{\"type\":\"boolean\"},\"has_relative_move\":{\"type\":\"boolean\"},\"has_zoom\":{\"type\":\"boolean\"},\"max_presets\":{\"type\":\"integer\"},\"max_x\":{\"type\":\"number\"},\"max_y\":{\"type\":\"number\"},\"max_z\":{\"type\":\"number\"},\"onvif_queried\":{\"type\":\"boolean\"},\"pan_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"ptz_service_url\":{\"type\":\"string\"},\"tilt_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"},\"zoom_range\":{\"$ref\":\"#/components/schemas/DeviceFloatRange\"}},\"required\":[\"ptz_enabled\",\"onvif_queried\",\"has_pan_tilt\",\"has_zoom\",\"has_continuous_move\",\"has_absolute_move\",\"has_relative_move\",\"has_home_position\",\"has_presets\",\"has_home\",\"pan_range\",\"tilt_range\",\"zoom_range\",\"max_x\",\"max_y\",\"max_z\",\"max_presets\",\"ptz_service_url\"],\"type\":\"object\"},\"PtzGotoPresetRequest\":{\"additionalProperties\":false,\"properties\":{\"token\":{\"maxLength\":63,\"minLength\":1,\"type\":\"string\"}},\"required\":[\"token\"],\"type\":\"object\"},\"PtzPresetRequest\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"maxLength\":63,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"PtzPresetResult\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true},\"token\":{\"type\":\"string\"}},\"required\":[\"success\",\"message\",\"token\"],\"type\":\"object\"},\"PtzPresets\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"type\":\"integer\"},\"presets\":{\"items\":{\"additionalProperties\":false,\"properties\":{\"name\":{\"type\":\"string\"},\"token\":{\"type\":\"string\"}},\"required\":[\"token\",\"name\"],\"type\":\"object\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"presets\",\"count\"],\"type\":\"object\"},\"RecordingBatchProtectionRequest\":{\"additionalProperties\":true,\"description\":\"Empty arrays are accepted. Invalid or missing IDs count as failures; an existing forbidden member rejects the entire request before writes.\",\"properties\":{\"ids\":{\"items\":{},\"type\":\"array\"},\"protected\":{\"type\":\"boolean\"}},\"required\":[\"ids\",\"protected\"],\"type\":\"object\"},\"RecordingBatchProtectionResult\":{\"additionalProperties\":true,\"properties\":{\"fail_count\":{\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"success_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"success_count\",\"fail_count\",\"protected\",\"message\"],\"type\":\"object\"},\"RecordingBatchTagsRequest\":{\"additionalProperties\":true,\"description\":\"Add/remove are optional; wrong-typed arrays and non-string or empty elements are ignored by the handler. All IDs are authorized before writes. Database failures may follow earlier successful tag writes.\",\"properties\":{\"add\":{\"items\":{},\"type\":\"array\"},\"ids\":{\"items\":{\"exclusiveMinimum\":0,\"type\":\"number\"},\"maxItems\":10000,\"minItems\":1,\"type\":\"array\"},\"remove\":{\"items\":{},\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},\"RecordingBatchTagsResult\":{\"additionalProperties\":true,\"properties\":{\"recordings_count\":{\"description\":\"Number of authorized input IDs processed, including duplicate IDs.\",\"minimum\":0,\"type\":\"integer\"},\"tags_added\":{\"description\":\"Successful per-recording add statement executions, including INSERT OR IGNORE no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"},\"tags_removed\":{\"description\":\"Successful per-recording remove statement executions, including absent-tag no-ops; not changed rows.\",\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"recordings_count\",\"tags_added\",\"tags_removed\"],\"type\":\"object\"},\"RecordingDeletionFilter\":{\"additionalProperties\":false,\"description\":\"stream_name (or stream fallback) selects comma-separated current camera names. Without a nonempty stream filter, all-fleet recording.delete authority is required. Authorized camera UUIDs and collection membership are captured before dispatch; caller-supplied camera UUID fields do not grant scope. Supplied start/end must be valid UTC YYYY-MM-DDTHH:mm:ss strings, optionally followed by fractional seconds and/or Z; fractions are truncated to whole seconds. Bounds must be after the Unix epoch and start must not exceed end. Invalid types, calendar dates, suffixes, or ranges return400 before job creation. Omitted bounds are unbounded. detection/protected must be -1, 0, or 1. Unknown keys, duplicate keys (including date-case aliases), simultaneous stream and stream_name, invalid types, and empty selectors return400. Date key spelling is case-insensitive; other keys are case-sensitive. Caller-supplied camera/stream UUID selectors are unsupported and rejected. In-progress rows are excluded.\",\"patternProperties\":{\"^(?!start$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"}},\"properties\":{\"capture_method\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"collection_uuid\":{\"maxLength\":36,\"minLength\":1,\"type\":\"string\"},\"detection\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"detection_label\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"end\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"protected\":{\"enum\":[-1,0,1],\"type\":\"integer\"},\"start\":{\"maxLength\":64,\"minLength\":1,\"type\":\"string\"},\"stream\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"stream_name\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"tag\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},\"required\":[],\"type\":\"object\"},\"RecordingDeletionJobAccepted\":{\"additionalProperties\":true,\"properties\":{\"job_id\":{\"type\":\"string\"},\"status\":{\"const\":\"started\"}},\"required\":[\"job_id\",\"status\"],\"type\":\"object\"},\"RecordingDeletionProgress\":{\"additionalProperties\":true,\"description\":\"complete is true for both complete and error terminal states. A complete job can have failed members or queued pending deletions; it does not imply every file is already removed. SQL failure is error; an empty successful filter completes with zero counts. No cancellation endpoint/state exists.\",\"properties\":{\"complete\":{\"type\":\"boolean\"},\"current\":{\"minimum\":0,\"type\":\"integer\"},\"error_message\":{\"type\":\"string\"},\"failed\":{\"minimum\":0,\"type\":\"integer\"},\"job_id\":{\"type\":\"string\"},\"pending_deletions\":{\"minimum\":0,\"type\":\"integer\"},\"status\":{\"enum\":[\"pending\",\"running\",\"complete\",\"error\",\"unknown\"],\"type\":\"string\"},\"status_message\":{\"type\":\"string\"},\"succeeded\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"job_id\",\"status\",\"total\",\"current\",\"succeeded\",\"pending_deletions\",\"failed\",\"status_message\",\"complete\"],\"type\":\"object\"},\"RecordingDeletionRequest\":{\"anyOf\":[{\"additionalProperties\":true,\"description\":\"IDs take precedence over filter. IDs accept safe positive integer JSON numbers or exact positive decimal uint64 strings. Invalid, unsafe numeric, or missing members become failed counts; a forbidden existing member rejects the whole request before job creation.\",\"properties\":{\"ids\":{\"items\":{},\"minItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"filter\":{\"$ref\":\"#/components/schemas/RecordingDeletionFilter\"},\"ids\":{\"anyOf\":[{\"items\":{},\"minItems\":1,\"type\":\"array\"},{\"type\":[\"null\",\"boolean\",\"object\",\"number\",\"string\"]}],\"description\":\"Array-valued ids take precedence over filter and must be nonempty. Other ids values fall through to the filter handler.\"}},\"required\":[\"filter\"],\"type\":\"object\"}]},\"RecordingDeletionResult\":{\"additionalProperties\":true,\"description\":\"Immediate200 has success/message. Queued202 also carries status deletion_pending; neither outcome promises deletion of unrelated recordings.\",\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"deletion_pending\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"RecordingDetail\":{\"additionalProperties\":true,\"description\":\"Authorized recording metadata. id is durable database identity, not list position. Times are UTC display strings with separate Unix seconds. In-progress rows have null end_time/end_time_unix and changing effective duration. playback_issue is empty for no saved issue; invalid_media/no_video/decode_error cause playback422. Other diagnostic values do not conclusively prohibit playback. Optional storage fields are omitted if their lookup fails. file_path is existing authorized metadata, not a client-selected playback path. A finalized row or timeline presence does not guarantee playable media.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pending\":{\"type\":\"boolean\"},\"detection_labels\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"durable_copy_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"in_progress\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"retention_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_time_unix\":{\"type\":\"integer\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_version\":{\"type\":\"integer\"},\"storage_state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable\",\"archiving\",\"archived\",\"hot_and_archive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"end_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"protected\"],\"type\":\"object\"},\"RecordingDetectionLabels\":{\"additionalProperties\":true,\"properties\":{\"labels\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"labels\"],\"type\":\"object\"},\"RecordingExportAccepted\":{\"additionalProperties\":true,\"properties\":{\"token\":{\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"token\",\"total\"],\"type\":\"object\"},\"RecordingExportRequest\":{\"additionalProperties\":true,\"description\":\"Positive numeric IDs are converted to uint64. Filename is sanitized for attachment delivery. Every member is authorized before a job is created; existing operational missing/incomplete errors can abort earlier in input order.\",\"properties\":{\"filename\":{\"type\":\"string\"},\"ids\":{\"items\":{\"description\":\"Recording ID: a number through 9007199254740991 (JavaScript maximum safe integer), otherwise an exact decimal string. Treat IDs as opaque values; do not convert strings to Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"maxItems\":200,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"ids\"],\"type\":\"object\"},\"RecordingExportStatus\":{\"additionalProperties\":true,\"description\":\"error is always present, usually empty until failure. Detected unavailable sources or ZIP32 member/size-limit failures put the job in error and remove its temporary result. The current writer does not consistently check filesystem read/write/close errors: complete is not a guarantee against undetected I/O failure or a valid intact archive. No cancellation endpoint/state exists.\",\"properties\":{\"current\":{\"minimum\":0,\"type\":\"integer\"},\"error\":{\"type\":\"string\"},\"status\":{\"enum\":[\"pending\",\"running\",\"complete\",\"error\"],\"type\":\"string\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"status\",\"current\",\"total\",\"error\"],\"type\":\"object\"},\"RecordingListItem\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"deletion_pending\":{\"type\":\"boolean\"},\"detection_labels\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"label\":{\"type\":\"string\"}},\"required\":[\"label\",\"count\"],\"type\":\"object\"},\"type\":\"array\"},\"durable_copy_count\":{\"type\":\"integer\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":[\"string\",\"null\"]},\"end_time_unix\":{\"type\":[\"integer\",\"null\"]},\"external_source\":{\"type\":\"boolean\"},\"file_path\":{\"type\":\"string\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"minimum\":1,\"type\":\"integer\"},\"in_progress\":{\"type\":\"boolean\"},\"playback_issue\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"},\"retention_mode\":{\"enum\":[\"inherit\",\"indefinite\",\"finite\"]},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_time_unix\":{\"type\":\"integer\"},\"storage_policy_uuid\":{\"type\":\"string\"},\"storage_policy_version\":{\"type\":\"integer\"},\"storage_state\":{\"enum\":[\"deletion_pending\",\"preparing\",\"unavailable\",\"archiving\",\"archived\",\"hot_and_archive\",\"hot\"]},\"storage_target_uuid\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"tags\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"file_path\",\"start_time\",\"end_time\",\"start_time_unix\",\"end_time_unix\",\"in_progress\",\"playback_issue\",\"duration\",\"size\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"protected\",\"tags\"],\"type\":\"object\"},\"RecordingPagination\":{\"additionalProperties\":true,\"description\":\"Counts use the same authorized predicate as rows. Ordinary totals may use a ten-second cache invalidated by supported database mutations; limit=all recounts and resets page1. No top-level total is emitted. When replay scope is empty, the early empty envelope retains the normalized requested page and ordinary limit (all is treated as20).\",\"properties\":{\"limit\":{\"minimum\":1,\"type\":\"integer\"},\"page\":{\"minimum\":1,\"type\":\"integer\"},\"pages\":{\"minimum\":0,\"type\":\"integer\"},\"total\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"page\",\"pages\",\"total\",\"limit\"],\"type\":\"object\"},\"RecordingPlaybackPreparing\":{\"additionalProperties\":true,\"properties\":{\"error\":{\"type\":\"string\"},\"source\":{\"const\":\"archive\"},\"status\":{\"const\":\"preparing\"}},\"required\":[\"status\"],\"type\":\"object\"},\"RecordingPlaybackReady\":{\"additionalProperties\":true,\"properties\":{\"source\":{\"const\":\"archive\"},\"status\":{\"const\":\"ready\"}},\"required\":[\"status\"],\"type\":\"object\"},\"RecordingProtectionRequest\":{\"additionalProperties\":true,\"properties\":{\"protected\":{\"type\":\"boolean\"}},\"required\":[\"protected\"],\"type\":\"object\"},\"RecordingProtectionResult\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"protected\":{\"type\":\"boolean\"}},\"required\":[\"id\",\"protected\",\"message\"],\"type\":\"object\"},\"RecordingRetentionRequest\":{\"additionalProperties\":true,\"description\":\"Numeric input is truncated toward zero through the C JSON integer accessor. Any negative value clears the override; zero means indefinite; positive values are days.\",\"properties\":{\"retention_days\":{\"type\":\"number\"}},\"required\":[\"retention_days\"],\"type\":\"object\"},\"RecordingRetentionResult\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"retention_days\":{\"type\":\"integer\"}},\"required\":[\"id\",\"retention_days\",\"message\"],\"type\":\"object\"},\"RecordingTags\":{\"additionalProperties\":true,\"properties\":{\"tags\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTagsById\":{\"additionalProperties\":true,\"properties\":{\"id\":{\"minimum\":1,\"type\":\"integer\"},\"tags\":{\"items\":{\"type\":\"string\"},\"type\":\"array\"}},\"required\":[\"id\",\"tags\"],\"type\":\"object\"},\"RecordingTagsRequest\":{\"additionalProperties\":true,\"description\":\"Only nonempty string elements are used; other elements are ignored. The database trims tag text, truncates to its fixed UTF-8 byte buffer, and deduplicates. This is replacement, including clearing with an empty array.\",\"properties\":{\"tags\":{\"items\":{},\"type\":\"array\"}},\"required\":[\"tags\"],\"type\":\"object\"},\"RecordingTimeline\":{\"additionalProperties\":true,\"description\":\"One authorized camera and time window, capped at8640 segments. Optional exact external-motion intervals may be absent on allocation failure.\",\"properties\":{\"detection_interval_count\":{\"minimum\":0,\"type\":\"integer\"},\"detection_intervals\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"end_timestamp\":{\"type\":\"integer\"},\"start_timestamp\":{\"type\":\"integer\"}},\"required\":[\"start_timestamp\",\"end_timestamp\"],\"type\":\"object\"},\"type\":\"array\"},\"end_time\":{\"type\":\"string\"},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\",\"segment_count\"],\"type\":\"object\"},\"RecordingTimelineByIds\":{\"additionalProperties\":true,\"description\":\"Requested IDs are processed in request order. Missing, deleted-camera and denied recordings are omitted. Counts and overall range reflect retained segments only; an empty result has empty start_time/end_time. Duplicate input IDs can produce duplicate segments.\",\"properties\":{\"end_time\":{\"type\":\"string\"},\"multi_stream\":{\"const\":true},\"segment_count\":{\"minimum\":0,\"type\":\"integer\"},\"segments\":{\"items\":{\"$ref\":\"#/components/schemas/RecordingTimelineSegment\"},\"type\":\"array\"},\"start_time\":{\"type\":\"string\"},\"stream\":{\"const\":\"(selected)\"}},\"required\":[\"segments\",\"stream\",\"start_time\",\"end_time\",\"segment_count\",\"multi_stream\"],\"type\":\"object\"},\"RecordingTimelineSegment\":{\"additionalProperties\":true,\"description\":\"Timeline timestamps are Unix seconds; display strings use server local YYYY-MM-DD HH:MM:SS. local_* fields currently equal the Unix timestamp fields. In-progress segments use an effective end for display. Timeline segments omit playback_issue and protected/storage metadata: use detail/playback before treating a finalized segment as playable.\",\"properties\":{\"camera_uuid\":{\"pattern\":\"^[0-9a-fA-F-]{36}$\",\"type\":[\"string\",\"null\"]},\"capture_method\":{\"type\":\"string\"},\"duration\":{\"type\":\"number\"},\"end_time\":{\"type\":\"string\"},\"end_timestamp\":{\"type\":\"integer\"},\"has_detection\":{\"type\":\"boolean\"},\"id\":{\"description\":\"Recording ID: a number through 9007199254740991 (JavaScript maximum safe integer), otherwise an exact decimal string. Treat IDs as opaque values; do not convert strings to Number.\",\"oneOf\":[{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},{\"pattern\":\"^[1-9][0-9]{0,19}$\",\"type\":\"string\"}]},\"in_progress\":{\"type\":\"boolean\"},\"local_end_timestamp\":{\"type\":\"integer\"},\"local_start_timestamp\":{\"type\":\"integer\"},\"schedule_restricted\":{\"type\":[\"boolean\",\"null\"]},\"size\":{\"type\":\"string\"},\"start_time\":{\"type\":\"string\"},\"start_timestamp\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"id\",\"stream\",\"camera_uuid\",\"start_time\",\"in_progress\",\"duration\",\"size\",\"capture_method\",\"schedule_restricted\",\"has_detection\",\"end_time\",\"start_timestamp\",\"end_timestamp\",\"local_start_timestamp\",\"local_end_timestamp\"],\"type\":\"object\"},\"RecordingsPage\":{\"additionalProperties\":true,\"properties\":{\"pagination\":{\"$ref\":\"#/components/schemas/RecordingPagination\"},\"recordings\":{\"items\":{\"$ref\":\"#/components/schemas/RecordingListItem\"},\"type\":\"array\"}},\"required\":[\"recordings\",\"pagination\"],\"type\":\"object\"},\"SetupStatus\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\":\"boolean\"},\"setup_completed_at\":{\"type\":\"number\"}},\"required\":[\"complete\",\"setup_completed_at\"],\"type\":\"object\"},\"SetupUpdate\":{\"additionalProperties\":true,\"properties\":{\"complete\":{\"type\":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"SetupUpdated\":{\"additionalProperties\":true,\"properties\":{\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\"],\"type\":\"object\"},\"StorageArchiveDeletion\":{\"additionalProperties\":false,\"properties\":{\"attempts\":{\"minimum\":0,\"type\":\"integer\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"next_attempt_at\":{\"minimum\":0,\"type\":\"integer\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"remaining_objects\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"recording_id\",\"created_at\",\"remaining_objects\",\"attempts\",\"next_attempt_at\",\"last_error\"],\"type\":\"object\"},\"StorageArchiveResponse\":{\"additionalProperties\":false,\"properties\":{\"archive_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"pending_deletion_count\":{\"minimum\":0,\"type\":\"integer\"},\"pending_deletions\":{\"items\":{\"$ref\":\"#/components/schemas/StorageArchiveDeletion\"},\"maxItems\":100,\"type\":\"array\"},\"recovery_read_only\":{\"type\":\"boolean\"},\"retrieval_cache_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"retrieval_jobs\":{\"minimum\":0,\"type\":\"integer\"},\"s3_enabled\":{\"type\":\"boolean\"},\"transfer_backlog_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"s3_enabled\",\"recovery_read_only\",\"archive_bytes\",\"transfer_backlog_bytes\",\"retrieval_cache_bytes\",\"retrieval_jobs\",\"pending_deletion_count\",\"pending_deletions\"],\"type\":\"object\"},\"StorageArchiveRetryRequest\":{\"additionalProperties\":false,\"properties\":{\"deletion_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"deletion_uuid\"],\"type\":\"object\"},\"StorageArchiveRetryResponse\":{\"additionalProperties\":false,\"properties\":{\"status\":{\"const\":\"retry_queued\",\"type\":\"string\"}},\"required\":[\"status\"],\"type\":\"object\"},\"StorageCleanupRequest\":{\"additionalProperties\":false,\"properties\":{\"aggressive\":{\"type\":\"boolean\"}},\"required\":[],\"type\":\"object\"},\"StorageCleanupResponse\":{\"additionalProperties\":false,\"properties\":{\"aggressive\":{\"type\":\"boolean\"},\"message\":{\"const\":\"Cleanup triggered successfully\",\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"aggressive\",\"message\"],\"type\":\"object\"},\"StorageCompliancePolicy\":{\"additionalProperties\":false,\"properties\":{\"achieved_retention_days\":{\"type\":\"number\"},\"active_violation_count\":{\"minimum\":0,\"type\":\"integer\"},\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"copy_deficit_recordings\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"expected_retention_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"maximum_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"minimum_forecast_met\":{\"type\":\"boolean\"},\"minimum_retention_days\":{\"minimum\":0,\"type\":\"integer\"},\"observed_daily_bytes\":{\"type\":\"number\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"required_copy_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_window_days\":{\"type\":\"number\"}},\"required\":[\"policy_uuid\",\"policy_name\",\"recording_count\",\"recording_bytes\",\"minimum_retention_days\",\"desired_retention_days\",\"maximum_retention_days\",\"required_copy_count\",\"copy_deficit_recordings\",\"active_violation_count\",\"sample_window_days\",\"observed_daily_bytes\",\"achieved_retention_days\",\"confidence\",\"expected_retention_days\",\"minimum_forecast_met\"],\"type\":\"object\"},\"StorageComplianceResponse\":{\"additionalProperties\":false,\"properties\":{\"active_violations\":{\"items\":{\"$ref\":\"#/components/schemas/StorageComplianceViolation\"},\"type\":\"array\"},\"forecast_note\":{\"type\":\"string\"},\"forecast_window_days\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/schemas/StorageCompliancePolicy\"},\"type\":\"array\"},\"targets\":{\"items\":{\"$ref\":\"#/components/schemas/StorageComplianceTarget\"},\"type\":\"array\"}},\"required\":[\"forecast_window_days\",\"forecast_note\",\"targets\",\"policies\",\"active_violations\"],\"type\":\"object\"},\"StorageComplianceTarget\":{\"additionalProperties\":false,\"properties\":{\"confidence\":{\"enum\":[\"low\",\"medium\",\"high\"],\"type\":\"string\"},\"days_to_high_watermark\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"expected_retention_days\":{\"anyOf\":[{\"minimum\":0,\"type\":\"number\"},{\"type\":\"null\"}]},\"observed_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"observed_daily_bytes\":{\"minimum\":0,\"type\":\"number\"},\"sample_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample_window_days\":{\"minimum\":0,\"type\":\"number\"},\"target_name\":{\"type\":\"string\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"target_uuid\",\"target_name\",\"sample_window_days\",\"observed_daily_bytes\",\"sample_count\",\"observed_bytes\",\"confidence\",\"days_to_high_watermark\",\"expected_retention_days\"],\"type\":\"object\"},\"StorageComplianceViolation\":{\"additionalProperties\":false,\"properties\":{\"details\":{\"type\":\"string\"},\"first_seen_at\":{\"minimum\":0,\"type\":\"integer\"},\"last_seen_at\":{\"minimum\":0,\"type\":\"integer\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"recording_id\":{\"anyOf\":[{\"minimum\":1,\"type\":\"integer\"},{\"type\":\"null\"}]},\"type\":{\"type\":\"string\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"policy_uuid\",\"policy_name\",\"recording_id\",\"type\",\"details\",\"first_seen_at\",\"last_seen_at\"],\"type\":\"object\"},\"StorageHealthResponse\":{\"additionalProperties\":false,\"properties\":{\"capacity_target_free_pct\":{\"minimum\":0,\"type\":\"integer\"},\"fill_rate_bytes_per_day\":{\"type\":\"number\"},\"free_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"free_space_pct\":{\"type\":\"number\"},\"last_check_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_deleted\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_freed\":{\"minimum\":0,\"type\":\"integer\"},\"last_cleanup_time\":{\"minimum\":0,\"type\":\"integer\"},\"last_deep_time\":{\"minimum\":0,\"type\":\"integer\"},\"pressure_level\":{\"type\":\"string\"},\"pressure_level_num\":{\"minimum\":0,\"type\":\"integer\"},\"projected_seconds_to_target\":{\"type\":\"number\"},\"total_space_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_space_bytes\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"pressure_level\",\"pressure_level_num\",\"free_space_pct\",\"fill_rate_bytes_per_day\",\"projected_seconds_to_target\",\"free_space_bytes\",\"total_space_bytes\",\"used_space_bytes\",\"last_check_time\",\"last_cleanup_time\",\"last_deep_time\",\"last_cleanup_deleted\",\"last_cleanup_freed\",\"capacity_target_free_pct\"],\"type\":\"object\"},\"StorageMigration\":{\"additionalProperties\":false,\"properties\":{\"attempt_count\":{\"minimum\":0,\"type\":\"integer\"},\"bandwidth_limit_bps\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_copied\":{\"minimum\":0,\"type\":\"integer\"},\"bytes_total\":{\"minimum\":0,\"type\":\"integer\"},\"cancel_requested\":{\"type\":\"boolean\"},\"checksum\":{\"anyOf\":[{\"pattern\":\"^[0-9a-f]{64}$\",\"type\":\"string\"},{\"type\":\"null\"}]},\"checksum_mode\":{\"const\":\"sha256\",\"type\":\"string\"},\"completed_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"destination_object_key\":{\"type\":\"string\"},\"destination_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"last_error\":{\"type\":\"string\"},\"max_attempts\":{\"minimum\":0,\"type\":\"integer\"},\"next_attempt_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"progress\":{\"minimum\":0,\"type\":\"number\"},\"recording_id\":{\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"source_object_key\":{\"type\":\"string\"},\"source_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"started_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"state\":{\"enum\":[\"queued\",\"copying\",\"verifying\",\"committing\",\"retry_wait\",\"cleanup_pending\",\"completed\",\"failed\",\"cancelled\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"uuid\",\"source_target_uuid\",\"destination_target_uuid\",\"recording_id\",\"operation\",\"source_object_key\",\"destination_object_key\",\"last_error\",\"state\",\"checksum_mode\",\"checksum\",\"bytes_total\",\"bytes_copied\",\"attempt_count\",\"max_attempts\",\"bandwidth_limit_bps\",\"progress\",\"next_attempt_at\",\"cancel_requested\",\"window_start_minute\",\"window_end_minute\",\"revision\",\"created_at\",\"updated_at\",\"started_at\",\"completed_at\"],\"type\":\"object\"},\"StorageMigrationCreate\":{\"additionalProperties\":false,\"properties\":{\"destination_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"operation\":{\"enum\":[\"move\",\"copy\"],\"type\":\"string\"},\"recording_id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"recording_id\",\"destination_target_uuid\"],\"type\":\"object\"},\"StorageMigrationsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"jobs\":{\"items\":{\"$ref\":\"#/components/schemas/StorageMigration\"},\"type\":\"array\"},\"worker_concurrency\":{\"const\":1,\"type\":\"integer\"}},\"required\":[\"jobs\",\"count\",\"worker_concurrency\"],\"type\":\"object\"},\"StoragePoliciesResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"policies\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicy\"},\"type\":\"array\"}},\"required\":[\"policies\",\"count\"],\"type\":\"object\"},\"StoragePolicy\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"enabled\",\"priority\",\"selector\",\"primary_target_uuid\",\"primary_pool_uuid\",\"fallback_target_uuid\",\"replication_pool_uuid\",\"migration_target_uuid\",\"fallback_mode\",\"minimum_retention_days\",\"desired_retention_days\",\"maximum_retention_days\",\"migration_after_days\",\"required_copy_count\",\"pressure_priority\",\"archive_after_seconds\",\"hot_residency_seconds\",\"archive_protected\",\"archive_on_pressure\",\"revision\",\"created_at\",\"updated_at\"],\"type\":\"object\"},\"StoragePolicyApplyRequest\":{\"additionalProperties\":false,\"properties\":{\"preview\":{\"type\":\"boolean\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"revision\",\"preview\"],\"type\":\"object\"},\"StoragePolicyApplyResponse\":{\"additionalProperties\":false,\"properties\":{\"bytes\":{\"minimum\":0,\"type\":\"integer\"},\"past_new_retention_limit\":{\"minimum\":0,\"type\":\"integer\"},\"preview\":{\"type\":\"boolean\"},\"recordings\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"unfinished_transfers\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"revision\",\"preview\",\"recordings\",\"bytes\",\"past_new_retention_limit\",\"unfinished_transfers\"],\"type\":\"object\"},\"StoragePolicyConflict\":{\"additionalProperties\":false,\"properties\":{\"draft_precedes\":{\"type\":\"boolean\"},\"overlap_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"policy_uuid\",\"policy_name\",\"priority\",\"overlap_camera_count\",\"draft_precedes\"],\"type\":\"object\"},\"StoragePolicyCreate\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"primary_target_uuid\"]},{\"properties\":{\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]}},\"required\":[\"primary_pool_uuid\"]}],\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\"],\"type\":\"object\"},\"StoragePolicyPreview\":{\"additionalProperties\":false,\"properties\":{\"conflict_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"conflict_policy_count\":{\"minimum\":0,\"type\":\"integer\"},\"conflicts\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyConflict\"},\"type\":\"array\"},\"draft_enabled\":{\"type\":\"boolean\"},\"effective_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"matched_camera_count\":{\"minimum\":0,\"type\":\"integer\"},\"sample\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePolicyPreviewSample\"},\"maxItems\":50,\"type\":\"array\"},\"sample_truncated\":{\"type\":\"boolean\"},\"shadowed_camera_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"draft_enabled\",\"sample_truncated\",\"matched_camera_count\",\"effective_camera_count\",\"shadowed_camera_count\",\"conflict_camera_count\",\"conflict_policy_count\",\"sample\",\"conflicts\"],\"type\":\"object\"},\"StoragePolicyPreviewRequest\":{\"additionalProperties\":false,\"anyOf\":[{\"properties\":{\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"revision\"]},{\"properties\":{\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\",\"primary_target_uuid\"]},{\"properties\":{\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"name\",\"selector\",\"primary_pool_uuid\"]}],\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[],\"type\":\"object\"},\"StoragePolicyPreviewSample\":{\"additionalProperties\":false,\"properties\":{\"camera_name\":{\"type\":\"string\"},\"camera_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"effective_policy\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StoragePolicyWinner\"},{\"type\":\"null\"}]},\"overlapping_policy_count\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"camera_name\",\"overlapping_policy_count\",\"effective_policy\"],\"type\":\"object\"},\"StoragePolicyUpdate\":{\"additionalProperties\":false,\"properties\":{\"archive_after_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"archive_on_pressure\":{\"type\":\"boolean\"},\"archive_protected\":{\"type\":\"boolean\"},\"desired_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"hot_residency_seconds\":{\"maximum\":2147483647,\"minimum\":-1,\"type\":\"integer\"},\"maximum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_after_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"migration_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"minimum_retention_days\":{\"maximum\":36500,\"minimum\":0,\"type\":\"integer\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"pressure_priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"maximum\":1000000,\"minimum\":-1000000,\"type\":\"integer\"},\"replication_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"required_copy_count\":{\"maximum\":8,\"minimum\":1,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"selector\":{\"$ref\":\"#/components/schemas/FleetSelector\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StoragePolicyWinner\":{\"additionalProperties\":false,\"properties\":{\"draft\":{\"type\":\"boolean\"},\"fallback_mode\":{\"enum\":[\"default\",\"target\",\"pause\",\"fail\"],\"type\":\"string\"},\"fallback_target_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"policy_name\":{\"type\":\"string\"},\"policy_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_pool_uuid\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/CameraUuid\"},{\"type\":\"null\"}]},\"primary_target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"priority\":{\"minimum\":-1000000,\"type\":\"integer\"}},\"required\":[\"draft\",\"policy_uuid\",\"policy_name\",\"priority\",\"primary_target_uuid\",\"primary_pool_uuid\",\"fallback_mode\",\"fallback_target_uuid\"],\"type\":\"object\"},\"StoragePool\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePoolMember\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"strategy\",\"enabled\",\"revision\",\"created_at\",\"updated_at\",\"members\"],\"type\":\"object\"},\"StoragePoolCreate\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"}},\"required\":[\"name\",\"members\"],\"type\":\"object\"},\"StoragePoolMember\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"integer\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"target_uuid\",\"position\",\"weight\"],\"type\":\"object\"},\"StoragePoolMemberRequest\":{\"additionalProperties\":false,\"properties\":{\"position\":{\"minimum\":0,\"type\":\"integer\"},\"target_uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"},\"weight\":{\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},\"required\":[\"target_uuid\"],\"type\":\"object\"},\"StoragePoolUpdate\":{\"additionalProperties\":false,\"properties\":{\"enabled\":{\"type\":\"boolean\"},\"members\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePoolMemberRequest\"},\"maxItems\":128,\"minItems\":1,\"type\":\"array\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"strategy\":{\"enum\":[\"most_free\",\"round_robin\",\"priority\"],\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StoragePoolsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"pools\":{\"items\":{\"$ref\":\"#/components/schemas/StoragePool\"},\"maxItems\":64,\"type\":\"array\"}},\"required\":[\"pools\",\"count\"],\"type\":\"object\"},\"StorageTarget\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"archive_only\":{\"type\":\"boolean\"},\"bucket\":{\"type\":\"string\"},\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"health\":{\"$ref\":\"#/components/schemas/StorageTargetHealth\"},\"high_watermark_pct\":{\"type\":\"number\"},\"is_default\":{\"type\":\"boolean\"},\"low_watermark_pct\":{\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"integer\"},\"mount_guard_path\":{\"anyOf\":[{\"type\":\"string\"},{\"type\":\"null\"}]},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"type\":\"string\"},\"recording_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"recording_count\":{\"minimum\":0,\"type\":\"integer\"},\"region\":{\"type\":\"string\"},\"replica_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"replica_count\":{\"minimum\":0,\"type\":\"integer\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"},\"updated_at\":{\"minimum\":0,\"type\":\"integer\"},\"uuid\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},\"required\":[\"uuid\",\"name\",\"root_path\",\"endpoint\",\"region\",\"bucket\",\"credential_ref\",\"target_type\",\"archive_only\",\"enabled\",\"is_default\",\"mount_required\",\"mount_guard_path\",\"storage_class\",\"archive_budget_bytes\",\"reserve_bytes\",\"recording_count\",\"recording_bytes\",\"replica_count\",\"replica_bytes\",\"migration_bandwidth_bps\",\"high_watermark_pct\",\"low_watermark_pct\",\"archival_window_start_minute\",\"archival_window_end_minute\",\"revision\",\"created_at\",\"updated_at\",\"health\"],\"type\":\"object\"},\"StorageTargetCreate\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_watermark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\":{\"exclusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"}},\"required\":[\"name\",\"root_path\"],\"type\":\"object\"},\"StorageTargetHealth\":{\"additionalProperties\":false,\"properties\":{\"available_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"capacity_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"capacity_known\":{\"type\":\"boolean\"},\"cleanup_target_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"duplicate_filesystem\":{\"type\":\"boolean\"},\"filesystem_device\":{\"minimum\":0,\"type\":\"integer\"},\"last_error\":{\"type\":\"string\"},\"last_probe_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"last_success_at\":{\"anyOf\":[{\"minimum\":0,\"type\":\"integer\"},{\"type\":\"null\"}]},\"pressure\":{\"type\":\"string\"},\"status\":{\"type\":\"string\"},\"used_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"used_pct\":{\"minimum\":0,\"type\":\"number\"}},\"required\":[\"status\",\"pressure\",\"last_error\",\"capacity_known\",\"duplicate_filesystem\",\"capacity_bytes\",\"available_bytes\",\"used_bytes\",\"cleanup_target_bytes\",\"filesystem_device\",\"used_pct\",\"last_probe_at\",\"last_success_at\"],\"type\":\"object\"},\"StorageTargetUpdate\":{\"additionalProperties\":false,\"properties\":{\"archival_window_end_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archival_window_start_minute\":{\"maximum\":1439,\"minimum\":0,\"type\":\"integer\"},\"archive_budget_bytes\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"bucket\":{\"type\":\"string\"},\"credential_ref\":{\"type\":\"string\"},\"enabled\":{\"type\":\"boolean\"},\"endpoint\":{\"type\":\"string\"},\"high_watermark_pct\":{\"exclusiveMaximum\":100,\"exclusiveMinimum\":0,\"type\":\"number\"},\"low_watermark_pct\":{\"exclusiveMaximum\":100,\"minimum\":0,\"type\":\"number\"},\"migration_bandwidth_bps\":{\"minimum\":0,\"type\":\"integer\"},\"mount_required\":{\"type\":\"boolean\"},\"name\":{\"maxLength\":127,\"minLength\":1,\"type\":\"string\"},\"region\":{\"type\":\"string\"},\"reserve_bytes\":{\"minimum\":0,\"type\":\"integer\"},\"revision\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"root_path\":{\"type\":\"string\"},\"storage_class\":{\"enum\":[\"hot\",\"warm\",\"cold\"],\"type\":\"string\"},\"target_type\":{\"enum\":[\"filesystem\",\"s3\"],\"type\":\"string\"}},\"required\":[\"revision\"],\"type\":\"object\"},\"StorageTargetsResponse\":{\"additionalProperties\":false,\"properties\":{\"count\":{\"minimum\":0,\"type\":\"integer\"},\"targets\":{\"items\":{\"$ref\":\"#/components/schemas/StorageTarget\"},\"maxItems\":128,\"type\":\"array\"}},\"required\":[\"targets\",\"count\"],\"type\":\"object\"},\"StreamCompatibilityList\":{\"description\":\"Default GET /api/streams response. An empty array is valid. Each row is filtered and redacted using runtime plus go2rtc health observations.\",\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/components/schemas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"StreamConfigurationUpdate\":{\"additionalProperties\":true,\"description\":\"Partial ordinary update. All fields are optional and omission preserves existing state. Unknown fields (including detected width/height/fps) are currently ignored. This typed contract describes intended values; existing handlers can ignore wrong types instead of returning 400. It does not promise every malformed value is rejected by C.\",\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabled\":{\"const\":false}},\"required\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"admin_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_based_recording\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_interval\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"detection_model\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter_list\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"detection_retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"detection_threshold\":{\"description\":\"Percentage confidence, including zero; divided by 100 for storage. Omission/null preserves the current value. The handler does not clamp this numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values must start with http://, https://, rtsp:// or rtsps:// (case-insensitive), at most 511 UTF-8 bytes.\",\"type\":[\"string\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty string disables; otherwise at most 1023 UTF-8 bytes containing version=1, projection=equidistant, mount=ceiling and bounded centerX/centerY [0,1], radius [.05,1], fov [160,360], rotation/defaultYaw [-360,360], defaultTilt [-90,30], defaultViewFov [20,120]. Null is rejected. Unknown document fields are tolerated.\",\"maxLength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Whitespace-only also clears. Maximum 8191 UTF-8 bytes. Nonblank multiline values are YAML-validated when YAML support is enabled; malformed YAML can return a structured error. Single-line values are not YAML-checked.\",\"type\":[\"string\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"motion_trigger_source\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"description\":\"Optional identifying metadata; ignored. This operation does not rename the stream.\",\"type\":\"string\"},\"observation_evidence_enabled\":{\"description\":\"INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"type\":\"boolean\"},\"observation_labels\":{\"description\":\"Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"maxLength\":511,\"type\":\"string\"},\"observation_max_age_days\":{\"description\":\"Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"maximum\":90,\"minimum\":1,\"type\":\"integer\"},\"observation_max_bytes\":{\"description\":\"Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"maximum\":1099511627776,\"minimum\":67108864,\"type\":\"integer\"},\"onvif_password\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_port\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"pre_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_y\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values are a supported RTMP/RTMPS destination list, at most 511 UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\":[\"string\",\"null\"]},\"record\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_audio\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tags\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]}},\"required\":[],\"type\":\"object\"},\"StreamCreateRequest\":{\"additionalProperties\":true,\"not\":{\"properties\":{\"enable_disabled\":{\"const\":true},\"enabled\":{\"const\":false}},\"required\":[\"enable_disabled\",\"enabled\"],\"type\":\"object\"},\"properties\":{\"admin_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"audio_voice_enhancement\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"backchannel_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"codec\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_based_recording\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_interval\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"detection_model\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_object_filter_list\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler.\",\"type\":[\"string\",\"null\"]},\"detection_record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"detection_recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"detection_retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"detection_threshold\":{\"description\":\"Percentage confidence, including zero; divided by 100 for storage. Omission/null preserves the current value. The handler does not clamp this numeric field.\",\"type\":[\"number\",\"null\"]},\"detection_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values must start with http://, https://, rtsp:// or rtsps:// (case-insensitive), at most 511 UTF-8 bytes.\",\"type\":[\"string\",\"null\"]},\"enable_disabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"eptz_config\":{\"description\":\"Empty string disables; otherwise at most 1023 UTF-8 bytes containing version=1, projection=equidistant, mount=ceiling and bounded centerX/centerY [0,1], radius [.05,1], fov [160,360], rotation/defaultYaw [-360,360], defaultTilt [-90,30], defaultViewFov [20,120]. Null is rejected. Unknown document fields are tolerated.\",\"maxLength\":1023,\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Whitespace-only also clears. Maximum 8191 UTF-8 bytes. Nonblank multiline values are YAML-validated when YAML support is enabled; malformed YAML can return a structured error. Single-line values are not YAML-checked.\",\"type\":[\"string\",\"null\"]},\"isOnvif\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"max_storage_mb\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"motion_trigger_source\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"name\":{\"maxLength\":255,\"minLength\":1,\"pattern\":\"^[A-Za-z0-9_.-]+$\",\"type\":\"string\"},\"observation_evidence_enabled\":{\"description\":\"INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"type\":\"boolean\"},\"observation_labels\":{\"description\":\"Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"maxLength\":511,\"type\":\"string\"},\"observation_max_age_days\":{\"description\":\"Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"maximum\":90,\"minimum\":1,\"type\":\"integer\"},\"observation_max_bytes\":{\"description\":\"Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart.\",\"maximum\":1099511627776,\"minimum\":67108864,\"type\":\"integer\"},\"onvif_password\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_port\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"onvif_profile\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"onvif_username\":{\"description\":\"Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response.\",\"type\":[\"string\",\"null\"]},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"pre_detection_buffer\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"protocol\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_has_home\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"ptz_max_x\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_y\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"ptz_max_z\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"publish_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value. Nonempty values are a supported RTMP/RTMPS destination list, at most 511 UTF-8 bytes; each destination must pass the publish URL validator.\",\"type\":[\"string\",\"null\"]},\"record\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_audio\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"record_on_schedule\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"recording_schedule\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/WeeklySchedule\"},{\"type\":\"null\"}],\"description\":\"Omission/null preserves; use exactly 168 booleans. The current handler ignores wrong-length arrays and treats each non-true element as false, rather than rejecting all malformed schedule input.\"},\"retention_days\":{\"description\":\"Numeric input is truncated toward zero and must yield -1..365: -1 inherits global, 0 unlimited, positive days. Null/omission preserves. For predictable clients, send an integer.\",\"exclusiveMaximum\":366,\"exclusiveMinimum\":-2,\"type\":[\"number\",\"null\"]},\"segment_duration\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"set_privacy_mode\":{\"description\":\"A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form.\",\"type\":\"null\"},\"storage_priority\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"streaming_enabled\":{\"description\":\"Omission or null preserves the current value.\",\"type\":[\"boolean\",\"null\"]},\"sub_stream_url\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tags\":{\"description\":\"Null or empty string clears this override; omission preserves the current value.\",\"type\":[\"string\",\"null\"]},\"tier_critical_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_ephemeral_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"tier_important_multiplier\":{\"description\":\"Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero.\",\"type\":[\"number\",\"null\"]},\"url\":{\"minLength\":1,\"type\":\"string\"}},\"required\":[\"name\",\"url\"],\"type\":\"object\"},\"StreamCreated\":{\"additionalProperties\":true,\"properties\":{\"camera_uuid\":{\"type\":\"string\"},\"location_uuid\":{\"type\":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"],\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"camera_uuid\",\"location_uuid\"],\"type\":\"object\"},\"StreamDeleteAccepted\":{\"additionalProperties\":true,\"properties\":{\"permanent\":{\"type\":\"boolean\"},\"status\":{\"const\":\"accepted\",\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"permanent\",\"status\"],\"type\":\"object\"},\"StreamDetailsBase\":{\"additionalProperties\":true,\"description\":\"Fields shared by compatibility-list rows and full detail. Strings use empty values rather than null; additional fields are preserved for additive compatibility.\",\"properties\":{\"audio_voice_enhancement\":{\"type\":\"boolean\"},\"backchannel_enabled\":{\"type\":\"boolean\"},\"camera_uuid\":{\"description\":\"Stable camera UUID. Older migrated rows may have an empty string; no instance identifiers are embedded in this document.\",\"type\":\"string\"},\"codec\":{\"type\":\"string\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"detection_interval\":{\"type\":\"integer\"},\"detection_model\":{\"type\":\"string\"},\"detection_object_filter\":{\"type\":\"string\"},\"detection_object_filter_list\":{\"type\":\"string\"},\"detection_record_on_schedule\":{\"type\":\"boolean\"},\"detection_recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"detection_retention_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive values are days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"detection_threshold\":{\"description\":\"Integer percentage emitted from stored float confidence, including zero. The existing update handler does not clamp this to 0\xE2\x80\x93100.\",\"type\":\"integer\"},\"enabled\":{\"type\":\"boolean\"},\"eptz_config\":{\"description\":\"Empty string disables browser dewarping; otherwise a serialized version-1 JSON document. It is a string on the wire, not an object.\",\"maxLength\":1023,\"type\":\"string\"},\"error_message\":{\"description\":\"Present only when an Error state has a recorded reason.\",\"type\":\"string\"},\"fps\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"integer\"},\"go2rtc_hls_available\":{\"type\":\"boolean\"},\"has_sub_stream\":{\"type\":\"boolean\"},\"height\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"integer\"},\"isOnvif\":{\"type\":\"boolean\"},\"location_uuid\":{\"description\":\"Primary location UUID, or an empty string when unset.\",\"type\":\"string\"},\"max_storage_mb\":{\"description\":\"Storage quota in MB; zero means unlimited.\",\"type\":\"integer\"},\"motion_trigger_source\":{\"type\":\"string\"},\"name\":{\"description\":\"Current stream name; lookup is by name, not camera_uuid.\",\"type\":\"string\"},\"observation_evidence_enabled\":{\"default\":false,\"description\":\"INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations.\",\"type\":\"boolean\"},\"observation_labels\":{\"default\":\"\",\"description\":\"Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13).\",\"maxLength\":511,\"type\":\"string\"},\"observation_max_age_days\":{\"default\":7,\"description\":\"Evidence media maximum age in days (contract section 13).\",\"maximum\":90,\"minimum\":1,\"type\":\"integer\"},\"observation_max_bytes\":{\"default\":2147483648,\"description\":\"Evidence media byte budget for this camera, 64 MiB..1 TiB (contract section 13).\",\"maximum\":1099511627776,\"minimum\":67108864,\"type\":\"integer\"},\"onvif_port\":{\"description\":\"Zero requests automatic port detection.\",\"type\":\"integer\"},\"onvif_profile\":{\"type\":\"string\"},\"playback_transport\":{\"$ref\":\"#/components/schemas/PlaybackTransport\"},\"post_detection_buffer\":{\"type\":\"integer\"},\"pre_detection_buffer\":{\"type\":\"integer\"},\"priority\":{\"type\":\"integer\"},\"privacy_mode\":{\"type\":\"boolean\"},\"protocol\":{\"description\":\"Stored transport code: 0 TCP, 1 UDP. Existing configuration paths may retain other numeric values.\",\"type\":\"integer\"},\"ptz_enabled\":{\"type\":\"boolean\"},\"ptz_has_home\":{\"type\":\"boolean\"},\"ptz_max_x\":{\"type\":\"integer\"},\"ptz_max_y\":{\"type\":\"integer\"},\"ptz_max_z\":{\"type\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record_audio\":{\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boolean\"},\"recording_schedule\":{\"$ref\":\"#/components/schemas/WeeklySchedule\"},\"retention_days\":{\"description\":\"-1 inherits the global policy; 0 means unlimited; positive values are days. PUT validates the truncated integer to -1 through 365.\",\"type\":\"integer\"},\"segment_duration\":{\"type\":\"integer\"},\"status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"storage_priority\":{\"type\":\"integer\"},\"streaming_enabled\":{\"type\":\"boolean\"},\"tags\":{\"type\":\"string\"},\"tier_critical_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"tier_ephemeral_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"tier_important_multiplier\":{\"description\":\"Stored tier multiplier. Zero is preserved; no client default is inserted.\",\"type\":\"number\"},\"url\":{\"description\":\"Configured input URL. For readers without camera.configure, URL userinfo is stripped; unsupported URL formats may become an empty string. For configurators the legacy/custom form may remain, while ONVIF/separate-credential configurations still strip URL userinfo.\",\"type\":\"string\"},\"width\":{\"description\":\"Detected metadata; zero is valid when unavailable.\",\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"location_uuid\",\"name\",\"url\",\"eptz_config\",\"codec\",\"detection_model\",\"detection_object_filter\",\"detection_object_filter_list\",\"onvif_profile\",\"tags\",\"motion_trigger_source\",\"enabled\",\"streaming_enabled\",\"record\",\"detection_based_recording\",\"record_audio\",\"audio_voice_enhancement\",\"isOnvif\",\"backchannel_enabled\",\"ptz_enabled\",\"ptz_has_home\",\"record_on_schedule\",\"detection_record_on_schedule\",\"privacy_mode\",\"has_sub_stream\",\"go2rtc_hls_available\",\"width\",\"height\",\"fps\",\"priority\",\"segment_duration\",\"detection_threshold\",\"detection_interval\",\"pre_detection_buffer\",\"post_detection_buffer\",\"protocol\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\",\"storage_priority\",\"ptz_max_x\",\"ptz_max_y\",\"ptz_max_z\",\"onvif_port\",\"tier_critical_multiplier\",\"tier_important_multiplier\",\"tier_ephemeral_multiplier\",\"playback_transport\",\"recording_schedule\",\"detection_recording_schedule\",\"status\"],\"type\":\"object\"},\"StreamDetectionEngine\":{\"additionalProperties\":true,\"properties\":{\"config\":{\"additionalProperties\":true,\"description\":\"Engine-specific configuration; current engine plugins own its keys. Serialized JSON must fit the native configuration buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"minimum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string\"},\"managed_by_legacy_stream_fields\":{\"type\":\"boolean\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"sort_order\":{\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"type\":\"string\"}},\"required\":[\"key\",\"type\",\"model_path\",\"enabled\",\"threshold\",\"interval_seconds\",\"sort_order\",\"config\",\"managed_by_legacy_stream_fields\"],\"type\":\"object\"},\"StreamDetectionEngineWrite\":{\"additionalProperties\":true,\"properties\":{\"config\":{\"additionalProperties\":true,\"description\":\"Engine-specific configuration; current engine plugins own its keys. Serialized JSON must fit the native configuration buffer.\",\"type\":\"object\"},\"enabled\":{\"type\":\"boolean\"},\"interval_seconds\":{\"maximum\":86400,\"minimum\":1,\"type\":\"integer\"},\"key\":{\"maxLength\":63,\"minLength\":1,\"pattern\":\"^[a-z0-9_-]+$\",\"type\":\"string\"},\"model_path\":{\"maxLength\":511,\"type\":\"string\"},\"sort_order\":{\"maximum\":2147483647,\"minimum\":-2147483648,\"type\":\"integer\"},\"threshold\":{\"maximum\":1,\"minimum\":0,\"type\":\"number\"},\"type\":{\"enum\":[\"object\",\"motion\",\"onvif\",\"api\",\"external\"],\"type\":\"string\"}},\"required\":[\"key\",\"type\",\"threshold\",\"interval_seconds\"],\"type\":\"object\"},\"StreamDetectionEngines\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"integer\"},\"engines\":{\"items\":{\"$ref\":\"#/components/schemas/StreamDetectionEngine\"},\"type\":\"array\"},\"restart_required\":{\"type\":\"boolean\"},\"stream_name\":{\"type\":\"string\"},\"trigger_policy\":{\"const\":\"any_of\",\"type\":\"string\"}},\"required\":[\"stream_name\",\"engines\",\"count\",\"trigger_policy\",\"restart_required\"],\"type\":\"object\"},\"StreamDetectionEnginesRequest\":{\"additionalProperties\":true,\"properties\":{\"engines\":{\"items\":{\"$ref\":\"#/components/schemas/StreamDetectionEngineWrite\"},\"maxItems\":7,\"type\":\"array\"}},\"required\":[\"engines\"],\"type\":\"object\"},\"StreamFullPrivilegedResponse\":{\"additionalProperties\":true,\"properties\":{\"stream\":{\"$ref\":\"#/components/schemas/StreamPrivileged\"}},\"required\":[\"stream\"],\"type\":\"object\"},\"StreamFullRedactedResponse\":{\"additionalProperties\":true,\"properties\":{\"stream\":{\"$ref\":\"#/components/schemas/StreamRedacted\"}},\"required\":[\"stream\"],\"type\":\"object\"},\"StreamFullResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamFullRedactedResponse\"},{\"$ref\":\"#/components/schemas/StreamFullPrivilegedResponse\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"runtime\"}],\"version\":1}}],\"description\":\"Full details are wrapped in stream. Read authorization uses raw camera facts; privileged fields additionally use runtime-enriched facts on the same camera.\"},\"StreamHealth\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"number\"},\"healthy\":{\"type\":\"boolean\"},\"status\":{\"type\":\"string\"},\"storage\":{\"additionalProperties\":true,\"properties\":{\"available_bytes\":{\"type\":\"number\"},\"used_bytes\":{\"type\":\"number\"}},\"required\":[\"used_bytes\",\"available_bytes\"],\"type\":\"object\"},\"streams\":{\"additionalProperties\":true,\"properties\":{\"degraded\":{\"type\":\"number\"},\"down\":{\"type\":\"number\"},\"total\":{\"type\":\"number\"},\"up\":{\"type\":\"number\"}},\"required\":[\"total\",\"up\",\"degraded\",\"down\"],\"type\":\"object\"},\"streams_detail\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"bitrate_bps\":{\"type\":\"number\"},\"configured_fps\":{\"type\":\"number\"},\"connection_latency_ms\":{\"type\":\"number\"},\"current_fps\":{\"type\":\"number\"},\"detector_operating\":{\"type\":\"boolean\"},\"expected_fps\":{\"type\":\"number\"},\"fps\":{\"type\":\"number\"},\"frames_dropped\":{\"type\":\"number\"},\"frames_total\":{\"type\":\"number\"},\"last_completed_segment_ts\":{\"type\":\"number\"},\"last_frame_ts\":{\"type\":\"number\"},\"last_packet_ts\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"observed_fps\":{\"type\":\"number\"},\"reconnects\":{\"type\":\"number\"},\"recorder_session_active\":{\"type\":\"boolean\"},\"recorder_writing\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"boolean\"},\"recording_gaps\":{\"type\":\"number\"},\"source_connected\":{\"type\":\"boolean\"},\"sparkline_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"status\":{\"type\":\"string\"},\"uptime_seconds\":{\"type\":\"number\"},\"video_advancing\":{\"type\":\"boolean\"}},\"required\":[\"name\",\"status\",\"observed_fps\",\"current_fps\",\"expected_fps\",\"fps\",\"configured_fps\",\"bitrate_bps\",\"uptime_seconds\",\"reconnects\",\"last_packet_ts\",\"last_frame_ts\",\"last_completed_segment_ts\",\"frames_total\",\"frames_dropped\",\"connection_latency_ms\",\"source_connected\",\"video_advancing\",\"recorder_writing\",\"detector_operating\",\"recorder_session_active\",\"recording_active\",\"recording_gaps\"],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"number\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"status\",\"streams\",\"storage\",\"streams_detail\"],\"type\":\"object\"},\"StreamHealthHistory\":{\"additionalProperties\":true,\"properties\":{\"failedRequests\":{\"type\":\"integer\"},\"healthy\":{\"type\":\"boolean\"},\"streams_detail\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"name\":{\"type\":\"string\"},\"sparkline_bitrate\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"},\"sparkline_fps\":{\"items\":{\"type\":\"number\"},\"type\":\"array\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"array\"},\"timestamp\":{\"type\":\"string\"},\"totalRequests\":{\"type\":\"integer\"},\"uptime\":{\"type\":\"number\"}},\"required\":[\"healthy\",\"uptime\",\"totalRequests\",\"failedRequests\",\"timestamp\",\"streams_detail\"],\"type\":\"object\"},\"StreamListResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamCompatibilityList\"},{\"$ref\":\"#/components/schemas/StreamSummaryPage\"},{\"$ref\":\"#/components/schemas/LiveStreamSummaryPage\"},{\"$ref\":\"#/components/schemas/LprStreamSummaryPage\"}],\"description\":\"Select the response with the request query. Consumers validating an admin or live summary must use its specific page schema: the identity-only LPR variant is deliberately minimal.\"},\"StreamPrivacyUpdate\":{\"additionalProperties\":false,\"description\":\"Synchronous lightweight privacy toggle. No other update fields may accompany it; unknown fields are rejected in this form.\",\"properties\":{\"name\":{\"description\":\"Optional ignored identifying metadata.\",\"type\":\"string\"},\"set_privacy_mode\":{\"type\":\"boolean\"}},\"required\":[\"set_privacy_mode\"],\"type\":\"object\"},\"StreamPrivacyUpdated\":{\"additionalProperties\":true,\"description\":\"HTTP 200 privacy result. A disabled stream can succeed with stream_registered=false; this is not evidence of a live stream.\",\"properties\":{\"privacy_mode\":{\"type\":\"boolean\"},\"stream_registered\":{\"type\":\"boolean\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"privacy_mode\",\"stream_registered\"],\"type\":\"object\"},\"StreamPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"type\":\"string\"},\"can_control_privacy\":{\"const\":true,\"type\":\"boolean\"},\"detection_url\":{\"type\":\"string\"},\"go2rtc_source_override\":{\"description\":\"Single source string or multiline YAML source list; empty means generated configuration.\",\"type\":\"string\"},\"onvif_password\":{\"description\":\"Configuration credential, conditionally visible for this resource. Empty is valid when unset.\",\"type\":\"string\"},\"onvif_username\":{\"type\":\"string\"},\"publish_url\":{\"type\":\"string\"},\"sub_stream_url\":{\"type\":\"string\"}},\"required\":[\"onvif_username\",\"onvif_password\",\"admin_url\",\"go2rtc_source_override\",\"sub_stream_url\",\"detection_url\",\"publish_url\",\"can_control_privacy\"],\"type\":\"object\"}],\"description\":\"This camera is both readable and configurable for the current principal. Privileged values may still be empty; authority over another camera is insufficient.\"},\"StreamProbeRequest\":{\"additionalProperties\":true,\"properties\":{\"onvif_password\":{\"type\":\"string\"},\"onvif_username\":{\"type\":\"string\"},\"protocol\":{\"type\":\"integer\"},\"url\":{\"type\":\"string\"}},\"required\":[\"url\",\"protocol\"],\"type\":\"object\"},\"StreamProbeResponse\":{\"oneOf\":[{\"additionalProperties\":true,\"properties\":{\"info\":{\"additionalProperties\":true,\"properties\":{\"codec\":{\"type\":\"string\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"width\",\"height\",\"fps\",\"codec\"],\"type\":\"object\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"info\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"}]},\"StreamRecordingChanged\":{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"type\":\"string\"},\"state\":{\"enum\":[\"starting\",\"idle\"],\"type\":\"string\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"stream\",\"action\",\"state\"],\"type\":\"object\"},\"StreamRecordingRequest\":{\"additionalProperties\":true,\"properties\":{\"action\":{\"enum\":[\"start\",\"stop\"],\"type\":\"string\"}},\"required\":[\"action\"],\"type\":\"object\"},\"StreamRecordingState\":{\"additionalProperties\":true,\"properties\":{\"capture_method\":{\"type\":[\"string\",\"null\"]},\"manual_control_allowed\":{\"type\":\"boolean\"},\"manual_control_reason\":{\"enum\":[\"read_only\",\"continuous_recording\",null],\"type\":[\"string\",\"null\"]},\"recording_active\":{\"type\":\"boolean\"},\"recording_id\":{\"type\":[\"integer\",\"null\"]},\"state\":{\"enum\":[\"starting\",\"recording\",\"idle\"],\"type\":\"string\"},\"stream\":{\"type\":\"string\"}},\"required\":[\"stream\",\"state\",\"recording_active\",\"capture_method\",\"recording_id\",\"manual_control_allowed\",\"manual_control_reason\"],\"type\":\"object\"},\"StreamRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamDetailsBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"const\":\"\",\"type\":\"string\"},\"can_control_privacy\":{\"const\":false,\"type\":\"boolean\"},\"detection_url\":{\"const\":\"\",\"type\":\"string\"},\"go2rtc_source_override\":{\"const\":\"\",\"type\":\"string\"},\"onvif_password\":{\"const\":\"\",\"type\":\"string\"},\"onvif_username\":{\"const\":\"\",\"type\":\"string\"},\"publish_url\":{\"const\":\"\",\"type\":\"string\"},\"sub_stream_url\":{\"const\":\"\",\"type\":\"string\"}},\"required\":[\"onvif_username\",\"onvif_password\",\"admin_url\",\"go2rtc_source_override\",\"sub_stream_url\",\"detection_url\",\"publish_url\",\"can_control_privacy\"],\"type\":\"object\"}],\"description\":\"This camera is readable but not configurable. Sensitive fields remain present as empty strings; has_sub_stream can still be true.\"},\"StreamRefreshAccepted\":{\"additionalProperties\":true,\"properties\":{\"coalesced\":{\"type\":\"boolean\"},\"message\":{\"type\":\"string\"},\"retry_after_seconds\":{\"type\":\"integer\"},\"stream\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"stream\"],\"type\":\"object\"},\"StreamRetention\":{\"additionalProperties\":true,\"properties\":{\"detection_retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"stream_name\":{\"type\":\"string\"}},\"required\":[\"stream_name\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\"],\"type\":\"object\"},\"StreamRetentionRequest\":{\"additionalProperties\":true,\"properties\":{\"detection_retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"},\"max_storage_mb\":{\"maximum\":9007199254740991,\"minimum\":0,\"type\":\"integer\"},\"retention_days\":{\"maximum\":365,\"minimum\":-1,\"type\":\"integer\"}},\"required\":[],\"type\":\"object\"},\"StreamSingleResponse\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamRedacted\"},{\"$ref\":\"#/components/schemas/StreamPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"},{\"action\":\"camera.configure\",\"resource\":\"runtime\"}],\"version\":1}}],\"description\":\"Single legacy stream object. URL and credential fields are redacted unless this same camera is configurable.\"},\"StreamStatus\":{\"enum\":[\"Stopped\",\"Starting\",\"Running\",\"Stopping\",\"Error\",\"Reconnecting\",\"Degraded\",\"Unknown\"],\"type\":\"string\"},\"StreamSummaryBase\":{\"additionalProperties\":true,\"description\":\"Administrative and live summary fields. Unlike detail, summary rows contain no input URL, credentials or error_message.\",\"properties\":{\"availability\":{\"enum\":[\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"},\"camera_uuid\":{\"type\":\"string\"},\"codec\":{\"type\":\"string\"},\"current_fps\":{\"type\":\"number\"},\"detection_based_recording\":{\"type\":\"boolean\"},\"enabled\":{\"type\":\"boolean\"},\"first_video_at\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"fps\":{\"type\":\"integer\"},\"height\":{\"type\":\"integer\"},\"isOnvif\":{\"type\":\"boolean\"},\"last_frame_ts\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"last_recording_at\":{\"description\":\"Unix seconds; zero means not observed.\",\"type\":\"integer\"},\"location_name\":{\"type\":\"string\"},\"location_path\":{\"type\":\"string\"},\"location_uuid\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"protocol\":{\"type\":\"integer\"},\"record\":{\"type\":\"boolean\"},\"record_on_schedule\":{\"type\":\"boolean\"},\"recording_active\":{\"type\":\"boolean\"},\"status\":{\"$ref\":\"#/components/schemas/StreamStatus\"},\"tags\":{\"type\":\"string\"},\"width\":{\"type\":\"integer\"}},\"required\":[\"camera_uuid\",\"name\",\"location_uuid\",\"location_name\",\"location_path\",\"codec\",\"tags\",\"enabled\",\"record\",\"record_on_schedule\",\"detection_based_recording\",\"isOnvif\",\"recording_active\",\"width\",\"height\",\"fps\",\"protocol\",\"first_video_at\",\"last_frame_ts\",\"last_recording_at\",\"current_fps\",\"status\",\"availability\"],\"type\":\"object\"},\"StreamSummaryPage\":{\"additionalProperties\":true,\"description\":\"summary=true, surface=admin (default). Totals count only authorized, searched and availability-filtered rows before pagination. A page beyond the end has an empty streams array; total_pages is zero when total is zero.\",\"properties\":{\"page\":{\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"},\"page_size\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"},\"streams\":{\"items\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryRedacted\"},{\"$ref\":\"#/components/schemas/StreamSummaryPrivileged\",\"x-lightnvr-visibility\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"live\"},{\"action\":\"camera.configure\",\"resource\":\"live\"}],\"version\":1}}]},\"type\":\"array\"},\"total\":{\"minimum\":0,\"type\":\"integer\"},\"total_pages\":{\"minimum\":0,\"type\":\"integer\"}},\"required\":[\"streams\",\"total\",\"page\",\"page_size\",\"total_pages\"],\"type\":\"object\"},\"StreamSummaryPrivileged\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"description\":\"Only emitted for surface=admin with include_admin_url=true or 1. Empty when not configurable.\",\"type\":\"string\"},\"can_configure\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"object\"}]},\"StreamSummaryRedacted\":{\"allOf\":[{\"$ref\":\"#/components/schemas/StreamSummaryBase\"},{\"additionalProperties\":true,\"properties\":{\"admin_url\":{\"const\":\"\",\"description\":\"Only emitted for surface=admin with include_admin_url=true or 1. Empty when not configurable.\",\"type\":\"string\"},\"can_configure\":{\"const\":false,\"type\":\"boolean\"}},\"required\":[\"can_configure\"],\"type\":\"object\"}]},\"StreamUpdateAccepted\":{\"additionalProperties\":true,\"description\":\"HTTP 202 acknowledges a queued update, not successful durable application. ONVIF results are optional. Re-fetch detail to observe eventual state; a request requiring ONVIF work may perform that probe before acceptance.\",\"properties\":{\"message\":{\"type\":\"string\"},\"onvif_message\":{\"type\":\"string\"},\"onvif_status\":{\"enum\":[\"success\",\"error\"],\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"StreamUpdateRequest\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/StreamConfigurationUpdate\"},{\"$ref\":\"#/components/schemas/StreamPrivacyUpdate\"}]},\"StreamZones\":{\"additionalProperties\":true,\"properties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZone\"},\"type\":\"array\"}},\"required\":[\"zones\"],\"type\":\"object\"},\"StreamZonesDeleted\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"StreamZonesRequest\":{\"additionalProperties\":true,\"properties\":{\"zones\":{\"items\":{\"$ref\":\"#/components/schemas/DetectionZoneWrite\"},\"maxItems\":16,\"type\":\"array\"}},\"required\":[\"zones\"],\"type\":\"object\"},\"StreamZonesSaved\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"maximum\":16,\"minimum\":0,\"type\":\"integer\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"count\"],\"type\":\"object\"},\"SuccessResponse\":{\"additionalProperties\":true,\"properties\":{\"api_key\":false,\"password\":false,\"password_hash\":false,\"secret\":false,\"success\":{\"const\":true},\"token_hash\":false,\"totp_secret\":false,\"totp_token\":false},\"required\":[\"success\"],\"type\":\"object\"},\"SystemActionAccepted\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"SystemBackupCreated\":{\"additionalProperties\":true,\"properties\":{\"backupUrl\":{\"type\":\"string\"},\"filename\":{\"type\":\"string\"},\"message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"message\",\"backupUrl\",\"filename\"],\"type\":\"object\"},\"SystemInformation\":{\"additionalProperties\":true,\"properties\":{\"cpu\":{\"additionalProperties\":true,\"properties\":{\"cores\":{\"type\":\"number\"},\"model\":{\"type\":\"string\"},\"usage\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"usageCapability\":{\"type\":\"string\"}},\"type\":\"object\"},\"detectorMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"disk\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"git_commit\":{\"type\":\"string\"},\"global_retention_days\":{\"type\":\"number\"},\"go2rtcMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"memory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"network\":{\"additionalProperties\":true,\"properties\":{\"interfaces\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"address\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"string\"}]},\"capability\":{\"type\":\"string\"},\"ipv6\":{\"type\":\"string\"},\"mac\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"up\":{\"type\":\"boolean\"}},\"required\":[\"name\"],\"type\":\"object\"},\"type\":\"array\"}},\"type\":\"object\"},\"observation_pipeline\":{\"$ref\":\"#/components/schemas/ObservationPipelineDiagnostics\"},\"recordings\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"count\",\"size\"],\"type\":\"object\"},\"streamStorage\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"count\":{\"type\":\"number\"},\"detection_retention_days\":{\"type\":\"number\"},\"max_storage_mb\":{\"type\":\"number\"},\"name\":{\"type\":\"string\"},\"retention_days\":{\"type\":\"number\"},\"size\":{\"type\":\"number\"}},\"required\":[\"name\",\"size\",\"count\",\"retention_days\",\"detection_retention_days\",\"max_storage_mb\"],\"type\":\"object\"},\"type\":\"array\"},\"streams\":{\"additionalProperties\":true,\"properties\":{\"active\":{\"type\":\"number\"},\"total\":{\"type\":\"number\"}},\"required\":[\"active\",\"total\"],\"type\":\"object\"},\"systemDisk\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"systemMemory\":{\"additionalProperties\":true,\"properties\":{\"capability\":{\"type\":\"string\"},\"free\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"total\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"used\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]}},\"type\":\"object\"},\"threads\":{\"anyOf\":[{\"type\":\"null\"},{\"type\":\"number\"}]},\"uptime\":{\"type\":\"number\"},\"uptimeCapability\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"},\"versions\":{\"additionalProperties\":true,\"properties\":{\"items\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"category\":{\"type\":\"string\"},\"details\":{\"type\":\"string\"},\"name\":{\"type\":\"string\"},\"version\":{\"type\":\"string\"}},\"required\":[\"name\",\"category\",\"version\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"items\"],\"type\":\"object\"},\"webThreadPoolSize\":{\"type\":\"number\"}},\"required\":[\"version\",\"git_commit\",\"cpu\",\"memory\",\"threads\",\"webThreadPoolSize\",\"uptime\",\"disk\",\"systemDisk\",\"global_retention_days\",\"versions\",\"network\",\"streams\",\"recordings\",\"streamStorage\"],\"type\":\"object\"},\"SystemLogs\":{\"additionalProperties\":true,\"properties\":{\"file\":{\"type\":\"string\"},\"level\":{\"type\":\"string\"},\"logs\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"level\":{\"type\":\"string\"},\"message\":{\"type\":\"string\"},\"timestamp\":{\"type\":\"string\"}},\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"logs\",\"file\",\"level\"],\"type\":\"object\"},\"SystemSettings\":{\"additionalProperties\":true,\"properties\":{\"api_detection_backend\":{\"type\":\"string\"},\"api_detection_detector_name\":{\"type\":\"string\"},\"api_detection_format\":{\"type\":\"string\"},\"api_detection_url\":{\"type\":\"string\"},\"audio_disabled\":{\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"buffer_size\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_backup_retention_count\":{\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"string\"},\"db_post_backup_script\":{\"type\":\"string\"},\"default_detection_threshold\":{\"type\":\"number\"},\"default_playback_transport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engine_delegate\":{\"type\":\"string\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_threads\":{\"type\":\"number\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_base_path\":{\"type\":\"string\"},\"go2rtc_binary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc_config_override\":{\"type\":\"string\"},\"go2rtc_config_override_disabled_reason\":{\"type\":\"string\"},\"go2rtc_config_override_quarantined\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"type\":\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_server\":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{\"type\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"type\":\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_device_interval_seconds\":{\"type\":\"number\"},\"health_effective_policy\":{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"}},\"type\":\"object\"},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_interval_seconds\":{\"type\":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"},\"health_incident_retention_days\":{\"type\":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"number\"},\"health_presence_interval_seconds\":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"health_slow_interval_seconds\":{\"type\":\"number\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"log_file\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabled\":{\"type\":\"boolean\"},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"login_rate_limit_window_seconds\":{\"type\":\"number\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":\"number\"},\"max_streams\":{\"type\":\"number\"},\"max_streams_ceiling\":{\"type\":\"number\"},\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{\"type\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqtt_client_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"boolean\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean\"},\"mqtt_topic_prefix\":{\"type\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type\":\"boolean\"},\"onvif_discovery_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"number\"},\"onvif_discovery_network\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_buffer\":{\"type\":\"number\"},\"pre_detection_buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"number\"},\"storage_min_free_pct\":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hls\":{\"type\":\"string\"},\"storage_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pressure_emergency_pct\":{\"type\":\"number\"},\"storage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"number\"},\"syslog_enabled\":{\"type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":{\"type\":\"string\"},\"thumbnails_per_recording\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"number\"},\"trusted_proxy_cidrs\":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\":{\"enum\":[\"\",\"********\"],\"type\":\"string\"},\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"string\"},\"use_swap\":{\"type\":\"boolean\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":\"string\"},\"web_port\":{\"type\":\"number\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"type\":\"number\"},\"webrtc_connection_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"integer\"}},\"required\":[\"log_level\",\"syslog_enabled\",\"syslog_ident\",\"syslog_facility\",\"storage_path\",\"storage_path_hls\",\"mp4_directory_format\",\"max_storage_size\",\"retention_days\",\"auto_delete_oldest\",\"storage_min_free_pct\",\"storage_pressure_warning_pct\",\"storage_pressure_critical_pct\",\"storage_pressure_emergency_pct\",\"generate_thumbnails\",\"thumbnails_per_recording\",\"db_path\",\"db_backup_interval_minutes\",\"db_backup_retention_count\",\"db_backup_verify\",\"db_post_backup_script\",\"web_port\",\"web_bind_ip\",\"web_thread_pool_size\",\"max_streams\",\"max_streams_ceiling\",\"web_auth_enabled\",\"demo_mode\",\"audio_disabled\",\"auto_disabled\",\"webrtc_disabled\",\"hls_disabled\",\"mse_disabled\",\"auth_timeout_hours\",\"auth_absolute_timeout_hours\",\"trusted_device_days\",\"trusted_proxy_cidrs\",\"force_mfa_on_login\",\"login_rate_limit_enabled\",\"login_rate_limit_max_attempts\",\"login_rate_limit_window_seconds\",\"buffer_size\",\"use_swap\",\"swap_size\",\"models_path\",\"api_detection_url\",\"api_detection_backend\",\"api_detection_format\",\"api_detection_detector_name\",\"default_detection_threshold\",\"pre_detection_buffer\",\"post_detection_buffer\",\"default_playback_transport\",\"detection_grace_period\",\"detection_engine_enabled\",\"detection_engine_threads\",\"detection_engine_delegate\",\"go2rtc_enabled\",\"go2rtc_binary_path\",\"go2rtc_config_dir\",\"go2rtc_api_port\",\"go2rtc_rtsp_port\",\"go2rtc_webrtc_enabled\",\"go2rtc_webrtc_listen_port\",\"go2rtc_stun_enabled\",\"go2rtc_stun_server\",\"go2rtc_external_ip\",\"go2rtc_ice_servers\",\"go2rtc_force_native_hls\",\"go2rtc_config_override\",\"go2rtc_config_override_disabled_reason\",\"go2rtc_config_override_quarantined\",\"mqtt_enabled\",\"mqtt_broker_host\",\"mqtt_broker_port\",\"mqtt_username\",\"mqtt_password\",\"mqtt_client_id\",\"mqtt_topic_prefix\",\"mqtt_tls_enabled\",\"mqtt_keepalive\",\"mqtt_qos\",\"mqtt_retain\",\"mqtt_ha_discovery\",\"mqtt_ha_discovery_prefix\",\"mqtt_ha_snapshot_interval\",\"turn_enabled\",\"turn_server_url\",\"turn_username\",\"turn_password\",\"onvif_discovery_enabled\",\"onvif_discovery_interval\",\"onvif_discovery_network\",\"health_enabled\",\"health_profile\",\"health_fast_interval_seconds\",\"health_normal_interval_seconds\",\"health_slow_interval_seconds\",\"health_device_interval_seconds\",\"health_write_probe_enabled\",\"health_hardware_provider\",\"health_presence_interval_seconds\",\"health_incident_retention_days\",\"health_condition_overrides\",\"web_root\",\"log_file\",\"pid_file\",\"go2rtc_base_path\",\"lpr_retention_days\",\"webrtc_connection_timeout_ms\",\"webrtc_ice_recovery_timeout_ms\",\"mp4_faststart\"],\"type\":\"object\"},\"SystemSettingsSaved\":{\"additionalProperties\":true,\"properties\":{\"restart_required\":{\"type\":\"boolean\"},\"restart_required_message\":{\"type\":\"string\"},\"success\":{\"const\":true,\"type\":\"boolean\"}},\"required\":[\"success\",\"restart_required\"],\"type\":\"object\"},\"SystemSettingsUpdate\":{\"additionalProperties\":true,\"description\":\"Partial settings update; absent fields are retained. Secret mask values preserve the stored password.\",\"properties\":{\"api_detection_backend\":{\"type\":\"string\"},\"api_detection_detector_name\":{\"type\":\"string\"},\"api_detection_format\":{\"enum\":[\"light-object-detect\",\"doods2\"],\"type\":\"string\"},\"api_detection_url\":{\"type\":\"string\"},\"audio_disabled\":{\"type\":\"boolean\"},\"auth_absolute_timeout_hours\":{\"type\":\"number\"},\"auth_timeout_hours\":{\"type\":\"number\"},\"auto_delete_oldest\":{\"type\":\"boolean\"},\"auto_disabled\":{\"type\":\"boolean\"},\"buffer_size\":{\"type\":\"number\"},\"db_backup_interval_minutes\":{\"type\":\"number\"},\"db_backup_retention_count\":{\"type\":\"number\"},\"db_backup_verify\":{\"type\":\"string\"},\"db_path\":{\"type\":\"string\"},\"db_post_backup_script\":{\"type\":\"string\"},\"default_detection_threshold\":{\"type\":\"number\"},\"default_playback_transport\":{\"type\":\"string\"},\"demo_mode\":{\"type\":\"boolean\"},\"detection_engine_delegate\":{\"type\":\"string\"},\"detection_engine_enabled\":{\"type\":\"boolean\"},\"detection_engine_threads\":{\"type\":\"number\"},\"detection_grace_period\":{\"type\":\"number\"},\"force_mfa_on_login\":{\"type\":\"boolean\"},\"generate_thumbnails\":{\"type\":\"boolean\"},\"go2rtc_api_port\":{\"type\":\"number\"},\"go2rtc_binary_path\":{\"type\":\"string\"},\"go2rtc_config_dir\":{\"type\":\"string\"},\"go2rtc_config_override\":{\"type\":\"string\"},\"go2rtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_external_ip\":{\"type\":\"string\"},\"go2rtc_force_native_hls\":{\"type\":\"boolean\"},\"go2rtc_ice_servers\":{\"type\":\"string\"},\"go2rtc_rtsp_port\":{\"type\":\"number\"},\"go2rtc_stun_enabled\":{\"type\":\"boolean\"},\"go2rtc_stun_server\":{\"type\":\"string\"},\"go2rtc_webrtc_enabled\":{\"type\":\"boolean\"},\"go2rtc_webrtc_listen_port\":{\"type\":\"number\"},\"health_condition_overrides\":{\"anyOf\":[{\"type\":\"null\"},{\"additionalProperties\":true,\"properties\":{\"conditions\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"code\":{\"type\":\"string\"},\"critical\":{\"type\":\"number\"},\"critical_for_seconds\":{\"type\":\"number\"},\"profile\":{\"type\":\"string\"},\"recovery\":{\"type\":\"number\"},\"recovery_for_seconds\":{\"type\":\"number\"},\"unit\":{\"type\":\"string\"},\"warning\":{\"type\":\"number\"},\"warning_for_seconds\":{\"type\":\"number\"}},\"required\":[\"code\"],\"type\":\"object\"},\"type\":\"array\"},\"version\":{\"type\":\"number\"}},\"type\":\"object\"}]},\"health_device_interval_seconds\":{\"type\":\"number\"},\"health_enabled\":{\"type\":\"boolean\"},\"health_fast_interval_seconds\":{\"type\":\"number\"},\"health_hardware_provider\":{\"type\":\"string\"},\"health_incident_retention_days\":{\"type\":\"number\"},\"health_normal_interval_seconds\":{\"type\":\"number\"},\"health_presence_interval_seconds\":{\"type\":\"number\"},\"health_profile\":{\"type\":\"string\"},\"health_slow_interval_seconds\":{\"type\":\"number\"},\"health_write_probe_enabled\":{\"type\":\"boolean\"},\"hls_disabled\":{\"type\":\"boolean\"},\"log_file\":{\"type\":\"string\"},\"log_level\":{\"type\":\"number\"},\"login_rate_limit_enabled\":{\"type\":\"boolean\"},\"login_rate_limit_max_attempts\":{\"type\":\"number\"},\"login_rate_limit_window_seconds\":{\"type\":\"number\"},\"lpr_retention_days\":{\"type\":\"integer\"},\"max_storage_size\":{\"type\":\"number\"},\"max_streams\":{\"type\":\"number\"},\"models_path\":{\"type\":\"string\"},\"mp4_directory_format\":{\"type\":\"string\"},\"mp4_faststart\":{\"type\":\"boolean\"},\"mqtt_broker_host\":{\"type\":\"string\"},\"mqtt_broker_port\":{\"type\":\"number\"},\"mqtt_client_id\":{\"type\":\"string\"},\"mqtt_enabled\":{\"type\":\"boolean\"},\"mqtt_ha_discovery\":{\"type\":\"boolean\"},\"mqtt_ha_discovery_prefix\":{\"type\":\"string\"},\"mqtt_ha_snapshot_interval\":{\"type\":\"number\"},\"mqtt_keepalive\":{\"type\":\"number\"},\"mqtt_password\":{\"type\":\"string\"},\"mqtt_qos\":{\"type\":\"number\"},\"mqtt_retain\":{\"type\":\"boolean\"},\"mqtt_tls_enabled\":{\"type\":\"boolean\"},\"mqtt_topic_prefix\":{\"type\":\"string\"},\"mqtt_username\":{\"type\":\"string\"},\"mse_disabled\":{\"type\":\"boolean\"},\"onvif_discovery_enabled\":{\"type\":\"boolean\"},\"onvif_discovery_interval\":{\"type\":\"number\"},\"onvif_discovery_network\":{\"type\":\"string\"},\"pid_file\":{\"type\":\"string\"},\"post_detection_buffer\":{\"type\":\"number\"},\"pre_detection_buffer\":{\"type\":\"number\"},\"retention_days\":{\"type\":\"number\"},\"storage_min_free_pct\":{\"type\":\"number\"},\"storage_path\":{\"type\":\"string\"},\"storage_path_hls\":{\"type\":\"string\"},\"storage_pressure_critical_pct\":{\"type\":\"number\"},\"storage_pressure_emergency_pct\":{\"type\":\"number\"},\"storage_pressure_warning_pct\":{\"type\":\"number\"},\"swap_size\":{\"type\":\"number\"},\"syslog_enabled\":{\"type\":\"boolean\"},\"syslog_facility\":{\"type\":\"string\"},\"syslog_ident\":{\"type\":\"string\"},\"thumbnails_per_recording\":{\"type\":\"number\"},\"trusted_device_days\":{\"type\":\"number\"},\"trusted_proxy_cidrs\":{\"type\":\"string\"},\"turn_enabled\":{\"type\":\"boolean\"},\"turn_password\":{\"type\":\"string\"},\"turn_server_url\":{\"type\":\"string\"},\"turn_username\":{\"type\":\"string\"},\"use_swap\":{\"type\":\"boolean\"},\"web_auth_enabled\":{\"type\":\"boolean\"},\"web_bind_ip\":{\"type\":\"string\"},\"web_port\":{\"type\":\"number\"},\"web_root\":{\"type\":\"string\"},\"web_thread_pool_size\":{\"type\":\"number\"},\"webrtc_connection_timeout_ms\":{\"type\":\"integer\"},\"webrtc_disabled\":{\"type\":\"boolean\"},\"webrtc_ice_recovery_timeout_ms\":{\"type\":\"integer\"}},\"type\":\"object\"},\"SystemStatus\":{\"additionalProperties\":true,\"properties\":{\"message\":{\"type\":\"string\"},\"status\":{\"const\":\"ok\",\"type\":\"string\"}},\"required\":[\"status\",\"message\"],\"type\":\"object\"},\"TokenSelector\":{\"additionalProperties\":true,\"description\":\"Existing fleet selector grammar. Server additionally enforces depth\xE2\x89\xA48, total nodes\xE2\x89\xA464, UTF-8 byte limits and serialized selector<8192 bytes. Evaluation and delegation containment remain server decisions.\",\"properties\":{\"expression\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"version\":{\"const\":1}},\"required\":[\"version\",\"expression\"],\"type\":\"object\"},\"TokenSelectorExpression\":{\"anyOf\":[{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"all\"}},\"required\":[\"op\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"children\":{\"items\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"},\"op\":{\"enum\":[\"and\",\"or\"]}},\"required\":[\"op\",\"children\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"child\":{\"$ref\":\"#/components/schemas/TokenSelectorExpression\"},\"op\":{\"const\":\"not\"}},\"required\":[\"op\",\"child\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"location_subtree\"},\"uuid\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}},\"required\":[\"op\",\"uuid\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"const\":\"enabled\"},\"value\":{\"type\":\"boolean\"}},\"required\":[\"op\",\"value\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"camera_uuid\"]},\"values\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"tag_any\",\"tag_all\",\"tag_none\"]},\"uuids\":{\"items\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"uuids\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"recording_mode\"]},\"values\":{\"items\":{\"enum\":[\"off\",\"continuous\",\"detection\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"vendor\",\"model\"]},\"values\":{\"items\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"capability_any\",\"capability_all\"]},\"values\":{\"items\":{\"enum\":[\"onvif\",\"ptz\",\"backchannel\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"},{\"additionalProperties\":true,\"properties\":{\"op\":{\"enum\":[\"health\"]},\"values\":{\"items\":{\"enum\":[\"unknown\",\"up\",\"degraded\",\"down\",\"disabled\"]},\"maxItems\":64,\"minItems\":1,\"type\":\"array\"}},\"required\":[\"op\",\"values\"],\"type\":\"object\"}]},\"TotpChangedResponse\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"message\"],\"type\":\"object\"},\"TotpCodeRequest\":{\"additionalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"code\"],\"type\":\"object\"},\"TotpLoginRequest\":{\"additionalProperties\":false,\"properties\":{\"code\":{\"pattern\":\"^[0-9]{6}$\",\"type\":\"string\",\"writeOnly\":true},\"remember_device\":{\"type\":\"boolean\"},\"totp_token\":{\"minLength\":1,\"type\":\"string\",\"writeOnly\":true}},\"required\":[\"totp_token\",\"code\"],\"type\":\"object\"},\"TotpLoginResponse\":{\"additionalProperties\":false,\"properties\":{\"redirect\":{\"const\":\"/index.html\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"redirect\"],\"type\":\"object\"},\"TotpSetupResponse\":{\"additionalProperties\":false,\"properties\":{\"message\":{\"type\":\"string\"},\"otpauth_uri\":{\"format\":\"uri\",\"pattern\":\"^otpauth://totp/\",\"readOnly\":true,\"type\":\"string\"},\"secret\":{\"pattern\":\"^[A-Z2-7]{32}$\",\"readOnly\":true,\"type\":\"string\"},\"success\":{\"const\":true}},\"required\":[\"success\",\"secret\",\"otpauth_uri\",\"message\"],\"type\":\"object\"},\"TotpStatusResponse\":{\"additionalProperties\":false,\"properties\":{\"totp_configured\":{\"type\":\"boolean\"},\"totp_enabled\":{\"type\":\"boolean\"}},\"required\":[\"totp_enabled\",\"totp_configured\"],\"type\":\"object\"},\"TrustedDevice\":{\"additionalProperties\":false,\"properties\":{\"created_at\":{\"minimum\":0,\"type\":\"integer\"},\"current\":{\"type\":\"boolean\"},\"expires_at\":{\"minimum\":0,\"type\":\"integer\"},\"id\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"},\"ip_address\":{\"maxLength\":45,\"type\":\"string\"},\"last_used_at\":{\"minimum\":0,\"type\":\"integer\"},\"user_agent\":{\"maxLength\":255,\"type\":\"string\"}},\"required\":[\"id\",\"created_at\",\"expires_at\",\"ip_address\",\"user_agent\",\"current\",\"last_used_at\"],\"type\":\"object\"},\"TrustedDevicesResponse\":{\"additionalProperties\":false,\"properties\":{\"trusted_devices\":{\"items\":{\"$ref\":\"#/components/schemas/TrustedDevice\"},\"maxItems\":32,\"type\":\"array\"}},\"required\":[\"trusted_devices\"],\"type\":\"object\"},\"WeeklySchedule\":{\"description\":\"Seven days \xC3\x97 24 hours, indexed day*24+hour. true permits recording in that slot; interpreted using the configured recorder schedule timezone.\",\"items\":{\"type\":\"boolean\"},\"maxItems\":168,\"minItems\":168,\"type\":\"array\"},\"WorkspacesResponse\":{\"additionalProperties\":true,\"properties\":{\"configurable\":{\"type\":\"boolean\"},\"scope\":{\"enum\":[\"user\",\"installation\"],\"type\":\"string\"},\"workspaces\":{\"items\":{\"additionalProperties\":true,\"properties\":{\"authorization_unchanged\":{\"const\":true,\"type\":\"boolean\"},\"configurable\":{\"type\":\"boolean\"},\"description\":{\"type\":\"string\"},\"key\":{\"enum\":[\"live.navigator\",\"investigation\"],\"type\":\"string\"},\"label\":{\"type\":\"string\"},\"visible\":{\"type\":\"boolean\"}},\"required\":[\"key\",\"label\",\"description\",\"visible\",\"configurable\",\"authorization_unchanged\"],\"type\":\"object\"},\"type\":\"array\"}},\"required\":[\"workspaces\",\"configurable\",\"scope\"],\"type\":\"object\"},\"WorkspacesUpdate\":{\"additionalProperties\":true,\"properties\":{\"workspaces\":{\"additionalProperties\":false,\"minProperties\":1,\"properties\":{\"investigation\":{\"type\":\"boolean\"},\"live.navigator\":{\"type\":\"boolean\"}},\"type\":\"object\"}},\"required\":[\"workspaces\"],\"type\":\"object\"}},\"securitySchemes\":{\"apiKeyHeader\":{\"description\":\"Legacy API key or scoped token. Scope intersects the active owner\xE2\x80\x99s authority.\",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"basicAuth\":{\"description\":\"Existing database username/password authentication; restricted when a required password change is pending.\",\"scheme\":\"basic\",\"type\":\"http\"},\"bearerToken\":{\"description\":\"Legacy API key or scoped token through Authorization. Use a cookie-free client when discovering token-specific permissions.\",\"scheme\":\"bearer\",\"type\":\"http\"},\"legacyApiKeyHeader\":{\"description\":\"Legacy API key only. This operation rejects scoped API tokens even when their owner could call it.\",\"in\":\"header\",\"name\":\"X-API-Key\",\"type\":\"apiKey\"},\"legacyBearer\":{\"description\":\"Legacy API key through Authorization: Bearer. Scoped tokens are not accepted on this operation. A valid session cookie still takes precedence.\",\"scheme\":\"bearer\",\"type\":\"http\"},\"sessionCookie\":{\"description\":\"Existing authenticated session cookie; checked before other credentials. The explorer uses this method only.\",\"in\":\"cookie\",\"name\":\"session\",\"type\":\"apiKey\"}}},\"info\":{\"description\":\"Canonical contract for the current C backend: authentication and administration, fleet and workspaces, streams and devices, recordings and investigations, storage, events, system settings and bootstrap. The authenticated discovery endpoint projects operations and response variants for the current principal. Eight media-protocol surfaces and one internal callback are documented separately in the coverage inventory. Discovery is descriptive and does not grant access.\",\"title\":\"LightNVR API \xE2\x80\x94 permission-aware contracts\",\"version\":\"0.46.3-contracts.3\"},\"openapi\":\"3.1.1\",\"paths\":{\"/api/audit/events\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuditEvents\",\"parameters\":[{\"description\":\"One-based page.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Page size, including CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"schema\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditEventsPage\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List audit events\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/audit/events/export\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"exportAuditEvents\",\"parameters\":[{\"description\":\"One-based page.\",\"in\":\"query\",\"name\":\"page\",\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Page size, including CSV exports.\",\"in\":\"query\",\"name\":\"page_size\",\"schema\":{\"default\":100,\"maximum\":1000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"since\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"until\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Nonnegative signed 64-bit integer; since must not exceed a nonzero until.\",\"in\":\"query\",\"name\":\"principal_user_id\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"action\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"outcome\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"target_uuid\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"request_id\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Trimmed exact filter; native field capacity applies.\",\"in\":\"query\",\"name\":\"event_type\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"text/csv\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"One filtered page as UTF-8 CSV, quoted cells and neutralized spreadsheet formula prefixes. This is not an export of all pages.\",\"headers\":{\"Content-Disposition\":{\"schema\":{\"const\":\"attachment; filename=lightnvr-audit.csv\",\"type\":\"string\"}},\"X-Total-Count\":{\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export audit events\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/audit/settings\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getAuditSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettings\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get audit settings\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires system.admin. All submitted fields are validated before writes. Retention saves and pruning occur before decision modes: on internal failure some work may have applied. Read settings before deciding to retry. Successful retention changes include pruned_events.\",\"operationId\":\"updateAuditSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettingsWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuditSettings\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update audit settings\",\"tags\":[\"Audit\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/login\":{\"post\":{\"description\":\"Public credential submission. Successful JSON login sets the HttpOnly session cookie; MFA challenge returns totp_required/totp_token without a cookie. Optional inline TOTP is described; MFA completion and enrollment have separate identity contracts. Form submissions redirect for success, credential rejection and rate limit. Form parsing uses username/password only; JSON optional fields are not form inputs.\",\"operationId\":\"login\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginRequest\"}},\"application/x-www-form-urlencoded\":{\"schema\":{\"additionalProperties\":true,\"properties\":{\"password\":{\"type\":\"string\",\"writeOnly\":true},\"username\":{\"type\":\"string\"}},\"required\":[\"username\",\"password\"],\"type\":\"object\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginResponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Set-Cookie\":{\"description\":\"Session cookie on full login success; absent for MFA challenge. Additional trusted-device cookie may be set.\",\"schema\":{\"type\":\"string\"}}}},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Login rate limited.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[],\"summary\":\"Start an authenticated session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/login/config\":{\"get\":{\"description\":\"Public booleans and timeout values needed before sign-in. No personalized discovery is exposed.\",\"operationId\":\"getLoginConfig\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginConfig\"}}},\"description\":\"Successful response\"}},\"security\":[],\"summary\":\"Get public login configuration\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/login/totp\":{\"post\":{\"description\":\"Consumes an unexpired password-verified challenge bound to the issuing IP/user agent. Six-digit codes are single-use per account time step; account throttling applies. Success sets an HttpOnly session cookie, and optionally a trusted-device cookie. Never automatically retry a completion after an ambiguous response.\",\"operationId\":\"completeTotpLogin\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpLoginRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpLoginResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Authenticated session; optional trusted device if enabled and requested.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[],\"summary\":\"Complete an MFA login challenge\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/logout\":{\"post\":{\"description\":\"Public and idempotent. Deletes the session from the supplied cookie and clears session/trusted-device cookies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 requires Accept containing application/json or X-Requested-With; otherwise302 redirects to login.\",\"operationId\":\"logout\",\"parameters\":[{\"description\":\"Use application/json for the JSON success envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Presence also selects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[],\"summary\":\"End the current browser session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/auth/sessions\":{\"get\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. Returns at most 32 unexpired entries ordered by most recent activity, then descending ID. There is no pagination in this bounded contract. Pending MFA challenges are excluded from sessions.\",\"operationId\":\"listAuthSessions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthSessionsResponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List own sessions\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/sessions/{id}\":{\"delete\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. The ID must be a canonical positive decimal safe integer without signs, leading zeros, escapes or trailing path segments. Revocation is independent of the 32-entry list limit, can remove expired records, and excludes pending MFA sessions. Current-cookie clearing uses the exact removed credential. Missing or another owner\xE2\x80\x99s ID returns the same 404; database failure is 500. A successful revoke invalidates future use, but does not cancel already-authorized in-flight requests.\",\"operationId\":\"revokeAuthSession\",\"parameters\":[{\"description\":\"Canonical positive decimal safe integer; no leading zeros or encoded aliases.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CredentialRevoked\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Clears session or trusted_device only when the exact current credential was removed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential ID.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No matching owned credential.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke own session\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/trusted-devices\":{\"get\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. Returns at most 32 unexpired entries ordered by most recent activity, then descending ID. There is no pagination in this bounded contract. Pending MFA challenges are excluded from sessions.\",\"operationId\":\"listTrustedDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TrustedDevicesResponse\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List own trusted devices\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/trusted-devices/{id}\":{\"delete\":{\"description\":\"Session, Basic or legacy key personal self-service only. Scoped tokens, including users.manage tokens, cannot resolve a principal here; a valid session cookie retains existing precedence. Ownership is always the authenticated account, including administrators. Synthetic auth-disabled principals are rejected. Responses are private, no-store; credential material is never returned. The ID must be a canonical positive decimal safe integer without signs, leading zeros, escapes or trailing path segments. Revocation is independent of the 32-entry list limit, can remove expired records, and excludes pending MFA sessions. Current-cookie clearing uses the exact removed credential. Missing or another owner\xE2\x80\x99s ID returns the same 404; database failure is 500. A successful revoke invalidates future use, but does not cancel already-authorized in-flight requests.\",\"operationId\":\"revokeTrustedDevice\",\"parameters\":[{\"description\":\"Canonical positive decimal safe integer; no leading zeros or encoded aliases.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CredentialRevoked\"}}},\"description\":\"Successful response\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}},\"Set-Cookie\":{\"description\":\"Clears session or trusted_device only when the exact current credential was removed.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential ID.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable personal credential, including scoped-token-only, revoked/inactive/CIDR-restricted credentials or a pending password change for session/Basic authentication. Legacy keys retain their existing independent policy.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth-disabled principal has no personal credential inventory.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No matching owned credential.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes private and no-store. The common JSON response helper also appends no-cache, no-store, must-revalidate.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke own trusted device\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/users\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAdminUsers\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUsers\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List admin users\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires a non-scoped users.manage principal. Initial legacy role is converted to built-in policy grants; the caller may only delegate actions it holds. Returns 200. New users are provisioned inactive until policy and CIDR rules have been applied.\",\"operationId\":\"createAdminUser\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserCreate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}\":{\"delete\":{\"description\":\"Requires users.manage. Cannot delete yourself or the last legacy administrator; deletion and credential revocation are durable.\",\"operationId\":\"deleteAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"A non-scoped owner may edit its username/email. Other fields and other users require effective users.manage. Role is rejected; use versioned policy management. CIDR input is validated before saving. Password changes invalidate existing sessions/challenges/trusted devices; locked passwords cannot change.\",\"operationId\":\"updateAdminUser\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update admin user\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/api-key\":{\"post\":{\"description\":\"Only a non-scoped owner or non-scoped users.manage principal may rotate the legacy key. Scoped tokens are always forbidden, including tokens with users.manage. The unrestricted key is returned once; lists never expose it. Discovery does not grant access to another user.\",\"operationId\":\"createLegacyApiKey\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LegacyApiKeyCreated\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create legacy api key\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/auth/users/{id}/login-lockout/clear\":{\"post\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"clearUserLoginLockout\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LoginLockoutCleared\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear user login lockout\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}/password\":{\"put\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Requires old_password for self-service without users.manage and for a forced own password change. Success atomically changes the password and revokes sessions, pending challenges and trusted devices. Changing your own password clears browser cookies. Basic/key compatibility remains supported.\",\"operationId\":\"changePassword\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordChangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Change or reset a password\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/password-lock\":{\"put\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"setUserPasswordLock\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PasswordLockRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUser\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set user password lock\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/auth/users/{id}/totp/disable\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Disables MFA, erases the secret and revokes other sessions/challenges/trusted devices. The initiating authenticated session is retained.\",\"operationId\":\"disableTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Disable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/setup\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Returns enrollment secret only here; setup expires after ten minutes. Replacing pending enrollment invalidates the previous secret. An enabled account returns 409.\",\"operationId\":\"setupTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpSetupResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start MFA enrollment\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/status\":{\"get\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Returns enabled/configured booleans without secrets. Unavailability is not evidence that MFA is disabled.\",\"operationId\":\"getTotpStatus\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpStatusResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read MFA enrollment status\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/users/{id}/totp/verify\":{\"post\":{\"description\":\"A session, Basic credential or legacy key may manage its own account. Other accounts, and all scoped-token requests, require effective users.manage (owner permission intersected with token action/scope). Discovery describes the operation; it does not grant access to an arbitrary target ID. Consumes a fresh enrollment code, enables MFA and revokes other sessions/challenges/trusted devices. The initiating authenticated session is retained.\",\"operationId\":\"verifyTotp\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Leading zeroes are accepted by the handler; browser adapters require safe positive integer IDs. Concrete self-service targets only the current account.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpCodeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/TotpChangedResponse\"}}},\"description\":\"Validated success.\",\"headers\":{\"Cache-Control\":{\"description\":\"Includes no-store; authentication data must not be cached.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid credential, current password, challenge or code (including replay).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Principal lacks self-service or effective users.manage; password may be locked.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target user not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Enrollment expired or state changed; refresh status.\"},\"429\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"TOTP attempts are account-throttled. Do not automatically retry.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal/database failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Verify and enable MFA\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"identity_management\",\"version\":1}}},\"/api/auth/verify\":{\"get\":{\"description\":\"Session, Basic or legacy key only; scoped tokens do not resolve here. Required-password-change accounts may use verify. Auth-disabled mode returns synthetic admin and anonymous demo returns synthetic viewer; neither permits personalized discovery. Missing authentication clears stale session cookie.\",\"operationId\":\"verifyIdentity\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AuthVerifyResponse\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Verify current account identity\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/authorization/actions\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuthorizationActions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminActionCatalog\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorization actions\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"listAuthorizationRoles\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoles\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List authorization roles\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"createAuthorizationRole\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWritten\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/roles/{uuid}\":{\"delete\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"deleteAuthorizationRole\",\"parameters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminVersionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Delete authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"updateAuthorizationRole\",\"parameters\":[{\"description\":\"Role UUID.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminRoleWritten\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Update authorization role\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/simulate\":{\"post\":{\"description\":\"Requires users.manage. Evaluates the target user, not a token, against a concrete existing camera for camera-scoped actions or no camera for global actions. Does not modify grants; an audit event is appended.\",\"operationId\":\"simulateAuthorization\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminSimulationRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminSimulation\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Simulate authorization\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{id}\":{\"get\":{\"description\":\"Discovery is an operation-level capability. The server independently checks the concrete target and current effective authority. Basic credentials, session cookies and legacy keys retain their existing behavior. Mutations must not be automatically retried.\",\"operationId\":\"getUserAuthorizationPolicy\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserPolicy\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Get user authorization policy\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires a non-scoped users.manage principal, current expected_policy_version, and mode policy. Replaces the grants atomically. Delegated actions and camera scope cannot exceed caller authority. Self-lockout is rejected. Effective permissions and scoped-token intersections are re-evaluated on subsequent requests.\",\"operationId\":\"setUserAuthorizationPolicy\",\"parameters\":[{\"description\":\"Positive decimal signed 64-bit user ID. Browser adapters require a positive safe integer. Concrete target access is checked independently of operation discovery.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"maxLength\":19,\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminPolicyWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/AdminUserPolicy\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or body.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required or credential invalid.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Effective authority is insufficient; scoped credentials cannot perform non-scoped management operations.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Target does not exist.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Policy revision, self-lockout, immutable role, duplicate or in-use conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database or internal failure; a mutation may already have applied. Read current state before retrying.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Set user authorization policy\",\"tags\":[\"Identity administration\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"users.manage\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/authorization/users/{user_id}/tokens\":{\"get\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 before authentication. Current target and policy checks still run for each request.\",\"operationId\":\"listUserTokens\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenList\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"List token metadata for an allowed owner\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"parameters\":[{\"description\":\"Positive user ID. Non-scoped caller may manage their own tokens; other owners require users.manage. Visibility never grants authority over arbitrary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"post\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 before authentication. Current target and policy checks still run for each request. Requested actions and scope must be contained by both owner and requester. Only201 returns the one-time secret. Denied/invalid issuance leaves tokens and policy unchanged; audit entries may be written.\",\"operationId\":\"createUserToken\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiTokenCreated\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Active token limit reached.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Create a scoped token\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/authorization/users/{user_id}/tokens/{token_uuid}\":{\"delete\":{\"description\":\"Requires a non-scoped principal: self is allowed; another owner requires users.manage. Scoped credentials are always403 even with users.manage. Invalid path may produce400 before authentication. Current target and policy checks still run for each request. Revocation invalidates subsequent credential requests; it does not rewrite owner policy.\",\"operationId\":\"revokeUserToken\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SuccessResponse\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation or requested scope forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"User or token not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"legacyApiKeyHeader\":[]},{\"legacyBearer\":[]}],\"summary\":\"Revoke an allowed owner token\",\"tags\":[\"Tokens\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"parameters\":[{\"description\":\"Positive user ID. Non-scoped caller may manage their own tokens; other owners require users.manage. Visibility never grants authority over arbitrary IDs.\",\"in\":\"path\",\"name\":\"user_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Token UUID belonging to this owner.\",\"in\":\"path\",\"name\":\"token_uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}]},\"/api/camera-collections\":{\"get\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"listCameraCollections\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera collections\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"createCameraCollection\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"getCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera collection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollection\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateCameraCollection\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}/members\":{\"get\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"getCameraCollectionMembers\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Only static collections have explicit members.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized static collection members\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"setCameraCollectionMembers\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionMembersResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraCollectionMembers\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-collections/{uuid}/preview\":{\"post\":{\"description\":\"Collection visibility requires global camera.configure, ownership or sharing; a shared collection with zero visible live.view matches is hidden from a non-owner/non-manager. Member/effective counts and samples are scoped to authorized cameras. Smart selectors are disclosed only to the owner or manager; other readers receive null plus selector_redacted. Read-like preview performs no mutation.\",\"operationId\":\"previewCameraCollection\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraCollectionPreview\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview authorized collection matches\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/camera-favorites\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listCameraFavorites\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavoritesResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListCameraFavorites\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/camera-favorites/{uuid}\":{\"delete\":{\"description\":\"Remove only the current user favorite, including cameras no longer visible. Idempotent; scoped tokens and demo identities cannot mutate.\",\"operationId\":\"deleteCameraFavorite\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavoriteRemoval\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"addCameraFavorite\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraFavorite\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"AddCameraFavorite\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"listFleetTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera tags\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"createFleetTag\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera tag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTag\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/camera-tags/{uuid}/merge\":{\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"mergeFleetTag\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagMergeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetTagMergeResult\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"MergeFleetTag\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/cameras/{uuid}/location\":{\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"setCameraLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraLocationAssignmentRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraLocationAssignment\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/cameras/{uuid}/tags\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getCameraTagAssignments\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read tags for an authorized camera\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"setCameraTags\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/CameraTagAssignmentsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetCameraTags\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"},{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/client-config\":{\"get\":{\"description\":\"Any usable principal, including scoped tokens, can read this intentionally limited shape. Existing auth-disabled/demo behavior remains; this is not public in normal authenticated mode.\",\"operationId\":\"getClientConfig\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ClientConfig\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal or policy evaluation failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get safe browser playback configuration\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/detection/models\":{\"get\":{\"description\":\"Requires global camera.configure or that permission on an existing camera. Built-in models remain present if the configured model directory is absent. File models include paths and support/build metadata only for an authorized configurator.\",\"operationId\":\"listEvidenceModels\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceModels\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence models\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"global_or_camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/detection/results/{name}\":{\"get\":{\"description\":\"Current camera live.view authorizes the query. Captured detection UUIDs take precedence over reused stream names; only legacy NULL/empty UUID rows fall back to name. Optional start/end are epoch-second filters; absent bounds use a recent interval of at least 30 seconds.\",\"operationId\":\"getEvidenceDetections\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"start\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"end\",\"schema\":{\"minimum\":0,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceDetections\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence detections\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-destinations\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"listEventDestinations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinations\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List event destinations\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"createEventDestination\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-destinations/{uuid}\":{\"delete\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"deleteEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutationAck\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"updateEventDestination\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestinationUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventDestination\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update event destination\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"listEventRoutes\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutes\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List event routes\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"createEventRoute\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRouteCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/preview\":{\"post\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"previewEventRoute\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutePreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoutePreview\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/event-routes/{uuid}\":{\"delete\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"deleteEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventMutationAck\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"updateEventRoute\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"format\":\"uuid\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRouteUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventRoute\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update event route\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/events/catalog\":{\"get\":{\"description\":\"Global event administration. Native validation additionally enforces referenced destinations, registered event/health types, compatible predicates, available timezones and topic/TLS constraints. Preview never publishes; mutation acknowledgments do not imply delivery.\",\"operationId\":\"getEventCatalog\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EventCatalog\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get event catalog\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"events.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/fleet/cameras/query\":{\"post\":{\"description\":\"Read-only query over live.view-filtered, runtime-enriched inventory. Rows, totals and facets use matching authorized cameras; operational queues use all authorized cameras before query filters. can_configure is evaluated separately for each returned camera. Unknown collection is 404; out-of-range pages may be empty with nonzero totals. No stream credentials are returned.\",\"operationId\":\"queryFleetCameras\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQueryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetQueryResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Query authorized fleet cameras\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/selectors/preview\":{\"post\":{\"description\":\"Read-only query over live.view-filtered, runtime-enriched inventory. Rows, totals and facets use matching authorized cameras; operational queues use all authorized cameras before query filters. can_configure is evaluated separately for each returned camera. Unknown collection is 404; out-of-range pages may be empty with nonzero totals. No stream credentials are returned.\",\"operationId\":\"previewFleetSelector\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetPreviewResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview a selector over authorized fleet cameras\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/views\":{\"get\":{\"description\":\"Owned and shared saved filter configurations; up to 256 list entries. Stored selector references do not grant camera access and must be reauthorized on query. Existing can_share reflects the legacy admin role, not arbitrary action grants. Mutation contracts remain outside this read slice.\",\"operationId\":\"listFleetViews\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedViewsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List owned and shared fleet views\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shared visibility does not grant modification.\",\"operationId\":\"createFleetView\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetViewCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/fleet/views/{uuid}\":{\"delete\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shared visibility does not grant modification.\",\"operationId\":\"deleteFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"get\":{\"description\":\"Owned and shared saved filter configurations; up to 256 list entries. Stored selector references do not grant camera access and must be reauthorized on query. Existing can_share reflects the legacy admin role, not arbitrary action grants. Mutation contracts remain outside this read slice.\",\"operationId\":\"getFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an owned or shared fleet view\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Personal views are owner-managed; only administrators may share. Scoped tokens retain the existing owner identity behavior. Demo identities cannot mutate. Updates and deletion require the current revision; shared visibility does not grant modification.\",\"operationId\":\"updateFleetView\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetViewUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetSavedView\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetView\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/health\":{\"get\":{\"operationId\":\"getStreamHealth\",\"parameters\":[{\"description\":\"Return only process liveness.\",\"in\":\"query\",\"name\":\"liveness\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Return authorized stream histories.\",\"in\":\"query\",\"name\":\"history_only\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Include stream history arrays.\",\"in\":\"query\",\"name\":\"sparklines\",\"schema\":{\"type\":\"boolean\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HealthResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get stream health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/health/hls\":{\"get\":{\"operationId\":\"getHlsHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HlsHealth\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get hls health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}},\"/api/ice-servers\":{\"get\":{\"description\":\"Return configured ICE servers for a principal permitted to view at least one camera. TURN credentials are intentionally provided to that authorized browser.\",\"operationId\":\"getIceServers\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/IceServersResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Return configured ICE servers for a principal permitted to view at least one camera. TURN credentials are intentionally provided to that authorized browser.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/integrations/capabilities\":{\"get\":{\"description\":\"Requires an authenticated principal and returns only its scope: source identity, contract versions, operations (supported and authorized), limits, history, delivery and the cameras where the principal holds live.view, snapshot.create or observations.annotate (contract \xC2\xA79). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"getIntegrationCapabilities\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/IntegrationCapabilities\"}}},\"description\":\"Capability document for the calling principal.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer body).\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get integration capabilities\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigation-bookmarks\":{\"get\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"listEvidenceBookmarks\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarks\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List evidence bookmarks\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"createEvidenceBookmark\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigation-bookmarks/{uuid}\":{\"delete\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"deleteEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkDelete\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"get\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"getEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Owned by current user ID, including scoped-token owners; token replay scope must still permit every saved camera. Foreign ownership is concealed as404; own inaccessible cameras may return403. List filters invisible bookmarks/counts. Updates only title/note with current revision. Scoped-token siblings retain existing same-owner compatibility. Bookmarks do not hold recordings.\",\"operationId\":\"updateEvidenceBookmark\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmarkUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceBookmark\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update evidence bookmark\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/recordings/preview\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"previewEvidenceRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidencePreview\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Preview evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/search\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"searchEvidence\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSearchRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSearch\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Search evidence\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/investigations/segment-at\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"resolveEvidenceSegment\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSegmentAtRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSegmentAt\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Resolve evidence segment\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/thumbnail-samples\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"sampleEvidenceThumbnails\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThumbnailRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceThumbnails\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Sample evidence thumbnails\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/thumbnail/{recording_id}/{offset_ms}\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"getEvidenceThumbnail\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}},{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"offset_ms\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9]+$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence thumbnail\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/investigations/timeline\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"queryEvidenceTimeline\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceRangeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceTimeline\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Query evidence timeline\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/layouts\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listLiveLayouts\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListLiveLayouts\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"createLiveLayout\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayout\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/live/layouts/{uuid}\":{\"delete\":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"deleteLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}},\"put\":{\"description\":\"Personal layouts are owner-managed. Only administrators share. Scoped tokens and demo identities cannot mutate. Camera slots must all be live.view-visible; columns times rows must not exceed 36 and slots cannot exceed capacity. Updates/deletion require current revision.\",\"operationId\":\"updateLiveLayout\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayoutUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LiveLayout\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateLiveLayout\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/api/live/plans\":{\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"listFloorPlans\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlansResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListFloorPlans\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"createFloorPlan\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlanCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/plans/{uuid}\":{\"delete\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"deleteFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetRevisionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlanDeletion\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"getFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Shared plan library. Requires global camera.configure and a non-scoped identity; demo identities cannot mutate. Camera placements must be visible. Updates/deletion require current revision. Omitted sketch preserves it; null clears it.\",\"operationId\":\"updateFloorPlan\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlanUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFloorPlan\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/live/plans/{uuid}/background\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"getFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Exact uploaded PNG/JPEG bytes, accessible only when the plan is visible.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Raw structurally validated PNG/JPEG, at most 786432 bytes. Atomic publication preserves previous bytes on invalid upload. Returns the new plan revision; no request revision is used.\",\"operationId\":\"setFloorPlanBackground\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"maxLength\":786432,\"type\":\"string\"}},\"image/png\":{\"schema\":{\"format\":\"binary\",\"maxLength\":786432,\"type\":\"string\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FloorPlan\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Image too large or too short.\"},\"415\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Unsupported image content type.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"SetFloorPlanBackground\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/locations\":{\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"listFleetLocations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationsResponse\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List visible camera locations\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"createFleetLocation\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/locations/{uuid}\":{\"delete\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"deleteFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Visibility and counts follow existing live.view scope; location ancestors may be included. Global live.view can expose the full organization catalog. Camera-specific tag access requires live.view on that raw camera identity; missing objects may return 404 before authentication.\",\"operationId\":\"getFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful authorized read.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request/path or selector.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated principal.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera or action access denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, authorization or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read a visible camera location\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"description\":\"Authorization is checked before mutation. Requests preserve existing Basic authentication and legacy API-key compatibility. Resource-specific visibility, ownership and reference checks still apply.\",\"operationId\":\"updateFleetLocation\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocationUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetLocation\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation is not allowed for this principal.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Missing or invisible resource.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Operation failed.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateFleetLocation\",\"tags\":[\"Fleet\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/export\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"exportEvidenceLpr\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprExportRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprReads\"}}},\"description\":\"Validated success.\",\"headers\":{\"Content-Disposition\":{\"schema\":{\"const\":\"attachment; filename=\\\"lightnvr-lpr-export.json\\\"\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Export evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.export\",\"resource\":\"raw\"},{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\":\"lpr.read\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/reads/{read_uuid}\":{\"delete\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"deleteEvidenceLpr\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"read_uuid\",\"required\":true,\"schema\":{\"pattern\":\"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprDeleted\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/lpr/search\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"searchEvidenceLpr\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprSearchRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceLprReads\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Search evidence lpr\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"lpr.search\",\"resource\":\"raw\"},{\"action\":\"lpr.read\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/metrics\":{\"get\":{\"operationId\":\"getMetrics\",\"responses\":{\"200\":{\"content\":{\"text/plain\":{\"schema\":{\"type\":\"string\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get metrics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/motion/trigger\":{\"post\":{\"description\":\"Requires camera.configure on the named stream. Start/stop/pulse report annotation, tag and recording effects independently; tags require an active recording and detection-recording schedules may suppress recording. Pulse is bounded to 600000 ms. Invalid requests may return 400 before principal evaluation.\",\"operationId\":\"triggerMotion\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MotionTrigger\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/MotionTriggered\"}}},\"description\":\"Accepted motion effects are enumerated separately.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request or reference.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required effective authority denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Object not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision conflict, name conflict, limit or referenced object.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Persistence or internal failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Trigger motion\",\"tags\":[\"Events\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/observations\":{\"get\":{\"description\":\"Cursor-paged observation changes, ordered by persistence sequence and bounded by a watermark taken at query start (contract \xC2\xA77). Results are limited to capture cameras where the principal holds live.view; an explicitly requested camera without it returns 403. limit counts observations after in-page dedup. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"listObservations\",\"parameters\":[{\"description\":\"Comma-separated capture camera UUIDs, at most 10 (contract D1). Default: all cameras where the principal holds live.view.\",\"in\":\"query\",\"name\":\"camera_uuid\",\"required\":false,\"schema\":{\"maxLength\":369,\"pattern\":\"^[0-9a-fA-F-]{36}(,[0-9a-fA-F-]{36}){0,9}$\",\"type\":\"string\"}},{\"description\":\"Comma-separated original detector labels, at most 8 of 1..31 bytes; exact ASCII case-insensitive match (contract D1).\",\"in\":\"query\",\"name\":\"label\",\"required\":false,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}},{\"description\":\"Inclusive lower bound on observed_at.\",\"in\":\"query\",\"name\":\"since\",\"required\":false,\"schema\":{\"$ref\":\"#/components/schemas/ObservationTime\"}},{\"description\":\"Exclusive upper bound on observed_at; must be after since.\",\"in\":\"query\",\"name\":\"until\",\"required\":false,\"schema\":{\"$ref\":\"#/components/schemas/ObservationTime\"}},{\"description\":\"Opaque cursor from next_cursor or oldest_cursor, or the literal head (start live-only). Absent: start of retained history.\",\"in\":\"query\",\"name\":\"cursor\",\"required\":false,\"schema\":{\"maxLength\":256,\"minLength\":1,\"type\":\"string\"}},{\"description\":\"Observations per page.\",\"in\":\"query\",\"name\":\"limit\",\"required\":false,\"schema\":{\"default\":100,\"maximum\":500,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationPage\"}}},\"description\":\"History page. Items hold current state; dedupe by (source, id, revision).\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Invalid filter (invalid_request) or malformed cursor (invalid_cursor).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer body).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"A requested camera_uuid is not authorized for live.view (code forbidden).\"},\"410\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Cursor older than retained history, from another DB epoch, or ahead of the head (code cursor_expired); resume from oldest_cursor.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List observation history\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/observations/{id}\":{\"get\":{\"description\":\"One observation with its classifications, authorized by live.view on its immutable capture camera (contract \xC2\xA78.1). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"getObservation\",\"parameters\":[{\"description\":\"Observation ID.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Observation\"}}},\"description\":\"Current observation state.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Invalid observation ID (code invalid_request).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer body).\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Unknown observation, no live.view on its capture camera, or the capture camera was deleted; indistinguishable (code observation_not_found).\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get observation\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/observations/{id}/classifications/{namespace}/{name}\":{\"put\":{\"description\":\"Create or update one classification annotation on an observation (contract \xC2\xA710). Authorized with observations.annotate on the observation's capture-time camera (contract \xC2\xA712.1); authorization is evaluated before the namespace and the body, so an observation the caller cannot see is an indistinguishable 404. The key is two path segments, {namespace}/{name}; namespace must equal the server-derived writer namespace of the authenticated user (D3, advertised as capabilities principal.writer_namespace). Identical retries are no-ops (200, changed=false). A stored change bumps the observation revision and publishes an io.lightnvr.detection.observation_changed.v1 event with change_cause classification. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"putObservationClassification\",\"parameters\":[{\"description\":\"Observation ID.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}},{\"description\":\"Writer namespace.\",\"in\":\"path\",\"name\":\"namespace\",\"required\":true,\"schema\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":\"string\"}},{\"description\":\"Annotation name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"pattern\":\"^[a-z0-9][a-z0-9_.-]{0,31}$\",\"type\":\"string\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationClassificationWrite\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationClassificationResult\"}}},\"description\":\"Annotation updated, or identical retry (changed=false).\"},\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationClassificationResult\"}}},\"description\":\"Annotation created.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Invalid or unknown body field (code invalid_request).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer body).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"live.view without observations.annotate on the capture camera (forbidden), or a key namespace or body writer field that is not the caller's derived namespace, or a key created by another user (namespace_forbidden).\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Unknown observation, neither live.view nor observations.annotate on its capture camera, or the capture camera was deleted; indistinguishable (code observation_not_found).\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"revision_conflict or owner_decision_locked (current carries the stored annotation when the key exists), or annotation_limit (limit).\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"input_asset_id, input_revision or the evidence state does not match the observation evidence (code evidence_mismatch, with observation_asset_id and evidence_state).\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Put observation classification\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"observations.annotate\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/observations/{id}/snapshot\":{\"get\":{\"description\":\"The unannotated evidence JPEG that was the inference input or submitted image (contract \xC2\xA78.2). Requires snapshot.create on the capture camera; a caller with live.view but not snapshot.create gets 403 (contract D4). Never substitutes a latest frame or another asset. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md.\",\"operationId\":\"getObservationSnapshot\",\"parameters\":[{\"description\":\"Observation ID.\",\"in\":\"path\",\"name\":\"id\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/ObservationUuid\"}},{\"in\":\"header\",\"name\":\"If-None-Match\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Evidence image bytes; not JSON.\",\"headers\":{\"Cache-Control\":{\"description\":\"private, max-age=300\",\"schema\":{\"type\":\"string\"}},\"ETag\":{\"description\":\"Quoted asset ID.\",\"schema\":{\"type\":\"string\"}}}},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Evidence is still pending (code evidence_pending). Honour Retry-After and give up polling after 120 s total.\",\"headers\":{\"Retry-After\":{\"description\":\"Integer seconds, 1..30.\",\"schema\":{\"maximum\":30,\"minimum\":1,\"type\":\"integer\"}}}},\"304\":{\"description\":\"If-None-Match matched the ETag.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Invalid observation ID (code invalid_request).\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential (existing auth layer body).\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"live.view is held but snapshot.create is not (code forbidden).\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Evidence unavailable with a reason (evidence_unavailable), or the observation is unknown or not visible (observation_not_found).\"},\"410\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Evidence media expired (code evidence_expired); metadata remains readable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Internal failure (code internal_error).\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ObservationError\"}}},\"description\":\"Temporarily unavailable (code service_unavailable).\",\"headers\":{\"Retry-After\":{\"description\":\"Seconds before retrying.\",\"schema\":{\"minimum\":1,\"type\":\"integer\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get observation evidence image\",\"tags\":[\"Integrations\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"snapshot.create\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/add\":{\"post\":{\"operationId\":\"addOnvifDevice\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAddRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifAddResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"addOnvifDevice\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/claim\":{\"post\":{\"operationId\":\"claimOnvifDevice\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifClaimRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceSuccess\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"claimOnvifDevice\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/profiles\":{\"get\":{\"operationId\":\"getOnvifProfiles\",\"parameters\":[{\"in\":\"header\",\"name\":\"X-Device-URL\",\"required\":true,\"schema\":{\"maxLength\":511,\"minLength\":1,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Username\",\"required\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}},{\"in\":\"header\",\"name\":\"X-Password\",\"required\":false,\"schema\":{\"maxLength\":63,\"minLength\":0,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifProfilesResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifProfiles\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/device/test\":{\"post\":{\"operationId\":\"testOnvifDevice\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifTestRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"testOnvifDevice\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/devices\":{\"get\":{\"operationId\":\"getOnvifDevices\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDevicesResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDevices\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/discovery/discover\":{\"post\":{\"operationId\":\"discoverOnvifDevices\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDiscoveryRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDevicesResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"discoverOnvifDevices\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/onvif/discovery/status\":{\"get\":{\"operationId\":\"getOnvifDiscoveryStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OnvifDiscoveryStatus\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getOnvifDiscoveryStatus\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/openapi.json\":{\"get\":{\"description\":\"Uses actual session, Basic, legacy API-key or scoped-token identity with existing credential precedence and restrictions. Synthetic auth_disabled/anonymous demo principals are rejected with403. Real authenticated demo users retain their actual action permissions. No requested-user/role override. Every response is private and non-cacheable; downloaded documentation never grants authority.\",\"operationId\":\"getOpenApi\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/OpenApiDocument\"}}},\"description\":\"Permission-aware partial document. Never a fallback to the canonical unfiltered source.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Synthetic auth_disabled or anonymous demo identity is excluded from personalized discovery.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\",\"headers\":{\"Cache-Control\":{\"description\":\"Applied to success and error responses. No shared cache, ETag or persistent personalized-spec cache.\",\"schema\":{\"const\":\"private, no-store\",\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Discover the current principal\xE2\x80\x99s API contract\",\"tags\":[\"Discovery\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings\":{\"get\":{\"description\":\"Replay-authorized rows and counts; empty scope without an explicit stream returns an empty page. Every explicitly named stream must be authorized or the request returns generic403. Completed rows and sufficiently recent in-progress rows are included; stale incomplete rows age out. Filters and collection membership intersect authorization. Current handlers preserve auth-disabled/demo deployment behavior.\",\"operationId\":\"listRecordings\",\"parameters\":[{\"description\":\"Comma-separated current stream names; every requested name must be replay authorized.\",\"in\":\"query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Optional collection UUID, resolved on the server and intersected with replay scope.\",\"in\":\"query\",\"name\":\"collection_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 start bound; invalid strings currently behave as no bound.\",\"in\":\"query\",\"name\":\"start\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"UTC ISO8601 end bound; invalid strings currently behave as no bound.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Default1; nonpositive/invalid numeric values normalize to1.\",\"in\":\"query\",\"name\":\"page\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Default20, numeric maximum1000; nonpositive/invalid values normalize to20. Case-insensitive all returns all matching authorized rows, sets page1 and recounts.\",\"in\":\"query\",\"name\":\"limit\",\"required\":false,\"schema\":{\"anyOf\":[{\"type\":\"integer\"},{\"const\":\"all\"}]}},{\"description\":\"Unsupported fields fall back to start_time.\",\"in\":\"query\",\"name\":\"sort\",\"required\":false,\"schema\":{\"enum\":[\"id\",\"stream_name\",\"start_time\",\"end_time\",\"size_bytes\"]}},{\"description\":\"asc or desc; unsupported values fall back to desc.\",\"in\":\"query\",\"name\":\"order\",\"required\":false,\"schema\":{\"enum\":[\"asc\",\"desc\"]}},{\"description\":\"0 all,1 with detection,-1 without; numeric values clamp to this interval.\",\"in\":\"query\",\"name\":\"has_detection\",\"required\":false,\"schema\":{\"type\":\"integer\"}},{\"description\":\"Comma-separated labels; implies detection-only filter.\",\"in\":\"query\",\"name\":\"detection_label\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Leading0 means unprotected, leading1 protected; otherwise all.\",\"in\":\"query\",\"name\":\"protected\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Comma-separated recording tags.\",\"in\":\"query\",\"name\":\"tag\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Comma-separated capture methods, including continuous,scheduled,detection,motion,manual.\",\"in\":\"query\",\"name\":\"capture_method\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingsPage\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized recordings\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-delete\":{\"post\":{\"description\":\"Fixed IDs preflight every existing member before dispatch; forbidden members reject the whole batch. Filter jobs capture authorized UUID scope before dispatch. Operational missing/protected failures are counted separately and can coexist with successes. Poll the owned job; cancellation is unsupported.\",\"operationId\":\"deleteRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionJobAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create a recording deletion job\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/batch-delete/progress/{job_id}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Terminal complete includes partial operational failures; inspect all counters. There is no cancel operation.\",\"operationId\":\"getRecordingDeletionProgress\",\"parameters\":[{\"description\":\"Opaque deletion job identifier; possession alone grants no access.\",\"in\":\"path\",\"name\":\"job_id\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionProgress\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned recording deletion progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-download\":{\"post\":{\"description\":\"Preflights every requested recording before creating the job. Missing404/in-progress409 may abort in input order before a later forbidden member; these early errors create no job or archive. ZIP32 uses original media, including recovery-download files with saved playback issues. Detected unavailable-source/member-count and ZIP32-limit failures reject the result. The current writer does not consistently detect filesystem read/write/close failures; complete alone does not prove archive integrity. Cancellation is unsupported.\",\"operationId\":\"createRecordingExport\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportAccepted\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Busy job slots, unavailable source, or shutdown.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create an authorized ZIP export job\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/batch-download/result/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Export ownership also matches username. Pending/running jobs return409 with error Job is not_ready; failed jobs return409 with error Job is error. Only complete archives are downloadable. Results can be read repeatedly while retained; result access refreshes job retention. This is not a one-time token. No cancellation endpoint exists.\",\"operationId\":\"downloadRecordingExport\",\"parameters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"path\",\"name\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single byte range using the common file-serving parser.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Complete ZIP archive.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"206\":{\"content\":{\"application/zip\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Single ZIP byte range.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Download an owned completed ZIP export\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-download/status/{export_token}\":{\"get\":{\"description\":\"Requires the exact creator user ID and exact scoped-token UUID, when created by a scoped token. An administrator, sibling token or owner using another credential cannot borrow a scoped-token-owned job. Unknown or differently owned jobs return404. Discovery visibility is authenticated; camera grants are not rechecked for job lookup. Export ownership also matches the stored username. Jobs can be reclaimed after their retention interval; polling does not make a job permanent.\",\"operationId\":\"getRecordingExportStatus\",\"parameters\":[{\"description\":\"Opaque export job identifier, not a bearer credential.\",\"in\":\"path\",\"name\":\"export_token\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingExportStatus\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read owned recording export progress\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/batch-protect\":{\"post\":{\"description\":\"An otherwise-valid forbidden existing member yields403 before any protection writes. Invalid/missing IDs and subsequent database write failures are counted, so operational failures may produce partial success.\",\"operationId\":\"protectRecordings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchProtectionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchProtectionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set protection on recording IDs\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/batch-tags\":{\"post\":{\"description\":\"Preflights all IDs and rejects missing/forbidden members before writes. Invalid IDs return400. Operational database failures do not promise an all-or-nothing transaction.\",\"operationId\":\"updateRecordingTagsBatch\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchTagsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingBatchTagsResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Add or remove tags on recording IDs\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/detection-labels\":{\"get\":{\"description\":\"Unique detection labels within replay-authorized camera scope. An empty scope returns labels:[].\",\"operationId\":\"listRecordingDetectionLabels\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDetectionLabels\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized detection label facets\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/download/{recording_id}\":{\"get\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Requires recordings.export before source probes. In-progress media returns409. Saved playback failures do not block original-byte recovery download. prepare=1 returns ready/preparing JSON; ordinary success is an attachment. No compatibility transcoding is requested by this route. Byte ranges use the same permissive local parser as playback: prefix/suffix/open-ended accepted and clamped, malformed missing-dash/multiple ranges may parse as a single range. Local416 omits Content-Range.\",\"operationId\":\"downloadRecording\",\"parameters\":[{\"description\":\"Exactly1 selects JSON readiness without media bytes.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"UI hint for externally stored recordings. Actual source selection follows stored metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=start-, or bytes=-suffix range. Actual permissive parser limitations are documented above.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"prepare=1 JSON ready, or complete media body.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Attachment with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"description\":\"Requested media is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"schema\":{\"type\":\"string\"}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A single satisfiable media byte range. Validate bytes/protocol separately from JSON.\",\"headers\":{\"Accept-Ranges\":{\"schema\":{\"const\":\"bytes\"}},\"Content-Disposition\":{\"description\":\"Attachment with sanitized recording filename; absent on JSON readiness.\",\"schema\":{\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\",\"headers\":{\"Retry-After\":{\"description\":\"Present when preparation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Download or prepare original recording media\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.export\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/files\":{\"delete\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"deleteEvidenceRecordingFile\",\"parameters\":[{\"description\":\"Exact registered recording file path. Unknown paths return404; this is not an arbitrary filesystem operation. Captured recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileDeleted\"}}},\"description\":\"Validated success.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileDeleted\"}}},\"description\":\"Deletion scheduled; not proof of file removal.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete evidence recording file\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/files/check\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"checkEvidenceRecordingFile\",\"parameters\":[{\"description\":\"Exact registered recording file path. Unknown paths return404; this is not an arbitrary filesystem operation. Captured recording UUID controls authority.\",\"in\":\"query\",\"name\":\"path\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceFileCheck\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Check evidence recording file\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/play/{recording_id}\":{\"get\":{\"description\":\"Requires recordings.replay for capture identity before inspecting source availability. In-progress409; saved invalid_media/no_video/decode_error422 with recovery explanation. prepare=1 returns JSON ready/preparing; ordinary success serves media bytes. Archive/HEVC preparation can return202 (prepare) or503 (ordinary) with Retry-After2. Authorization uses the logical recording, never a caller-supplied filesystem path. Local byte ranges accept prefix, suffix and open-ended forms; excessive end/suffix clamp to EOF. Invalid unit, start\xE2\x89\xA5size, reversed bounds and zero suffix yield416 JSON. Local416 currently omits Content-Range; archive416 supplies bytes */size. Existing parser is permissive: bytes=0 (missing dash) becomes full206 and multi-ranges are treated as their first numeric range, not multipart. Do not infer strict RFC rejection of malformed ranges.\",\"operationId\":\"playRecording\",\"parameters\":[{\"description\":\"Exactly1 selects JSON readiness without media bytes.\",\"in\":\"query\",\"name\":\"prepare\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"Exactly1 requests compatibility cache;0 requests original. Absent defaults to prepare=1 compatibility behavior.\",\"in\":\"query\",\"name\":\"transcode\",\"required\":false,\"schema\":{\"enum\":[\"0\",\"1\"]}},{\"description\":\"UI hint for externally stored recordings. Actual source selection follows stored metadata; does not grant authority.\",\"in\":\"query\",\"name\":\"archive\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Single bytes=start-end, bytes=start-, or bytes=-suffix range. Actual permissive parser limitations are documented above.\",\"in\":\"header\",\"name\":\"Range\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackReady\"}},\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"prepare=1 JSON ready, or complete media body.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}}}},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}}},\"description\":\"Requested media is preparing; retry after header interval.\",\"headers\":{\"Retry-After\":{\"schema\":{\"type\":\"string\"}}}},\"206\":{\"content\":{\"video/mp4\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/webm\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-matroska\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}},\"video/x-msvideo\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A single satisfiable media byte range. Validate bytes/protocol separately from JSON.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Conclusive saved interrupted-media failure; playback is unavailable. Download recovery is a separately authorized export operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\",\"headers\":{\"Retry-After\":{\"description\":\"Present when preparation is pending, usually2seconds.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Prepare or play an authorized recording\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/protected\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"countEvidenceProtected\",\"parameters\":[{\"description\":\"Optional current camera name; otherwise count only recordings visible by replay scope.\",\"in\":\"query\",\"name\":\"stream\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceProtectedCount\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Count evidence protected\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/sync\":{\"post\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"syncEvidenceRecordings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/EvidenceSyncComplete\"}}},\"description\":\"Validated success.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Sync evidence recordings\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/tags\":{\"get\":{\"description\":\"Unique recording tags within replay-authorized camera scope. An empty scope returns tags:[]; no hidden-camera facet values are published.\",\"operationId\":\"listRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTags\"}}},\"description\":\"Successful response\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized recording tag facets\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/recordings/thumbnail/{recording_id}/{index}\":{\"get\":{\"description\":\"JPEG cache/generation requires capture-camera recordings.replay. Global disabled thumbnails403 and disabled hover frames403 are checked before auth. Otherwise authorization precedes cache/file access. Index0,1,2 selects start/middle/end samples; index>0 may be disabled by thumbnails_per_recording=1. Cache uses private max-age86400; source preparation/deletion can return503/409. Thumbnails do not assert original media is playable.\",\"operationId\":\"getRecordingThumbnail\",\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"JPEG thumbnail bytes.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Single cached/generated JPEG byte range.\",\"headers\":{\"Accept-Ranges\":{\"description\":\"The local playback handler and file server both emit this header; clients can observe combined bytes, bytes. Archive responses emit one bytes value.\",\"schema\":{\"enum\":[\"bytes\",\"bytes, bytes\"],\"type\":\"string\"}},\"Content-Length\":{\"schema\":{\"type\":\"integer\"}},\"Content-Range\":{\"description\":\"For206, bytes start-end/total. Local416 currently omits it.\",\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording still in progress, deletion pending or source conflict.\"},\"416\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested byte range is not satisfiable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"anyOf\":[{\"$ref\":\"#/components/schemas/ApiError\"},{\"$ref\":\"#/components/schemas/RecordingPlaybackPreparing\"}]}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized recording thumbnail\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Thumbnail sample index.\",\"in\":\"path\",\"name\":\"index\",\"required\":true,\"schema\":{\"maximum\":2,\"minimum\":0,\"type\":\"integer\"}}]},\"/api/recordings/{recording_id}\":{\"delete\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Protected/in-progress/active-transfer media returns409. A202 response means deletion is queued, not that bytes have disappeared.\",\"operationId\":\"deleteRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionResult\"}}},\"description\":\"Successful response\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDeletionResult\"}}},\"description\":\"Deletion accepted and pending; status is deletion_pending.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected/in-progress media, unavailable source, or job not ready.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Delete a recording\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recording.delete\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Authorizes recordings.replay using current raw facts for stored capture identity before emitting metadata. This response does not include tags; use the separate tags endpoint.\",\"operationId\":\"getRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingDetail\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Shutdown, unavailable source or busy media preparation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read recording detail\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}]},\"/api/recordings/{recording_id}/protect\":{\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Protected media cannot be deleted by ordinary recording deletion.\",\"operationId\":\"setRecordingProtection\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingProtectionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingProtectionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set recording protection\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recording_id}/retention\":{\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Negative input clears the override, zero means indefinite. The returned integer reflects the converted input; clearing does not normalize it to -1.\",\"operationId\":\"setRecordingRetention\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingRetentionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingRetentionResult\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Set or clear a recording retention override\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/recordings/{recording_id}/tags\":{\"get\":{\"description\":\"Uses stored capture identity and recordings.replay before returning tags.\",\"operationId\":\"getRecordingTags\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized recording\xE2\x80\x99s tags\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Stable recording database ID. Most existing handlers use permissive strtoull parsing; zero/invalid IDs fail400, unknown IDs fail404 before authorization. Stored camera UUID is authoritative; a deleted capture camera yields404, without rebinding to a reused display name. Legacy rows with no UUID use the current stream name.\",\"in\":\"path\",\"name\":\"recording_id\",\"required\":true,\"schema\":{\"minimum\":1,\"type\":\"integer\"}}],\"put\":{\"description\":\"Authorizes the stored capture camera UUID using current raw facts. A deleted capture camera returns404; legacy null/empty UUID rows fall back to the current stream name. Authorization precedes tag mutation.\",\"operationId\":\"setRecordingTags\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTagsById\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Malformed path or request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Capture identity or batch member forbidden; authorization denial occurs before mutation or job creation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection, or owned job not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, allocation, worker creation or file failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Replace recording tags\",\"tags\":[\"Recording actions\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"evidence.protect\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/settings\":{\"get\":{\"operationId\":\"getSystemSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettings\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system settings\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"operationId\":\"updateSystemSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettingsUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemSettingsSaved\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update system settings\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/settings/go2rtc/validate\":{\"post\":{\"operationId\":\"validateGo2rtcConfiguration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcConfigurationInput\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcConfigurationValidation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Validate go2rtc configuration\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/setup/status\":{\"get\":{\"operationId\":\"getSetupStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupStatus\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get setup status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}},\"post\":{\"description\":\"Before setup completion this bootstrap operation is public. Once complete it requires system.admin; resetting requires that authority. Malformed/absent optional fields retain the native default of completing setup.\",\"operationId\":\"updateSetupStatus\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupUpdate\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SetupUpdated\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{},{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update setup status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"bootstrap_setup\",\"version\":1}}},\"/api/snapshots/{camera}/{filename}\":{\"get\":{\"description\":\"New MQTT snapshot URLs use the captured immutable camera UUID namespace. UUID URLs authorize that UUID and never fall back to a display-name directory. Existing name-only snapshot URLs remain compatible and authorize the current named camera; old files do not contain recoverable capture identity. Camera snapshot.create is required.\",\"operationId\":\"getEvidenceSnapshot\",\"parameters\":[{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"camera\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}},{\"description\":\"Concrete resource identifier; authorized independently.\",\"in\":\"path\",\"name\":\"filename\",\"required\":true,\"schema\":{\"minLength\":1,\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"image/jpeg\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence snapshot\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"snapshot.create\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-archive\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageArchive\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-archive/retry\":{\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"retryStorageArchive\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveRetryRequest\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageArchiveRetryResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageArchive\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-compliance\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageCompliance\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageComplianceResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageCompliance\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStorageMigrations\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigrationsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageMigrations\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Queues move (default) or copy of a complete recording. A 202 job is not completion. Poll the job state; verify checksum/publication and cleanup independently. Permission is global storage.configure, not creator ownership.\",\"operationId\":\"createStorageMigration\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigrationCreate\"}}},\"required\":true},\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{uuid}\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{uuid}/cancel\":{\"post\":{\"description\":\"Global storage.configure permits acting on any job. Cancel may request cancellation of an active job; retry requeues eligible failed/cancelled work. Poll state to observe completion and cleanup; the acknowledgement is not a completed transfer.\",\"operationId\":\"cancelStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CancelStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-migrations/{uuid}/retry\":{\"post\":{\"description\":\"Global storage.configure permits acting on any job. Cancel may request cancellation of an active job; retry requeues eligible failed/cancelled work. Poll state to observe completion and cleanup; the acknowledgement is not a completed transfer.\",\"operationId\":\"retryStorageMigration\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageMigration\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"RetryStorageMigration\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStoragePolicies\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoliciesResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStoragePolicies\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"createStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/preview\":{\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"previewStoragePolicy\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyPreviewRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyPreview\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"PreviewStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/{uuid}\":{\"delete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"deleteStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicy\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-policies/{uuid}/apply\":{\"post\":{\"description\":\"Preview counts records whose captured policy revision differs. Apply updates those revision references transactionally and requires unfinished transfers to finish first. It does not itself delete files or complete asynchronous archive/migration work.\",\"operationId\":\"applyStoragePolicy\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyApplyRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePolicyApplyResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ApplyStoragePolicy\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStoragePools\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStoragePools\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"createStoragePool\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-pools/{uuid}\":{\"delete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"deleteStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStoragePool\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePoolUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StoragePool\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStoragePool\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"listStorageTargets\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetsResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ListStorageTargets\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"createStorageTarget\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetCreate\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"CreateStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}\":{\"delete\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"deleteStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}},{\"in\":\"query\",\"name\":\"revision\",\"required\":true,\"schema\":{\"maximum\":9007199254740991,\"minimum\":1,\"type\":\"integer\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/FleetMutationAck\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"DeleteStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}},\"put\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"updateStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTargetUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"UpdateStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage-targets/{uuid}/probe\":{\"post\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"probeStorageTarget\",\"parameters\":[{\"in\":\"path\",\"name\":\"uuid\",\"required\":true,\"schema\":{\"$ref\":\"#/components/schemas/CameraUuid\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageTarget\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"ProbeStorageTarget\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage/cleanup\":{\"post\":{\"description\":\"Signals the asynchronous cleanup controller. Success acknowledges scheduling, not deletion or reclaimed bytes. Invalid bodies are rejected before scheduling. Protected media and retention constraints still apply.\",\"operationId\":\"triggerStorageCleanup\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCleanupRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageCleanupResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"TriggerStorageCleanup\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/storage/health\":{\"get\":{\"description\":\"Requires global storage.configure, including for scoped tokens. Resource existence, revisions and reference/state checks still apply. Basic and legacy-key compatibility is preserved.\",\"operationId\":\"getStorageHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StorageHealthResponse\"}}},\"description\":\"Successful operation.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Global storage.configure denied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource missing.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, reference or state conflict.\"},\"422\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage target unavailable.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Storage operation failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Catalog or storage service unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"GetStorageHealth\",\"tags\":[\"Storage\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"storage.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams\":{\"get\":{\"description\":\"Authenticated list discovery remains available even with no matching camera grants. Actual rows and totals are filtered per camera. Permission checks use runtime plus bounded go2rtc health observations; this can differ from full-read raw-health authorization. This pilot describes the existing auth-disabled/demo behavior only as a deployment caveat; personalized discovery itself requires a real identity.\",\"operationId\":\"listStreams\",\"parameters\":[{\"description\":\"Exactly true or 1 selects a paginated summary envelope. All other values, including absence, select the compatibility array. Other list query options only apply to summary mode.\",\"in\":\"query\",\"name\":\"summary\",\"required\":false,\"schema\":{\"default\":\"false\",\"type\":\"string\"}},{\"description\":\"Summary row shape: admin/default or live filters live.view; lpr filters the conjunction lpr.search and lpr.read and emits only identity.\",\"in\":\"query\",\"name\":\"surface\",\"required\":false,\"schema\":{\"default\":\"admin\",\"enum\":[\"admin\",\"live\",\"lpr\"],\"type\":\"string\"}},{\"description\":\"Summary page number.\",\"in\":\"query\",\"name\":\"page\",\"required\":false,\"schema\":{\"default\":1,\"maximum\":1000000,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Summary page size.\",\"in\":\"query\",\"name\":\"page_size\",\"required\":false,\"schema\":{\"default\":50,\"maximum\":100,\"minimum\":1,\"type\":\"integer\"}},{\"description\":\"Case-insensitive substring over authorized name, UUID, tags, location path and safe address. Handler buffer retains up to 255 bytes.\",\"in\":\"query\",\"name\":\"search\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Summary sort key; equal values break ties by name.\",\"in\":\"query\",\"name\":\"sort_by\",\"required\":false,\"schema\":{\"default\":\"name\",\"enum\":[\"name\",\"status\",\"resolution\",\"fps\",\"recording\"],\"type\":\"string\"}},{\"description\":\"Summary sort direction.\",\"in\":\"query\",\"name\":\"sort_order\",\"required\":false,\"schema\":{\"default\":\"asc\",\"enum\":[\"asc\",\"desc\"],\"type\":\"string\"}},{\"description\":\"Summary availability filter, applied before total.\",\"in\":\"query\",\"name\":\"availability\",\"required\":false,\"schema\":{\"default\":\"all\",\"enum\":[\"all\",\"live\",\"offline\",\"never_connected\",\"disabled\"],\"type\":\"string\"}},{\"description\":\"Exactly true or 1 requests admin_url for the admin summary surface. Other values omit it; live/LPR surfaces never include it. Values are redacted unless that same camera is configurable.\",\"in\":\"query\",\"name\":\"include_admin_url\",\"required\":false,\"schema\":{\"default\":\"false\",\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamListResponse\"}}},\"description\":\"Compatibility array or requested summary page; empty authorized results are valid.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"List authorized streams\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"post\":{\"description\":\"Create a camera using global camera.configure. Creation intent is distinct from editing an existing camera.\",\"operationId\":\"createStream\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamCreateRequest\"}}},\"required\":true},\"responses\":{\"201\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamCreated\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create a camera using global camera.configure. Creation intent is distinct from editing an existing camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/test\":{\"post\":{\"description\":\"Probe an administrator-supplied media URL. A failed media probe returns 200 with success=false; it does not create a camera.\",\"operationId\":\"probeStream\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamProbeRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamProbeResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Probe an administrator-supplied media URL. A failed media probe returns 200 with success=false; it does not create a camera.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}\":{\"delete\":{\"description\":\"Queue camera deletion using captured identity. Acknowledgment does not prove completion. Default disables the camera; permanent=true removes its configuration.\",\"operationId\":\"deleteStream\",\"parameters\":[{\"description\":\"Exactly true requests permanent deletion; absence or another value disables.\",\"in\":\"query\",\"name\":\"permanent\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDeleteAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Queue camera deletion using captured identity. Acknowledgment does not prove completion. Default disables the camera; permanent=true removes its configuration.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"operationId\":\"getStream\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamSingleResponse\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStream\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"description\":\"Requires camera.configure on the named camera using raw DB facts; no read permission is implied. Boolean set_privacy_mode selects a privacy-only request and rejects accompanying configuration fields. Ordinary updates are partial; enable_disabled=true with enabled=false is rejected. Unknown ordinary fields are ignored, and 202 is not a durable-success guarantee. A 502 privacy response can follow a saved flag when go2rtc removal fails; retry is required and the stream may remain viewable in the meantime.\",\"operationId\":\"updateStream\",\"requestBody\":{\"content\":{\"application/json\":{\"examples\":{\"privacy\":{\"value\":{\"set_privacy_mode\":true}},\"zeroRetention\":{\"value\":{\"detection_threshold\":0,\"retention_days\":0}}},\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamPrivacyUpdated\"}}},\"description\":\"Privacy-only update completed.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamUpdateAccepted\"}}},\"description\":\"Ordinary update accepted for asynchronous application.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"The authenticated principal cannot perform the action on this camera.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No runtime stream with this name exists. Existing stream handlers may resolve this before checking credentials.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"An explicitly bounded override exceeds its supported byte size.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Privacy flag was saved, but removing the stream from go2rtc failed. The camera may still be viewable; retry the privacy-only request. This error is not a rollback of the saved privacy flag.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update stream configuration or privacy\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/daynight\":{\"get\":{\"operationId\":\"getDaynight\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getDaynight\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setDaynight\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DaynightUpdated\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setDaynight\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/detection-engines\":{\"get\":{\"operationId\":\"getStreamDetectionEngines\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setStreamDetectionEngines\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEnginesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamDetectionEngines\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setStreamDetectionEngines\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/full\":{\"get\":{\"description\":\"Requires live.view on this camera using raw DB facts. camera.configure on the same camera using runtime facts determines sensitive fields. A configure-only grant does not imply read access.\",\"operationId\":\"getStreamFull\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamFullResponse\"}}},\"description\":\"Wrapped details with per-camera conditional redaction.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path, query or typed update values.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authenticated credential, including expired/revoked tokens, inactive users, CIDR or password-change restrictions.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"The authenticated principal cannot perform the action on this camera.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No runtime stream with this name exists. Existing stream handlers may resolve this before checking credentials.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal, database, policy evaluation, serialization or update scheduling failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read full stream configuration\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/imaging/options\":{\"get\":{\"operationId\":\"getImagingOptions\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingOptionsResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getImagingOptions\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/imaging/settings\":{\"get\":{\"operationId\":\"getImagingSettings\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsResponse\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getImagingSettings\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setImagingSettings\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ImagingSettingsRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setImagingSettings\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/absolute\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"absolutePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"absolutePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/capabilities\":{\"get\":{\"operationId\":\"getPtzCapabilities\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzCapabilities\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getPtzCapabilities\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/ptz/goto-preset\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"gotoPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzGotoPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"gotoPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/home\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"homePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"homePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/move\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"movePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"movePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/preset\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setPtzPreset\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresetRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresetResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setPtzPreset\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/presets\":{\"get\":{\"operationId\":\"getPtzPresets\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzPresets\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getPtzPresets\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}]},\"/api/streams/{name}/ptz/relative\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"relativePtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PtzAxesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"relativePtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/set-home\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"setPtzHome\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setPtzHome\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/ptz/stop\":{\"parameters\":[{\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"stopPtz\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceEmptyRequest\"}}},\"required\":false},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/DeviceCommandResult\"}}},\"description\":\"Device result; optional device capabilities remain explicitly optional.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input, denied access, unavailable device or failed device operation.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"stopPtz\",\"tags\":[\"Device controls\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"ptz.control\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/recording\":{\"get\":{\"operationId\":\"getStreamRecording\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingState\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamRecording\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Start manual recording (202 starting) or stop it (200 idle). Continuous scheduled recording cannot be manually stopped. Follow the recording state to observe completion.\",\"operationId\":\"controlStreamRecording\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingChanged\"}}},\"description\":\"Successful response.\"},\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRecordingChanged\"}}},\"description\":\"Recording start accepted; not a durable recording guarantee.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Start manual recording (202 starting) or stop it (200 idle). Continuous scheduled recording cannot be manually stopped. Follow the recording state to observe completion.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/refresh\":{\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"description\":\"Queue refresh of the captured camera identity. A response may coalesce with an existing refresh; it does not prove a successful camera connection.\",\"operationId\":\"refreshStream\",\"responses\":{\"202\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRefreshAccepted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Queue refresh of the captured camera identity. A response may coalesce with an existing refresh; it does not prove a successful camera connection.\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/retention\":{\"get\":{\"operationId\":\"getStreamRetention\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamRetention\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"put\":{\"operationId\":\"setStreamRetention\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetentionRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamRetention\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"setStreamRetention\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/streams/{name}/zones\":{\"delete\":{\"operationId\":\"deleteStreamZones\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesDeleted\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"deleteStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}},\"get\":{\"operationId\":\"getStreamZones\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZones\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"getStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}},\"parameters\":[{\"description\":\"Current stream name, percent-encoded as one URL segment. The runtime stores up to 255 UTF-8 bytes. Embedded slash is unsupported by the full-read suffix parser; normal creation restricts names to letters, digits, dot, hyphen and underscore. Legacy configured names may require encoding. UUID is not accepted in place of the name.\",\"in\":\"path\",\"name\":\"name\",\"required\":true,\"schema\":{\"maxLength\":255,\"minLength\":1,\"type\":\"string\"}}],\"post\":{\"operationId\":\"saveStreamZones\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesRequest\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/StreamZonesSaved\"}}},\"description\":\"Successful response.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid input. No mutation is applied.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable authentication credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Action or camera denied. No mutation is applied.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Camera unavailable.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Current state or capacity conflicts with the operation.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, scheduling or internal failure.\"},\"502\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Upstream media service failed.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"saveStreamZones\",\"tags\":[\"Streams\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"camera.configure\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system\":{\"get\":{\"operationId\":\"getSystemInformationLegacy\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system information legacy\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/backup\":{\"post\":{\"operationId\":\"createSystemBackup\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemBackupCreated\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Create system backup\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/go2rtc/effective-config\":{\"get\":{\"operationId\":\"getGo2rtcEffectiveConfiguration\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcEffectiveConfiguration\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc effective configuration\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/go2rtc/override-status\":{\"get\":{\"operationId\":\"getGo2rtcOverrideDiagnostics\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/Go2rtcOverrideDiagnostics\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get go2rtc override diagnostics\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/health\":{\"get\":{\"operationId\":\"getHostHealth\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HostHealth\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get host health\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/health/incidents\":{\"get\":{\"operationId\":\"getHostHealthIncidents\",\"parameters\":[{\"in\":\"query\",\"name\":\"limit\",\"schema\":{\"maximum\":100,\"minimum\":1,\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"include_closed\",\"schema\":{\"type\":\"boolean\"}},{\"description\":\"Opaque versioned cursor from the preceding page; malformed cursors return 400.\",\"in\":\"query\",\"name\":\"cursor\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/HostHealthIncidents\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get host health incidents\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/info\":{\"get\":{\"operationId\":\"getSystemInformation\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemInformation\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system information\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/logs\":{\"get\":{\"operationId\":\"getSystemLogs\",\"parameters\":[{\"in\":\"query\",\"name\":\"level\",\"schema\":{\"type\":\"string\"}},{\"in\":\"query\",\"name\":\"count\",\"schema\":{\"type\":\"integer\"}},{\"in\":\"query\",\"name\":\"last_ts\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemLogs\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system logs\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/logs/clear\":{\"post\":{\"operationId\":\"clearSystemLogs\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Clear system logs\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/restart\":{\"post\":{\"operationId\":\"restartSystem\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Restart system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/shutdown\":{\"post\":{\"operationId\":\"shutdownSystem\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemActionAccepted\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Shutdown system\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/system/status\":{\"get\":{\"operationId\":\"getSystemStatus\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/SystemStatus\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get system status\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"global\",\"requirements\":[{\"action\":\"system.admin\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/telemetry/player\":{\"post\":{\"description\":\"A named stream requires live.view on that stream. Empty/malformed unnamed telemetry is ignored with 204; an acknowledgment does not imply storage of an event.\",\"operationId\":\"recordPlayerTelemetry\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/PlayerTelemetry\"}}},\"required\":false},\"responses\":{\"204\":{\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Record player telemetry\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"live.view\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/manifest\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"getEvidenceManifest\",\"parameters\":[{\"in\":\"query\",\"name\":\"stream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601 or date-only; defaults to recent24hours.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601 or date-only; defaults to recent24hours.\",\"in\":\"query\",\"name\":\"end\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/vnd.apple.mpegurl\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"Authorized media bytes; not JSON. Thumbnail offsets are milliseconds and snap to the cache grid. Generation may be disabled. Manifest references recording IDs whose playback rechecks captured identity.\"},\"206\":{\"content\":{\"application/vnd.apple.mpegurl\":{\"schema\":{\"format\":\"binary\",\"type\":\"string\"}}},\"description\":\"A supported byte range of authorized media.\",\"headers\":{\"Content-Range\":{\"schema\":{\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"416\":{\"description\":\"Unsatisfiable range.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get evidence manifest\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/play\":{\"get\":{\"description\":\"Discovery indicates an operation; concrete cameras, captured identities, ownership and current permissions are rechecked for every request. Basic, legacy keys and scoped-token owner intersections remain supported.\",\"operationId\":\"redirectEvidencePlayback\",\"parameters\":[{\"in\":\"query\",\"name\":\"stream\",\"required\":true,\"schema\":{\"type\":\"string\"}},{\"description\":\"Positive epoch seconds or ISO8601; defaults to now.\",\"in\":\"query\",\"name\":\"start\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"302\":{\"description\":\"Empty redirect to the chosen recording playback endpoint; playback independently reauthorizes the recording.\",\"headers\":{\"Location\":{\"schema\":{\"pattern\":\"^/api/recordings/play/[0-9]+$\",\"type\":\"string\"}}}},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Denied by current camera policy, feature configuration or demo write restriction.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Resource not found or not visible.\"},\"409\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Revision, protected media or transfer conflict.\"},\"413\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Request exceeds operation limits.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Internal failure; a mutation may have applied.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Protected storage or media work temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Redirect evidence playback\",\"tags\":[\"Evidence\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments\":{\"get\":{\"description\":\"Requires stream or camera_uuid. Explicit camera_uuid is authoritative even with a stale stream name. Raw capture-camera replay authorization precedes timeline lookup; deleted cameras return404. Timeline presence does not imply playable media.\",\"operationId\":\"getTimelineSegments\",\"parameters\":[{\"description\":\"Current stream name, required unless camera_uuid is present.\",\"in\":\"query\",\"name\":\"stream\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"Capture camera UUID; takes precedence over stream, invalid UUID400.\",\"in\":\"query\",\"name\":\"camera_uuid\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defaults24hours ago. Invalid nonempty time parses as epoch rather than400.\",\"in\":\"query\",\"name\":\"start\",\"required\":false,\"schema\":{\"type\":\"string\"}},{\"description\":\"ISO8601/date bound; defaultsnow. Date-only end expands to end of day.\",\"in\":\"query\",\"name\":\"end\",\"required\":false,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTimeline\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Requested camera or media option forbidden.\"},\"404\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Recording, current capture camera, collection or source file not found.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read an authorized camera timeline\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"camera\",\"requirements\":[{\"action\":\"recordings.replay\",\"resource\":\"raw\"}],\"version\":1}}},\"/api/timeline/segments-by-ids\":{\"get\":{\"description\":\"Filters each recording through stored capture identity and raw replay permission. Hidden/missing/orphaned entries do not contribute counts or overall time bounds. Does not reveal which requested IDs were denied.\",\"operationId\":\"getTimelineSegmentsByIds\",\"parameters\":[{\"description\":\"Required comma-separated positive decimal uint64 recording IDs. Surrounding whitespace and leading zeros are accepted; empty comma-separated fields are skipped. Signed, zero, overflowing, or partially numeric tokens reject the entire request with HTTP 400. Order and duplicates are preserved; missing or unauthorized IDs are omitted. The HTTP query string is limited to 1023 bytes; an additional handler bound rejects more than 8640 IDs.\",\"in\":\"query\",\"name\":\"ids\",\"required\":true,\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/RecordingTimelineByIds\"}}},\"description\":\"Successful response\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid path or query.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"No usable credential.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Database, policy, media preparation or serialization failure.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Read authorized timeline segments by stable IDs\",\"tags\":[\"Recording reads\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}}},\"/api/ui/workspaces\":{\"get\":{\"operationId\":\"getWorkspaces\",\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Get workspaces\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"authenticated\",\"version\":1}},\"put\":{\"operationId\":\"updateWorkspaces\",\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesUpdate\"}}},\"required\":true},\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/WorkspacesResponse\"}}},\"description\":\"Successful response. Process-control responses acknowledge a request; verify eventual completion independently.\"},\"400\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Invalid request.\"},\"401\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Authentication required.\"},\"403\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Required authority denied.\"},\"500\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Server or persistence failure.\"},\"503\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/ApiError\"}}},\"description\":\"Service temporarily unavailable.\"}},\"security\":[{\"sessionCookie\":[]},{\"basicAuth\":[]},{\"apiKeyHeader\":[]},{\"bearerToken\":[]}],\"summary\":\"Update workspaces\",\"tags\":[\"System\"],\"x-lightnvr-authorization\":{\"kind\":\"non_scoped_identity\",\"version\":1}}},\"/logout\":{\"get\":{\"description\":\"Public and idempotent. Deletes the session from the supplied cookie and clears session/trusted-device cookies. Does not revoke Basic credentials, legacy API keys or scoped API tokens. JSON200 requires Accept containing application/json or X-Requested-With; otherwise302 redirects to login.\",\"operationId\":\"legacyLogout\",\"parameters\":[{\"description\":\"Use application/json for the JSON success envelope.\",\"in\":\"header\",\"name\":\"Accept\",\"schema\":{\"type\":\"string\"}},{\"description\":\"Presence also selects the JSON success envelope.\",\"in\":\"header\",\"name\":\"X-Requested-With\",\"schema\":{\"type\":\"string\"}}],\"responses\":{\"200\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"#/components/schemas/LogoutResponse\"}}},\"description\":\"Successful response\"},\"302\":{\"description\":\"Browser/form redirect; Location identifies the next page and no JSON body is required.\",\"headers\":{\"Location\":{\"description\":\"Relative application page.\",\"schema\":{\"type\":\"string\"}}}}},\"security\":[],\"summary\":\"End a browser session using the legacy route\",\"tags\":[\"Identity\"],\"x-lightnvr-authorization\":{\"kind\":\"public\",\"version\":1}}}},\"servers\":[{\"description\":\"Application root relative to the actual served /api/openapi.json URL, including an existing proxy path prefix.\",\"url\":\"../\"}],\"tags\":[{\"description\":\"Camera inventory and configuration.\",\"name\":\"Streams\"},{\"description\":\"Current-principal contract visibility.\",\"name\":\"Discovery\"},{\"description\":\"Existing login, session verification and safe browser bootstrap.\",\"name\":\"Identity\"},{\"description\":\"Self-service or users.manage token lifecycle; scoped credentials cannot manage tokens.\",\"name\":\"Tokens\"},{\"description\":\"Replay-authorized recording inventory, metadata, timeline and media protocols.\",\"name\":\"Recording reads\"},{\"description\":\"Capture-identity protection, tags, deletion and export jobs. Job access remains creator/token-bound; cancellation is unsupported.\",\"name\":\"Recording actions\"},{\"description\":\"User, policy and audit administration.\",\"name\":\"Identity administration\"},{\"description\":\"User, policy and audit administration.\",\"name\":\"Audit\"},{\"description\":\"System administration, health, settings and bootstrap.\",\"name\":\"System\"},{\"description\":\"Event routing, destinations and external motion.\",\"name\":\"Events\"},{\"description\":\"Storage configuration, local lifecycle and archive operations.\",\"name\":\"Storage\"},{\"description\":\"Investigations, detections, bookmarks, LPR and recording media protocols.\",\"name\":\"Evidence\"},{\"description\":\"PTZ, imaging and ONVIF inventory and device commands.\",\"name\":\"Device controls\"},{\"description\":\"Durable detection observations, evidence images, history cursors and integration capabilities (INT_01).\",\"name\":\"Integrations\"}],\"x-lightnvr-coverage\":{\"canonical_revision\":\"eng-api-four-pr-v1\",\"covered_operations\":[\"listStreams\",\"getStreamFull\",\"updateStream\",\"getOpenApi\",\"getLoginConfig\",\"login\",\"logout\",\"verifyIdentity\",\"getClientConfig\",\"listUserTokens\",\"createUserToken\",\"revokeUserToken\",\"listRecordings\",\"getRecording\",\"listRecordingTags\",\"listRecordingDetectionLabels\",\"getRecordingTags\",\"getTimelineSegments\",\"getTimelineSegmentsByIds\",\"playRecording\",\"getRecordingThumbnail\",\"setRecordingProtection\",\"setRecordingRetention\",\"protectRecordings\",\"setRecordingTags\",\"updateRecordingTagsBatch\",\"deleteRecording\",\"deleteRecordings\",\"getRecordingDeletionProgress\",\"createRecordingExport\",\"getRecordingExportStatus\",\"downloadRecording\",\"downloadRecordingExport\",\"listAuthSessions\",\"revokeAuthSession\",\"listTrustedDevices\",\"revokeTrustedDevice\",\"completeTotpLogin\",\"legacyLogout\",\"changePassword\",\"disableTotp\",\"getTotpStatus\",\"setupTotp\",\"verifyTotp\",\"queryFleetCameras\",\"previewFleetSelector\",\"listFleetViews\",\"getFleetView\",\"listCameraCollections\",\"getCameraCollection\",\"getCameraCollectionMembers\",\"previewCameraCollection\",\"listFleetLocations\",\"getFleetLocation\",\"listFleetTags\",\"getFleetTag\",\"getCameraTagAssignments\",\"getSystemSettings\",\"updateSystemSettings\",\"validateGo2rtcConfiguration\",\"getSystemInformationLegacy\",\"getSystemInformation\",\"getSystemStatus\",\"getHostHealth\",\"getHostHealthIncidents\",\"getSystemLogs\",\"clearSystemLogs\",\"createSystemBackup\",\"restartSystem\",\"shutdownSystem\",\"getGo2rtcEffectiveConfiguration\",\"getGo2rtcOverrideDiagnostics\",\"getWorkspaces\",\"updateWorkspaces\",\"getSetupStatus\",\"updateSetupStatus\",\"getStreamHealth\",\"getHlsHealth\",\"getMetrics\",\"recordPlayerTelemetry\",\"getEventCatalog\",\"listEventDestinations\",\"createEventDestination\",\"getEventDestination\",\"updateEventDestination\",\"deleteEventDestination\",\"listEventRoutes\",\"createEventRoute\",\"getEventRoute\",\"updateEventRoute\",\"deleteEventRoute\",\"previewEventRoute\",\"triggerMotion\",\"createFleetLocation\",\"updateFleetLocation\",\"deleteFleetLocation\",\"createFleetTag\",\"updateFleetTag\",\"deleteFleetTag\",\"createCameraCollection\",\"updateCameraCollection\",\"deleteCameraCollection\",\"setCameraLocation\",\"setCameraTags\",\"mergeFleetTag\",\"setCameraCollectionMembers\",\"createFleetView\",\"updateFleetView\",\"deleteFleetView\",\"listLiveLayouts\",\"createLiveLayout\",\"updateLiveLayout\",\"deleteLiveLayout\",\"listCameraFavorites\",\"addCameraFavorite\",\"deleteCameraFavorite\",\"listFloorPlans\",\"getFloorPlan\",\"createFloorPlan\",\"updateFloorPlan\",\"deleteFloorPlan\",\"getFloorPlanBackground\",\"setFloorPlanBackground\",\"deleteFloorPlanBackground\",\"listAdminUsers\",\"createAdminUser\",\"getAdminUser\",\"updateAdminUser\",\"deleteAdminUser\",\"createLegacyApiKey\",\"setUserPasswordLock\",\"clearUserLoginLockout\",\"listAuthorizationActions\",\"simulateAuthorization\",\"listAuthorizationRoles\",\"createAuthorizationRole\",\"updateAuthorizationRole\",\"deleteAuthorizationRole\",\"getUserAuthorizationPolicy\",\"setUserAuthorizationPolicy\",\"listAuditEvents\",\"exportAuditEvents\",\"getAuditSettings\",\"updateAuditSettings\",\"listStorageTargets\",\"createStorageTarget\",\"getStorageTarget\",\"updateStorageTarget\",\"deleteStorageTarget\",\"listStoragePools\",\"createStoragePool\",\"getStoragePool\",\"updateStoragePool\",\"deleteStoragePool\",\"listStoragePolicies\",\"createStoragePolicy\",\"getStoragePolicy\",\"updateStoragePolicy\",\"deleteStoragePolicy\",\"probeStorageTarget\",\"previewStoragePolicy\",\"applyStoragePolicy\",\"listStorageMigrations\",\"createStorageMigration\",\"getStorageMigration\",\"cancelStorageMigration\",\"retryStorageMigration\",\"getStorageArchive\",\"retryStorageArchive\",\"getStorageCompliance\",\"getStorageHealth\",\"triggerStorageCleanup\",\"getEvidenceDetections\",\"listEvidenceModels\",\"searchEvidenceLpr\",\"exportEvidenceLpr\",\"deleteEvidenceLpr\",\"checkEvidenceRecordingFile\",\"deleteEvidenceRecordingFile\",\"countEvidenceProtected\",\"syncEvidenceRecordings\",\"queryEvidenceTimeline\",\"resolveEvidenceSegment\",\"searchEvidence\",\"previewEvidenceRecordings\",\"sampleEvidenceThumbnails\",\"listEvidenceBookmarks\",\"createEvidenceBookmark\",\"getEvidenceBookmark\",\"updateEvidenceBookmark\",\"deleteEvidenceBookmark\",\"getEvidenceSnapshot\",\"getEvidenceThumbnail\",\"getEvidenceManifest\",\"redirectEvidencePlayback\",\"getPtzCapabilities\",\"getPtzPresets\",\"movePtz\",\"absolutePtz\",\"relativePtz\",\"stopPtz\",\"homePtz\",\"setPtzHome\",\"gotoPtzPreset\",\"setPtzPreset\",\"getImagingSettings\",\"setImagingSettings\",\"getImagingOptions\",\"getDaynight\",\"setDaynight\",\"getOnvifDiscoveryStatus\",\"getOnvifDevices\",\"discoverOnvifDevices\",\"getOnvifProfiles\",\"addOnvifDevice\",\"claimOnvifDevice\",\"testOnvifDevice\",\"createStream\",\"probeStream\",\"getStream\",\"deleteStream\",\"refreshStream\",\"getStreamRecording\",\"controlStreamRecording\",\"getStreamZones\",\"saveStreamZones\",\"deleteStreamZones\",\"getStreamRetention\",\"setStreamRetention\",\"getStreamDetectionEngines\",\"setStreamDetectionEngines\",\"getIceServers\",\"getIntegrationCapabilities\",\"listObservations\",\"getObservation\",\"getObservationSnapshot\",\"putObservationClassification\"],\"description\":\"237 reviewed OpenAPI operations; eight registered media-protocol surfaces and one internal callback have explicit inventory dispositions outside this document. Discovery is filtered by current authority and never grants access.\",\"partial\":true},\"x-lightnvr-validators\":[\"StreamSummaryPage\",\"LiveStreamSummaryPage\",\"LprStreamSummaryPage\",\"StreamCompatibilityList\",\"StreamFullResponse\",\"StreamUpdateRequest\",\"StreamUpdateAccepted\",\"StreamPrivacyUpdated\",\"ApiError\",\"OpenApiDocument\",\"LoginRequest\",\"LoginResponse\",\"LoginConfig\",\"AuthVerifyResponse\",\"LogoutResponse\",\"ClientConfig\",\"ApiTokenCreateRequest\",\"ApiTokenMetadata\",\"ApiTokenList\",\"ApiTokenCreated\",\"SuccessResponse\",\"RecordingDetail\",\"RecordingsPage\",\"RecordingTags\",\"RecordingTagsById\",\"RecordingDetectionLabels\",\"RecordingTimeline\",\"RecordingTimelineByIds\",\"RecordingPlaybackReady\",\"RecordingPlaybackPreparing\",\"RecordingProtectionRequest\",\"RecordingProtectionResult\",\"RecordingRetentionRequest\",\"RecordingRetentionResult\",\"RecordingBatchProtectionRequest\",\"RecordingBatchProtectionResult\",\"RecordingTagsRequest\",\"RecordingBatchTagsRequest\",\"RecordingBatchTagsResult\",\"RecordingDeletionResult\",\"RecordingDeletionFilter\",\"RecordingDeletionRequest\",\"RecordingDeletionJobAccepted\",\"RecordingDeletionProgress\",\"RecordingExportRequest\",\"RecordingExportAccepted\",\"RecordingExportStatus\",\"AuthSessionsResponse\",\"TrustedDevicesResponse\",\"CredentialRevoked\",\"TotpLoginRequest\",\"TotpLoginResponse\",\"TotpCodeRequest\",\"TotpSetupResponse\",\"TotpStatusResponse\",\"TotpChangedResponse\",\"PasswordSelfChange\",\"PasswordAdminReset\",\"PasswordChangeRequest\",\"PasswordChangedResponse\",\"LoginMfaChallenge\",\"LoginSuccess\",\"FleetQueryRequest\",\"FleetPreviewRequest\",\"FleetQueryResponse\",\"FleetPreviewResponse\",\"FleetSavedView\",\"FleetSavedViewsResponse\",\"FleetLocation\",\"FleetLocationsResponse\",\"FleetTag\",\"FleetTagsResponse\",\"CameraTagAssignmentsResponse\",\"CameraCollection\",\"CameraCollectionsResponse\",\"CameraCollectionMembersResponse\",\"CameraCollectionPreview\",\"SystemSettings\",\"SystemSettingsUpdate\",\"SystemSettingsSaved\",\"SystemInformation\",\"SystemLogs\",\"HostHealth\",\"HostHealthIncidents\",\"Go2rtcConfigurationValidation\",\"Go2rtcConfigurationInput\",\"Go2rtcEffectiveConfiguration\",\"Go2rtcOverrideDiagnostics\",\"SetupStatus\",\"SetupUpdate\",\"SetupUpdated\",\"SystemStatus\",\"SystemActionAccepted\",\"SystemBackupCreated\",\"WorkspacesResponse\",\"WorkspacesUpdate\",\"StreamHealth\",\"ProcessLiveness\",\"StreamHealthHistory\",\"HlsHealth\",\"PlayerTelemetry\",\"EventDestination\",\"EventDestinations\",\"EventDestinationCreate\",\"EventDestinationUpdate\",\"EventPredicate\",\"EventCameraScope\",\"EventSchedule\",\"EventRoute\",\"EventRoutes\",\"EventRouteCreate\",\"EventRouteUpdate\",\"EventRoutePreviewRequest\",\"EventCatalog\",\"EventRoutePreview\",\"EventMutationAck\",\"MotionTrigger\",\"MotionTriggered\",\"FleetMutationAck\",\"FleetLocationCreate\",\"FleetLocationUpdate\",\"CameraLocationAssignmentRequest\",\"CameraLocationAssignment\",\"FleetTagCreate\",\"FleetTagUpdate\",\"FleetTagMergeRequest\",\"FleetTagMergeResult\",\"CameraTagAssignmentsRequest\",\"CameraCollectionCreate\",\"CameraCollectionUpdate\",\"CameraCollectionMembersRequest\",\"FleetViewCreate\",\"FleetViewUpdate\",\"FleetRevisionRequest\",\"LiveCameraSlot\",\"LiveLayoutCreate\",\"LiveLayoutUpdate\",\"LiveLayout\",\"LiveLayoutsResponse\",\"CameraFavorite\",\"CameraFavoritesResponse\",\"CameraFavoriteRemoval\",\"FloorSketchRect\",\"FloorSketchWall\",\"FloorSketchArea\",\"FloorSketchLabel\",\"FloorSketch\",\"FloorPlanCamera\",\"FloorPlanCameraRequest\",\"FloorPlanCreate\",\"FloorPlanUpdate\",\"FloorPlan\",\"FloorPlansResponse\",\"FloorPlanDeletion\",\"CameraUuid\",\"AdminUser\",\"AdminUsers\",\"AdminUserCreate\",\"AdminUserUpdate\",\"AdminUserDeleted\",\"LegacyApiKeyCreated\",\"PasswordLockRequest\",\"LoginLockoutCleared\",\"AdminActionCatalogEntry\",\"AdminActionCatalog\",\"AdminAuthorizationRole\",\"AdminRoles\",\"AdminRoleWrite\",\"AdminRoleWritten\",\"AdminVersionRequest\",\"AdminRoleDeleted\",\"AdminPolicyGrant\",\"AdminUserPolicy\",\"AdminPolicyWrite\",\"AdminSimulationRequest\",\"AdminSimulation\",\"AuditEvent\",\"AuditEventsPage\",\"AuditDecisionMode\",\"AuditSettings\",\"AuditSettingsWrite\",\"StorageTargetHealth\",\"StorageTarget\",\"StorageTargetsResponse\",\"StorageTargetCreate\",\"StorageTargetUpdate\",\"StoragePoolMember\",\"StoragePoolMemberRequest\",\"StoragePool\",\"StoragePoolsResponse\",\"StoragePoolCreate\",\"StoragePoolUpdate\",\"StoragePolicy\",\"StoragePoliciesResponse\",\"StoragePolicyCreate\",\"StoragePolicyUpdate\",\"StoragePolicyPreviewRequest\",\"StoragePolicyWinner\",\"StoragePolicyPreviewSample\",\"StoragePolicyConflict\",\"StoragePolicyPreview\",\"StoragePolicyApplyRequest\",\"StoragePolicyApplyResponse\",\"StorageMigration\",\"StorageMigrationsResponse\",\"StorageMigrationCreate\",\"StorageArchiveDeletion\",\"StorageArchiveResponse\",\"StorageArchiveRetryRequest\",\"StorageArchiveRetryResponse\",\"StorageHealthResponse\",\"StorageCleanupRequest\",\"StorageCleanupResponse\",\"StorageComplianceTarget\",\"StorageCompliancePolicy\",\"StorageComplianceViolation\",\"StorageComplianceResponse\",\"EvidenceDetection\",\"EvidenceDetections\",\"EvidenceModel\",\"EvidenceModels\",\"EvidenceFileCheck\",\"EvidenceFileDeleted\",\"EvidenceProtectedCount\",\"EvidenceSyncComplete\",\"EvidenceSegment\",\"EvidenceTimelineTrack\",\"EvidenceTimeline\",\"EvidenceSegmentAt\",\"EvidenceRangeRequest\",\"EvidenceSegmentAtRequest\",\"EvidenceThumbnailRequest\",\"EvidencePreviewRecording\",\"EvidencePreview\",\"EvidenceThumbnailSample\",\"EvidenceThumbnails\",\"EvidenceSearchResult\",\"EvidenceSearch\",\"EvidenceSearchRequest\",\"EvidenceBookmarkFilters\",\"EvidenceBookmarkResult\",\"EvidenceBookmark\",\"EvidenceBookmarks\",\"EvidenceBookmarkCreate\",\"EvidenceBookmarkUpdate\",\"EvidenceBookmarkDelete\",\"EvidenceLprRead\",\"EvidenceLprReads\",\"EvidenceLprSearchRequest\",\"EvidenceLprExportRequest\",\"EvidenceLprDeleted\",\"EvidenceBookmarkDeleted\",\"DeviceFloatRange\",\"DeviceCommandResult\",\"DeviceSuccess\",\"DeviceEmptyRequest\",\"PtzAxesRequest\",\"PtzPresetRequest\",\"PtzGotoPresetRequest\",\"PtzPresetResult\",\"PtzPresets\",\"PtzCapabilities\",\"ImagingModeLevel\",\"ImagingModeLevelUpdate\",\"ImagingSettings\",\"ImagingSettingsResponse\",\"ImagingModeOptions\",\"ImagingOptions\",\"ImagingOptionsResponse\",\"ImagingSettingsRequest\",\"DaynightRequest\",\"DaynightResponse\",\"DaynightUpdated\",\"OnvifDiscoveryStatus\",\"OnvifDevice\",\"OnvifDevicesResponse\",\"OnvifDiscoveryRequest\",\"OnvifProfile\",\"OnvifProfilesResponse\",\"OnvifTestRequest\",\"OnvifAddRequest\",\"OnvifAddResult\",\"OnvifClaimRequest\",\"StreamSingleResponse\",\"StreamCreateRequest\",\"StreamCreated\",\"StreamProbeRequest\",\"StreamProbeResponse\",\"StreamDeleteAccepted\",\"StreamRefreshAccepted\",\"StreamRecordingState\",\"StreamRecordingRequest\",\"StreamRecordingChanged\",\"DetectionZoneWrite\",\"DetectionZone\",\"StreamZones\",\"StreamZonesRequest\",\"StreamZonesSaved\",\"StreamZonesDeleted\",\"StreamRetentionRequest\",\"StreamRetention\",\"StreamDetectionEngineWrite\",\"StreamDetectionEngine\",\"StreamDetectionEnginesRequest\",\"StreamDetectionEngines\",\"IceServersResponse\",\"Observation\",\"ObservationPage\",\"ObservationEvidence\",\"ObservationTransform\",\"ObservationChangeEvent\",\"ObservationClassification\",\"ObservationClassificationWrite\",\"ObservationClassificationResult\",\"IntegrationCapabilities\",\"ObservationError\",\"ObservationPipelineDiagnostics\"]}\n\0", ) }; static mut lightnvr_openapi_document_length: size_t = 0; static mut lightnvr_openapi_document_revision: [::core::ffi::c_char; 65] = unsafe { ::core::mem::transmute::<[u8; 65], [::core::ffi::c_char; 65]>( - *b"8fe32ddf7330eb66c2bac8cbdbcb531b16d7a3b18b1666e25f4e9419e7a2e16d\0", + *b"a064e9bcff92e5a21938b8394d711cc3ef103230f1b2863e12c06de62f7609d2\0", ) }; unsafe extern "C" fn error_response( @@ -430,7 +430,7 @@ pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi:: pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; unsafe extern "C" fn c2rust_run_static_initializers() { lightnvr_openapi_document_length = - ::core::mem::size_of::<[::core::ffi::c_uchar; 701977]>().wrapping_sub(1 as size_t); + ::core::mem::size_of::<[::core::ffi::c_uchar; 742642]>().wrapping_sub(1 as size_t); } #[used] #[cfg_attr(target_os = "linux", unsafe(link_section = ".init_array"))] diff --git a/rust/generated/src__web__api_handlers_operator_floor_plans.rs b/rust/generated/src__web__api_handlers_operator_floor_plans.rs index df2502406..b5f8f5044 100644 --- a/rust/generated/src__web__api_handlers_operator_floor_plans.rs +++ b/rust/generated/src__web__api_handlers_operator_floor_plans.rs @@ -423,6 +423,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -528,7 +532,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -780,7 +784,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_ptz.rs b/rust/generated/src__web__api_handlers_ptz.rs index feedf6611..0d904844f 100644 --- a/rust/generated/src__web__api_handlers_ptz.rs +++ b/rust/generated/src__web__api_handlers_ptz.rs @@ -277,6 +277,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -497,7 +501,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -983,6 +988,10 @@ pub unsafe extern "C" fn handle_ptz_move( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -1299,6 +1308,10 @@ pub unsafe extern "C" fn handle_ptz_stop( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -1561,6 +1574,10 @@ pub unsafe extern "C" fn handle_ptz_absolute( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -1883,6 +1900,10 @@ pub unsafe extern "C" fn handle_ptz_relative( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -2222,6 +2243,10 @@ pub unsafe extern "C" fn handle_ptz_home( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -2499,6 +2524,10 @@ pub unsafe extern "C" fn handle_ptz_set_home( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -2684,6 +2713,10 @@ pub unsafe extern "C" fn handle_ptz_get_presets( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -3012,6 +3045,10 @@ pub unsafe extern "C" fn handle_ptz_goto_preset( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -3319,6 +3356,10 @@ pub unsafe extern "C" fn handle_ptz_set_preset( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -3603,6 +3644,10 @@ pub unsafe extern "C" fn handle_ptz_capabilities( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; let mut rc: ::core::ffi::c_int = get_ptz_stream_config( &raw mut stream_name as *mut ::core::ffi::c_char, diff --git a/rust/generated/src__web__api_handlers_recording_control.rs b/rust/generated/src__web__api_handlers_recording_control.rs index 7bee2f547..f3b776aba 100644 --- a/rust/generated/src__web__api_handlers_recording_control.rs +++ b/rust/generated/src__web__api_handlers_recording_control.rs @@ -172,6 +172,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] @@ -313,7 +317,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -927,6 +932,10 @@ pub unsafe extern "C" fn handle_get_stream_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name( &raw mut stream_name as *mut ::core::ffi::c_char, @@ -1404,6 +1413,10 @@ pub unsafe extern "C" fn handle_post_stream_recording( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config_by_name( &raw mut stream_name as *mut ::core::ffi::c_char, diff --git a/rust/generated/src__web__api_handlers_recording_tags.rs b/rust/generated/src__web__api_handlers_recording_tags.rs index bbb61e8a2..bbaf62732 100644 --- a/rust/generated/src__web__api_handlers_recording_tags.rs +++ b/rust/generated/src__web__api_handlers_recording_tags.rs @@ -302,7 +302,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_recordings_backend_agnostic.rs b/rust/generated/src__web__api_handlers_recordings_backend_agnostic.rs index 43f983047..a580008be 100644 --- a/rust/generated/src__web__api_handlers_recordings_backend_agnostic.rs +++ b/rust/generated/src__web__api_handlers_recordings_backend_agnostic.rs @@ -541,7 +541,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_recordings_batch_download.rs b/rust/generated/src__web__api_handlers_recordings_batch_download.rs index 7236fda0c..d65e62985 100644 --- a/rust/generated/src__web__api_handlers_recordings_batch_download.rs +++ b/rust/generated/src__web__api_handlers_recordings_batch_download.rs @@ -476,7 +476,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_recordings_download.rs b/rust/generated/src__web__api_handlers_recordings_download.rs index 1407e9b7d..fcd97aed3 100644 --- a/rust/generated/src__web__api_handlers_recordings_download.rs +++ b/rust/generated/src__web__api_handlers_recordings_download.rs @@ -342,7 +342,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_recordings_files_backend_agnostic.rs b/rust/generated/src__web__api_handlers_recordings_files_backend_agnostic.rs index e8acbb93f..20cf3113b 100644 --- a/rust/generated/src__web__api_handlers_recordings_files_backend_agnostic.rs +++ b/rust/generated/src__web__api_handlers_recordings_files_backend_agnostic.rs @@ -361,7 +361,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const LOG_COMPONENT: [::core::ffi::c_char; 14] = unsafe { ::core::mem::transmute::<[u8; 14], [::core::ffi::c_char; 14]>(*b"RecordingsAPI\0") }; diff --git a/rust/generated/src__web__api_handlers_recordings_list.rs b/rust/generated/src__web__api_handlers_recordings_list.rs index 06c8b5ac2..9bbc86008 100644 --- a/rust/generated/src__web__api_handlers_recordings_list.rs +++ b/rust/generated/src__web__api_handlers_recordings_list.rs @@ -540,7 +540,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const CAMERA_UUID_STRING_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; pub const MAX_STREAMS: ::core::ffi::c_int = LIGHTNVR_MAX_STREAMS; diff --git a/rust/generated/src__web__api_handlers_recordings_playback.rs b/rust/generated/src__web__api_handlers_recordings_playback.rs index 667788cbd..a9c021489 100644 --- a/rust/generated/src__web__api_handlers_recordings_playback.rs +++ b/rust/generated/src__web__api_handlers_recordings_playback.rs @@ -226,6 +226,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -331,7 +335,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -519,7 +523,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_recordings_sync.rs b/rust/generated/src__web__api_handlers_recordings_sync.rs index fd1535075..1bfa2f94c 100644 --- a/rust/generated/src__web__api_handlers_recordings_sync.rs +++ b/rust/generated/src__web__api_handlers_recordings_sync.rs @@ -263,7 +263,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const LOG_COMPONENT: [::core::ffi::c_char; 14] = unsafe { ::core::mem::transmute::<[u8; 14], [::core::ffi::c_char; 14]>(*b"RecordingsAPI\0") }; diff --git a/rust/generated/src__web__api_handlers_recordings_thumbnail.rs b/rust/generated/src__web__api_handlers_recordings_thumbnail.rs index b149a855d..02a189011 100644 --- a/rust/generated/src__web__api_handlers_recordings_thumbnail.rs +++ b/rust/generated/src__web__api_handlers_recordings_thumbnail.rs @@ -318,6 +318,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -423,7 +427,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1162,7 +1166,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_retention.rs b/rust/generated/src__web__api_handlers_retention.rs index 89348e703..c8b01f544 100644 --- a/rust/generated/src__web__api_handlers_retention.rs +++ b/rust/generated/src__web__api_handlers_retention.rs @@ -252,7 +252,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_settings.rs b/rust/generated/src__web__api_handlers_settings.rs index 856b5b27c..bb9c581d7 100644 --- a/rust/generated/src__web__api_handlers_settings.rs +++ b/rust/generated/src__web__api_handlers_settings.rs @@ -188,6 +188,12 @@ unsafe extern "C" { unsafe fn stream_start_hls(stream_name: *const ::core::ffi::c_char) -> ::core::ffi::c_int; unsafe fn stream_stop_hls(stream_name: *const ::core::ffi::c_char) -> ::core::ffi::c_int; unsafe fn mp4_directory_format_is_valid(value: *const ::core::ffi::c_char) -> bool; + unsafe fn go2rtc_parse_external_candidates( + list: *const ::core::ffi::c_char, + port: ::core::ffi::c_int, + out: *mut [::core::ffi::c_char; 128], + max: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; unsafe fn go2rtc_process_stop() -> bool; unsafe fn go2rtc_process_is_running() -> bool; unsafe fn go2rtc_process_clear_override_quarantine(); @@ -468,6 +474,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -573,7 +583,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -681,7 +691,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -785,6 +796,7 @@ pub const LOG_COMPONENT: [::core::ffi::c_char; 12] = unsafe { ::core::mem::transmute::<[u8; 12], [::core::ffi::c_char; 12]>(*b"SettingsAPI\0") }; pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const MAX_STREAM_NAME: ::core::ffi::c_int = 256 as ::core::ffi::c_int; +pub const GO2RTC_MAX_EXTERNAL_CANDIDATES: ::core::ffi::c_int = 8 as ::core::ffi::c_int; pub const NULL: *mut ::core::ffi::c_void = ::core::ptr::null_mut::<::core::ffi::c_void>(); pub const r#true: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; @@ -4379,10 +4391,36 @@ pub unsafe extern "C" fn handle_post_settings( b"go2rtc_external_ip\0".as_ptr() as *const ::core::ffi::c_char, ); if !go2rtc_external_ip.is_null() && cJSON_IsString(go2rtc_external_ip) != 0 { + let mut candidates: [[::core::ffi::c_char; 128]; 8] = [[0; 128]; 8]; + if strlen((*go2rtc_external_ip).valuestring) + >= ::core::mem::size_of::<[::core::ffi::c_char; 256]>() + || go2rtc_parse_external_candidates( + (*go2rtc_external_ip).valuestring, + 8555 as ::core::ffi::c_int, + &raw mut candidates as *mut [::core::ffi::c_char; 128], + GO2RTC_MAX_EXTERNAL_CANDIDATES, + ) < 0 as ::core::ffi::c_int + { + _log_message_ctx( + log_level_t::LOG_LEVEL_WARN, + LOG_COMPONENT.as_ptr(), + _log_stream_name, + b"Rejected invalid go2rtc_external_ip setting\0".as_ptr() + as *const ::core::ffi::c_char, + ); + cJSON_Delete(settings); + http_response_set_json_error( + res, + 400 as ::core::ffi::c_int, + b"Invalid go2rtc_external_ip: expected up to 8 comma-separated IPs or hostnames (optionally :port), '*' for auto-detect, or stun:host[:port]\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + return; + } safe_strcpy( &raw mut g_config.go2rtc_external_ip as *mut ::core::ffi::c_char, (*go2rtc_external_ip).valuestring, - ::core::mem::size_of::<[::core::ffi::c_char; 64]>(), + ::core::mem::size_of::<[::core::ffi::c_char; 256]>(), 0 as size_t, ); settings_changed = r#true != 0; @@ -5582,6 +5620,10 @@ pub unsafe extern "C" fn handle_post_settings( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream_0, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -5909,6 +5951,10 @@ pub unsafe extern "C" fn handle_post_settings( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream_1, &raw mut config_1) != 0 as ::core::ffi::c_int { _log_message_ctx( diff --git a/rust/generated/src__web__api_handlers_setup.rs b/rust/generated/src__web__api_handlers_setup.rs index 85942af22..deca8cc70 100644 --- a/rust/generated/src__web__api_handlers_setup.rs +++ b/rust/generated/src__web__api_handlers_setup.rs @@ -308,7 +308,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const CLOCK_MONOTONIC: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const LOG_COMPONENT: [::core::ffi::c_char; 9] = diff --git a/rust/generated/src__web__api_handlers_storage.rs b/rust/generated/src__web__api_handlers_storage.rs index 0d75d1e3e..e84795955 100644 --- a/rust/generated/src__web__api_handlers_storage.rs +++ b/rust/generated/src__web__api_handlers_storage.rs @@ -190,6 +190,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -295,7 +299,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -417,7 +421,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_storage_compliance.rs b/rust/generated/src__web__api_handlers_storage_compliance.rs index 61e98e68c..4c026da7f 100644 --- a/rust/generated/src__web__api_handlers_storage_compliance.rs +++ b/rust/generated/src__web__api_handlers_storage_compliance.rs @@ -474,7 +474,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const SQLITE_OK: ::core::ffi::c_int = 0 as ::core::ffi::c_int; pub const SQLITE_ROW: ::core::ffi::c_int = 100 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_storage_migrations.rs b/rust/generated/src__web__api_handlers_storage_migrations.rs index c661ca87c..7d5e24148 100644 --- a/rust/generated/src__web__api_handlers_storage_migrations.rs +++ b/rust/generated/src__web__api_handlers_storage_migrations.rs @@ -411,7 +411,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_storage_policies.rs b/rust/generated/src__web__api_handlers_storage_policies.rs index f1ec6fc62..ce775f181 100644 --- a/rust/generated/src__web__api_handlers_storage_policies.rs +++ b/rust/generated/src__web__api_handlers_storage_policies.rs @@ -493,7 +493,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_storage_pools.rs b/rust/generated/src__web__api_handlers_storage_pools.rs index 0d700f239..21824d9ed 100644 --- a/rust/generated/src__web__api_handlers_storage_pools.rs +++ b/rust/generated/src__web__api_handlers_storage_pools.rs @@ -367,7 +367,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const LIGHTNVR_UUID_STRING_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; pub const STORAGE_TARGET_MAX_COUNT: ::core::ffi::c_int = 128 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_storage_targets.rs b/rust/generated/src__web__api_handlers_storage_targets.rs index 99b4e1575..e58ef3d81 100644 --- a/rust/generated/src__web__api_handlers_storage_targets.rs +++ b/rust/generated/src__web__api_handlers_storage_targets.rs @@ -415,7 +415,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const INT64_MAX: ::core::ffi::c_long = 9223372036854775807 as ::core::ffi::c_long; pub const LIGHTNVR_UUID_STRING_SIZE: ::core::ffi::c_int = 37 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_streaming.rs b/rust/generated/src__web__api_handlers_streaming.rs index d7abe363a..2b52260d9 100644 --- a/rust/generated/src__web__api_handlers_streaming.rs +++ b/rust/generated/src__web__api_handlers_streaming.rs @@ -1620,6 +1620,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -1725,7 +1729,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1824,7 +1828,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_streams_get.rs b/rust/generated/src__web__api_handlers_streams_get.rs index 8eecc9124..921b62a29 100644 --- a/rust/generated/src__web__api_handlers_streams_get.rs +++ b/rust/generated/src__web__api_handlers_streams_get.rs @@ -86,6 +86,10 @@ unsafe extern "C" { camera: *mut fleet_camera_t, evaluation: *mut authorization_evaluation_t, ) -> ::core::ffi::c_int; + unsafe fn stream_observation_settings_to_json( + object: *mut cJSON, + config: *const stream_config_t, + ); unsafe fn clock_gettime(__clock_id: clockid_t, __tp: *mut timespec) -> ::core::ffi::c_int; unsafe fn fleet_health_enrich_go2rtc_activity( cameras: *mut fleet_camera_t, @@ -312,6 +316,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -417,7 +425,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -717,7 +725,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -2746,6 +2755,7 @@ pub unsafe extern "C" fn handle_get_streams( &raw mut (*db_streams.offset(i as isize)).detection_object_filter_list as *mut ::core::ffi::c_char, ); + stream_observation_settings_to_json(stream_obj, db_streams.offset(i as isize)); cJSON_AddNumberToObject( stream_obj, b"protocol\0".as_ptr() as *const ::core::ffi::c_char, @@ -3202,6 +3212,10 @@ pub unsafe extern "C" fn handle_get_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -3448,6 +3462,7 @@ pub unsafe extern "C" fn handle_get_stream( b"detection_object_filter_list\0".as_ptr() as *const ::core::ffi::c_char, &raw mut config.detection_object_filter_list as *mut ::core::ffi::c_char, ); + stream_observation_settings_to_json(stream_obj, &raw mut config); cJSON_AddNumberToObject( stream_obj, b"protocol\0".as_ptr() as *const ::core::ffi::c_char, @@ -3884,6 +3899,10 @@ pub unsafe extern "C" fn handle_get_stream_full( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -4130,6 +4149,7 @@ pub unsafe extern "C" fn handle_get_stream_full( b"detection_object_filter_list\0".as_ptr() as *const ::core::ffi::c_char, &raw mut config.detection_object_filter_list as *mut ::core::ffi::c_char, ); + stream_observation_settings_to_json(stream_obj, &raw mut config); cJSON_AddNumberToObject( stream_obj, b"protocol\0".as_ptr() as *const ::core::ffi::c_char, diff --git a/rust/generated/src__web__api_handlers_streams_modify.rs b/rust/generated/src__web__api_handlers_streams_modify.rs index fd2e25e84..3a672162e 100644 --- a/rust/generated/src__web__api_handlers_streams_modify.rs +++ b/rust/generated/src__web__api_handlers_streams_modify.rs @@ -105,6 +105,13 @@ unsafe extern "C" { camera: *mut fleet_camera_t, evaluation: *mut authorization_evaluation_t, ) -> ::core::ffi::c_int; + unsafe fn stream_observation_settings_apply_json( + body: *const cJSON, + config: *mut stream_config_t, + changed: *mut bool, + error: *mut ::core::ffi::c_char, + error_size: size_t, + ) -> ::core::ffi::c_int; unsafe fn _log_message_ctx( level: log_level_t, component: *const ::core::ffi::c_char, @@ -195,6 +202,11 @@ unsafe extern "C" { stream_name: *const ::core::ffi::c_char, ) -> ::core::ffi::c_int; unsafe fn is_unified_detection_running(stream_name: *const ::core::ffi::c_char) -> bool; + unsafe fn unified_detection_set_observation_settings( + stream_name: *const ::core::ffi::c_char, + enabled: bool, + labels_csv: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; unsafe fn add_stream_config(stream: *const stream_config_t) -> uint64_t; unsafe fn update_stream_config( name: *const ::core::ffi::c_char, @@ -453,6 +465,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -558,7 +574,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -796,7 +812,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -1418,6 +1435,10 @@ unsafe extern "C" fn put_stream_worker_locked(mut task: *mut put_stream_task_t) publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut updated_config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -1431,6 +1452,11 @@ unsafe extern "C" fn put_stream_worker_locked(mut task: *mut put_stream_task_t) put_stream_task_free(task); return; } + unified_detection_set_observation_settings( + &raw mut (*task).stream_id as *mut ::core::ffi::c_char, + updated_config.observation_evidence_enabled, + &raw mut updated_config.observation_labels as *mut ::core::ffi::c_char, + ); if set_stream_detection_params( stream, (*task).config.detection_interval, @@ -2726,6 +2752,10 @@ pub unsafe extern "C" fn handle_post_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; memset( &raw mut config as *mut ::core::ffi::c_void, @@ -2916,6 +2946,23 @@ pub unsafe extern "C" fn handle_post_stream( return; } } + let mut observation_error: [::core::ffi::c_char; 160] = [0; 160]; + if stream_observation_settings_apply_json( + stream_json, + &raw mut config, + ::core::ptr::null_mut::(), + &raw mut observation_error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 160]>(), + ) != 0 as ::core::ffi::c_int + { + cJSON_Delete(stream_json); + http_response_set_json_error( + res, + 400 as ::core::ffi::c_int, + &raw mut observation_error as *mut ::core::ffi::c_char, + ); + return; + } let mut priority: *mut cJSON = cJSON_GetObjectItem( stream_json, b"priority\0".as_ptr() as *const ::core::ffi::c_char, @@ -3942,6 +3989,10 @@ pub unsafe extern "C" fn handle_put_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { _log_message_ctx( @@ -4371,6 +4422,27 @@ pub unsafe extern "C" fn handle_put_stream( config_changed = r#true != 0; } } + let mut observation_error: [::core::ffi::c_char; 160] = [0; 160]; + let mut observation_changed: bool = r#false != 0; + if stream_observation_settings_apply_json( + stream_json, + &raw mut config, + &raw mut observation_changed, + &raw mut observation_error as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 160]>(), + ) != 0 as ::core::ffi::c_int + { + cJSON_Delete(stream_json); + http_response_set_json_error( + res, + 400 as ::core::ffi::c_int, + &raw mut observation_error as *mut ::core::ffi::c_char, + ); + return; + } + if observation_changed { + config_changed = r#true != 0; + } let mut priority: *mut cJSON = cJSON_GetObjectItem( stream_json, b"priority\0".as_ptr() as *const ::core::ffi::c_char, @@ -6140,6 +6212,10 @@ pub unsafe extern "C" fn handle_delete_stream( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if get_stream_config(stream, &raw mut config) != 0 as ::core::ffi::c_int { http_response_set_json_error( @@ -6671,6 +6747,10 @@ unsafe extern "C" fn refresh_stream_worker( publish_url: [0; 512], playback_transport: [0; 24], eptz_config: [0; 1024], + observation_evidence_enabled: false, + observation_labels: [0; 512], + observation_max_age_days: 0, + observation_max_bytes: 0, }; if !stream.is_null() && get_stream_config(stream, &raw mut config) == 0 as ::core::ffi::c_int diff --git a/rust/generated/src__web__api_handlers_streams_test.rs b/rust/generated/src__web__api_handlers_streams_test.rs index e63885182..8213612ac 100644 --- a/rust/generated/src__web__api_handlers_streams_test.rs +++ b/rust/generated/src__web__api_handlers_streams_test.rs @@ -1601,7 +1601,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const LOG_COMPONENT: [::core::ffi::c_char; 11] = unsafe { ::core::mem::transmute::<[u8; 11], [::core::ffi::c_char; 11]>(*b"StreamsAPI\0") }; diff --git a/rust/generated/src__web__api_handlers_system.rs b/rust/generated/src__web__api_handlers_system.rs index 507820602..3b8f2f467 100644 --- a/rust/generated/src__web__api_handlers_system.rs +++ b/rust/generated/src__web__api_handlers_system.rs @@ -147,6 +147,14 @@ unsafe extern "C" { format: *const ::core::ffi::c_char, ... ); + unsafe fn observation_diagnostics_collect( + out: *mut observation_diagnostics_t, + now_ms: int64_t, + ) -> ::core::ffi::c_int; + unsafe fn observation_diagnostics_free(diagnostics: *mut observation_diagnostics_t); + unsafe fn observation_diagnostics_to_json( + diagnostics: *const observation_diagnostics_t, + ) -> *mut cJSON; unsafe fn ensure_dir(path: *const ::core::ffi::c_char) -> ::core::ffi::c_int; unsafe fn initiate_shutdown(); unsafe fn request_background_abort(); @@ -157,6 +165,18 @@ unsafe extern "C" { src_size: size_t, ) -> ::core::ffi::c_int; unsafe fn get_enabled_stream_count() -> ::core::ffi::c_int; + unsafe fn add_cached_stream_storage_usage_to_json( + json_obj: *mut cJSON, + force_refresh: ::core::ffi::c_int, + ) -> ::core::ffi::c_int; + unsafe fn system_health_snapshot_copy(snapshot: *mut system_health_snapshot_t) -> bool; + unsafe fn go2rtc_api_get_application_info( + rtsp_port: *mut ::core::ffi::c_int, + version: *mut ::core::ffi::c_char, + version_size: size_t, + revision: *mut ::core::ffi::c_char, + revision_size: size_t, + ) -> bool; unsafe fn get_recording_count( start_time: time_t, end_time: time_t, @@ -171,18 +191,6 @@ unsafe extern "C" { include_in_progress: bool, ) -> ::core::ffi::c_int; unsafe fn get_stream_storage_bytes(stream_name: *const ::core::ffi::c_char) -> int64_t; - unsafe fn add_cached_stream_storage_usage_to_json( - json_obj: *mut cJSON, - force_refresh: ::core::ffi::c_int, - ) -> ::core::ffi::c_int; - unsafe fn system_health_snapshot_copy(snapshot: *mut system_health_snapshot_t) -> bool; - unsafe fn go2rtc_api_get_application_info( - rtsp_port: *mut ::core::ffi::c_int, - version: *mut ::core::ffi::c_char, - version_size: size_t, - revision: *mut ::core::ffi::c_char, - revision_size: size_t, - ) -> bool; unsafe fn memcpy( __dest: *mut ::core::ffi::c_void, __src: *const ::core::ffi::c_void, @@ -887,6 +895,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -992,7 +1004,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1089,6 +1101,112 @@ impl log_level_t { pub const LOG_LEVEL_INFO: Self = Self(2); pub const LOG_LEVEL_DEBUG: Self = Self(3); } +pub type observation_diagnostics_t = observation_diagnostics_snapshot; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_diagnostics_snapshot { + pub collected_at_ms: int64_t, + pub evidence: observation_evidence_stats_t, + pub relay: observation_relay_stats_t, + pub db_available: bool, + pub db: observation_db_diagnostics_t, + pub cameras: *mut observation_retention_camera_status_t, + pub camera_count: ::core::ffi::c_int, + pub budget_exhausted_cameras: ::core::ffi::c_int, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_retention_camera_status_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub retained_bytes: int64_t, + pub max_bytes: int64_t, + pub max_age_days: ::core::ffi::c_int, + pub budget_exhausted: bool, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_db_diagnostics_t { + pub meta: observation_meta_t, + pub has_oldest_observed_at: bool, + pub oldest_observed_at_ms: int64_t, + pub observations: int64_t, + pub publications_pending: int64_t, + pub publications_failed: int64_t, + pub publications_expired: int64_t, + pub oldest_pending_created_at_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_meta_t { + pub epoch_uuid: [::core::ffi::c_char; 37], + pub oldest_seq: int64_t, + pub head_seq: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_relay_stats_t { + pub running: bool, + pub passes: uint64_t, + pub relayed: uint64_t, + pub duplicates: uint64_t, + pub relayed_no_route: uint64_t, + pub expired: uint64_t, + pub failed: uint64_t, + pub enqueue_full: uint64_t, + pub enqueue_errors: uint64_t, + pub mark_errors: uint64_t, + pub read_errors: uint64_t, + pub cap_pauses: uint64_t, + pub backoffs: uint64_t, + pub sidecar_writes: uint64_t, + pub sidecar_errors: uint64_t, + pub epoch_regenerations: uint64_t, + pub last_relayed_seq: int64_t, + pub oldest_pending_created_at_ms: int64_t, + pub last_pass_pending: ::core::ffi::c_int, + pub outbox_type_pending: int64_t, + pub consecutive_failures: ::core::ffi::c_int, + pub backoff_until_ms: int64_t, +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct observation_evidence_stats_t { + pub initialized: bool, + pub worker_running: bool, + pub allocated: bool, + pub accepted: uint64_t, + pub dropped_queue_full: uint64_t, + pub dropped_budget: uint64_t, + pub dropped_disabled: uint64_t, + pub dropped_untracked: uint64_t, + pub unavailable_submitted: uint64_t, + pub ready: uint64_t, + pub failed_encode: uint64_t, + pub failed_write: uint64_t, + pub failed_db: uint64_t, + pub disabled_at_encode: uint64_t, + pub interrupted: uint64_t, + pub bytes_written: uint64_t, + pub backlog_items: uint32_t, + pub backlog_bytes: uint64_t, + pub drop_ring_depth: uint32_t, + pub high_water_items: uint32_t, + pub high_water_bytes: uint64_t, + pub budget_exhausted_cameras: uint32_t, + pub limit_items: uint32_t, + pub limit_camera_items: uint32_t, + pub limit_bytes: uint64_t, + pub limit_camera_bytes: uint64_t, + pub camera_high_water_items: uint32_t, + pub submit_latency_count: uint64_t, + pub submit_latency_sum_ns: uint64_t, + pub submit_latency_max_ns: uint64_t, + pub submit_latency_bucket: [uint64_t; 10], + pub discard_deferred: uint64_t, + pub discard_inline: uint64_t, + pub discard_pending_bytes: uint64_t, + pub limit_discard_bytes: uint64_t, +} #[derive(Copy, Clone)] #[repr(C)] pub struct system_info_interface_t { @@ -1131,7 +1249,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -4218,6 +4337,102 @@ pub unsafe extern "C" fn handle_get_system_info( ); } add_cached_stream_storage_usage_to_json(info, 0 as ::core::ffi::c_int); + let mut observation_diagnostics: observation_diagnostics_t = observation_diagnostics_t { + collected_at_ms: 0, + evidence: observation_evidence_stats_t { + initialized: false, + worker_running: false, + allocated: false, + accepted: 0, + dropped_queue_full: 0, + dropped_budget: 0, + dropped_disabled: 0, + dropped_untracked: 0, + unavailable_submitted: 0, + ready: 0, + failed_encode: 0, + failed_write: 0, + failed_db: 0, + disabled_at_encode: 0, + interrupted: 0, + bytes_written: 0, + backlog_items: 0, + backlog_bytes: 0, + drop_ring_depth: 0, + high_water_items: 0, + high_water_bytes: 0, + budget_exhausted_cameras: 0, + limit_items: 0, + limit_camera_items: 0, + limit_bytes: 0, + limit_camera_bytes: 0, + camera_high_water_items: 0, + submit_latency_count: 0, + submit_latency_sum_ns: 0, + submit_latency_max_ns: 0, + submit_latency_bucket: [0; 10], + discard_deferred: 0, + discard_inline: 0, + discard_pending_bytes: 0, + limit_discard_bytes: 0, + }, + relay: observation_relay_stats_t { + running: false, + passes: 0, + relayed: 0, + duplicates: 0, + relayed_no_route: 0, + expired: 0, + failed: 0, + enqueue_full: 0, + enqueue_errors: 0, + mark_errors: 0, + read_errors: 0, + cap_pauses: 0, + backoffs: 0, + sidecar_writes: 0, + sidecar_errors: 0, + epoch_regenerations: 0, + last_relayed_seq: 0, + oldest_pending_created_at_ms: 0, + last_pass_pending: 0, + outbox_type_pending: 0, + consecutive_failures: 0, + backoff_until_ms: 0, + }, + db_available: false, + db: observation_db_diagnostics_t { + meta: observation_meta_t { + epoch_uuid: [0; 37], + oldest_seq: 0, + head_seq: 0, + }, + has_oldest_observed_at: false, + oldest_observed_at_ms: 0, + observations: 0, + publications_pending: 0, + publications_failed: 0, + publications_expired: 0, + oldest_pending_created_at_ms: 0, + }, + cameras: ::core::ptr::null_mut::(), + camera_count: 0, + budget_exhausted_cameras: 0, + }; + if observation_diagnostics_collect(&raw mut observation_diagnostics, 0 as int64_t) + == 0 as ::core::ffi::c_int + { + let mut pipeline: *mut cJSON = + observation_diagnostics_to_json(&raw mut observation_diagnostics); + if !pipeline.is_null() { + cJSON_AddItemToObject( + info, + b"observation_pipeline\0".as_ptr() as *const ::core::ffi::c_char, + pipeline, + ); + } + observation_diagnostics_free(&raw mut observation_diagnostics); + } let mut json_str: *mut ::core::ffi::c_char = cJSON_PrintUnformatted(info); if json_str.is_null() { _log_message_ctx( diff --git a/rust/generated/src__web__api_handlers_system_go2rtc.rs b/rust/generated/src__web__api_handlers_system_go2rtc.rs index 1629efa23..ab1d341d2 100644 --- a/rust/generated/src__web__api_handlers_system_go2rtc.rs +++ b/rust/generated/src__web__api_handlers_system_go2rtc.rs @@ -311,7 +311,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const __S_IFMT: ::core::ffi::c_int = 0o170000 as ::core::ffi::c_int; pub const __O_CLOEXEC: ::core::ffi::c_int = 0o2000000 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_system_health.rs b/rust/generated/src__web__api_handlers_system_health.rs index 4513f81c0..0e22bb3ca 100644 --- a/rust/generated/src__web__api_handlers_system_health.rs +++ b/rust/generated/src__web__api_handlers_system_health.rs @@ -426,6 +426,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -531,7 +535,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -860,7 +864,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__api_handlers_system_logs.rs b/rust/generated/src__web__api_handlers_system_logs.rs index 678c06d1f..7ca143dde 100644 --- a/rust/generated/src__web__api_handlers_system_logs.rs +++ b/rust/generated/src__web__api_handlers_system_logs.rs @@ -95,6 +95,7 @@ pub type __time_t = ::core::ffi::c_long; pub type __blksize_t = ::core::ffi::c_long; pub type __blkcnt_t = ::core::ffi::c_long; pub type __syscall_slong_t = ::core::ffi::c_long; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct timespec { @@ -205,6 +206,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -310,7 +315,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -431,7 +436,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const O_WRONLY: ::core::ffi::c_int = 0o1 as ::core::ffi::c_int; pub const O_CREAT: ::core::ffi::c_int = 0o100 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_system_logs_tail.rs b/rust/generated/src__web__api_handlers_system_logs_tail.rs index 358fd8c4d..99694f5e2 100644 --- a/rust/generated/src__web__api_handlers_system_logs_tail.rs +++ b/rust/generated/src__web__api_handlers_system_logs_tail.rs @@ -66,6 +66,7 @@ pub type size_t = usize; pub type __uint64_t = u64; pub type __off_t = ::core::ffi::c_long; pub type __off64_t = ::core::ffi::c_long; +pub type int64_t = i64; #[derive(Copy, Clone, ::c2rust_bitfields::BitfieldStruct)] #[repr(C)] pub struct _IO_FILE { @@ -190,6 +191,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -295,7 +300,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__web__api_handlers_timeline.rs b/rust/generated/src__web__api_handlers_timeline.rs index a3941bb11..b51191647 100644 --- a/rust/generated/src__web__api_handlers_timeline.rs +++ b/rust/generated/src__web__api_handlers_timeline.rs @@ -458,6 +458,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -563,7 +567,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -875,7 +879,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const O_WRONLY: ::core::ffi::c_int = 0o1 as ::core::ffi::c_int; pub const O_CREAT: ::core::ffi::c_int = 0o100 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__api_handlers_totp.rs b/rust/generated/src__web__api_handlers_totp.rs index e7655b4a2..6d32096d2 100644 --- a/rust/generated/src__web__api_handlers_totp.rs +++ b/rust/generated/src__web__api_handlers_totp.rs @@ -368,7 +368,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_users_backend_agnostic.rs b/rust/generated/src__web__api_handlers_users_backend_agnostic.rs index 57baed0cd..95e0b5ff5 100644 --- a/rust/generated/src__web__api_handlers_users_backend_agnostic.rs +++ b/rust/generated/src__web__api_handlers_users_backend_agnostic.rs @@ -295,6 +295,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -400,7 +404,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -539,7 +543,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__api_handlers_zones.rs b/rust/generated/src__web__api_handlers_zones.rs index 65a3f86f1..46ce689ed 100644 --- a/rust/generated/src__web__api_handlers_zones.rs +++ b/rust/generated/src__web__api_handlers_zones.rs @@ -239,7 +239,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const _log_stream_name: *const ::core::ffi::c_char = NULL as *const ::core::ffi::c_char; pub const MAX_STREAM_NAME: ::core::ffi::c_int = 256 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__audit_log.rs b/rust/generated/src__web__audit_log.rs index 5414c0db7..92b0516ba 100644 --- a/rust/generated/src__web__audit_log.rs +++ b/rust/generated/src__web__audit_log.rs @@ -334,7 +334,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] @@ -895,7 +896,7 @@ pub unsafe extern "C" fn audit_log_append( free(serialized as *mut ::core::ffi::c_void); } pub const AUDIT_SUMMARY_FLUSH_BATCH: ::core::ffi::c_int = 8 as ::core::ffi::c_int; -static mut decision_modes: [atomic_int; 19] = [0; 19]; +static mut decision_modes: [atomic_int; 20] = [0; 20]; static mut decision_mutex: pthread_mutex_t = pthread_mutex_t { __data: __pthread_mutex_s { __lock: 0 as ::core::ffi::c_int, @@ -946,8 +947,8 @@ pub unsafe extern "C" fn audit_log_reset_summaries_for_testing( } #[unsafe(no_mangle)] pub unsafe extern "C" fn audit_log_decision_modes_init() -> ::core::ffi::c_int { - let mut modes: [audit_decision_mode_t; 19] = - [audit_decision_mode_t::AUDIT_DECISION_MODE_RECORD; 19]; + let mut modes: [audit_decision_mode_t; 20] = + [audit_decision_mode_t::AUDIT_DECISION_MODE_RECORD; 20]; let mut rc: ::core::ffi::c_int = db_audit_load_decision_modes(&raw mut modes as *mut audit_decision_mode_t); let mut i: ::core::ffi::c_int = 0 as ::core::ffi::c_int; diff --git a/rust/generated/src__web__httpd_utils.rs b/rust/generated/src__web__httpd_utils.rs index cfc080a05..688a00f75 100644 --- a/rust/generated/src__web__httpd_utils.rs +++ b/rust/generated/src__web__httpd_utils.rs @@ -331,6 +331,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -436,7 +440,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -624,7 +628,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Copy, Clone)] #[repr(C)] diff --git a/rust/generated/src__web__libuv_api_handlers.rs b/rust/generated/src__web__libuv_api_handlers.rs index c9028060b..6f4771239 100644 --- a/rust/generated/src__web__libuv_api_handlers.rs +++ b/rust/generated/src__web__libuv_api_handlers.rs @@ -409,6 +409,20 @@ unsafe extern "C" { unsafe fn handle_delete_lpr_read(req: *const http_request_t, res: *mut http_response_t); unsafe fn handle_get_detection_engines(req: *const http_request_t, res: *mut http_response_t); unsafe fn handle_put_detection_engines(req: *const http_request_t, res: *mut http_response_t); + unsafe fn handle_get_integration_capabilities( + req: *const http_request_t, + res: *mut http_response_t, + ); + unsafe fn handle_get_observations(req: *const http_request_t, res: *mut http_response_t); + unsafe fn handle_get_observation(req: *const http_request_t, res: *mut http_response_t); + unsafe fn handle_get_observation_snapshot( + req: *const http_request_t, + res: *mut http_response_t, + ); + unsafe fn handle_put_observation_classification( + req: *const http_request_t, + res: *mut http_response_t, + ); unsafe fn _log_message_ctx( level: log_level_t, component: *const ::core::ffi::c_char, @@ -1523,6 +1537,51 @@ pub unsafe extern "C" fn register_all_libuv_handlers( as unsafe extern "C" fn(*const http_request_t, *mut http_response_t) -> (), ), ); + http_server_register_handler( + server, + b"/api/integrations/capabilities\0".as_ptr() as *const ::core::ffi::c_char, + b"GET\0".as_ptr() as *const ::core::ffi::c_char, + Some( + handle_get_integration_capabilities + as unsafe extern "C" fn(*const http_request_t, *mut http_response_t) -> (), + ), + ); + http_server_register_handler( + server, + b"/api/observations\0".as_ptr() as *const ::core::ffi::c_char, + b"GET\0".as_ptr() as *const ::core::ffi::c_char, + Some( + handle_get_observations + as unsafe extern "C" fn(*const http_request_t, *mut http_response_t) -> (), + ), + ); + http_server_register_handler( + server, + b"/api/observations/#/snapshot\0".as_ptr() as *const ::core::ffi::c_char, + b"GET\0".as_ptr() as *const ::core::ffi::c_char, + Some( + handle_get_observation_snapshot + as unsafe extern "C" fn(*const http_request_t, *mut http_response_t) -> (), + ), + ); + http_server_register_handler( + server, + b"/api/observations/#/classifications/#/#\0".as_ptr() as *const ::core::ffi::c_char, + b"PUT\0".as_ptr() as *const ::core::ffi::c_char, + Some( + handle_put_observation_classification + as unsafe extern "C" fn(*const http_request_t, *mut http_response_t) -> (), + ), + ); + http_server_register_handler( + server, + b"/api/observations/#\0".as_ptr() as *const ::core::ffi::c_char, + b"GET\0".as_ptr() as *const ::core::ffi::c_char, + Some( + handle_get_observation + as unsafe extern "C" fn(*const http_request_t, *mut http_response_t) -> (), + ), + ); http_server_register_handler( server, b"/api/storage/health\0".as_ptr() as *const ::core::ffi::c_char, diff --git a/rust/generated/src__web__libuv_connection.rs b/rust/generated/src__web__libuv_connection.rs index 435b02707..3530c7e28 100644 --- a/rust/generated/src__web__libuv_connection.rs +++ b/rust/generated/src__web__libuv_connection.rs @@ -971,6 +971,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -1076,7 +1080,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, @@ -1337,7 +1341,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } pub const CLOCK_MONOTONIC: ::core::ffi::c_int = 1 as ::core::ffi::c_int; pub const ZLIB_VERSION: [::core::ffi::c_char; 6] = diff --git a/rust/generated/src__web__libuv_server.rs b/rust/generated/src__web__libuv_server.rs index b73b03315..64cc28be1 100644 --- a/rust/generated/src__web__libuv_server.rs +++ b/rust/generated/src__web__libuv_server.rs @@ -126,6 +126,7 @@ pub type size_t = usize; pub type __useconds_t = ::core::ffi::c_uint; pub type ssize_t = isize; pub type int32_t = i32; +pub type int64_t = i64; #[derive(Copy, Clone)] #[repr(C)] pub struct __pthread_internal_list { @@ -690,6 +691,10 @@ pub struct stream_config_t { pub publish_url: [::core::ffi::c_char; 512], pub playback_transport: [::core::ffi::c_char; 24], pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, } #[derive(Copy, Clone)] #[repr(C)] @@ -795,7 +800,7 @@ pub struct config_t { pub go2rtc_webrtc_listen_port: ::core::ffi::c_int, pub go2rtc_stun_enabled: bool, pub go2rtc_stun_server: [::core::ffi::c_char; 256], - pub go2rtc_external_ip: [::core::ffi::c_char; 64], + pub go2rtc_external_ip: [::core::ffi::c_char; 256], pub go2rtc_ice_servers: [::core::ffi::c_char; 512], pub webrtc_connection_timeout_ms: ::core::ffi::c_int, pub webrtc_ice_recovery_timeout_ms: ::core::ffi::c_int, diff --git a/rust/generated/src__web__openapi_projection.rs b/rust/generated/src__web__openapi_projection.rs index 0ad389e9a..6dfaa42b7 100644 --- a/rust/generated/src__web__openapi_projection.rs +++ b/rust/generated/src__web__openapi_projection.rs @@ -124,7 +124,8 @@ impl authorization_action_t { pub const AUTHZ_LPR_SEARCH: Self = Self(16); pub const AUTHZ_LPR_EXPORT: Self = Self(17); pub const AUTHZ_LPR_DELETE: Self = Self(18); - pub const AUTHZ_ACTION_COUNT: Self = Self(19); + pub const AUTHZ_OBSERVATIONS_ANNOTATE: Self = Self(19); + pub const AUTHZ_ACTION_COUNT: Self = Self(20); } #[derive(Clone, Copy, PartialEq, Eq)] #[repr(transparent)] diff --git a/rust/generated/src__web__stream_observation_settings.rs b/rust/generated/src__web__stream_observation_settings.rs new file mode 100644 index 000000000..c20094a8c --- /dev/null +++ b/rust/generated/src__web__stream_observation_settings.rs @@ -0,0 +1,499 @@ +unsafe extern "C" { + unsafe fn cJSON_GetObjectItemCaseSensitive( + object: *const cJSON, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn cJSON_IsTrue(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsBool(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsNumber(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_IsString(item: *const cJSON) -> cJSON_bool; + unsafe fn cJSON_AddBoolToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + boolean: cJSON_bool, + ) -> *mut cJSON; + unsafe fn cJSON_AddNumberToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + number: ::core::ffi::c_double, + ) -> *mut cJSON; + unsafe fn cJSON_AddStringToObject( + object: *mut cJSON, + name: *const ::core::ffi::c_char, + string: *const ::core::ffi::c_char, + ) -> *mut cJSON; + unsafe fn floor(__x: ::core::ffi::c_double) -> ::core::ffi::c_double; + unsafe fn snprintf( + __s: *mut ::core::ffi::c_char, + __maxlen: size_t, + __format: *const ::core::ffi::c_char, + ... + ) -> ::core::ffi::c_int; + unsafe fn safe_strcpy( + dest: *mut ::core::ffi::c_char, + src: *const ::core::ffi::c_char, + dst_size: size_t, + src_size: size_t, + ) -> ::core::ffi::c_int; + unsafe fn memcpy( + __dest: *mut ::core::ffi::c_void, + __src: *const ::core::ffi::c_void, + __n: size_t, + ) -> *mut ::core::ffi::c_void; + unsafe fn strcmp( + __s1: *const ::core::ffi::c_char, + __s2: *const ::core::ffi::c_char, + ) -> ::core::ffi::c_int; + unsafe fn strchr( + __s: *const ::core::ffi::c_char, + __c: ::core::ffi::c_int, + ) -> *mut ::core::ffi::c_char; + unsafe fn strlen(__s: *const ::core::ffi::c_char) -> size_t; +} +pub type size_t = usize; +pub type int64_t = i64; +pub type uint8_t = u8; +#[derive(Copy, Clone)] +#[repr(C)] +pub struct cJSON { + pub next: *mut cJSON, + pub prev: *mut cJSON, + pub child: *mut cJSON, + pub r#type: ::core::ffi::c_int, + pub valuestring: *mut ::core::ffi::c_char, + pub valueint: ::core::ffi::c_int, + pub valuedouble: ::core::ffi::c_double, + pub string: *mut ::core::ffi::c_char, +} +pub type cJSON_bool = ::core::ffi::c_int; +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct stream_protocol_t(pub ::core::ffi::c_uint); +impl stream_protocol_t { + pub const STREAM_PROTOCOL_TCP: Self = Self(0); + pub const STREAM_PROTOCOL_UDP: Self = Self(1); +} +#[derive(Copy, Clone)] +#[repr(C)] +pub struct stream_config_t { + pub camera_uuid: [::core::ffi::c_char; 37], + pub location_uuid: [::core::ffi::c_char; 37], + pub name: [::core::ffi::c_char; 256], + pub url: [::core::ffi::c_char; 512], + pub enabled: bool, + pub width: ::core::ffi::c_int, + pub height: ::core::ffi::c_int, + pub fps: ::core::ffi::c_int, + pub codec: [::core::ffi::c_char; 16], + pub priority: ::core::ffi::c_int, + pub record: bool, + pub segment_duration: ::core::ffi::c_int, + pub detection_based_recording: bool, + pub detection_model: [::core::ffi::c_char; 512], + pub detection_interval: ::core::ffi::c_int, + pub detection_threshold: ::core::ffi::c_float, + pub pre_detection_buffer: ::core::ffi::c_int, + pub post_detection_buffer: ::core::ffi::c_int, + pub detection_api_url: [::core::ffi::c_char; 512], + pub detection_object_filter: [::core::ffi::c_char; 16], + pub detection_object_filter_list: [::core::ffi::c_char; 256], + pub streaming_enabled: bool, + pub protocol: stream_protocol_t, + pub record_audio: bool, + pub audio_voice_enhancement: bool, + pub onvif_username: [::core::ffi::c_char; 64], + pub onvif_password: [::core::ffi::c_char; 64], + pub onvif_profile: [::core::ffi::c_char; 64], + pub onvif_port: ::core::ffi::c_int, + pub onvif_discovery_enabled: bool, + pub is_onvif: bool, + pub backchannel_enabled: bool, + pub retention_days: ::core::ffi::c_int, + pub detection_retention_days: ::core::ffi::c_int, + pub max_storage_mb: ::core::ffi::c_int, + pub tier_critical_multiplier: ::core::ffi::c_double, + pub tier_important_multiplier: ::core::ffi::c_double, + pub tier_ephemeral_multiplier: ::core::ffi::c_double, + pub storage_priority: ::core::ffi::c_int, + pub ptz_enabled: bool, + pub ptz_max_x: ::core::ffi::c_int, + pub ptz_max_y: ::core::ffi::c_int, + pub ptz_max_z: ::core::ffi::c_int, + pub ptz_has_home: bool, + pub record_on_schedule: bool, + pub recording_schedule: [uint8_t; 168], + pub detection_record_on_schedule: bool, + pub detection_recording_schedule: [uint8_t; 168], + pub tags: [::core::ffi::c_char; 256], + pub admin_url: [::core::ffi::c_char; 512], + pub privacy_mode: bool, + pub motion_trigger_source: [::core::ffi::c_char; 256], + pub go2rtc_source_override: [::core::ffi::c_char; 8192], + pub sub_stream_url: [::core::ffi::c_char; 512], + pub detection_url: [::core::ffi::c_char; 512], + pub publish_url: [::core::ffi::c_char; 512], + pub playback_transport: [::core::ffi::c_char; 24], + pub eptz_config: [::core::ffi::c_char; 1024], + pub observation_evidence_enabled: bool, + pub observation_labels: [::core::ffi::c_char; 512], + pub observation_max_age_days: ::core::ffi::c_int, + pub observation_max_bytes: int64_t, +} +pub const OBSERVATION_DEFAULT_MAX_AGE_DAYS: ::core::ffi::c_int = 7 as ::core::ffi::c_int; +pub const OBSERVATION_DEFAULT_MAX_BYTES: ::core::ffi::c_longlong = + 2147483648 as ::core::ffi::c_longlong; +pub const OBSERVATION_LABELS_MAX_COUNT: ::core::ffi::c_int = 16 as ::core::ffi::c_int; +pub const OBSERVATION_LABEL_MAX_BYTES: ::core::ffi::c_int = 31 as ::core::ffi::c_int; +pub const OBSERVATION_MAX_AGE_DAYS_MIN: ::core::ffi::c_int = 1 as ::core::ffi::c_int; +pub const OBSERVATION_MAX_AGE_DAYS_MAX: ::core::ffi::c_int = 90 as ::core::ffi::c_int; +pub const OBSERVATION_MAX_BYTES_MIN: ::core::ffi::c_longlong = 64 as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong; +pub const OBSERVATION_MAX_BYTES_MAX: ::core::ffi::c_longlong = 1024 as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong + * 1024 as ::core::ffi::c_longlong; +pub const r#false: ::core::ffi::c_int = 0 as ::core::ffi::c_int; +unsafe extern "C" fn fail( + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, + mut message: *const ::core::ffi::c_char, +) -> ::core::ffi::c_int { + if !error.is_null() && error_size != 0 { + snprintf( + error, + error_size, + b"%s\0".as_ptr() as *const ::core::ffi::c_char, + message, + ); + } + return -1 as ::core::ffi::c_int; +} +unsafe extern "C" fn is_integer_in_range( + mut value: *const cJSON, + mut minimum: ::core::ffi::c_double, + mut maximum: ::core::ffi::c_double, +) -> bool { + return cJSON_IsNumber(value) != 0 + && (*value).valuedouble.is_finite() as ::core::ffi::c_int != 0 + && (*value).valuedouble == floor((*value).valuedouble) + && (*value).valuedouble >= minimum + && (*value).valuedouble <= maximum; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stream_observation_labels_normalize( + mut input: *const ::core::ffi::c_char, + mut out: *mut ::core::ffi::c_char, + mut out_size: size_t, + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, +) -> ::core::ffi::c_int { + if input.is_null() || out.is_null() || out_size == 0 as size_t { + return fail( + error, + error_size, + b"observation_labels is invalid\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + let mut result: [::core::ffi::c_char; 512] = ::core::mem::transmute::< + [u8; 512], + [::core::ffi::c_char; 512], + >( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + let mut used: size_t = 0 as size_t; + let mut count: ::core::ffi::c_int = 0 as ::core::ffi::c_int; + let mut scan: *const ::core::ffi::c_char = input; + while *scan as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *scan as ::core::ffi::c_int == '\t' as ::core::ffi::c_int + { + scan = scan.offset(1); + } + if *scan as ::core::ffi::c_int == '\0' as ::core::ffi::c_int { + *out.offset(0isize) = '\0' as ::core::ffi::c_char; + return 0 as ::core::ffi::c_int; + } + let mut cursor: *const ::core::ffi::c_char = input; + loop { + let mut end: *const ::core::ffi::c_char = strchr(cursor, ',' as ::core::ffi::c_int); + let mut stop: *const ::core::ffi::c_char = if !end.is_null() { + end + } else { + cursor.offset(strlen(cursor) as isize) + }; + let mut first: *const ::core::ffi::c_char = cursor; + let mut last: *const ::core::ffi::c_char = stop; + while first < last + && (*first as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *first as ::core::ffi::c_int == '\t' as ::core::ffi::c_int) + { + first = first.offset(1); + } + while last > first + && (*last.offset(-1isize) as ::core::ffi::c_int == ' ' as ::core::ffi::c_int + || *last.offset(-1isize) as ::core::ffi::c_int == '\t' as ::core::ffi::c_int) + { + last = last.offset(-1); + } + let mut length: size_t = last.offset_from(first) as size_t; + if length == 0 as size_t { + return fail( + error, + error_size, + b"observation_labels must not contain empty labels\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + if length > OBSERVATION_LABEL_MAX_BYTES as size_t { + return fail( + error, + error_size, + b"observation_labels entries must be 1..31 bytes\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + let mut c: *const ::core::ffi::c_char = first; + while c < last { + let mut ch: ::core::ffi::c_uchar = *c as ::core::ffi::c_uchar; + if (ch as ::core::ffi::c_int) < 0x20 as ::core::ffi::c_int + || ch as ::core::ffi::c_int > 0x7e as ::core::ffi::c_int + { + return fail( + error, + error_size, + b"observation_labels entries must be printable ASCII\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + c = c.offset(1); + } + count += 1; + if count > OBSERVATION_LABELS_MAX_COUNT { + return fail( + error, + error_size, + b"observation_labels allows at most 16 labels\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + if used.wrapping_add(length).wrapping_add( + (if count > 1 as ::core::ffi::c_int { + 1 as ::core::ffi::c_int + } else { + 0 as ::core::ffi::c_int + }) as size_t, + ) >= ::core::mem::size_of::<[::core::ffi::c_char; 512]>() + { + return fail( + error, + error_size, + b"observation_labels is too long\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + if count > 1 as ::core::ffi::c_int { + let c2rust_fresh0 = used; + used = used.wrapping_add(1); + result[c2rust_fresh0] = ',' as ::core::ffi::c_char; + } + memcpy( + (&raw mut result as *mut ::core::ffi::c_char).offset(used as isize) + as *mut ::core::ffi::c_void, + first as *const ::core::ffi::c_void, + length, + ); + used = used.wrapping_add(length); + result[used] = '\0' as ::core::ffi::c_char; + if end.is_null() { + break; + } + cursor = end.offset(1 as ::core::ffi::c_int as isize); + } + if used >= out_size { + return fail( + error, + error_size, + b"observation_labels is too long\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + memcpy( + out as *mut ::core::ffi::c_void, + &raw mut result as *mut ::core::ffi::c_char as *const ::core::ffi::c_void, + used.wrapping_add(1 as size_t), + ); + return 0 as ::core::ffi::c_int; +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stream_observation_settings_to_json( + mut object: *mut cJSON, + mut config: *const stream_config_t, +) { + if object.is_null() || config.is_null() { + return; + } + cJSON_AddBoolToObject( + object, + b"observation_evidence_enabled\0".as_ptr() as *const ::core::ffi::c_char, + (*config).observation_evidence_enabled as cJSON_bool, + ); + cJSON_AddStringToObject( + object, + b"observation_labels\0".as_ptr() as *const ::core::ffi::c_char, + &raw const (*config).observation_labels as *const ::core::ffi::c_char, + ); + cJSON_AddNumberToObject( + object, + b"observation_max_age_days\0".as_ptr() as *const ::core::ffi::c_char, + (if (*config).observation_max_age_days > 0 as ::core::ffi::c_int { + (*config).observation_max_age_days + } else { + OBSERVATION_DEFAULT_MAX_AGE_DAYS + }) as ::core::ffi::c_double, + ); + cJSON_AddNumberToObject( + object, + b"observation_max_bytes\0".as_ptr() as *const ::core::ffi::c_char, + (if (*config).observation_max_bytes > 0 as int64_t { + (*config).observation_max_bytes as ::core::ffi::c_longlong + } else { + OBSERVATION_DEFAULT_MAX_BYTES + }) as ::core::ffi::c_double, + ); +} +#[unsafe(no_mangle)] +pub unsafe extern "C" fn stream_observation_settings_apply_json( + mut body: *const cJSON, + mut config: *mut stream_config_t, + mut changed: *mut bool, + mut error: *mut ::core::ffi::c_char, + mut error_size: size_t, +) -> ::core::ffi::c_int { + if !changed.is_null() { + *changed = r#false != 0; + } + if body.is_null() || config.is_null() { + return fail( + error, + error_size, + b"observation settings are invalid\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + let mut enabled: *const cJSON = cJSON_GetObjectItemCaseSensitive( + body, + b"observation_evidence_enabled\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut labels: *const cJSON = cJSON_GetObjectItemCaseSensitive( + body, + b"observation_labels\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut max_age: *const cJSON = cJSON_GetObjectItemCaseSensitive( + body, + b"observation_max_age_days\0".as_ptr() as *const ::core::ffi::c_char, + ); + let mut max_bytes: *const cJSON = cJSON_GetObjectItemCaseSensitive( + body, + b"observation_max_bytes\0".as_ptr() as *const ::core::ffi::c_char, + ); + if !enabled.is_null() && cJSON_IsBool(enabled) == 0 { + return fail( + error, + error_size, + b"observation_evidence_enabled must be a boolean\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + let mut normalized: [::core::ffi::c_char; 512] = ::core::mem::transmute::< + [u8; 512], + [::core::ffi::c_char; 512], + >( + *b"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", + ); + if !labels.is_null() { + if cJSON_IsString(labels) == 0 || (*labels).valuestring.is_null() { + return fail( + error, + error_size, + b"observation_labels must be a string\0".as_ptr() as *const ::core::ffi::c_char, + ); + } + if stream_observation_labels_normalize( + (*labels).valuestring, + &raw mut normalized as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + error, + error_size, + ) != 0 as ::core::ffi::c_int + { + return -1 as ::core::ffi::c_int; + } + } + if !max_age.is_null() + && !is_integer_in_range( + max_age, + OBSERVATION_MAX_AGE_DAYS_MIN as ::core::ffi::c_double, + OBSERVATION_MAX_AGE_DAYS_MAX as ::core::ffi::c_double, + ) + { + return fail( + error, + error_size, + b"observation_max_age_days must be an integer between 1 and 90\0".as_ptr() + as *const ::core::ffi::c_char, + ); + } + if !max_bytes.is_null() + && !is_integer_in_range( + max_bytes, + OBSERVATION_MAX_BYTES_MIN as ::core::ffi::c_double, + OBSERVATION_MAX_BYTES_MAX as ::core::ffi::c_double, + ) + { + return fail( + error, + error_size, + b"observation_max_bytes must be an integer between 67108864 (64 MiB) and 1099511627776 (1 TiB)\0" + .as_ptr() as *const ::core::ffi::c_char, + ); + } + let mut any_change: bool = r#false != 0; + if !enabled.is_null() { + let mut value: bool = cJSON_IsTrue(enabled) != 0; + any_change = any_change as ::core::ffi::c_int + | (value as ::core::ffi::c_int + != (*config).observation_evidence_enabled as ::core::ffi::c_int) + as ::core::ffi::c_int + != 0; + (*config).observation_evidence_enabled = value; + } + if !labels.is_null() { + any_change = any_change as ::core::ffi::c_int + | (strcmp( + &raw mut normalized as *mut ::core::ffi::c_char, + &raw mut (*config).observation_labels as *mut ::core::ffi::c_char, + ) != 0 as ::core::ffi::c_int) as ::core::ffi::c_int + != 0; + safe_strcpy( + &raw mut (*config).observation_labels as *mut ::core::ffi::c_char, + &raw mut normalized as *mut ::core::ffi::c_char, + ::core::mem::size_of::<[::core::ffi::c_char; 512]>(), + 0 as size_t, + ); + } + if !max_age.is_null() { + let mut value_0: ::core::ffi::c_int = (*max_age).valuedouble as ::core::ffi::c_int; + any_change = any_change as ::core::ffi::c_int + | (value_0 != (*config).observation_max_age_days) as ::core::ffi::c_int + != 0; + (*config).observation_max_age_days = value_0; + } + if !max_bytes.is_null() { + let mut value_1: int64_t = (*max_bytes).valuedouble as int64_t; + any_change = any_change as ::core::ffi::c_int + | (value_1 != (*config).observation_max_bytes) as ::core::ffi::c_int + != 0; + (*config).observation_max_bytes = value_1; + } + if !changed.is_null() { + *changed = any_change; + } + return 0 as ::core::ffi::c_int; +} diff --git a/rust/main.rs b/rust/main.rs index 9c7338b54..3de4a455b 100644 --- a/rust/main.rs +++ b/rust/main.rs @@ -66,6 +66,14 @@ pub mod src__core__mqtt_destination_client; pub mod src__core__mqtt_event_adapter; #[path = "generated/src__core__mqtt_presence.rs"] pub mod src__core__mqtt_presence; +#[path = "generated/src__core__observation_diagnostics.rs"] +pub mod src__core__observation_diagnostics; +#[path = "generated/src__core__observation_evidence.rs"] +pub mod src__core__observation_evidence; +#[path = "generated/src__core__observation_relay.rs"] +pub mod src__core__observation_relay; +#[path = "generated/src__core__observation_retention.rs"] +pub mod src__core__observation_retention; #[path = "generated/src__core__path_utils.rs"] pub mod src__core__path_utils; #[path = "generated/src__core__sentry.rs"] @@ -132,6 +140,10 @@ pub mod src__database__db_maintenance; pub mod src__database__db_migrations; #[path = "generated/src__database__db_motion_config.rs"] pub mod src__database__db_motion_config; +#[path = "generated/src__database__db_observation_classifications.rs"] +pub mod src__database__db_observation_classifications; +#[path = "generated/src__database__db_observations.rs"] +pub mod src__database__db_observations; #[path = "generated/src__database__db_onvif_discovery_inventory.rs"] pub mod src__database__db_onvif_discovery_inventory; #[path = "generated/src__database__db_operator_floor_plans.rs"] @@ -254,6 +266,8 @@ pub mod src__utils__memory; pub mod src__utils__strings; #[path = "generated/src__utils__uuid.rs"] pub mod src__utils__uuid; +#[path = "generated/src__utils__webrtc_candidates.rs"] +pub mod src__utils__webrtc_candidates; #[path = "generated/src__utils__yaml_redact.rs"] pub mod src__utils__yaml_redact; #[path = "generated/src__utils__yaml_validate.rs"] @@ -268,6 +282,8 @@ pub mod src__video__detection; pub mod src__video__detection_config; #[path = "generated/src__video__detection_embedded.rs"] pub mod src__video__detection_embedded; +#[path = "generated/src__video__detection_evidence.rs"] +pub mod src__video__detection_evidence; #[path = "generated/src__video__detection_integration.rs"] pub mod src__video__detection_integration; #[path = "generated/src__video__detection_model.rs"] @@ -446,6 +462,8 @@ pub mod src__web__api_handlers_lpr; pub mod src__web__api_handlers_metrics; #[path = "generated/src__web__api_handlers_motion.rs"] pub mod src__web__api_handlers_motion; +#[path = "generated/src__web__api_handlers_observations.rs"] +pub mod src__web__api_handlers_observations; #[path = "generated/src__web__api_handlers_onvif_backend_agnostic.rs"] pub mod src__web__api_handlers_onvif_backend_agnostic; #[path = "generated/src__web__api_handlers_openapi.rs"] @@ -558,6 +576,8 @@ pub mod src__web__recording_source; pub mod src__web__recordings_playback_state; #[path = "generated/src__web__request_response.rs"] pub mod src__web__request_response; +#[path = "generated/src__web__stream_observation_settings.rs"] +pub mod src__web__stream_observation_settings; #[path = "generated/src__web__thumbnail_thread.rs"] pub mod src__web__thumbnail_thread; fn main() { diff --git a/scripts/contracts/coverage.mjs b/scripts/contracts/coverage.mjs new file mode 100644 index 000000000..3856b5753 --- /dev/null +++ b/scripts/contracts/coverage.mjs @@ -0,0 +1,106 @@ +#!/usr/bin/env node +/** + * Refresh docs/api/contracts/coverage.json from src/web/libuv_api_handlers.c. + * + * tests/contracts/tooling/coverage-inventory.test.mjs pins the registration + * source by sha256 and every entry by handler name and line number. This + * script recomputes those facts after a registration edit while preserving + * every reviewed field (coverage, classification, eligibility, reason). + * + * node scripts/contracts/coverage.mjs # refresh lines/handlers/sha256 + * node scripts/contracts/coverage.mjs --add-coverage observations + * # also add new routes + * node scripts/contracts/coverage.mjs --check # fail on drift, write nothing + * + * A registration with no inventory entry is an error unless --add-coverage is + * given. A new entry is then matched to exactly one canonical OpenAPI + * operation with the same method and route ('{param}' segments become '#'); + * a route with no operation is still an error, because protocol exclusions + * need a written reason. An inventory entry whose route is no longer + * registered is always an error: removals are reviewed by hand. + */ +import {readFile, writeFile} from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {createHash} from 'node:crypto'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const sourcePath = 'src/web/libuv_api_handlers.c'; +const coveragePath = 'docs/api/contracts/coverage.json'; +const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']; + +export function registrations(source) { + // Same literal form the inventory test accepts. + const matches = [...source.matchAll(/http_server_register_handler\(\s*server\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*(\w+)\s*\)/g)]; + if (matches.length !== [...source.matchAll(/http_server_register_handler\s*\(/g)].length) { + throw new Error(`${sourcePath} has a non-literal registration; review the inventory parser`); + } + return matches.map(m => ({route: m[1], method: m[2], handler: m[3], line: source.slice(0, m.index).split('\n').length})); +} + +export function refresh(coverage, source, canonical, {addCoverage = null, classification = 'protected-review-per-route'} = {}) { + const regs = registrations(source); + const byKey = new Map(coverage.operations.map(entry => [`${entry.method} ${entry.route_pattern}`, entry])); + const registered = new Set(regs.map(r => `${r.method} ${r.route}`)); + const stale = [...byKey.keys()].filter(key => !registered.has(key)); + if (stale.length) throw new Error(`Inventory entries no longer registered (remove by hand): ${stale.join(', ')}`); + const operations = []; + for (const [path, item] of Object.entries(canonical.paths)) { + for (const [method, operation] of Object.entries(item)) { + if (METHODS.includes(method)) operations.push({path, method: method.toUpperCase(), operation}); + } + } + const added = []; + let previous = null; + for (const reg of regs) { + const key = `${reg.method} ${reg.route}`; + let entry = byKey.get(key); + if (!entry) { + if (!addCoverage) throw new Error(`Registration without inventory entry: ${key} (pass --add-coverage )`); + const matches = operations.filter(op => op.method === reg.method && op.path.replace(/\{[^}]+\}/g, '#') === reg.route); + if (matches.length !== 1) throw new Error(`New registration ${key} matches ${matches.length} OpenAPI operations; expected 1`); + const [{path, operation}] = matches; + entry = {method: reg.method, route_pattern: reg.route, handler: reg.handler, source: sourcePath, line: reg.line, + coverage: addCoverage, classification, operation_id: operation.operationId, + eligibility: operation['x-lightnvr-authorization']?.kind || 'See canonical principal predicate.', + canonical_path: path}; + // Keep the inventory in its existing (roughly source) order. + const index = previous ? coverage.operations.indexOf(previous) + 1 : coverage.operations.length; + coverage.operations.splice(index, 0, entry); + byKey.set(key, entry); + added.push(key); + } + entry.handler = reg.handler; + entry.source = sourcePath; + entry.line = reg.line; + previous = entry; + } + coverage.source_sha256 = createHash('sha256').update(source).digest('hex'); + return added; +} + +async function main() { + const args = process.argv.slice(2); + let check = false, addCoverage = null, classification; + for (let i = 0; i < args.length; i++) { + if (args[i] === '--check') check = true; + else if (args[i] === '--add-coverage') addCoverage = args[++i]; + else if (args[i] === '--classification') classification = args[++i]; + else throw new Error(`Unknown argument: ${args[i]}`); + } + const read = file => readFile(path.join(root, file), 'utf8'); + const [source, coverageText, canonicalText] = await Promise.all([read(sourcePath), read(coveragePath), read('docs/api/openapi.json')]); + const coverage = JSON.parse(coverageText); + const added = refresh(coverage, source, JSON.parse(canonicalText), {addCoverage, ...(classification ? {classification} : {})}); + const output = JSON.stringify(coverage, null, 2) + '\n'; + if (check) { + if (output !== coverageText) throw new Error(`${coveragePath} is stale; run node scripts/contracts/coverage.mjs`); + console.log(`Checked ${coveragePath}`); + return; + } + await writeFile(path.join(root, coveragePath), output); + console.log(`Refreshed ${coveragePath} (${coverage.operations.length} routes${added.length ? `; added ${added.join(', ')}` : ''})`); +} +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + main().catch(error => { console.error(error.message); process.exitCode = 1; }); +} diff --git a/scripts/contracts/observation-fixtures.mjs b/scripts/contracts/observation-fixtures.mjs new file mode 100644 index 000000000..90732922d --- /dev/null +++ b/scripts/contracts/observation-fixtures.mjs @@ -0,0 +1,585 @@ +#!/usr/bin/env node +// INT_01 C3: build the canonical observation contract fixtures in +// tests/fixtures/integrations/observations/ from one coherent scenario, and +// vendor them into YA-WAMF (backend/tests/fixtures/lightnvr/). +// +// node scripts/contracts/observation-fixtures.mjs write the JSON fixtures +// node scripts/contracts/observation-fixtures.mjs --check fail if any JSON fixture is stale +// node scripts/contracts/observation-fixtures.mjs --jpeg also re-encode the evidence JPEG +// node scripts/contracts/observation-fixtures.mjs --vendor [--allow-dirty] +// node scripts/contracts/observation-fixtures.mjs --check-vendor +// +// The JPEG is encoded only when missing or with --jpeg, because libjpeg output +// can differ between sharp releases; the committed bytes are canonical and the +// JSON (bytes, sha256) is derived from them. Contract: +// docs/integrations/OBSERVATIONS_CONTRACT.md. +import {createHash} from 'node:crypto'; +import {execFileSync} from 'node:child_process'; +import {existsSync} from 'node:fs'; +import {mkdir, readFile, readdir, rm, writeFile, copyFile} from 'node:fs/promises'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; + +export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +export const fixtureRel = 'tests/fixtures/integrations/observations'; +export const fixtureDir = path.join(root, fixtureRel); +export const vendorRel = 'backend/tests/fixtures/lightnvr'; + +// ---- contract helpers (§5.4, §7.4) ------------------------------------------------- +const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; +export function uuidv5(namespace, name) { + const hash = createHash('sha1').update(Buffer.from(namespace.replaceAll('-', ''), 'hex')) + .update(Buffer.from(name, 'utf8')).digest(); + hash[6] = (hash[6] & 0x0f) | 0x50; + hash[8] = (hash[8] & 0x3f) | 0x80; + const hex = hash.subarray(0, 16).toString('hex'); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} +const NS_NAME = 'https://lightnvr.io/ns/observation-change/v1'; +export const MESSAGE_ID_NAMESPACE = uuidv5(NAMESPACE_URL, NS_NAME); +export const messageId = (source, observationId, revision) => + uuidv5(MESSAGE_ID_NAMESPACE, `${source}|${observationId}|${revision}`); +export const encodeCursor = (seq, epoch) => Buffer.from(JSON.stringify({v: 1, seq, epoch})).toString('base64url'); +const secondPrecision = time => time.replace(/\.\d{3}Z$/, 'Z'); +const round6 = value => Number(value.toFixed(6)); + +// ---- scenario constants ------------------------------------------------------------ +const INSTALLATION = '11111111-1111-4111-8111-111111111111'; +const SOURCE = `urn:lightnvr:${INSTALLATION}`; +const CAMERA = '22222222-2222-4222-8222-222222222222'; +const EPOCH = '66666666-6666-4666-8666-666666666666'; +const TOPIC_PREFIX = 'lightnvr'; +const EVENT_TYPE = 'io.lightnvr.detection.observation_changed.v1'; +const OLDEST_SEQ = 1001; +const PAGE_WATERMARK = 1010; +const WRITER = {username: 'yawamf', writer_namespace: 'yawamf'}; +const TRACK_SESSION = '9b0c7d1e-5f43-4b8e-9d0e-0b6c1f2a3d4e'; + +const IDS = { + A: {id: '55555555-5555-4555-8555-555555555555', batch: '77777777-7777-4777-8777-777777777777', asset: '88888888-8888-4888-8888-888888888888'}, + B: {id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', batch: 'b0b0b0b0-b0b0-4b0b-8b0b-b0b0b0b0b0b0', asset: 'b1b1b1b1-b1b1-4b1b-8b1b-b1b1b1b1b1b1'}, + C0: {id: 'cccccccc-cccc-4ccc-8ccc-cccccccccc00', batch: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', asset: '99999999-9999-4999-8999-999999999999'}, + C1: {id: 'cccccccc-cccc-4ccc-8ccc-cccccccccc01', batch: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', asset: '99999999-9999-4999-8999-999999999999'}, + D: {id: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', batch: 'ffffffff-ffff-4fff-8fff-ffffffffffff', asset: 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee'}, + E: {id: 'e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0', batch: 'e1e1e1e1-e1e1-4e1e-8e1e-e1e1e1e1e1e1', asset: 'e2e2e2e2-e2e2-4e2e-8e2e-e2e2e2e2e2e2'}, +}; + +// Evidence JPEG: 160x80, boxes on 8-pixel block boundaries so the edges are +// exact after 4:4:4 JPEG coding. +const JPEG_FILE = 'media/two-birds-160x80.jpg'; +const JPEG = { + width: 160, height: 80, background_rgb: [198, 204, 188], + boxes: [ + {key: 'C0', rgb: [196, 52, 40], pixels: {x: 16, y: 16, width: 32, height: 32}}, + {key: 'C1', rgb: [40, 84, 196], pixels: {x: 96, y: 24, width: 48, height: 40}}, + ], +}; +const normalizedBox = ({x, y, width, height}, w, h) => + ({format: 'xywh_normalized', x: round6(x / w), y: round6(y / h), width: round6(width / w), height: round6(height / h)}); + +// Letterboxed HTTP detector: 1280x720 submitted JPEG, server letterboxed to +// 640x640 (scale 0.5, 140 px top/bottom padding). +const LETTERBOX = {stored: {width: 1280, height: 720}, inference: {width: 640, height: 640}, scale: 0.5, pad_x: 0, pad_y: 140, + inference_box: {u: 256, v: 185, width: 128, height: 72}}; +const letterboxBox = (() => { + const {u, v, width, height} = LETTERBOX.inference_box; + const {stored, scale, pad_x: padX, pad_y: padY} = LETTERBOX; + return {format: 'xywh_normalized', x: round6((u - padX) / scale / stored.width), y: round6((v - padY) / scale / stored.height), + width: round6(width / scale / stored.width), height: round6(height / scale / stored.height)}; +})(); + +// ---- annotations (§10) ------------------------------------------------------------- +const automatedWrite = { + classifier: 'yawamf', model_version: 'convnext-v2-2026.10', label: 'Black-capped Chickadee', taxonomy_id: 'inat:144815', + confidence: 0.93, classified_at: '2026-11-08T14:03:15.002Z', input_asset_id: IDS.A.asset, input_revision: 2, + kind: 'automated', expected_revision: 0, +}; +const ownerWrite = { + classifier: 'yawamf', model_version: null, label: 'Carolina Chickadee', taxonomy_id: null, confidence: 1, + classified_at: '2026-11-08T18:20:41.100Z', input_asset_id: IDS.A.asset, input_revision: 3, + kind: 'owner_confirmed', expected_revision: 1, +}; +const annotation = (write, annotationRevision, createdAt, updatedAt) => { + const {expected_revision: _, ...fields} = write; + return {key: 'yawamf/species', writer_namespace: 'yawamf', ...fields, annotation_revision: annotationRevision, + created_at: createdAt, updated_at: updatedAt}; +}; +const automatedAnnotation = annotation(automatedWrite, 1, '2026-11-08T14:03:15.120Z', '2026-11-08T14:03:15.120Z'); +const ownerAnnotation = annotation(ownerWrite, 2, '2026-11-08T14:03:15.120Z', '2026-11-08T18:20:41.300Z'); + +// ---- change log (§5): the single persistence order of the scenario ------------------ +const changes = [ + {seq: 1001, key: 'A', revision: 1, cause: 'created', state: 'pending', reason: null, at: '2026-11-08T14:03:12.530Z'}, + {seq: 1002, key: 'B', revision: 1, cause: 'created', state: 'unavailable', reason: 'queue_full', at: '2026-11-08T14:03:12.611Z'}, + {seq: 1003, key: 'A', revision: 2, cause: 'evidence', state: 'ready', reason: null, at: '2026-11-08T14:03:12.712Z'}, + {seq: 1004, key: 'C0', revision: 1, cause: 'created', state: 'pending', reason: null, at: '2026-11-08T14:03:13.291Z'}, + {seq: 1005, key: 'C1', revision: 1, cause: 'created', state: 'pending', reason: null, at: '2026-11-08T14:03:13.291Z'}, + {seq: 1006, key: 'C0', revision: 2, cause: 'evidence', state: 'ready', reason: null, at: '2026-11-08T14:03:13.402Z'}, + {seq: 1007, key: 'C1', revision: 2, cause: 'evidence', state: 'ready', reason: null, at: '2026-11-08T14:03:13.402Z'}, + {seq: 1008, key: 'D', revision: 1, cause: 'created', state: 'pending', reason: null, at: '2026-11-08T14:03:14.085Z'}, + {seq: 1009, key: 'D', revision: 2, cause: 'evidence', state: 'ready', reason: null, at: '2026-11-08T14:03:14.140Z'}, + {seq: 1010, key: 'A', revision: 3, cause: 'classification', state: 'ready', reason: null, at: '2026-11-08T14:03:15.120Z'}, + {seq: 1011, key: 'E', revision: 1, cause: 'created', state: 'unavailable', reason: 'no_matching_media', at: '2026-11-08T15:30:02.018Z'}, + {seq: 1012, key: 'A', revision: 4, cause: 'classification', state: 'ready', reason: null, at: '2026-11-08T18:20:41.300Z'}, + {seq: 1013, key: 'A', revision: 5, cause: 'evidence', state: 'expired', reason: 'retention_expired', at: '2026-11-15T14:03:13.004Z'}, +]; +// seq is persistence order (§7): B is dropped (queue_full) while A is still +// encoding, so its created row commits before A's ready transition. + +// ---- observations (§3, §4) --------------------------------------------------------- +const detectorLocal = {engine_key: 'legacy-primary', engine_type: 'object', model: 'ssd_mobilenet_v2.sod', model_version: null}; +const none = (w, h) => ({mode: 'none', source_width: w, source_height: h, inference_width: w, inference_height: h, + scale_x: 1, scale_y: 1, pad_x: 0, pad_y: 0}); +const noMedia = asset => ({asset_id: asset, state: 'unavailable', reason: null, origin: null, media_type: null, width: null, + height: null, bytes: null, captured_at: null, expires_at: null, transform: null}); + +function observationA(revision) { + const base = { + id: IDS.A.id, source: SOURCE, camera_uuid: CAMERA, stream_name_at_capture: 'feeder-east', batch_id: IDS.A.batch, + object_index: 0, observed_at: '2026-11-08T14:03:12.481Z', created_at: '2026-11-08T14:03:12.530Z', + detector: detectorLocal, label: 'bird', confidence: 0.87, + box: {format: 'xywh_normalized', x: 0.412, y: 0.288, width: 0.12, height: 0.165}, + }; + const media = {asset_id: IDS.A.asset, origin: 'inference_input', media_type: 'image/jpeg', width: 640, height: 360, bytes: 48211, + captured_at: '2026-11-08T14:03:12.481Z', expires_at: '2026-11-15T14:03:12.481Z', transform: none(640, 360)}; + const evidence = revision === 1 + ? {...media, state: 'pending', reason: null, width: null, height: null, bytes: null, expires_at: null} + : revision === 5 + ? {...media, state: 'expired', reason: 'retention_expired'} + : {...media, state: 'ready', reason: null, snapshot_url: `/api/observations/${IDS.A.id}/snapshot`}; + const classifications = revision <= 2 ? {automated: [], owner_confirmed: []} + : revision === 3 ? {automated: [automatedAnnotation], owner_confirmed: []} + : {automated: [], owner_confirmed: [ownerAnnotation]}; + return {...base, evidence: order(evidence), track: null, recording_reference: {recording_id: 81234}, revision, classifications}; +} +// Evidence key order as in contract §3.6. +const EVIDENCE_KEYS = ['asset_id', 'state', 'reason', 'origin', 'media_type', 'width', 'height', 'bytes', 'captured_at', + 'expires_at', 'transform', 'snapshot_url']; +const order = evidence => Object.fromEntries(EVIDENCE_KEYS.filter(k => k in evidence).map(k => [k, evidence[k]])); + +function observationB() { + return { + id: IDS.B.id, source: SOURCE, camera_uuid: CAMERA, stream_name_at_capture: 'feeder-east', batch_id: IDS.B.batch, + object_index: 0, observed_at: '2026-11-08T14:03:12.598Z', created_at: '2026-11-08T14:03:12.611Z', + detector: detectorLocal, label: 'bird', confidence: 0.71, + box: {format: 'xywh_normalized', x: 0.05, y: 0.4, width: 0.1, height: 0.14}, + evidence: {...noMedia(IDS.B.asset), reason: 'queue_full'}, + track: null, recording_reference: {recording_id: 81234}, revision: 1, classifications: {automated: [], owner_confirmed: []}, + }; +} + +function observationC(index, jpegBytes) { + const key = index === 0 ? 'C0' : 'C1'; + const box = JPEG.boxes[index]; + return { + id: IDS[key].id, source: SOURCE, camera_uuid: CAMERA, stream_name_at_capture: 'feeder-east', batch_id: IDS[key].batch, + object_index: index, observed_at: '2026-11-08T14:03:13.250Z', created_at: '2026-11-08T14:03:13.291Z', + detector: detectorLocal, label: 'bird', confidence: index === 0 ? 0.91 : 0.78, + box: normalizedBox(box.pixels, JPEG.width, JPEG.height), + evidence: { + asset_id: IDS[key].asset, state: 'ready', reason: null, origin: 'inference_input', media_type: 'image/jpeg', + width: JPEG.width, height: JPEG.height, bytes: jpegBytes.length, + captured_at: '2026-11-08T14:03:13.250Z', expires_at: '2026-11-15T14:03:13.250Z', + // SOD pre-resize to a 320x320 tensor: independent x/y scale (§4.4). + transform: {mode: 'resize', source_width: JPEG.width, source_height: JPEG.height, inference_width: 320, + inference_height: 320, scale_x: 320 / JPEG.width, scale_y: 320 / JPEG.height, pad_x: 0, pad_y: 0}, + snapshot_url: `/api/observations/${IDS[key].id}/snapshot`, + }, + track: null, recording_reference: {recording_id: 81235}, revision: 2, classifications: {automated: [], owner_confirmed: []}, + }; +} + +function observationD() { + const {stored, inference, scale} = LETTERBOX; + return { + id: IDS.D.id, source: SOURCE, camera_uuid: CAMERA, stream_name_at_capture: 'feeder-east', batch_id: IDS.D.batch, + object_index: 0, observed_at: '2026-11-08T14:03:14.000Z', created_at: '2026-11-08T14:03:14.085Z', + detector: {engine_key: 'http-yolo', engine_type: 'api', model: 'yolov8n', model_version: '8.3.0'}, label: 'bird', + confidence: 0.84, box: letterboxBox, + evidence: { + asset_id: IDS.D.asset, state: 'ready', reason: null, origin: 'submitted_image', media_type: 'image/jpeg', + width: stored.width, height: stored.height, bytes: 182340, + captured_at: '2026-11-08T14:03:14.000Z', expires_at: '2026-11-15T14:03:14.000Z', + transform: {mode: 'letterbox', source_width: stored.width, source_height: stored.height, inference_width: inference.width, + inference_height: inference.height, scale_x: scale, scale_y: scale, pad_x: LETTERBOX.pad_x, pad_y: LETTERBOX.pad_y}, + snapshot_url: `/api/observations/${IDS.D.id}/snapshot`, + }, + track: {id: '17', namespace: 'http-yolo', session: TRACK_SESSION}, + recording_reference: {recording_id: 81236}, revision: 2, classifications: {automated: [], owner_confirmed: []}, + }; +} + +function observationE() { + return { + id: IDS.E.id, source: SOURCE, camera_uuid: CAMERA, stream_name_at_capture: 'feeder-east', batch_id: IDS.E.batch, + object_index: 0, observed_at: '2026-11-08T15:30:01.900Z', created_at: '2026-11-08T15:30:02.018Z', + detector: {engine_key: 'onvif-events', engine_type: 'onvif', model: null, model_version: null}, label: 'animal', + confidence: 1, box: null, evidence: {...noMedia(IDS.E.asset), reason: 'no_matching_media'}, + track: null, recording_reference: null, revision: 1, classifications: {automated: [], owner_confirmed: []}, + }; +} + +// ---- builders ---------------------------------------------------------------------- +const json = value => `${JSON.stringify(value, null, 2)}\n`; +const errorBody = (code, error, extra = {}) => ({error, code, ...extra}); + +async function encodeJpeg() { + const {default: sharp} = await import('sharp'); + const {width, height} = JPEG; + const raw = Buffer.alloc(width * height * 3); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const box = JPEG.boxes.find(({pixels: p}) => x >= p.x && x < p.x + p.width && y >= p.y && y < p.y + p.height); + raw.set(box ? box.rgb : JPEG.background_rgb, (y * width + x) * 3); + } + } + return sharp(raw, {raw: {width, height, channels: 3}}) + .jpeg({quality: 95, chromaSubsampling: '4:4:4', progressive: false, mozjpeg: false, optimiseCoding: false}) + .toBuffer(); +} + +export async function loadJpeg({regenerate = false} = {}) { + const file = path.join(fixtureDir, JPEG_FILE); + if (!regenerate && existsSync(file)) return readFile(file); + return encodeJpeg(); +} + +// Returns Map for every generated JSON fixture. +export async function buildFixtures({jpeg} = {}) { + const jpegBytes = jpeg ?? await loadJpeg(); + const files = new Map(); + const fixtures = []; + const add = (file, value, meta) => { files.set(file, json(value)); fixtures.push({file, ...meta}); }; + + // Observations. + const obs = { + A1: observationA(1), A2: observationA(2), A3: observationA(3), A4: observationA(4), A5: observationA(5), + B1: observationB(), C0: observationC(0, jpegBytes), C1: observationC(1, jpegBytes), D2: observationD(), E1: observationE(), + }; + const obsFile = { + A1: 'observations/single-pending.json', A2: 'observations/single-ready.json', + A3: 'observations/single-ready-classified.json', A4: 'observations/single-owner-confirmed.json', + A5: 'observations/single-expired.json', B1: 'observations/single-unavailable.json', + C0: 'observations/two-birds-object-0.json', C1: 'observations/two-birds-object-1.json', + D2: 'observations/http-letterbox.json', E1: 'observations/onvif-no-matching-media.json', + }; + const obsDescription = { + A1: 'Single bird, revision 1 (created): evidence pending; dimensions and bytes are set once ready.', + A2: 'Single bird, revision 2 (evidence): evidence ready. Identical to contract §3.6.', + A3: 'Single bird, revision 3 (classification): automated yawamf/species annotation stored.', + A4: 'Single bird, revision 4 (classification): owner_confirmed correction replaced the automated annotation.', + A5: 'Single bird, revision 5 (evidence): media expired by retention; metadata, dimensions and transform kept.', + B1: 'Single bird created directly unavailable (queue_full): no media fields, no transform.', + C0: 'Two birds in one frame, object 0: shares asset and batch with object 1. Box matches media/two-birds-160x80.json.', + C1: 'Two birds in one frame, object 1: shares asset and batch with object 0. Box matches media/two-birds-160x80.json.', + D2: 'HTTP API detector, submitted 1280x720 JPEG letterboxed server-side to 640x640; box already mapped to the stored image.', + E1: 'ONVIF object-class event: no box, evidence unavailable (no_matching_media).', + }; + for (const key of Object.keys(obs)) { + add(obsFile[key], obs[key], {kind: 'observation', schema: 'Observation', observation_id: obs[key].id, + revision: obs[key].revision, description: obsDescription[key], + ...(key === 'D2' ? {inference_box: LETTERBOX.inference_box} : {})}); + } + const obsAt = (key, revision) => ({A: {1: 'A1', 2: 'A2', 3: 'A3', 4: 'A4', 5: 'A5'}, B: {1: 'B1'}, C0: {2: 'C0'}, + C1: {2: 'C1'}, D: {2: 'D2'}, E: {1: 'E1'}})[key]?.[revision]; + + // Change events: complete MQTT envelopes (event_envelope.c key order). + const eventFiles = {1001: 'a-r1-created-pending', 1002: 'b-r1-created-unavailable', 1003: 'a-r2-evidence-ready', + 1006: 'c0-r2-evidence-ready', 1007: 'c1-r2-evidence-ready', 1010: 'a-r3-classification', + 1011: 'e-r1-created-no-matching-media', 1013: 'a-r5-evidence-expired'}; + const vectors = []; + for (const change of changes) { + const name = eventFiles[change.seq]; + if (!name) continue; + const observation = obs[obsAt(change.key, change.revision)]; + const id = messageId(SOURCE, observation.id, change.revision); + const data = { + observation_id: observation.id, revision: change.revision, camera_uuid: observation.camera_uuid, + label: observation.label, observed_at: observation.observed_at, change_cause: change.cause, + evidence_state: change.state, evidence_reason: change.reason, asset_id: observation.evidence.asset_id, + ...(change.state === 'ready' ? {snapshot_url: `/api/observations/${observation.id}/snapshot`} : {}), + }; + const envelope = {specversion: '1.0', id, type: EVENT_TYPE, source: SOURCE, subject: `camera/${observation.camera_uuid}`, + time: secondPrecision(change.at), datacontenttype: 'application/json', severity: 'info', sensitivity: 'operational', data}; + add(`events/${name}.json`, envelope, {kind: 'event', seq: change.seq, change_cause: change.cause, + evidence_state: change.state, topic: `${TOPIC_PREFIX}/v1/events/${EVENT_TYPE}/${observation.camera_uuid}`, retain: false, + observation: obsFile[obsAt(change.key, change.revision)], + description: `MQTT payload for change seq ${change.seq} (${change.cause}, evidence ${change.state}).`}); + vectors.push({source: SOURCE, observation_id: observation.id, revision: change.revision, + name: `${SOURCE}|${observation.id}|${change.revision}`, message_id: id}); + } + + // History pages (§7.3) at watermark PAGE_WATERMARK, limit 2. + const current = (key, watermark) => { + const latest = changes.filter(c => c.key === key && c.seq <= watermark).at(-1); + return obs[obsAt(key, latest.revision)]; + }; + const page = (start, limit, watermark) => { + const keys = []; + let last = start; + for (const change of changes.filter(c => c.seq > start && c.seq <= watermark)) { + if (!keys.includes(change.key)) { + if (keys.length === limit) break; + keys.push(change.key); + } + last = change.seq; + } + return {items: keys.map(k => current(k, watermark)), next_cursor: encodeCursor(last, EPOCH), + has_more: changes.some(c => c.seq > last && c.seq <= watermark), last}; + }; + let start = OLDEST_SEQ - 1; + for (let chain = 1; chain <= 3; chain++) { + const {last, ...body} = page(start, 2, PAGE_WATERMARK); + const query = {...(chain > 1 ? {cursor: encodeCursor(start, EPOCH)} : {}), limit: '2'}; + add(`pages/page-${chain}.json`, body, {kind: 'page', schema: 'ObservationPage', chain, status: 200, + request: {method: 'GET', path: '/api/observations', query}, watermark: PAGE_WATERMARK, next_seq: last, + description: `History page ${chain} of 3 (limit 2, watermark seq ${PAGE_WATERMARK}). Items hold current state; ` + + 'next_cursor is the last change scanned.'}); + start = last; + } + add('pages/head.json', {items: [], next_cursor: encodeCursor(PAGE_WATERMARK, EPOCH), has_more: false}, + {kind: 'page', schema: 'ObservationPage', status: 200, request: {method: 'GET', path: '/api/observations', query: {cursor: 'head'}}, + watermark: PAGE_WATERMARK, next_seq: PAGE_WATERMARK, description: 'cursor=head (D10): no items, cursor at the watermark.'}); + + // Error bodies (§8.2, §11). + const oldestCursor = encodeCursor(OLDEST_SEQ - 1, EPOCH); + const errors = [ + ['errors/cursor-expired.json', 410, {}, errorBody('cursor_expired', 'Cursor is older than retained observation history', + {oldest_cursor: oldestCursor, oldest_observed_at: '2026-11-08T14:03:12.481Z'}), + {method: 'GET', path: '/api/observations', query: {cursor: encodeCursor(412, EPOCH)}}, + 'A cursor before the prune boundary. An epoch mismatch (DB restore) or a seq ahead of head returns the same body, e.g. ' + + `cursor ${encodeCursor(1010, '0f0f0f0f-0f0f-4f0f-8f0f-0f0f0f0f0f0f')}.`], + ['errors/invalid-cursor.json', 400, {}, errorBody('invalid_cursor', 'Malformed cursor'), + {method: 'GET', path: '/api/observations', query: {cursor: 'not-a-cursor!'}}, 'Malformed cursor; restart without one.'], + ['errors/evidence-pending.json', 202, {'Retry-After': '2'}, errorBody('evidence_pending', 'Evidence is not ready yet', + {retry_after_seconds: 2}), {method: 'GET', path: `/api/observations/${IDS.A.id}/snapshot`}, + 'Snapshot of single-pending.json. Retry-After header equals retry_after_seconds.'], + ['errors/evidence-unavailable.json', 404, {}, errorBody('evidence_unavailable', 'Evidence is unavailable', + {reason: 'queue_full'}), {method: 'GET', path: `/api/observations/${IDS.B.id}/snapshot`}, + 'Snapshot of single-unavailable.json.'], + ['errors/evidence-expired.json', 410, {}, errorBody('evidence_expired', 'Evidence media has expired', + {reason: 'retention_expired', expired_at: '2026-11-15T14:03:13.004Z'}), + {method: 'GET', path: `/api/observations/${IDS.A.id}/snapshot`}, 'Snapshot of single-expired.json; metadata stays readable.'], + ['errors/observation-not-found.json', 404, {}, errorBody('observation_not_found', 'Observation not found'), + {method: 'GET', path: '/api/observations/00000000-0000-4000-8000-000000000000'}, + 'Unknown, unauthorized and deleted-camera observations are indistinguishable.'], + ['errors/snapshot-forbidden.json', 403, {}, errorBody('forbidden', 'Forbidden'), + {method: 'GET', path: `/api/observations/${IDS.A.id}/snapshot`}, 'live.view held without snapshot.create (D4).'], + ['errors/not-implemented.json', 501, {}, errorBody('not_implemented', 'Not implemented'), + {method: 'GET', path: '/api/observations'}, 'C2 stub response, until the operation lands (L8/L14).'], + ['errors/service-unavailable.json', 503, {'Retry-After': '5'}, errorBody('service_unavailable', 'Service temporarily unavailable'), + {method: 'GET', path: '/api/observations'}, 'TemporarilyUnreachable outcome; honour Retry-After.'], + ]; + for (const [file, status, headers, body, request, description] of errors) { + add(file, body, {kind: 'error', schema: 'ObservationError', status, code: body.code, headers, request, description}); + } + + // Capability documents (§9). + const operation = (supported, authorized, reason) => ({supported, authorized, ...(reason ? {reason} : {})}); + const capabilities = (p2) => ({ + contract_versions: {observations: 1, classifications: 1, observation_event: EVENT_TYPE}, + source: {id: SOURCE, kind: 'lightnvr', installation_uuid: INSTALLATION, product_version: p2 ? '0.49.0' : '0.48.0'}, + principal: {writer_namespace: WRITER.writer_namespace}, + operations: { + 'observations.history': operation(true, true), + 'observations.snapshot': operation(true, true), + 'observations.annotate': p2 ? operation(true, true) : operation(false, false, 'not_available_in_this_version'), + track_lifecycle: operation(false, false, 'not_implemented'), + recorded_frame: operation(false, false, 'not_implemented'), + event_clip: operation(false, false, 'not_implemented'), + range_clip: operation(false, false, 'not_implemented'), + }, + limits: {history_page_default: 100, history_page_max: 500, history_filter_cameras_max: 10, history_filter_labels_max: 8, + max_objects_per_batch: 20, label_max_bytes: 31, snapshot_retry_after_max_seconds: 30, annotations_per_observation_max: 8}, + history: {retention_days: 30, oldest_cursor: oldestCursor, oldest_observed_at: '2026-11-08T14:03:12.481Z'}, + delivery: {mqtt_enabled: true, topic: `${TOPIC_PREFIX}/v1/events/${EVENT_TYPE}/{camera_uuid}`}, + cameras: [{camera_uuid: CAMERA, evidence_enabled: true, + actions: p2 ? ['live.view', 'snapshot.create', 'observations.annotate'] : ['live.view', 'snapshot.create']}], + }); + add('capabilities/p1.json', capabilities(false), {kind: 'capabilities', schema: 'IntegrationCapabilities', status: 200, + request: {method: 'GET', path: '/api/integrations/capabilities'}, + description: 'P1 installation: annotate not available. writer_namespace is derived from the user (D3), here "yawamf".'}); + add('capabilities/p2.json', capabilities(true), {kind: 'capabilities', schema: 'IntegrationCapabilities', status: 200, + request: {method: 'GET', path: '/api/integrations/capabilities'}, + description: 'P2 installation: annotate supported and authorized on the camera.'}); + + // Classification PUT exchanges (§10.4). observation_file is the state before the request. + const putPath = (id, namespace = 'yawamf', name = 'species') => `/api/observations/${id}/classifications/${namespace}/${name}`; + const exchange = (description, observationFile, body, status, responseBody, path = putPath(IDS.A.id)) => + ({description, observation_file: observationFile, request: {method: 'PUT', path, principal: WRITER, body}, + response: {status, body: responseBody}}); + const conflictingAutomated = {...automatedWrite, model_version: 'convnext-v2-2026.11', confidence: 0.95, + classified_at: '2026-11-08T14:04:02.550Z'}; + const rerunAfterOwner = {...automatedWrite, model_version: 'convnext-v2-2026.11', confidence: 0.95, + classified_at: '2026-11-09T02:00:00.000Z', input_revision: 4, expected_revision: 2}; + const exchanges = [ + ['classifications/01-create-automated.json', exchange('Create an automated annotation: 201, observation revision 2 -> 3.', + obsFile.A2, automatedWrite, 201, {changed: true, observation_revision: 3, annotation: automatedAnnotation})], + ['classifications/02-retry-noop.json', exchange('Identical retry after a lost response: 200 changed=false regardless of ' + + 'expected_revision; nothing written, revision not bumped.', obsFile.A3, automatedWrite, 200, + {changed: false, observation_revision: 3, annotation: automatedAnnotation})], + ['classifications/03-revision-conflict.json', exchange('Different automated result sent with a stale expected_revision ' + + '(0, key exists at 1): 409 revision_conflict with current. Refetch and reconcile.', obsFile.A3, conflictingAutomated, 409, + errorBody('revision_conflict', 'Annotation revision does not match', {current: automatedAnnotation}))], + ['classifications/04-owner-confirm.json', exchange('Owner correction replaces the automated annotation: 200, ' + + 'annotation_revision 1 -> 2, observation revision 3 -> 4.', obsFile.A3, ownerWrite, 200, + {changed: true, observation_revision: 4, annotation: ownerAnnotation})], + ['classifications/05-owner-decision-locked.json', exchange('Automated re-run after an owner decision: 409 ' + + 'owner_decision_locked with current. Drop the automated write.', obsFile.A4, rerunAfterOwner, 409, + errorBody('owner_decision_locked', 'An owner-confirmed annotation cannot be replaced by an automated one', + {current: ownerAnnotation}))], + ['classifications/06-evidence-mismatch-asset.json', exchange('input_asset_id names another observation\'s asset: 422.', + obsFile.A2, {...automatedWrite, input_asset_id: IDS.C0.asset}, 422, + errorBody('evidence_mismatch', 'Annotation input does not match the observation evidence', + {observation_asset_id: IDS.A.asset, evidence_state: 'ready'}))], + ['classifications/07-evidence-mismatch-pending.json', exchange('Evidence still pending (not ready or expired): 422.', + obsFile.A1, {...automatedWrite, input_revision: 1}, 422, + errorBody('evidence_mismatch', 'Annotation input does not match the observation evidence', + {observation_asset_id: IDS.A.asset, evidence_state: 'pending'}))], + ['classifications/08-namespace-forbidden.json', exchange('Key namespace is not the principal\'s derived namespace: 403.', + obsFile.A2, automatedWrite, 403, errorBody('namespace_forbidden', 'Writer namespace not permitted'), + putPath(IDS.A.id, 'frigate-bridge'))], + ]; + for (const [file, value] of exchanges) { + add(file, value, {kind: 'classification_exchange', status: value.response.status, + code: value.response.body.code ?? null, description: value.description}); + } + + // Evidence JPEG and its box manifest. + const media = { + description: 'Unannotated evidence image shared by two-birds-object-0/1. Solid-color boxes on 8-pixel block ' + + 'boundaries; pixel coordinates are exact, colors are within JPEG tolerance (±12 per channel).', + asset_id: IDS.C0.asset, file: JPEG_FILE, media_type: 'image/jpeg', width: JPEG.width, height: JPEG.height, + bytes: jpegBytes.length, sha256: createHash('sha256').update(jpegBytes).digest('hex'), + background_rgb: JPEG.background_rgb, + boxes: JPEG.boxes.map(box => { + const {format: _, ...normalized} = normalizedBox(box.pixels, JPEG.width, JPEG.height); + return {observation_file: obsFile[box.key], observation_id: IDS[box.key].id, rgb: box.rgb, pixels: box.pixels, normalized}; + }), + }; + fixtures.push({file: JPEG_FILE, kind: 'jpeg', manifest: 'media/two-birds-160x80.json', description: media.description}); + add('media/two-birds-160x80.json', media, {kind: 'jpeg_manifest', description: 'Box pixel coordinates for the JPEG.'}); + + // Message-ID vectors (§5.4). + add('message-ids.json', { + rule: 'message_id = UUIDv5(namespace.uuid, source + "|" + observation_id + "|" + revision), revision as a decimal string', + namespace: {name: NS_NAME, parent: `NAMESPACE_URL ${NAMESPACE_URL}`, uuid: MESSAGE_ID_NAMESPACE}, + contract_test_vector: messageId(SOURCE, IDS.A.id, 2), + vectors, + }, {kind: 'message_id_vectors', description: 'Every change-event message ID, including the contract §5.4 test vector.'}); + + const manifest = { + description: 'INT_01 canonical observation contract fixtures (task C3). Generated by ' + + 'scripts/contracts/observation-fixtures.mjs; validated by tests/contracts/tooling/observation-fixtures.test.mjs.', + contract: 'docs/integrations/OBSERVATIONS_CONTRACT.md', + constants: {source: SOURCE, installation_uuid: INSTALLATION, camera_uuid: CAMERA, stream_name: 'feeder-east', + epoch_uuid: EPOCH, oldest_seq: OLDEST_SEQ, topic_prefix: TOPIC_PREFIX, event_type: EVENT_TYPE, + message_id_namespace: MESSAGE_ID_NAMESPACE, writer: WRITER}, + changes: changes.map(c => ({seq: c.seq, observation_id: IDS[c.key].id, revision: c.revision, cause: c.cause, + evidence_state: c.state, evidence_reason: c.reason, created_at: c.at})), + fixtures: fixtures.sort((a, b) => a.file.localeCompare(b.file)), + }; + files.set('manifest.json', json(manifest)); + return files; +} + +// ---- vendoring --------------------------------------------------------------------- +async function listFiles(dir, prefix = '') { + const out = []; + for (const entry of await readdir(dir, {withFileTypes: true})) { + const rel = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) out.push(...await listFiles(path.join(dir, entry.name), rel)); + else out.push(rel); + } + return out.sort(); +} +const sha256 = bytes => createHash('sha256').update(bytes).digest('hex'); +const git = (...args) => execFileSync('git', ['-C', root, ...args], {encoding: 'utf8'}).trim(); + +async function vendor(target, {allowDirty}) { + const dirty = git('status', '--porcelain', '--', fixtureRel, 'scripts/contracts/observation-fixtures.mjs'); + if (dirty && !allowDirty) throw new Error(`Commit the fixtures before vendoring (dirty:\n${dirty})`); + const commit = git('rev-parse', 'HEAD'); + const dest = path.join(target, vendorRel); + await rm(dest, {recursive: true, force: true}); + const files = await listFiles(fixtureDir); + const sums = []; + for (const file of files) { + await mkdir(path.dirname(path.join(dest, file)), {recursive: true}); + await copyFile(path.join(fixtureDir, file), path.join(dest, file)); + sums.push(`${sha256(await readFile(path.join(fixtureDir, file)))} ${file}`); + } + await writeFile(path.join(dest, 'SOURCE.md'), `# lightNVR observation contract fixtures (vendored) + +These files are an identical copy of \`${fixtureRel}/\` from +lightNVR commit \`${commit}\`${dirty ? ' (plus uncommitted changes)' : ''}. +Do not edit them here. The contract is lightNVR +\`docs/integrations/OBSERVATIONS_CONTRACT.md\`; \`manifest.json\` describes every file. + +Re-sync from a lightNVR checkout: + +\`\`\`sh +node scripts/contracts/observation-fixtures.mjs --vendor +node scripts/contracts/observation-fixtures.mjs --check-vendor +\`\`\` + +lightNVR's \`tests/contracts/tooling/observation-fixtures.test.mjs\` also compares +this copy when the YA-WAMF checkout is a sibling directory (or +\`LIGHTNVR_YAWAMF_DIR\` points at it). + +## SHA-256 + +\`\`\`text +${sums.join('\n')} +\`\`\` +`); + return {commit, count: files.length, dest}; +} + +async function checkVendor(target) { + const dest = path.join(target, vendorRel); + const expected = await listFiles(fixtureDir); + const actual = (await listFiles(dest)).filter(f => f !== 'SOURCE.md'); + const problems = []; + for (const file of new Set([...expected, ...actual])) { + if (!expected.includes(file)) problems.push(`extra: ${file}`); + else if (!actual.includes(file)) problems.push(`missing: ${file}`); + else if (!(await readFile(path.join(dest, file))).equals(await readFile(path.join(fixtureDir, file)))) problems.push(`differs: ${file}`); + } + return problems; +} + +// ---- CLI --------------------------------------------------------------------------- +if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + const args = process.argv.slice(2); + const valueOf = flag => args[args.indexOf(flag) + 1]; + if (args.includes('--vendor')) { + const {commit, count, dest} = await vendor(path.resolve(valueOf('--vendor')), {allowDirty: args.includes('--allow-dirty')}); + console.log(`Vendored ${count} files from ${commit} into ${dest}`); + } else if (args.includes('--check-vendor')) { + const problems = await checkVendor(path.resolve(valueOf('--check-vendor'))); + if (problems.length) { console.error(problems.join('\n')); process.exit(1); } + console.log('Vendored observation fixtures match.'); + } else { + const regenerate = args.includes('--jpeg'); + const jpeg = await loadJpeg({regenerate}); + const files = await buildFixtures({jpeg}); + if (args.includes('--check')) { + const stale = []; + for (const [file, content] of files) { + const target = path.join(fixtureDir, file); + if (!existsSync(target) || await readFile(target, 'utf8') !== content) stale.push(file); + } + if (stale.length) { console.error(`Stale observation fixtures:\n${stale.join('\n')}`); process.exit(1); } + console.log(`Observation fixtures up to date (${files.size} JSON files).`); + } else { + await mkdir(path.join(fixtureDir, 'media'), {recursive: true}); + if (regenerate || !existsSync(path.join(fixtureDir, JPEG_FILE))) await writeFile(path.join(fixtureDir, JPEG_FILE), jpeg); + for (const [file, content] of files) { + await mkdir(path.dirname(path.join(fixtureDir, file)), {recursive: true}); + await writeFile(path.join(fixtureDir, file), content); + } + console.log(`Wrote ${files.size} JSON fixtures and ${JPEG_FILE} to ${fixtureRel}/`); + } + } +} diff --git a/src/core/authorization.c b/src/core/authorization.c index f0a46bfdb..d8c9efa52 100644 --- a/src/core/authorization.c +++ b/src/core/authorization.c @@ -62,6 +62,10 @@ static const authorization_action_metadata_t action_catalog[] = { "Export protected plate reads", true, false, true}, {AUTHZ_LPR_DELETE, "lpr.delete", "License plates", "Permanently delete protected plate reads", true, true, true}, + /* INT_01 P2 (migration 0096, bit 19). Enforced by + * PUT /api/observations/{id}/classifications/{namespace}/{name} (L14). */ + {AUTHZ_OBSERVATIONS_ANNOTATE, "observations.annotate", "Integrations", + "Annotate detection observations with classifier results", true, false, true}, }; _Static_assert(sizeof(action_catalog) / sizeof(action_catalog[0]) == diff --git a/src/core/config.c b/src/core/config.c index 4fc13cfae..0a1d5edf5 100644 --- a/src/core/config.c +++ b/src/core/config.c @@ -164,7 +164,7 @@ static const env_config_mapping_t env_config_mappings[] = { {"GO2RTC_WEBRTC_ENABLED", CONFIG_TYPE_BOOL, CONFIG_OFFSET(go2rtc_webrtc_enabled), 0, NULL, 0, true}, {"GO2RTC_STUN_ENABLED", CONFIG_TYPE_BOOL, CONFIG_OFFSET(go2rtc_stun_enabled), 0, NULL, 0, true}, {"GO2RTC_STUN_SERVER", CONFIG_TYPE_STRING, CONFIG_OFFSET(go2rtc_stun_server), 256, "stun.l.google.com:19302", 0, false}, - {"GO2RTC_EXTERNAL_IP", CONFIG_TYPE_STRING, CONFIG_OFFSET(go2rtc_external_ip), 64, "", 0, false}, + {"GO2RTC_EXTERNAL_IP", CONFIG_TYPE_STRING, CONFIG_OFFSET(go2rtc_external_ip), 256, "", 0, false}, {"GO2RTC_ICE_SERVERS", CONFIG_TYPE_STRING, CONFIG_OFFSET(go2rtc_ice_servers), 512, "", 0, false}, {"GO2RTC_WEBRTC_LISTEN_PORT", CONFIG_TYPE_INT, CONFIG_OFFSET(go2rtc_webrtc_listen_port), 0, NULL, 8555, false}, {"WEBRTC_CONNECTION_TIMEOUT_MS", CONFIG_TYPE_INT, CONFIG_OFFSET(webrtc_connection_timeout_ms), 0, NULL, 30000, false}, @@ -557,6 +557,12 @@ void load_default_config(config_t *config) { config->streams[i].tier_important_multiplier = 2.0; config->streams[i].tier_ephemeral_multiplier = 0.25; config->streams[i].storage_priority = 5; + + // INT_01 observation evidence is off by default (contract section 13) + config->streams[i].observation_evidence_enabled = false; + config->streams[i].observation_labels[0] = '\0'; + config->streams[i].observation_max_age_days = OBSERVATION_DEFAULT_MAX_AGE_DAYS; + config->streams[i].observation_max_bytes = OBSERVATION_DEFAULT_MAX_BYTES; } // MQTT settings for detection event streaming diff --git a/src/core/event_envelope.c b/src/core/event_envelope.c index d10c7de88..c58e92234 100644 --- a/src/core/event_envelope.c +++ b/src/core/event_envelope.c @@ -18,6 +18,7 @@ #include #include +#include "database/db_observations.h" #include "utils/uuid.h" static const event_type_definition_t EVENT_TYPES[] = { @@ -32,6 +33,18 @@ static const event_type_definition_t EVENT_TYPES[] = { .subject_kind = EVENT_SUBJECT_CAMERA, .default_expiry_seconds = 3600, }, + { + /* INT_01 (docs/integrations/OBSERVATIONS_CONTRACT.md section 6). */ + .type = "io.lightnvr.detection.observation_changed.v1", + .family = "detection", + .description = "A durable object observation was created or changed", + .severity = EVENT_SEVERITY_INFO, + .sensitivity = EVENT_SENSITIVITY_OPERATIONAL, + .media_policy = EVENT_MEDIA_REFERENCE_ALLOWED, + .expected_rate = EVENT_RATE_HIGH, + .subject_kind = EVENT_SUBJECT_CAMERA, + .default_expiry_seconds = 3600, + }, { .type = "io.lightnvr.recognition.license_plate.v1", .family = "recognition", @@ -364,10 +377,149 @@ static bool system_subject_matches_scope(const char *subject, return false; } +/* RFC 3339 UTC with optional fraction: YYYY-MM-DDTHH:MM:SS[.f{1,9}]Z. */ +static bool valid_rfc3339_utc(const char *value) { + if (!value) return false; + size_t length = strnlen(value, 40); + if (length < 20 || length >= 40) return false; + const char *pattern = "dddd-dd-ddTdd:dd:dd"; + for (size_t index = 0; index < 19; index++) { + if (pattern[index] == 'd') { + if (!isdigit((unsigned char)value[index])) return false; + } else if (value[index] != pattern[index]) { + return false; + } + } + size_t cursor = 19; + if (value[cursor] == '.') { + size_t digits = 0; + cursor++; + while (isdigit((unsigned char)value[cursor]) && digits < 9) { + cursor++; + digits++; + } + if (digits == 0) return false; + } + return value[cursor] == 'Z' && value[cursor + 1] == '\0'; +} + +static bool valid_observation_label(const char *value) { + if (!value) return false; + size_t length = strnlen(value, OBS_LABEL_SIZE); + if (length == 0 || length >= OBS_LABEL_SIZE) return false; + for (const unsigned char *cursor = (const unsigned char *)value; *cursor; + cursor++) { + if (iscntrl(*cursor)) return false; + } + return true; +} + +/* Section 6.3 payload rules for io.lightnvr.detection.observation_changed.v1. */ +static int validate_observation_changed(const event_envelope_t *event, + char *error, size_t error_size) { + const cJSON *data = event->data; + const cJSON *observation_id = required_field( + data, "observation_id", cJSON_String, error, error_size); + const cJSON *revision = required_field(data, "revision", cJSON_Number, + error, error_size); + const cJSON *camera_uuid = required_field(data, "camera_uuid", cJSON_String, + error, error_size); + const cJSON *label = required_field(data, "label", cJSON_String, error, + error_size); + const cJSON *observed_at = required_field(data, "observed_at", cJSON_String, + error, error_size); + const cJSON *cause = required_field(data, "change_cause", cJSON_String, + error, error_size); + const cJSON *state = required_field(data, "evidence_state", cJSON_String, + error, error_size); + const cJSON *asset_id = required_field(data, "asset_id", cJSON_String, + error, error_size); + if (!observation_id || !revision || !camera_uuid || !label || + !observed_at || !cause || !state || !asset_id) { + return -1; + } + const cJSON *reason = + cJSON_GetObjectItemCaseSensitive(data, "evidence_reason"); + if (!reason) { + set_error(error, error_size, + "event data is missing required field 'evidence_reason'"); + return -1; + } + if (!lightnvr_uuid_is_valid(observation_id->valuestring) || + !lightnvr_uuid_is_valid(asset_id->valuestring) || + !lightnvr_uuid_is_valid(camera_uuid->valuestring)) { + set_error(error, error_size, "observation identifiers must be UUIDs"); + return -1; + } + if (strcmp(event->subject + 7, camera_uuid->valuestring) != 0) { + set_error(error, error_size, + "observation camera_uuid must match the event subject"); + return -1; + } + if (revision->valuedouble < 1 || revision->valuedouble > INT_MAX || + revision->valuedouble != (double)revision->valueint) { + set_error(error, error_size, "observation revision must be an integer >= 1"); + return -1; + } + if (!valid_observation_label(label->valuestring)) { + set_error(error, error_size, "observation label must be 1..31 bytes"); + return -1; + } + if (!valid_rfc3339_utc(observed_at->valuestring)) { + set_error(error, error_size, "observation observed_at must be RFC 3339 UTC"); + return -1; + } + if (strcmp(cause->valuestring, "created") != 0 && + strcmp(cause->valuestring, "evidence") != 0 && + strcmp(cause->valuestring, "classification") != 0) { + set_error(error, error_size, "observation change_cause is unknown"); + return -1; + } + observation_asset_state_t evidence_state; + if (observation_asset_state_parse(state->valuestring, &evidence_state) != 0) { + set_error(error, error_size, "observation evidence_state is unknown"); + return -1; + } + if (!cJSON_IsNull(reason) && !cJSON_IsString(reason)) { + set_error(error, error_size, "observation evidence_reason must be a string or null"); + return -1; + } + const char *reason_text = cJSON_IsString(reason) ? reason->valuestring : NULL; + if (reason_text && reason_text[0] == '\0') { + set_error(error, error_size, "observation evidence_reason must not be empty"); + return -1; + } + if (!observation_reason_is_valid(evidence_state, reason_text)) { + set_error(error, error_size, + "observation evidence_reason is inconsistent with evidence_state"); + return -1; + } + const cJSON *snapshot = cJSON_GetObjectItemCaseSensitive(data, "snapshot_url"); + if (evidence_state == OBS_ASSET_READY) { + char expected[96]; + snprintf(expected, sizeof(expected), "/api/observations/%s/snapshot", + observation_id->valuestring); + if (!cJSON_IsString(snapshot) || + strcmp(snapshot->valuestring, expected) != 0) { + set_error(error, error_size, + "ready observation events need their own snapshot_url"); + return -1; + } + } else if (snapshot) { + set_error(error, error_size, + "snapshot_url is only allowed when evidence is ready"); + return -1; + } + return 0; +} + static int validate_type_data(const event_envelope_t *event, char *error, size_t error_size) { const char *type = event->type; const cJSON *data = event->data; + if (strcmp(type, "io.lightnvr.detection.observation_changed.v1") == 0) { + return validate_observation_changed(event, error, error_size); + } if (strcmp(type, "io.lightnvr.detection.object.v1") == 0) { const cJSON *count = required_field(data, "count", cJSON_Number, error, error_size); diff --git a/src/core/event_producers.c b/src/core/event_producers.c index 815a09954..00a2d84ea 100644 --- a/src/core/event_producers.c +++ b/src/core/event_producers.c @@ -88,37 +88,57 @@ static int publish_event(const char *type, const char *subject, error_size); } -static int durably_enqueue_event(const event_envelope_t *event, - char *error, size_t error_size) { +const char *event_producer_enqueue_result_name( + event_producer_enqueue_result_t result) { + switch (result) { + case EVENT_PRODUCER_ENQUEUE_FAILED: return "failed"; + case EVENT_PRODUCER_ENQUEUE_ENQUEUED: return "enqueued"; + case EVENT_PRODUCER_ENQUEUE_DUPLICATE: return "duplicate"; + case EVENT_PRODUCER_ENQUEUE_NO_ROUTE: return "no_route"; + case EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED: return "mqtt_disabled"; + case EVENT_PRODUCER_ENQUEUE_FULL: return "full"; + } + return "unknown"; +} + +event_producer_enqueue_result_t event_producer_enqueue_durable( + const event_envelope_t *event, char *error, size_t error_size) { + if (error && error_size > 0) error[0] = '\0'; + if (!event) { + set_error(error, error_size, "event is required"); + return EVENT_PRODUCER_ENQUEUE_FAILED; + } event_route_delivery_plan_t plan = {0}; event_router_result_t route_result = event_router_evaluate_delivery(event, &plan); if (route_result == EVENT_ROUTER_ERROR) { event_route_delivery_plan_clear(&plan); set_error(error, error_size, "event route evaluation failed"); - return -1; + return EVENT_PRODUCER_ENQUEUE_FAILED; } if (route_result == EVENT_ROUTER_DEFAULT) { if (!g_config.mqtt_enabled) { event_route_delivery_plan_clear(&plan); - return 0; + return EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED; } event_outbox_enqueue_result_t result = mqtt_delivery_worker_enqueue( event, g_config.mqtt_topic_prefix, NULL); event_route_delivery_plan_clear(&plan); - if (result == EVENT_OUTBOX_ENQUEUED || - result == EVENT_OUTBOX_DUPLICATE) { - return 0; - } + if (result == EVENT_OUTBOX_ENQUEUED) return EVENT_PRODUCER_ENQUEUE_ENQUEUED; + if (result == EVENT_OUTBOX_DUPLICATE) return EVENT_PRODUCER_ENQUEUE_DUPLICATE; set_error(error, error_size, "durable event outbox enqueue failed"); - return -1; + return result == EVENT_OUTBOX_FULL ? EVENT_PRODUCER_ENQUEUE_FULL + : EVENT_PRODUCER_ENQUEUE_FAILED; } if (route_result == EVENT_ROUTER_NO_MATCH) { event_route_delivery_plan_clear(&plan); - return 0; + return EVENT_PRODUCER_ENQUEUE_NO_ROUTE; } bool failed = false; + bool any_error = false; + bool any_enqueued = false; + bool any_duplicate = false; bool destination_accepted[EVENT_ROUTE_MAX_COUNT] = {false}; for (size_t index = 0; index < plan.count; index++) { const event_route_delivery_plan_entry_t *entry = &plan.entries[index]; @@ -144,8 +164,11 @@ static int durably_enqueue_event(const event_envelope_t *event, if (result != EVENT_OUTBOX_ENQUEUED && result != EVENT_OUTBOX_DUPLICATE) { failed = true; + if (result != EVENT_OUTBOX_FULL) any_error = true; } else { destination_accepted[index] = true; + if (result == EVENT_OUTBOX_ENQUEUED) any_enqueued = true; + else any_duplicate = true; } } if (!failed) { @@ -164,15 +187,32 @@ static int durably_enqueue_event(const event_envelope_t *event, event_router_record_destination_enqueued( event, &plan, entry->destination_key) != 0) { failed = true; + any_error = true; } } } event_route_delivery_plan_clear(&plan); if (failed) { set_error(error, error_size, "durable event route enqueue failed"); - return -1; + return any_error ? EVENT_PRODUCER_ENQUEUE_FAILED + : EVENT_PRODUCER_ENQUEUE_FULL; } - return 0; + if (any_enqueued) return EVENT_PRODUCER_ENQUEUE_ENQUEUED; + if (any_duplicate) return EVENT_PRODUCER_ENQUEUE_DUPLICATE; + /* Every matched destination was the disabled default broker. */ + return EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED; +} + +/* Legacy 0/-1 contract for the producers in this file: every non-failure + * outcome (including no route and MQTT disabled) is success. */ +static int durably_enqueue_event(const event_envelope_t *event, + char *error, size_t error_size) { + event_producer_enqueue_result_t result = + event_producer_enqueue_durable(event, error, error_size); + return result == EVENT_PRODUCER_ENQUEUE_FAILED || + result == EVENT_PRODUCER_ENQUEUE_FULL + ? -1 + : 0; } static int camera_subject_for_stream( diff --git a/src/core/main.c b/src/core/main.c index 78a895006..cadc44b2c 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -41,6 +41,9 @@ #include "core/mqtt_event_adapter.h" #include "core/mqtt_client.h" #include "core/mqtt_delivery_worker.h" +#include "core/observation_evidence.h" +#include "core/observation_relay.h" +#include "core/observation_retention.h" #include "core/path_utils.h" #include "utils/strings.h" #include "video/stream_manager.h" @@ -861,6 +864,46 @@ int main(int argc, char *argv[]) { (void)storage_target_refresh_health_and_publish(); log_info("Storage manager initialized"); + /* INT_01 startup order: reconcile (L4) -> evidence worker (L3) -> relay (L5) */ + /* Evidence reconciliation needs the DB and event identity, and must finish + * before the evidence worker or relay start (design decision 2). The + * storage manager's periodic eviction stays off until it has completed. */ + { + observation_reconcile_result_t observation_reconcile; + if (observation_retention_startup(config.storage_path, &observation_reconcile) != 0) { + log_warn("Observation evidence reconciliation incomplete; evidence eviction disabled"); + } + } + + /* INT_01 L3: evidence queue/worker. Only records configuration here; the + * worker thread and queue memory start lazily on the first submission, so + * nothing is allocated while every camera has evidence disabled. */ + { + observation_evidence_config_t evidence_config = {0}; + char queue_bytes[32] = {0}; + evidence_config.storage_path = config.storage_path; + if (db_get_system_setting("observation_global_queue_bytes", queue_bytes, + sizeof(queue_bytes)) == 0 && queue_bytes[0]) { + long long value = strtoll(queue_bytes, NULL, 10); + if (value > 0) evidence_config.global_max_bytes = value; + } + if (observation_evidence_init(&evidence_config) != 0) { + log_error("Failed to initialize observation evidence; evidence admission disabled"); + } else { + observation_evidence_set_enabled_probe(observation_evidence_stream_enabled_probe, NULL); + } + } + + /* INT_01 L5: publication relay. Runs the D9 cursor-head check, drains + * publications staged by a previous process (bounded), then starts the + * steady-state thread. It only persists to the durable outbox, so it may + * start before the MQTT delivery worker; it must start after L4 + * reconciliation and the L3 evidence worker, and before detection. */ + if (observation_relay_start(NULL) != 0) { + log_error("Failed to start observation publication relay; " + "staged observation changes stay pending"); + } + // Build the immutable health policy only after database and storage // prerequisites are live. Collector registration is complete before any // evaluator or sampler worker can observe a partial startup state. @@ -1664,6 +1707,15 @@ int main(int argc, char *argv[]) { // hangs, the safety alarm will still fire. cleanup_detection_resources(); + /* INT_01 shutdown (reverse of startup): relay (L5) stops before the + * event bus and identity it uses; unrelayed rows stay pending. */ + observation_relay_stop(); + + /* INT_01 L3: then the evidence worker (detection producers are gone). + * It encodes for a bounded drain window and records the rest as + * unavailable(interrupted); it needs the DB and event identity. */ + observation_evidence_shutdown(OBS_EVIDENCE_DEFAULT_DRAIN_MS); + // Drain accepted events while MQTT is still alive, then detach the // compatibility subscriber before tearing down broker state. log_info("Draining asynchronous event pipeline..."); @@ -1815,6 +1867,10 @@ int main(int argc, char *argv[]) { cleanup_hls_streaming_backend(); cleanup_transcoding_backend(); + /* INT_01 shutdown (reverse of startup): relay (L5) first. */ + observation_relay_stop(); + observation_evidence_shutdown(OBS_EVIDENCE_DEFAULT_DRAIN_MS); /* then L3 */ + event_bus_shutdown(true); mqtt_event_adapter_unregister(); event_identity_shutdown(); diff --git a/src/core/observation_diagnostics.c b/src/core/observation_diagnostics.c new file mode 100644 index 000000000..6b2bf8ef2 --- /dev/null +++ b/src/core/observation_diagnostics.c @@ -0,0 +1,180 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L10 observation pipeline diagnostics. See + * include/core/observation_diagnostics.h. + */ + +#include "core/observation_diagnostics.h" + +#include +#include +#include +#include + +static int64_t wall_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* L4 keeps one row per camera holding ready media; grow until it fits. */ +static void collect_cameras(observation_diagnostics_t *out) { + int capacity = 64; + for (int attempt = 0; attempt < 8; ++attempt) { + observation_retention_camera_status_t *rows = calloc((size_t)capacity, sizeof(*rows)); + if (!rows) return; + int count = observation_retention_camera_status(rows, capacity); + if (count < capacity || attempt == 7) { + if (count <= 0) { + free(rows); + return; + } + out->cameras = rows; + out->camera_count = count; + return; + } + free(rows); + capacity *= 2; + } +} + +int observation_diagnostics_collect(observation_diagnostics_t *out, int64_t now_ms) { + if (!out) return -1; + memset(out, 0, sizeof(*out)); + out->collected_at_ms = now_ms > 0 ? now_ms : wall_ms(); + /* Stats reads only: never starts the evidence worker or allocates its queue. */ + observation_evidence_get_stats(&out->evidence); + observation_relay_get_stats(&out->relay); + out->db_available = db_observation_diagnostics_get(&out->db) == DB_OBS_OK; + collect_cameras(out); + out->budget_exhausted_cameras = observation_retention_budget_exhausted_count(); + return 0; +} + +void observation_diagnostics_free(observation_diagnostics_t *diagnostics) { + if (!diagnostics) return; + free(diagnostics->cameras); + diagnostics->cameras = NULL; + diagnostics->camera_count = 0; +} + +double observation_diagnostics_relay_lag_seconds(const observation_diagnostics_t *diagnostics) { + if (!diagnostics || !diagnostics->db_available) return 0.0; + int64_t oldest = diagnostics->db.oldest_pending_created_at_ms; + if (oldest <= 0 || diagnostics->collected_at_ms <= oldest) return 0.0; + return (double)(diagnostics->collected_at_ms - oldest) / 1000.0; +} + +int observation_snapshot_retry_after_seconds(int64_t asset_age_ms, uint64_t items_ahead) { + if (asset_age_ms < 5000) return 1; + if (items_ahead >= OBS_SNAPSHOT_RETRY_AFTER_MAX / 2) return OBS_SNAPSHOT_RETRY_AFTER_MAX; + int seconds = (int)(2 * items_ahead); + return seconds < 1 ? 1 : seconds; +} + +/* ---- JSON --------------------------------------------------------------- */ + +static void add_u64(cJSON *object, const char *name, uint64_t value) { + cJSON_AddNumberToObject(object, name, (double)value); +} + +static cJSON *time_or_null(bool present, int64_t ms) { + if (!present || ms <= 0) return cJSON_CreateNull(); + time_t seconds = (time_t)(ms / 1000); + struct tm tm; + if (!gmtime_r(&seconds, &tm)) return cJSON_CreateNull(); + char text[64]; + snprintf(text, sizeof(text), "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", tm.tm_year + 1900, + tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)(ms % 1000)); + return cJSON_CreateString(text); +} + +cJSON *observation_diagnostics_to_json(const observation_diagnostics_t *d) { + if (!d) return NULL; + const observation_evidence_stats_t *e = &d->evidence; + cJSON *root = cJSON_CreateObject(); + if (!root) return NULL; + + /* "active": anything has happened or is allocated in this process run, or + * observations are retained. False = the feature is effectively off. */ + bool active = e->allocated || e->accepted || e->dropped_queue_full || e->dropped_budget || + e->dropped_untracked || e->unavailable_submitted || + (d->db_available && d->db.observations > 0); + cJSON_AddBoolToObject(root, "active", active); + add_u64(root, "accepted", e->accepted); + + cJSON *dropped = cJSON_AddObjectToObject(root, "dropped"); + if (dropped) { + add_u64(dropped, "queue_full", e->dropped_queue_full); + add_u64(dropped, "budget_exhausted", e->dropped_budget); + add_u64(dropped, "disabled", e->dropped_disabled); + add_u64(dropped, "untracked", e->dropped_untracked); + } + + cJSON *evidence = cJSON_AddObjectToObject(root, "evidence"); + if (evidence) { + cJSON_AddBoolToObject(evidence, "queue_allocated", e->allocated); + cJSON_AddBoolToObject(evidence, "worker_running", e->worker_running); + add_u64(evidence, "backlog_items", e->backlog_items); + add_u64(evidence, "backlog_bytes", e->backlog_bytes); + add_u64(evidence, "high_water_items", e->high_water_items); + add_u64(evidence, "high_water_bytes", e->high_water_bytes); + add_u64(evidence, "drop_ring_depth", e->drop_ring_depth); + add_u64(evidence, "ready", e->ready); + add_u64(evidence, "unavailable_submitted", e->unavailable_submitted); + add_u64(evidence, "bytes_written", e->bytes_written); + cJSON *failures = cJSON_AddObjectToObject(evidence, "failures"); + if (failures) { + add_u64(failures, "encode_failed", e->failed_encode); + add_u64(failures, "write_failed", e->failed_write); + add_u64(failures, "db_error", e->failed_db); + add_u64(failures, "disabled", e->disabled_at_encode); + add_u64(failures, "interrupted", e->interrupted); + } + } + + cJSON *history = cJSON_AddObjectToObject(root, "history"); + if (history) { + cJSON_AddBoolToObject(history, "available", d->db_available); + cJSON_AddNumberToObject(history, "observations", (double)d->db.observations); + cJSON_AddNumberToObject(history, "oldest_seq", (double)d->db.meta.oldest_seq); + cJSON_AddNumberToObject(history, "head_seq", (double)d->db.meta.head_seq); + cJSON_AddItemToObject(history, "oldest_observed_at", + time_or_null(d->db_available && d->db.has_oldest_observed_at, + d->db.oldest_observed_at_ms)); + add_u64(history, "epoch_regenerations", d->relay.epoch_regenerations); + } + + cJSON *publications = cJSON_AddObjectToObject(root, "publications"); + if (publications) { + cJSON_AddNumberToObject(publications, "pending", (double)d->db.publications_pending); + cJSON_AddNumberToObject(publications, "failed", (double)d->db.publications_failed); + cJSON_AddNumberToObject(publications, "expired", (double)d->db.publications_expired); + cJSON_AddNumberToObject(publications, "relay_lag_seconds", + observation_diagnostics_relay_lag_seconds(d)); + cJSON_AddBoolToObject(publications, "relay_running", d->relay.running); + add_u64(publications, "relayed", d->relay.relayed); + add_u64(publications, "relayed_no_route", d->relay.relayed_no_route); + } + + cJSON_AddNumberToObject(root, "budget_exhausted_cameras", d->budget_exhausted_cameras); + cJSON *cameras = cJSON_AddArrayToObject(root, "cameras"); + for (int i = 0; cameras && i < d->camera_count; ++i) { + const observation_retention_camera_status_t *c = &d->cameras[i]; + cJSON *item = cJSON_CreateObject(); + if (!item) break; + cJSON_AddStringToObject(item, "camera_uuid", c->camera_uuid); + cJSON_AddNumberToObject(item, "retained_bytes", (double)c->retained_bytes); + cJSON_AddNumberToObject(item, "max_bytes", (double)c->max_bytes); + cJSON_AddNumberToObject(item, "max_age_days", c->max_age_days); + cJSON_AddBoolToObject(item, "budget_exhausted", c->budget_exhausted); + cJSON_AddItemToArray(cameras, item); + } + return root; +} diff --git a/src/core/observation_evidence.c b/src/core/observation_evidence.c new file mode 100644 index 000000000..b1a619990 --- /dev/null +++ b/src/core/observation_evidence.c @@ -0,0 +1,1108 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L3: evidence frame queue and encoder worker. See + * include/core/observation_evidence.h for the producer contract. + * + * Admission is lock-free with respect to the worker: + * - reservations (items/bytes, global and per camera) are atomic counters; + * - accepted items go onto a Treiber stack (CAS push, the single consumer + * takes the whole list with one exchange, so there is no ABA); + * - drop records go onto a bounded Vyukov MPSC ring preallocated at start; + * - the worker is woken with sem_post(), which never blocks. + * The only mutexes a producer can touch are the slot-registration lock (first + * submission of a camera) and the lazy-start lock (trylock); the worker never + * holds either. + */ + +#define _GNU_SOURCE + +#include "core/observation_evidence.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/config.h" +#include "core/logger.h" +#include "core/shutdown_coordinator.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" +#include "video/ffmpeg_utils.h" + +#define EVIDENCE_MAX_CAMERAS MAX_STREAMS +#define EVIDENCE_WAIT_MS 200 + +enum { LC_OFF = 0, LC_READY, LC_RUNNING, LC_STOPPING }; + +typedef struct evidence_item { + struct evidence_item *next; + bool is_jpeg; + int slot; + size_t charge; + uint8_t *data; + size_t data_size; + int width, height; + observation_batch_input_t batch; + observation_asset_input_t asset; +} evidence_item_t; + +typedef struct { + observation_batch_input_t batch; + char asset_uuid[OBS_UUID_SIZE]; + char reason[OBS_REASON_SIZE]; + uint8_t *discard; /* rejected frame the worker frees (may be NULL) */ + size_t discard_charge; /* bytes reserved against g.discard_bytes */ +} drop_record_t; + +typedef struct { + atomic_size_t seq; + drop_record_t record; +} drop_cell_t; + +typedef struct { + drop_cell_t cells[OBS_EVIDENCE_DROP_RING_SIZE]; + atomic_size_t enqueue_pos; + size_t dequeue_pos; /* consumer only */ +} drop_ring_t; + +_Static_assert((OBS_EVIDENCE_DROP_RING_SIZE & (OBS_EVIDENCE_DROP_RING_SIZE - 1)) == 0, + "drop ring size must be a power of two"); + +typedef struct { + char camera_uuid[OBS_UUID_SIZE]; /* immutable once published */ + atomic_int items; + atomic_llong bytes; + atomic_int budget_exhausted; /* 0/1: atomic_bool breaks the C->Rust conversion */ +} camera_slot_t; + +/* Per-camera slots are static (no allocation while disabled) and are never + * recycled: a slot index is valid forever once published via g_slot_count. */ +static camera_slot_t g_slots[EVIDENCE_MAX_CAMERAS]; +static atomic_int g_slot_count; +static pthread_mutex_t g_slot_lock = PTHREAD_MUTEX_INITIALIZER; + +static pthread_mutex_t g_hook_lock = PTHREAD_MUTEX_INITIALIZER; +static observation_evidence_stage_fn g_stage_hook; +static void *g_stage_ctx; +static observation_evidence_enabled_fn g_enabled_probe; +static void *g_enabled_ctx; + +static struct { + atomic_int lifecycle; + atomic_int inflight; + char storage_path[MAX_PATH_LENGTH]; + int64_t global_max_bytes; + int global_max_items; + int per_camera_max_items; + int64_t per_camera_max_bytes; + + pthread_mutex_t start_lock; + pthread_t thread; + atomic_int thread_started; + atomic_int worker_running; + int component_id; + + _Atomic(evidence_item_t *) head; + _Atomic(drop_ring_t *) ring; + atomic_int drop_pending; + sem_t wake; + atomic_int stop; + _Atomic int64_t drain_deadline_ns; + + atomic_int items; + atomic_llong bytes; + atomic_uint high_water_items; + atomic_ullong high_water_bytes; + + atomic_uint camera_high_water_items; + int64_t discard_max_bytes; + atomic_llong discard_bytes; + atomic_ullong discard_deferred, discard_inline; + atomic_ullong submit_latency_count, submit_latency_sum_ns, submit_latency_max_ns; + atomic_ullong submit_latency_bucket[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS]; + + atomic_ullong accepted, dropped_queue_full, dropped_budget, dropped_disabled, + dropped_untracked, unavailable_submitted, ready, failed_encode, failed_write, + failed_db, disabled_at_encode, interrupted, bytes_written; + + /* Worker-private encoder: keeps the worker independent of the global + * jpeg_encoder_get_cached() table, whose eviction could destroy an + * encoder another thread is using. */ + jpeg_encoder_cache_t *encoder; + int encoder_w, encoder_h; +} g = {.start_lock = PTHREAD_MUTEX_INITIALIZER, .component_id = -1}; + +/* ---- small helpers ---------------------------------------------------- */ + +static int64_t mono_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static void bump_max_uint(atomic_uint *target, unsigned value) { + unsigned current = atomic_load(target); + while (value > current && !atomic_compare_exchange_weak(target, ¤t, value)) {} +} + +static void bump_max_ull(atomic_ullong *target, unsigned long long value) { + unsigned long long current = atomic_load(target); + while (value > current && !atomic_compare_exchange_weak(target, ¤t, value)) {} +} + +const uint64_t observation_evidence_submit_latency_bounds_ns[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1] = { + 1000ULL, 5000ULL, 10000ULL, 50000ULL, 100000ULL, 250000ULL, 500000ULL, 1000000ULL, 10000000ULL, +}; + +/* Lock-free: three relaxed atomic adds and one CAS-max per submit. */ +static void record_submit_latency(int64_t start_ns) { + int64_t elapsed = mono_ns() - start_ns; + unsigned long long ns = elapsed > 0 ? (unsigned long long)elapsed : 0ULL; + int bucket = OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1; + for (int i = 0; i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1; ++i) { + if (ns <= observation_evidence_submit_latency_bounds_ns[i]) { + bucket = i; + break; + } + } + atomic_fetch_add_explicit(&g.submit_latency_bucket[bucket], 1, memory_order_relaxed); + atomic_fetch_add_explicit(&g.submit_latency_sum_ns, ns, memory_order_relaxed); + atomic_fetch_add_explicit(&g.submit_latency_count, 1, memory_order_relaxed); + bump_max_ull(&g.submit_latency_max_ns, ns); +} + +static int stage_hook(observation_evidence_stage_t stage, const char *path) { + pthread_mutex_lock(&g_hook_lock); + observation_evidence_stage_fn fn = g_stage_hook; + void *ctx = g_stage_ctx; + pthread_mutex_unlock(&g_hook_lock); + return fn ? fn(stage, path, ctx) : 0; +} + +static bool camera_enabled(const char *camera_uuid) { + pthread_mutex_lock(&g_hook_lock); + observation_evidence_enabled_fn fn = g_enabled_probe; + void *ctx = g_enabled_ctx; + pthread_mutex_unlock(&g_hook_lock); + return fn ? fn(camera_uuid, ctx) : true; +} + +void observation_evidence_set_stage_hook(observation_evidence_stage_fn fn, void *ctx) { + pthread_mutex_lock(&g_hook_lock); + g_stage_hook = fn; + g_stage_ctx = ctx; + pthread_mutex_unlock(&g_hook_lock); +} + +void observation_evidence_set_enabled_probe(observation_evidence_enabled_fn fn, void *ctx) { + pthread_mutex_lock(&g_hook_lock); + g_enabled_probe = fn; + g_enabled_ctx = ctx; + pthread_mutex_unlock(&g_hook_lock); +} + +bool observation_evidence_stream_enabled_probe(const char *camera_uuid, void *ctx) { + (void)ctx; + stream_config_t *stream = calloc(1, sizeof(*stream)); + if (!stream) return true; /* cannot tell: keep the evidence */ + bool enabled = get_stream_config_by_uuid(camera_uuid, stream) == 0 && + stream->observation_evidence_enabled; + free(stream); + return enabled; +} + +/* ---- camera slots ------------------------------------------------------ */ + +static int slot_find(const char *camera_uuid) { + int count = atomic_load_explicit(&g_slot_count, memory_order_acquire); + for (int i = 0; i < count; ++i) + if (strcmp(g_slots[i].camera_uuid, camera_uuid) == 0) return i; + return -1; +} + +/* Returns the slot, creating it when absent; -1 when the table is full (the + * camera is then bounded by the global limits only). */ +static int slot_get(const char *camera_uuid) { + int slot = slot_find(camera_uuid); + if (slot >= 0) return slot; + pthread_mutex_lock(&g_slot_lock); + slot = slot_find(camera_uuid); + if (slot < 0) { + int count = atomic_load_explicit(&g_slot_count, memory_order_relaxed); + if (count < EVIDENCE_MAX_CAMERAS) { + camera_slot_t *s = &g_slots[count]; + safe_strcpy(s->camera_uuid, camera_uuid, sizeof(s->camera_uuid), 0); + atomic_store(&s->items, 0); + atomic_store(&s->bytes, 0); + atomic_store(&s->budget_exhausted, 0); + atomic_store_explicit(&g_slot_count, count + 1, memory_order_release); + slot = count; + } + } + pthread_mutex_unlock(&g_slot_lock); + return slot; +} + +void observation_evidence_set_budget_exhausted(const char *camera_uuid, bool exhausted) { + if (!lightnvr_uuid_is_valid(camera_uuid)) return; + int slot = exhausted ? slot_get(camera_uuid) : slot_find(camera_uuid); + if (slot < 0) { + if (exhausted) log_warn("Evidence: camera table full; budget flag for %s not stored", + camera_uuid); + return; + } + bool previous = atomic_exchange(&g_slots[slot].budget_exhausted, exhausted ? 1 : 0) != 0; + if (previous != exhausted) + log_info("Evidence: budget_exhausted %s for camera %s", exhausted ? "set" : "cleared", + camera_uuid); +} + +bool observation_evidence_is_budget_exhausted(const char *camera_uuid) { + if (!camera_uuid) return false; + int slot = slot_find(camera_uuid); + return slot >= 0 && atomic_load(&g_slots[slot].budget_exhausted); +} + +/* ---- formatting --------------------------------------------------------- */ + +static bool format_number(double value, char *out, size_t size) { + if (!isfinite(value)) return false; + int n = snprintf(out, size, "%.6g", value); + return n > 0 && (size_t)n < size; +} + +int observation_evidence_transform_json(const observation_evidence_transform_t *transform, + int width, int height, char *out, size_t out_size) { + if (!out || out_size == 0) return -1; + out[0] = '\0'; + observation_evidence_transform_t t; + if (transform) { + t = *transform; + } else { + if (width <= 0 || height <= 0) return -1; + memset(&t, 0, sizeof(t)); + t.mode = OBS_TRANSFORM_NONE; + t.source_width = t.inference_width = width; + t.source_height = t.inference_height = height; + t.scale_x = t.scale_y = 1.0; + } + static const char *const modes[] = {"none", "resize", "letterbox"}; + if ((int)t.mode < 0 || (int)t.mode > OBS_TRANSFORM_LETTERBOX) return -1; + if (t.source_width <= 0 || t.source_height <= 0 || t.inference_width <= 0 || + t.inference_height <= 0 || !(t.scale_x > 0) || !(t.scale_y > 0) || + !(t.pad_x >= 0) || !(t.pad_y >= 0)) + return -1; + char sx[32], sy[32], px[32], py[32]; + if (!format_number(t.scale_x, sx, sizeof(sx)) || !format_number(t.scale_y, sy, sizeof(sy)) || + !format_number(t.pad_x, px, sizeof(px)) || !format_number(t.pad_y, py, sizeof(py))) + return -1; + int n = snprintf(out, out_size, + "{\"mode\":\"%s\",\"source_width\":%d,\"source_height\":%d," + "\"inference_width\":%d,\"inference_height\":%d," + "\"scale_x\":%s,\"scale_y\":%s,\"pad_x\":%s,\"pad_y\":%s}", + modes[t.mode], t.source_width, t.source_height, t.inference_width, + t.inference_height, sx, sy, px, py); + if (n < 0 || (size_t)n >= out_size) { + out[0] = '\0'; + return -1; + } + return 0; +} + +int observation_evidence_relative_path(const char *camera_uuid, const char *asset_uuid, + int64_t observed_at_ms, char *out, size_t out_size) { + if (!out || out_size == 0) return -1; + out[0] = '\0'; + /* UUID validation also guarantees no path separators or dot segments. */ + if (!lightnvr_uuid_is_valid(camera_uuid) || !lightnvr_uuid_is_valid(asset_uuid) || + observed_at_ms < 0) + return -1; + time_t seconds = (time_t)(observed_at_ms / 1000); + struct tm tm; + if (!gmtime_r(&seconds, &tm)) return -1; + int n = snprintf(out, out_size, "%s/%s/%04d-%02d-%02d/%s.jpg", OBS_EVIDENCE_DIR, + camera_uuid, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, asset_uuid); + if (n < 0 || (size_t)n >= out_size) { + out[0] = '\0'; + return -1; + } + return 0; +} + +/* ---- drop ring (Vyukov bounded MPSC) ------------------------------------ */ + +static drop_ring_t *ring_create(void) { + drop_ring_t *ring = calloc(1, sizeof(*ring)); + if (!ring) return NULL; + for (size_t i = 0; i < OBS_EVIDENCE_DROP_RING_SIZE; ++i) + atomic_store_explicit(&ring->cells[i].seq, i, memory_order_relaxed); + atomic_store(&ring->enqueue_pos, 0); + return ring; +} + +static bool ring_push(drop_ring_t *ring, const observation_batch_input_t *batch, + const char *asset_uuid, const char *reason, uint8_t *discard, + size_t discard_charge) { + if (!ring) return false; + size_t pos = atomic_load_explicit(&ring->enqueue_pos, memory_order_relaxed); + drop_cell_t *cell; + for (;;) { + cell = &ring->cells[pos & (OBS_EVIDENCE_DROP_RING_SIZE - 1)]; + size_t seq = atomic_load_explicit(&cell->seq, memory_order_acquire); + intptr_t diff = (intptr_t)seq - (intptr_t)pos; + if (diff == 0) { + if (atomic_compare_exchange_weak_explicit(&ring->enqueue_pos, &pos, pos + 1, + memory_order_relaxed, + memory_order_relaxed)) + break; + } else if (diff < 0) { + return false; /* full */ + } else { + pos = atomic_load_explicit(&ring->enqueue_pos, memory_order_relaxed); + } + } + cell->record.batch = *batch; + safe_strcpy(cell->record.asset_uuid, asset_uuid, sizeof(cell->record.asset_uuid), 0); + safe_strcpy(cell->record.reason, reason, sizeof(cell->record.reason), 0); + cell->record.discard = discard; + cell->record.discard_charge = discard_charge; + atomic_fetch_add(&g.drop_pending, 1); + atomic_store_explicit(&cell->seq, pos + 1, memory_order_release); + return true; +} + +static bool ring_pop(drop_ring_t *ring, drop_record_t *out) { + if (!ring) return false; + drop_cell_t *cell = &ring->cells[ring->dequeue_pos & (OBS_EVIDENCE_DROP_RING_SIZE - 1)]; + size_t seq = atomic_load_explicit(&cell->seq, memory_order_acquire); + if ((intptr_t)seq - (intptr_t)(ring->dequeue_pos + 1) < 0) return false; + *out = cell->record; + atomic_store_explicit(&cell->seq, ring->dequeue_pos + OBS_EVIDENCE_DROP_RING_SIZE, + memory_order_release); + ring->dequeue_pos++; + return true; +} + +/* ---- worker ------------------------------------------------------------- */ + +static void release_item(evidence_item_t *item) { + if (item->slot >= 0) { + atomic_fetch_sub(&g_slots[item->slot].items, 1); + atomic_fetch_sub(&g_slots[item->slot].bytes, (long long)item->charge); + } + atomic_fetch_sub(&g.items, 1); + atomic_fetch_sub(&g.bytes, (long long)item->charge); + free(item->data); + free(item); +} + +static void insert_unavailable(const observation_batch_input_t *batch, const char *asset_uuid, + const char *reason) { + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + safe_strcpy(asset.asset_uuid, asset_uuid, sizeof(asset.asset_uuid), 0); + asset.state = OBS_ASSET_UNAVAILABLE; + safe_strcpy(asset.reason, reason, sizeof(asset.reason), 0); + observation_insert_result_t result; + int rc = db_observation_insert_batch(batch, &asset, &result); + if (rc != DB_OBS_OK && rc != DB_OBS_DUPLICATE) { + atomic_fetch_add(&g.failed_db, 1); + log_warn("Evidence: recording batch %s as unavailable(%s) failed: %d", batch->batch_id, + reason, rc); + } +} + +static void process_drops(void) { + drop_ring_t *ring = atomic_load(&g.ring); + drop_record_t *record = NULL; + while (ring) { + if (!record) { + record = malloc(sizeof(*record)); + if (!record) return; + } + if (!ring_pop(ring, record)) break; + if (record->discard) { + free(record->discard); + atomic_fetch_sub(&g.discard_bytes, (long long)record->discard_charge); + } + insert_unavailable(&record->batch, record->asset_uuid, record->reason); + atomic_fetch_sub(&g.drop_pending, 1); + } + free(record); +} + +static void transition_failed(evidence_item_t *item, const char *reason) { + observation_transition_result_t result; + int rc = db_observation_asset_transition(item->asset.asset_uuid, OBS_ASSET_PENDING, + OBS_ASSET_UNAVAILABLE, reason, NULL, &result); + if (rc != DB_OBS_OK && rc != DB_OBS_CAS_LOST) { + atomic_fetch_add(&g.failed_db, 1); + log_warn("Evidence: asset %s pending -> unavailable(%s) failed: %d", + item->asset.asset_uuid, reason, rc); + } +} + +static int mkdir_parents(const char *path) { + char buf[MAX_PATH_LENGTH]; + if (safe_strcpy(buf, path, sizeof(buf), 0) != 0) return -1; + for (char *p = buf + 1; *p; ++p) { + if (*p != '/') continue; + *p = '\0'; + if (mkdir(buf, 0750) != 0 && errno != EEXIST) return -1; + *p = '/'; + } + if (mkdir(buf, 0750) != 0 && errno != EEXIST) return -1; + return 0; +} + +static int fsync_dir(const char *dir) { + int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fd < 0) return -1; + int rc = fsync(fd); + close(fd); + return rc; +} + +/* Write data to final_path atomically: .tmp, fsync, rename, fsync dir. + * On failure nothing is left under either name. */ +static int write_atomic(const char *dir, const char *final_path, const uint8_t *data, + size_t size) { + char tmp_path[MAX_PATH_LENGTH + 8]; + int n = snprintf(tmp_path, sizeof(tmp_path), "%s.tmp", final_path); + if (n < 0 || (size_t)n >= sizeof(tmp_path)) return -1; + if (mkdir_parents(dir) != 0) { + log_warn("Evidence: cannot create %s: %s", dir, strerror(errno)); + return -1; + } + int fd = open(tmp_path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC | O_NOFOLLOW, 0640); + if (fd < 0) { + log_warn("Evidence: cannot create %s: %s", tmp_path, strerror(errno)); + return -1; + } + size_t written = 0; + while (written < size) { + ssize_t w = write(fd, data + written, size - written); + if (w < 0 && errno == EINTR) continue; + if (w <= 0) break; + written += (size_t)w; + } + bool ok = written == size && fsync(fd) == 0; + if (close(fd) != 0) ok = false; + if (ok && stage_hook(OBS_EVIDENCE_STAGE_BEFORE_RENAME, final_path) != 0) ok = false; + if (!ok) { + unlink(tmp_path); + return -1; + } + if (rename(tmp_path, final_path) != 0) { + log_warn("Evidence: rename %s failed: %s", tmp_path, strerror(errno)); + unlink(tmp_path); + return -1; + } + if (fsync_dir(dir) != 0) { + log_warn("Evidence: fsync of %s failed: %s", dir, strerror(errno)); + unlink(final_path); + return -1; + } + return 0; +} + +static int encode_rgb(evidence_item_t *item, unsigned char **jpeg, size_t *jpeg_size) { + if (!g.encoder || g.encoder_w != item->width || g.encoder_h != item->height) { + if (g.encoder) jpeg_encoder_cache_destroy(g.encoder); + g.encoder = jpeg_encoder_cache_create(item->width, item->height, 3, + OBS_EVIDENCE_JPEG_QUALITY); + g.encoder_w = g.encoder ? item->width : 0; + g.encoder_h = g.encoder ? item->height : 0; + if (!g.encoder) return -1; + } + return jpeg_encoder_cache_encode_to_memory(g.encoder, item->data, jpeg, jpeg_size); +} + +static void clear_media_fields(observation_asset_input_t *asset) { + asset->origin[0] = '\0'; + asset->media_type[0] = '\0'; + asset->transform_json[0] = '\0'; +} + +static void abandon_item(evidence_item_t *item) { + clear_media_fields(&item->asset); + insert_unavailable(&item->batch, item->asset.asset_uuid, "interrupted"); + atomic_fetch_add(&g.interrupted, 1); +} + +static void process_item(evidence_item_t *item) { + const char *camera = item->batch.camera_uuid; + if (!camera_enabled(camera)) { + clear_media_fields(&item->asset); + insert_unavailable(&item->batch, item->asset.asset_uuid, "disabled"); + atomic_fetch_add(&g.disabled_at_encode, 1); + return; + } + + observation_insert_result_t inserted; + int rc = db_observation_insert_batch(&item->batch, &item->asset, &inserted); + if (rc == DB_OBS_DUPLICATE) { + log_warn("Evidence: batch %s already recorded; dropping its frame", item->batch.batch_id); + return; + } + if (rc != DB_OBS_OK) { + atomic_fetch_add(&g.failed_db, 1); + log_warn("Evidence: inserting batch %s failed: %d", item->batch.batch_id, rc); + return; + } + (void)stage_hook(OBS_EVIDENCE_STAGE_AFTER_INSERT, NULL); + + char relative[MAX_PATH_LENGTH]; + char final_path[MAX_PATH_LENGTH]; + char dir[MAX_PATH_LENGTH]; + if (observation_evidence_relative_path(camera, item->asset.asset_uuid, + item->batch.observed_at_ms, relative, + sizeof(relative)) != 0 || + snprintf(final_path, sizeof(final_path), "%s/%s", g.storage_path, relative) >= + (int)sizeof(final_path)) { + atomic_fetch_add(&g.failed_write, 1); + transition_failed(item, "write_failed"); + return; + } + safe_strcpy(dir, final_path, sizeof(dir), 0); + char *slash = strrchr(dir, '/'); + if (slash) *slash = '\0'; + + unsigned char *jpeg = NULL; + size_t jpeg_size = 0; + if (item->is_jpeg) { + jpeg = item->data; + jpeg_size = item->data_size; + } else if (encode_rgb(item, &jpeg, &jpeg_size) != 0 || !jpeg || jpeg_size == 0) { + free(jpeg); + atomic_fetch_add(&g.failed_encode, 1); + transition_failed(item, "encode_failed"); + return; + } else { + /* The RGB frame is no longer needed; release its memory early. */ + free(item->data); + item->data = NULL; + } + + int write_rc = write_atomic(dir, final_path, jpeg, jpeg_size); + if (!item->is_jpeg) free(jpeg); + if (write_rc != 0) { + atomic_fetch_add(&g.failed_write, 1); + transition_failed(item, "write_failed"); + return; + } + + observation_asset_media_t media = { + .path = relative, .bytes = (int64_t)jpeg_size, + .width = item->width, .height = item->height, .expires_at_ms = 0, + }; + observation_transition_result_t result; + (void)stage_hook(OBS_EVIDENCE_STAGE_BEFORE_COMMIT, final_path); + rc = db_observation_asset_transition(item->asset.asset_uuid, OBS_ASSET_PENDING, + OBS_ASSET_READY, NULL, &media, &result); + if (rc == DB_OBS_ERROR) + rc = db_observation_asset_transition(item->asset.asset_uuid, OBS_ASSET_PENDING, + OBS_ASSET_READY, NULL, &media, &result); + if (rc != DB_OBS_OK) { + /* CAS lost (someone else resolved the asset) or a DB failure: never + * leave a final file behind a non-ready asset. A still-pending asset + * without a file becomes unavailable(interrupted) at L4 startup. */ + unlink(final_path); + fsync_dir(dir); + if (rc != DB_OBS_CAS_LOST) { + atomic_fetch_add(&g.failed_db, 1); + transition_failed(item, "write_failed"); + } + log_warn("Evidence: asset %s pending -> ready failed: %d", item->asset.asset_uuid, rc); + return; + } + atomic_fetch_add(&g.ready, 1); + atomic_fetch_add(&g.bytes_written, (unsigned long long)jpeg_size); +} + +static evidence_item_t *take_all_fifo(void) { + evidence_item_t *list = atomic_exchange(&g.head, NULL); + evidence_item_t *fifo = NULL; + while (list) { + evidence_item_t *next = list->next; + list->next = fifo; + fifo = list; + list = next; + } + return fifo; +} + +static bool drain_expired(void) { + return atomic_load(&g.stop) && mono_ns() >= atomic_load(&g.drain_deadline_ns); +} + +static void run_items(evidence_item_t *list) { + while (list) { + evidence_item_t *item = list; + list = list->next; + stage_hook(OBS_EVIDENCE_STAGE_ITEM_BEGIN, NULL); + if (drain_expired()) abandon_item(item); + else process_item(item); + stage_hook(OBS_EVIDENCE_STAGE_ITEM_DONE, NULL); + release_item(item); + process_drops(); + } +} + +static void *evidence_worker(void *arg) { + (void)arg; + g.component_id = register_component("observation_evidence", COMPONENT_OTHER, NULL, 20); + atomic_store(&g.worker_running, 1); + log_info("Observation evidence worker started"); + for (;;) { + process_drops(); + evidence_item_t *list = take_all_fifo(); + if (list) { + run_items(list); + continue; + } + if (atomic_load(&g.stop)) break; + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_nsec += (long)EVIDENCE_WAIT_MS * 1000000L; + if (deadline.tv_nsec >= 1000000000L) { + deadline.tv_sec += 1; + deadline.tv_nsec -= 1000000000L; + } + while (sem_timedwait(&g.wake, &deadline) != 0 && errno == EINTR) {} + } + if (g.encoder) { + jpeg_encoder_cache_destroy(g.encoder); + g.encoder = NULL; + g.encoder_w = g.encoder_h = 0; + } + atomic_store(&g.worker_running, 0); + if (g.component_id >= 0) update_component_state(g.component_id, COMPONENT_STOPPED); + log_info("Observation evidence worker stopped"); + return NULL; +} + +/* Lazy start on the first submission. trylock: a producer that loses the + * race simply queues; the item is picked up once the winner starts. */ +static void ensure_started(void) { + if (atomic_load(&g.lifecycle) == LC_RUNNING) return; + if (pthread_mutex_trylock(&g.start_lock) != 0) return; + if (atomic_load(&g.lifecycle) == LC_READY && !atomic_load(&g.thread_started)) { + drop_ring_t *ring = ring_create(); + if (ring) { + atomic_store(&g.ring, ring); + atomic_store(&g.stop, 0); + if (pthread_create(&g.thread, NULL, evidence_worker, NULL) == 0) { + atomic_store(&g.thread_started, 1); + int expected = LC_READY; + atomic_compare_exchange_strong(&g.lifecycle, &expected, LC_RUNNING); + } else { + log_error("Evidence: cannot start worker thread"); + atomic_store(&g.ring, NULL); + free(ring); + } + } + } + pthread_mutex_unlock(&g.start_lock); +} + +/* ---- admission ---------------------------------------------------------- */ + +static bool enter(void) { + atomic_fetch_add(&g.inflight, 1); + int lc = atomic_load(&g.lifecycle); + if (lc == LC_READY || lc == LC_RUNNING) return true; + atomic_fetch_sub(&g.inflight, 1); + return false; +} + +static void leave(void) { atomic_fetch_sub(&g.inflight, 1); } + +static bool reserve_counter(atomic_int *counter, int limit) { + if (atomic_fetch_add(counter, 1) + 1 > limit) { + atomic_fetch_sub(counter, 1); + return false; + } + return true; +} + +static bool reserve_bytes(atomic_llong *counter, long long charge, long long limit) { + if (atomic_fetch_add(counter, charge) + charge > limit) { + atomic_fetch_sub(counter, charge); + return false; + } + return true; +} + +static bool reserve(int slot, size_t charge) { + long long c = (long long)charge; + if (!reserve_counter(&g.items, g.global_max_items)) return false; + if (!reserve_bytes(&g.bytes, c, g.global_max_bytes)) goto undo_items; + if (slot >= 0) { + if (!reserve_counter(&g_slots[slot].items, g.per_camera_max_items)) goto undo_bytes; + if (!reserve_bytes(&g_slots[slot].bytes, c, g.per_camera_max_bytes)) { + atomic_fetch_sub(&g_slots[slot].items, 1); + goto undo_bytes; + } + bump_max_uint(&g.camera_high_water_items, (unsigned)atomic_load(&g_slots[slot].items)); + } + return true; +undo_bytes: + atomic_fetch_sub(&g.bytes, c); +undo_items: + atomic_fetch_sub(&g.items, 1); + return false; +} + +static void wake_worker(void) { + if (atomic_load(&g.thread_started)) sem_post(&g.wake); +} + +/* Push a drop record; returns true when tracked. Takes ownership of discard + * (the rejected frame, may be NULL): releasing a multi-megabyte buffer is an + * munmap that measured 1.5-2.6 ms on a busy process (X2), so the worker frees + * it after popping the record. Pending deferred frames are bounded by + * g.discard_max_bytes; past that, or with the ring full, it is freed here. */ +static bool record_drop(const observation_batch_input_t *batch, const char *asset_uuid, + const char *reason, uint8_t *discard, size_t discard_size) { + size_t charge = 0; + if (discard) { + if (reserve_bytes(&g.discard_bytes, (long long)discard_size, g.discard_max_bytes)) { + charge = discard_size; + } else { + free(discard); + discard = NULL; + atomic_fetch_add(&g.discard_inline, 1); + } + } + if (ring_push(atomic_load(&g.ring), batch, asset_uuid, reason, discard, charge)) { + if (discard) atomic_fetch_add(&g.discard_deferred, 1); + wake_worker(); + return true; + } + if (discard) { + atomic_fetch_sub(&g.discard_bytes, (long long)charge); + free(discard); + atomic_fetch_add(&g.discard_inline, 1); + } + atomic_fetch_add(&g.dropped_untracked, 1); + return false; +} + +static bool batch_valid(const observation_batch_input_t *batch) { + return batch && lightnvr_uuid_is_valid(batch->camera_uuid) && + lightnvr_uuid_is_valid(batch->batch_id) && batch->object_count >= 1 && + batch->object_count <= OBS_MAX_BATCH && batch->observed_at_ms >= 0; +} + +static observation_evidence_result_t admit(const observation_batch_input_t *batch, + uint8_t *data, size_t data_size, int width, + int height, bool is_jpeg, + const observation_evidence_transform_t *transform, + char asset_uuid_out[OBS_UUID_SIZE]) { + if (asset_uuid_out) asset_uuid_out[0] = '\0'; + if (!enter()) { + free(data); + atomic_fetch_add(&g.dropped_disabled, 1); + return OBS_EVIDENCE_DISABLED; + } + observation_evidence_result_t result = OBS_EVIDENCE_INVALID; + char transform_json[OBS_TRANSFORM_SIZE]; + char asset_uuid[OBS_UUID_SIZE]; + if (!batch_valid(batch) || !data || data_size == 0 || width <= 0 || height <= 0 || + observation_evidence_transform_json(transform, width, height, transform_json, + sizeof(transform_json)) != 0 || + lightnvr_uuid_generate_v4(asset_uuid) != 0) { + free(data); + leave(); + return OBS_EVIDENCE_INVALID; + } + + ensure_started(); + int slot = slot_get(batch->camera_uuid); + size_t charge = data_size + (is_jpeg ? 0 : RGB24_SWS_TAIL_PADDING) + sizeof(evidence_item_t); + evidence_item_t *item = NULL; + if (slot >= 0 && atomic_load(&g_slots[slot].budget_exhausted)) { + result = OBS_EVIDENCE_DROPPED_BUDGET; + } else if (!reserve(slot, charge)) { + result = OBS_EVIDENCE_DROPPED_QUEUE_FULL; + } else if (!(item = malloc(sizeof(*item)))) { + if (slot >= 0) { + atomic_fetch_sub(&g_slots[slot].items, 1); + atomic_fetch_sub(&g_slots[slot].bytes, (long long)charge); + } + atomic_fetch_sub(&g.items, 1); + atomic_fetch_sub(&g.bytes, (long long)charge); + result = OBS_EVIDENCE_DROPPED_QUEUE_FULL; + } else { + result = OBS_EVIDENCE_ACCEPTED; + } + + if (result != OBS_EVIDENCE_ACCEPTED) { + bool budget = result == OBS_EVIDENCE_DROPPED_BUDGET; + atomic_fetch_add(budget ? &g.dropped_budget : &g.dropped_queue_full, 1); + if (record_drop(batch, asset_uuid, budget ? "budget_exhausted" : "queue_full", data, + data_size) && + asset_uuid_out) + safe_strcpy(asset_uuid_out, asset_uuid, OBS_UUID_SIZE, 0); + leave(); + return result; + } + + memset(item, 0, offsetof(evidence_item_t, batch)); + item->is_jpeg = is_jpeg; + item->slot = slot; + item->charge = charge; + item->data = data; + item->data_size = data_size; + item->width = width; + item->height = height; + item->batch = *batch; + memset(&item->asset, 0, sizeof(item->asset)); + safe_strcpy(item->asset.asset_uuid, asset_uuid, sizeof(item->asset.asset_uuid), 0); + item->asset.state = OBS_ASSET_PENDING; + safe_strcpy(item->asset.origin, is_jpeg ? "submitted_image" : "inference_input", + sizeof(item->asset.origin), 0); + safe_strcpy(item->asset.media_type, "image/jpeg", sizeof(item->asset.media_type), 0); + item->asset.captured_at_ms = batch->observed_at_ms; + safe_strcpy(item->asset.transform_json, transform_json, sizeof(item->asset.transform_json), 0); + + evidence_item_t *head = atomic_load(&g.head); + do { + item->next = head; + } while (!atomic_compare_exchange_weak(&g.head, &head, item)); + + bump_max_uint(&g.high_water_items, (unsigned)atomic_load(&g.items)); + bump_max_ull(&g.high_water_bytes, (unsigned long long)atomic_load(&g.bytes)); + atomic_fetch_add(&g.accepted, 1); + if (asset_uuid_out) safe_strcpy(asset_uuid_out, asset_uuid, OBS_UUID_SIZE, 0); + wake_worker(); + leave(); + return OBS_EVIDENCE_ACCEPTED; +} + +observation_evidence_result_t observation_evidence_submit( + const observation_batch_input_t *batch, uint8_t *rgb24, int width, int height, + const observation_evidence_transform_t *transform, char asset_uuid_out[OBS_UUID_SIZE]) { + int64_t start = mono_ns(); + size_t size = (width > 0 && height > 0) ? (size_t)width * (size_t)height * 3 : 0; + observation_evidence_result_t rc = + admit(batch, rgb24, size, width, height, false, transform, asset_uuid_out); + if (rc != OBS_EVIDENCE_DISABLED) record_submit_latency(start); + return rc; +} + +observation_evidence_result_t observation_evidence_submit_jpeg( + const observation_batch_input_t *batch, uint8_t *jpeg, size_t jpeg_size, int width, + int height, const observation_evidence_transform_t *transform, + char asset_uuid_out[OBS_UUID_SIZE]) { + int64_t start = mono_ns(); + observation_evidence_result_t rc = + admit(batch, jpeg, jpeg_size, width, height, true, transform, asset_uuid_out); + if (rc != OBS_EVIDENCE_DISABLED) record_submit_latency(start); + return rc; +} + +observation_evidence_result_t observation_evidence_submit_unavailable( + const observation_batch_input_t *batch, const char *reason, + char asset_uuid_out[OBS_UUID_SIZE]) { + int64_t start = mono_ns(); + if (asset_uuid_out) asset_uuid_out[0] = '\0'; + if (!enter()) { + atomic_fetch_add(&g.dropped_disabled, 1); + return OBS_EVIDENCE_DISABLED; + } + char asset_uuid[OBS_UUID_SIZE]; + /* Only the insert-only "no media" reason is a producer's to choose; + * queue_full / budget_exhausted are decided by admission. */ + if (!batch_valid(batch) || !reason || strcmp(reason, "no_matching_media") != 0 || + lightnvr_uuid_generate_v4(asset_uuid) != 0) { + leave(); + record_submit_latency(start); + return OBS_EVIDENCE_INVALID; + } + ensure_started(); + observation_evidence_result_t result = OBS_EVIDENCE_DROPPED_QUEUE_FULL; + if (record_drop(batch, asset_uuid, reason, NULL, 0)) { + atomic_fetch_add(&g.unavailable_submitted, 1); + if (asset_uuid_out) safe_strcpy(asset_uuid_out, asset_uuid, OBS_UUID_SIZE, 0); + result = OBS_EVIDENCE_ACCEPTED; + } + leave(); + record_submit_latency(start); + return result; +} + +/* ---- lifecycle ---------------------------------------------------------- */ + +static void reset_counters(void) { + atomic_ullong *counters[] = { + &g.accepted, &g.dropped_queue_full, &g.dropped_budget, &g.dropped_disabled, + &g.dropped_untracked, &g.unavailable_submitted, &g.ready, &g.failed_encode, + &g.failed_write, &g.failed_db, &g.disabled_at_encode, &g.interrupted, + &g.bytes_written, &g.high_water_bytes, &g.submit_latency_count, + &g.submit_latency_sum_ns, &g.submit_latency_max_ns, &g.discard_deferred, + &g.discard_inline, + }; + for (size_t i = 0; i < sizeof(counters) / sizeof(counters[0]); ++i) + atomic_store(counters[i], 0); + for (int i = 0; i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS; ++i) + atomic_store(&g.submit_latency_bucket[i], 0); + atomic_store(&g.high_water_items, 0); + atomic_store(&g.camera_high_water_items, 0); +} + +int observation_evidence_init(const observation_evidence_config_t *config) { + if (!config || !config->storage_path || !config->storage_path[0] || + config->global_max_bytes < 0 || config->global_max_items < 0 || + config->per_camera_max_items < 0 || config->per_camera_max_bytes < 0) + return -1; + if (strlen(config->storage_path) + 128 >= sizeof(g.storage_path)) return -1; + pthread_mutex_lock(&g.start_lock); + if (atomic_load(&g.lifecycle) != LC_OFF) { + pthread_mutex_unlock(&g.start_lock); + return -1; + } + safe_strcpy(g.storage_path, config->storage_path, sizeof(g.storage_path), 0); + size_t len = strlen(g.storage_path); + while (len > 1 && g.storage_path[len - 1] == '/') g.storage_path[--len] = '\0'; + g.global_max_bytes = config->global_max_bytes > 0 ? config->global_max_bytes + : OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES; + g.global_max_items = config->global_max_items > 0 ? config->global_max_items + : OBS_EVIDENCE_GLOBAL_DEPTH; + g.per_camera_max_items = config->per_camera_max_items > 0 + ? config->per_camera_max_items + : OBS_EVIDENCE_PER_CAMERA_DEPTH; + g.per_camera_max_bytes = config->per_camera_max_bytes > 0 ? config->per_camera_max_bytes + : g.global_max_bytes; + g.discard_max_bytes = g.global_max_bytes > OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES + ? g.global_max_bytes + : OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES; + atomic_store(&g.discard_bytes, 0); + if (sem_init(&g.wake, 0, 0) != 0) { + pthread_mutex_unlock(&g.start_lock); + return -1; + } + reset_counters(); + atomic_store(&g.items, 0); + atomic_store(&g.bytes, 0); + atomic_store(&g.drop_pending, 0); + atomic_store(&g.stop, 0); + atomic_store(&g.lifecycle, LC_READY); + pthread_mutex_unlock(&g.start_lock); + log_info("Observation evidence initialized (worker starts on first submission; " + "queue %d items / %lld bytes, %d per camera)", + g.global_max_items, (long long)g.global_max_bytes, g.per_camera_max_items); + return 0; +} + +void observation_evidence_shutdown(int drain_timeout_ms) { + int lc = atomic_load(&g.lifecycle); + if ((lc != LC_READY && lc != LC_RUNNING) || + !atomic_compare_exchange_strong(&g.lifecycle, &lc, LC_STOPPING)) + return; + + /* Producers never block, so in-flight submissions finish promptly. */ + for (int i = 0; atomic_load(&g.inflight) > 0 && i < 50000; ++i) usleep(100); + + if (drain_timeout_ms < 0) drain_timeout_ms = OBS_EVIDENCE_DEFAULT_DRAIN_MS; + atomic_store(&g.drain_deadline_ns, mono_ns() + (int64_t)drain_timeout_ms * 1000000LL); + atomic_store(&g.stop, 1); + + pthread_mutex_lock(&g.start_lock); + if (atomic_load(&g.thread_started)) { + sem_post(&g.wake); + pthread_join(g.thread, NULL); + atomic_store(&g.thread_started, 0); + } + pthread_mutex_unlock(&g.start_lock); + + /* Leftovers when the worker never started (or failed to start). */ + evidence_item_t *list = take_all_fifo(); + while (list) { + evidence_item_t *item = list; + list = list->next; + abandon_item(item); + release_item(item); + } + process_drops(); + if (g.encoder) { + jpeg_encoder_cache_destroy(g.encoder); + g.encoder = NULL; + } + free(atomic_exchange(&g.ring, NULL)); + atomic_store(&g.drop_pending, 0); + sem_destroy(&g.wake); + g.component_id = -1; + atomic_store(&g.lifecycle, LC_OFF); + log_info("Observation evidence shut down (ready=%llu interrupted=%llu dropped_untracked=%llu)", + (unsigned long long)atomic_load(&g.ready), + (unsigned long long)atomic_load(&g.interrupted), + (unsigned long long)atomic_load(&g.dropped_untracked)); +} + +void observation_evidence_get_stats(observation_evidence_stats_t *out) { + if (!out) return; + memset(out, 0, sizeof(*out)); + int lc = atomic_load(&g.lifecycle); + out->initialized = lc == LC_READY || lc == LC_RUNNING; + out->worker_running = atomic_load(&g.worker_running); + out->allocated = atomic_load(&g.ring) != NULL || atomic_load(&g.thread_started); + out->accepted = atomic_load(&g.accepted); + out->dropped_queue_full = atomic_load(&g.dropped_queue_full); + out->dropped_budget = atomic_load(&g.dropped_budget); + out->dropped_disabled = atomic_load(&g.dropped_disabled); + out->dropped_untracked = atomic_load(&g.dropped_untracked); + out->unavailable_submitted = atomic_load(&g.unavailable_submitted); + out->ready = atomic_load(&g.ready); + out->failed_encode = atomic_load(&g.failed_encode); + out->failed_write = atomic_load(&g.failed_write); + out->failed_db = atomic_load(&g.failed_db); + out->disabled_at_encode = atomic_load(&g.disabled_at_encode); + out->interrupted = atomic_load(&g.interrupted); + out->bytes_written = atomic_load(&g.bytes_written); + int items = atomic_load(&g.items); + long long bytes = atomic_load(&g.bytes); + int drops = atomic_load(&g.drop_pending); + out->backlog_items = items > 0 ? (uint32_t)items : 0; + out->backlog_bytes = bytes > 0 ? (uint64_t)bytes : 0; + out->drop_ring_depth = drops > 0 ? (uint32_t)drops : 0; + out->high_water_items = atomic_load(&g.high_water_items); + out->high_water_bytes = atomic_load(&g.high_water_bytes); + out->camera_high_water_items = atomic_load(&g.camera_high_water_items); + if (out->initialized) { + out->limit_items = (uint32_t)g.global_max_items; + out->limit_camera_items = (uint32_t)g.per_camera_max_items; + out->limit_bytes = (uint64_t)g.global_max_bytes; + out->limit_camera_bytes = (uint64_t)g.per_camera_max_bytes; + out->limit_discard_bytes = (uint64_t)g.discard_max_bytes; + } + out->discard_deferred = atomic_load(&g.discard_deferred); + out->discard_inline = atomic_load(&g.discard_inline); + long long pending_discard = atomic_load(&g.discard_bytes); + out->discard_pending_bytes = pending_discard > 0 ? (uint64_t)pending_discard : 0; + out->submit_latency_count = atomic_load(&g.submit_latency_count); + out->submit_latency_sum_ns = atomic_load(&g.submit_latency_sum_ns); + out->submit_latency_max_ns = atomic_load(&g.submit_latency_max_ns); + for (int i = 0; i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS; ++i) + out->submit_latency_bucket[i] = atomic_load(&g.submit_latency_bucket[i]); + int count = atomic_load_explicit(&g_slot_count, memory_order_acquire); + for (int i = 0; i < count; ++i) + if (atomic_load(&g_slots[i].budget_exhausted)) out->budget_exhausted_cameras++; +} diff --git a/src/core/observation_relay.c b/src/core/observation_relay.c new file mode 100644 index 000000000..1d03d6212 --- /dev/null +++ b/src/core/observation_relay.c @@ -0,0 +1,604 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L5 publication relay. See include/core/observation_relay.h and + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 5.5, 6 and 7.6. + * + * Ordering and delivery rules: + * - Pending rows are read in seq order in bounded batches. A FULL outbox, + * a router/outbox error, a mark error or the D8 cap stops the batch at + * that row, so later rows never overtake an earlier retryable one. + * - A row is marked relayed / relayed_no_route only after the outbox step. + * A crash in between replays the same message ID, which the outbox + * reports as DUPLICATE (still relayed). Delivery is at least once. + * - event_bus_publish runs after the mark, for in-process subscribers. + */ + +#define _GNU_SOURCE + +#include "core/observation_relay.h" +#include "core/observation_retention.h" + +#include +#include +#include +#include +#include + +#include + +#include "core/config.h" +#include "core/event_bus.h" +#include "core/event_identity.h" +#include "core/event_producers.h" +#include "core/logger.h" +#include "database/db_event_outbox.h" +#include "utils/strings.h" + +#define RELAY_DEFAULT_STARTUP_DRAIN_MS 5000 + +typedef struct { + int batch_size; + int poll_interval_ms; + int outbox_cap; + int startup_drain_ms; + char storage_path[MAX_PATH_LENGTH]; +} relay_settings_t; + +static struct { + pthread_mutex_t mutex; /* state, stats, wake flags */ + pthread_cond_t wake; + bool cond_ready; + pthread_mutex_t run_mutex; /* serializes passes */ + pthread_t thread; + bool running; + bool stop_requested; + bool notified; + relay_settings_t settings; + observation_relay_stats_t stats; + /* D9 sidecar bookkeeping (guarded by run_mutex). */ + bool sidecar_dirty; + int64_t sidecar_last_write_ms; +} RELAY = { + .mutex = PTHREAD_MUTEX_INITIALIZER, + .run_mutex = PTHREAD_MUTEX_INITIALIZER, + .sidecar_dirty = true, +}; + +static int64_t realtime_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +static int64_t monotonic_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +static void set_error(char *error, size_t error_size, const char *message) { + if (!error || error_size == 0) return; + snprintf(error, error_size, "%s", message ? message : "observation relay error"); +} + +static void ensure_cond_locked(void) { + if (RELAY.cond_ready) return; + pthread_condattr_t attr; + pthread_condattr_init(&attr); + pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); + pthread_cond_init(&RELAY.wake, &attr); + pthread_condattr_destroy(&attr); + RELAY.cond_ready = true; +} + +static relay_settings_t resolve_settings(const observation_relay_options_t *options) { + relay_settings_t settings; + memset(&settings, 0, sizeof(settings)); + settings.batch_size = OBSERVATION_RELAY_DEFAULT_BATCH; + settings.poll_interval_ms = OBSERVATION_RELAY_DEFAULT_POLL_MS; + settings.outbox_cap = OBSERVATION_RELAY_OUTBOX_CAP; + settings.startup_drain_ms = RELAY_DEFAULT_STARTUP_DRAIN_MS; + const char *storage = g_config.storage_path; + if (options) { + if (options->batch_size > 0) settings.batch_size = options->batch_size; + if (options->poll_interval_ms > 0) settings.poll_interval_ms = options->poll_interval_ms; + if (options->outbox_cap > 0) settings.outbox_cap = options->outbox_cap; + if (options->startup_drain_ms > 0) settings.startup_drain_ms = options->startup_drain_ms; + if (options->storage_path) storage = options->storage_path; + } + if (settings.batch_size > OBSERVATION_RELAY_MAX_BATCH) + settings.batch_size = OBSERVATION_RELAY_MAX_BATCH; + safe_strcpy(settings.storage_path, storage ? storage : "", sizeof(settings.storage_path), 0); + return settings; +} + +#define STAT_ADD(field, value) \ + do { \ + pthread_mutex_lock(&RELAY.mutex); \ + RELAY.stats.field += (value); \ + pthread_mutex_unlock(&RELAY.mutex); \ + } while (0) + +/* ---- envelope ------------------------------------------------------------ */ + +static int format_ms_utc(int64_t ms, char out[32]) { + if (ms <= 0) return -1; + time_t seconds = (time_t)(ms / 1000); + struct tm utc; + if (!gmtime_r(&seconds, &utc)) return -1; + char base[24]; + if (strftime(base, sizeof(base), "%Y-%m-%dT%H:%M:%S", &utc) == 0) return -1; + int written = snprintf(out, 32, "%s.%03dZ", base, (int)(ms % 1000)); + return written > 0 && written < 32 ? 0 : -1; +} + +int observation_relay_build_envelope(const observation_publication_t *publication, + const char *source, event_envelope_t *event, + char *error, size_t error_size) { + if (error && error_size > 0) error[0] = '\0'; + if (!publication || !source || !event) { + set_error(error, error_size, "publication, source and event are required"); + return -1; + } + memset(event, 0, sizeof(*event)); + const char *state = observation_asset_state_name(publication->evidence_state); + const char *cause = observation_change_cause_name(publication->cause); + char observed_at[32]; + if (!state || !cause || format_ms_utc(publication->observed_at_ms, observed_at) != 0 || + publication->change_created_at_ms < 1000) { + set_error(error, error_size, "publication state, cause or timestamps are invalid"); + return -1; + } + char subject[EVENT_SUBJECT_MAX]; + int written = snprintf(subject, sizeof(subject), "camera/%s", publication->camera_uuid); + if (written < 0 || (size_t)written >= sizeof(subject)) { + set_error(error, error_size, "publication camera identity is invalid"); + return -1; + } + + /* Field order is the section 6.3 / C3 fixture order. */ + cJSON *data = cJSON_CreateObject(); + bool ok = data && + cJSON_AddStringToObject(data, "observation_id", publication->observation_uuid) && + cJSON_AddNumberToObject(data, "revision", publication->revision) && + cJSON_AddStringToObject(data, "camera_uuid", publication->camera_uuid) && + cJSON_AddStringToObject(data, "label", publication->label) && + cJSON_AddStringToObject(data, "observed_at", observed_at) && + cJSON_AddStringToObject(data, "change_cause", cause) && + cJSON_AddStringToObject(data, "evidence_state", state) && + (publication->evidence_reason[0] + ? cJSON_AddStringToObject(data, "evidence_reason", publication->evidence_reason) != NULL + : cJSON_AddNullToObject(data, "evidence_reason") != NULL) && + cJSON_AddStringToObject(data, "asset_id", publication->asset_uuid); + if (ok && publication->evidence_state == OBS_ASSET_READY) { + char url[96]; + snprintf(url, sizeof(url), "/api/observations/%s/snapshot", + publication->observation_uuid); + ok = cJSON_AddStringToObject(data, "snapshot_url", url) != NULL; + } + if (!ok) { + cJSON_Delete(data); + set_error(error, error_size, "observation event allocation failed"); + return -1; + } + /* time = change created_at truncated to seconds: retries are identical. */ + int result = event_envelope_create_with_severity_and_id( + event, OBSERVATION_CHANGED_EVENT_TYPE, source, subject, + (time_t)(publication->change_created_at_ms / 1000), data, EVENT_SEVERITY_INFO, + publication->message_id, error, error_size); + cJSON_Delete(data); + return result; +} + +/* ---- D9 cursor-head sidecar ------------------------------------------------ */ + +/* The sidecar file, its format and the rewind check belong to L4 + * (observation_cursor_head_write/read/reconcile in observation_retention.c); + * the relay only decides when to write it. */ + +static int write_sidecar_now(const char *storage_path) { + observation_meta_t meta; + if (db_observation_meta_get(&meta) != DB_OBS_OK || + observation_cursor_head_write(storage_path, meta.epoch_uuid, meta.head_seq) != 0) { + STAT_ADD(sidecar_errors, 1); + return -1; + } + STAT_ADD(sidecar_writes, 1); + return 0; +} + +int observation_relay_write_cursor_head(const char *storage_path) { + const char *path = storage_path ? storage_path : g_config.storage_path; + if (!path || !path[0]) return -1; + return write_sidecar_now(path); +} + +int observation_relay_check_cursor_head(const char *storage_path) { + const char *path = storage_path ? storage_path : g_config.storage_path; + if (!path || !path[0]) return 0; + /* L4 already ran this at startup, before reconciliation could advance the + * head; here it is a no-op unless the sidecar changed in between. */ + bool regenerated = false; + if (observation_cursor_head_reconcile(path, ®enerated) != 0) { + STAT_ADD(sidecar_errors, 1); + return -1; + } + STAT_ADD(sidecar_writes, 1); + if (!regenerated) return 0; + STAT_ADD(epoch_regenerations, 1); + return 1; +} + +/* Caller holds run_mutex. Writes at most once per second after head moves. */ +static void maybe_write_sidecar_locked(const relay_settings_t *settings) { + if (!RELAY.sidecar_dirty || !settings->storage_path[0]) return; + int64_t now = monotonic_ms(); + if (RELAY.sidecar_last_write_ms != 0 && now - RELAY.sidecar_last_write_ms < 1000) return; + if (write_sidecar_now(settings->storage_path) == 0) { + RELAY.sidecar_dirty = false; + RELAY.sidecar_last_write_ms = now; + } +} + +/* ---- relay pass ------------------------------------------------------------ */ + +static pthread_mutex_t g_stage_lock = PTHREAD_MUTEX_INITIALIZER; +static observation_relay_stage_fn g_stage_hook; +static void *g_stage_ctx; + +void observation_relay_set_stage_hook(observation_relay_stage_fn fn, void *ctx) { + pthread_mutex_lock(&g_stage_lock); + g_stage_hook = fn; + g_stage_ctx = ctx; + pthread_mutex_unlock(&g_stage_lock); +} + +static void stage_hook(observation_relay_stage_t stage, const observation_publication_t *row) { + pthread_mutex_lock(&g_stage_lock); + observation_relay_stage_fn fn = g_stage_hook; + void *ctx = g_stage_ctx; + pthread_mutex_unlock(&g_stage_lock); + if (fn) fn(stage, row, ctx); +} + +static bool mark(int64_t seq, observation_publication_state_t state, const char *error, + bool *cas_lost) { + int rc = db_observation_publication_mark(seq, state, error); + *cas_lost = rc == DB_OBS_CAS_LOST || rc == DB_OBS_NOT_FOUND; + if (rc == DB_OBS_OK || *cas_lost) return true; + STAT_ADD(mark_errors, 1); + log_error("Observation relay could not mark publication %lld (%d)", (long long)seq, rc); + return false; +} + +static int run_pass_locked(const relay_settings_t *settings, observation_relay_pass_t *pass) { + observation_publication_t *rows = calloc((size_t)settings->batch_size, sizeof(*rows)); + if (!rows) { + pass->backoff = true; + STAT_ADD(read_errors, 1); + return -1; + } + int count = db_observation_pending_publications(rows, settings->batch_size); + if (count < 0) { + free(rows); + pass->backoff = true; + STAT_ADD(read_errors, 1); + return -1; + } + pass->read = count; + pthread_mutex_lock(&RELAY.mutex); + RELAY.stats.passes++; + RELAY.stats.last_pass_pending = count; + RELAY.stats.oldest_pending_created_at_ms = count > 0 ? rows[0].change_created_at_ms : 0; + pthread_mutex_unlock(&RELAY.mutex); + if (count == 0) { + free(rows); + maybe_write_sidecar_locked(settings); + return 0; + } + RELAY.sidecar_dirty = true; + + char source[EVENT_SOURCE_MAX]; + int64_t outbox_pending = 0; + if (event_identity_get_source(source, sizeof(source)) != 0 || + db_event_outbox_count_undelivered_type(OBSERVATION_CHANGED_EVENT_TYPE, + &outbox_pending) != 0) { + free(rows); + pass->backoff = true; + STAT_ADD(read_errors, 1); + return -1; + } + pthread_mutex_lock(&RELAY.mutex); + RELAY.stats.outbox_type_pending = outbox_pending; + pthread_mutex_unlock(&RELAY.mutex); + int64_t room = settings->outbox_cap - outbox_pending; + const event_type_definition_t *definition = + event_registry_find(OBSERVATION_CHANGED_EVENT_TYPE); + int64_t expiry_ms = (int64_t)(definition ? definition->default_expiry_seconds : 3600) * 1000; + + for (int index = 0; index < count; index++) { + const observation_publication_t *row = &rows[index]; + bool cas_lost = false; + + /* Section 5.5: past the type's delivery expiry -> expired. */ + if (realtime_ms() >= row->change_created_at_ms + expiry_ms) { + if (!mark(row->change_seq, OBS_PUBLICATION_EXPIRED, "delivery expiry elapsed", + &cas_lost)) { + pass->backoff = true; + break; + } + if (!cas_lost) { + pass->processed++; + STAT_ADD(expired, 1); + } + continue; + } + + event_envelope_t event; + char error[256] = ""; + if (observation_relay_build_envelope(row, source, &event, error, sizeof(error)) != 0) { + log_error("Observation publication %lld is not a valid event: %s", + (long long)row->change_seq, error); + if (!mark(row->change_seq, OBS_PUBLICATION_FAILED, error, &cas_lost)) { + pass->backoff = true; + break; + } + if (!cas_lost) { + pass->processed++; + STAT_ADD(failed, 1); + } + continue; + } + + /* D8: leave the rest pending while the type holds its outbox share. */ + if (room <= 0) { + event_envelope_clear(&event); + pass->cap_reached = true; + pass->backoff = true; + STAT_ADD(cap_pauses, 1); + break; + } + + stage_hook(OBS_RELAY_STAGE_BEFORE_ENQUEUE, row); + event_producer_enqueue_result_t result = + event_producer_enqueue_durable(&event, error, sizeof(error)); + if (result == EVENT_PRODUCER_ENQUEUE_ENQUEUED || result == EVENT_PRODUCER_ENQUEUE_DUPLICATE) + stage_hook(OBS_RELAY_STAGE_AFTER_ENQUEUE, row); + observation_publication_state_t state; + bool retry = false; + switch (result) { + case EVENT_PRODUCER_ENQUEUE_ENQUEUED: + room--; + state = OBS_PUBLICATION_RELAYED; + break; + case EVENT_PRODUCER_ENQUEUE_DUPLICATE: + state = OBS_PUBLICATION_RELAYED; + break; + case EVENT_PRODUCER_ENQUEUE_NO_ROUTE: + case EVENT_PRODUCER_ENQUEUE_MQTT_DISABLED: + state = OBS_PUBLICATION_RELAYED_NO_ROUTE; + break; + case EVENT_PRODUCER_ENQUEUE_FULL: + STAT_ADD(enqueue_full, 1); + state = OBS_PUBLICATION_PENDING; + retry = true; + set_error(error, sizeof(error), "outbox full"); + break; + case EVENT_PRODUCER_ENQUEUE_FAILED: + default: + STAT_ADD(enqueue_errors, 1); + state = OBS_PUBLICATION_PENDING; + retry = true; + if (!error[0]) set_error(error, sizeof(error), "durable enqueue failed"); + break; + } + /* PENDING records the attempt and error; the row stays retryable. */ + bool marked = mark(row->change_seq, state, retry ? error : NULL, &cas_lost); + if (retry || !marked) { + event_envelope_clear(&event); + pass->backoff = true; + break; + } + if (!cas_lost) { + stage_hook(OBS_RELAY_STAGE_AFTER_MARK, row); + pass->processed++; + pthread_mutex_lock(&RELAY.mutex); + if (state == OBS_PUBLICATION_RELAYED) { + RELAY.stats.relayed++; + if (result == EVENT_PRODUCER_ENQUEUE_DUPLICATE) RELAY.stats.duplicates++; + } else { + RELAY.stats.relayed_no_route++; + } + RELAY.stats.last_relayed_seq = row->change_seq; + pthread_mutex_unlock(&RELAY.mutex); + /* In-process subscribers after the durable step. The MQTT + * compatibility subscriber re-evaluates routes and hits outbox + * duplicate detection, as for system-health transitions. */ + (void)event_bus_publish(&event, NULL, 0); + } + event_envelope_clear(&event); + } + free(rows); + maybe_write_sidecar_locked(settings); + return pass->processed; +} + +int observation_relay_run_once(const observation_relay_options_t *options, + observation_relay_pass_t *pass) { + observation_relay_pass_t local; + if (!pass) pass = &local; + memset(pass, 0, sizeof(*pass)); + relay_settings_t settings; + if (options) { + settings = resolve_settings(options); + } else { + pthread_mutex_lock(&RELAY.mutex); + bool running = RELAY.running; + settings = RELAY.settings; + pthread_mutex_unlock(&RELAY.mutex); + if (!running) settings = resolve_settings(NULL); + } + pthread_mutex_lock(&RELAY.run_mutex); + int result = run_pass_locked(&settings, pass); + pthread_mutex_unlock(&RELAY.run_mutex); + return result; +} + +/* ---- thread ---------------------------------------------------------------- */ + +static void *relay_main(void *argument) { + (void)argument; + int backoff_ms = 0; + for (;;) { + pthread_mutex_lock(&RELAY.mutex); + bool stop = RELAY.stop_requested; + relay_settings_t settings = RELAY.settings; + RELAY.notified = false; + pthread_mutex_unlock(&RELAY.mutex); + if (stop) break; + + observation_relay_pass_t pass; + memset(&pass, 0, sizeof(pass)); + pthread_mutex_lock(&RELAY.run_mutex); + (void)run_pass_locked(&settings, &pass); + pthread_mutex_unlock(&RELAY.run_mutex); + + int wait_ms; + bool in_backoff = pass.backoff; + if (pass.backoff) { + backoff_ms = backoff_ms <= 0 ? 1000 : backoff_ms * 2; + if (backoff_ms > OBSERVATION_RELAY_MAX_BACKOFF_MS) + backoff_ms = OBSERVATION_RELAY_MAX_BACKOFF_MS; + wait_ms = backoff_ms; + } else { + backoff_ms = 0; + /* A full batch probably has more behind it. */ + wait_ms = pass.read >= settings.batch_size ? 0 : settings.poll_interval_ms; + } + + pthread_mutex_lock(&RELAY.mutex); + if (in_backoff) { + RELAY.stats.backoffs++; + RELAY.stats.consecutive_failures++; + RELAY.stats.backoff_until_ms = realtime_ms() + wait_ms; + } else { + RELAY.stats.consecutive_failures = 0; + RELAY.stats.backoff_until_ms = 0; + } + int64_t deadline = monotonic_ms() + wait_ms; + while (!RELAY.stop_requested && (in_backoff || !RELAY.notified)) { + int64_t remaining = deadline - monotonic_ms(); + if (remaining <= 0) break; + struct timespec until; + clock_gettime(CLOCK_MONOTONIC, &until); + until.tv_sec += remaining / 1000; + until.tv_nsec += (remaining % 1000) * 1000000L; + if (until.tv_nsec >= 1000000000L) { + until.tv_sec++; + until.tv_nsec -= 1000000000L; + } + pthread_cond_timedwait(&RELAY.wake, &RELAY.mutex, &until); + } + pthread_mutex_unlock(&RELAY.mutex); + } + return NULL; +} + +int observation_relay_start(const observation_relay_options_t *options) { + relay_settings_t settings = resolve_settings(options); + pthread_mutex_lock(&RELAY.mutex); + ensure_cond_locked(); + if (RELAY.running) { + pthread_mutex_unlock(&RELAY.mutex); + return 0; + } + RELAY.settings = settings; + RELAY.stop_requested = false; + RELAY.notified = false; + pthread_mutex_unlock(&RELAY.mutex); + db_observations_set_publication_hook(observation_relay_notify); + + /* D9 before any cursor is served for this generation. */ + if (settings.storage_path[0] && observation_relay_check_cursor_head(settings.storage_path) < 0) + log_warn("Observation relay could not verify the cursor-head sidecar"); + + /* Drain what a previous process staged, bounded so startup cannot stall. */ + int64_t drain_deadline = monotonic_ms() + settings.startup_drain_ms; + int drained = 0; + for (;;) { + observation_relay_pass_t pass; + memset(&pass, 0, sizeof(pass)); + pthread_mutex_lock(&RELAY.run_mutex); + (void)run_pass_locked(&settings, &pass); + pthread_mutex_unlock(&RELAY.run_mutex); + drained += pass.processed; + if (pass.backoff || pass.read < settings.batch_size || monotonic_ms() >= drain_deadline) + break; + } + if (drained > 0) log_info("Observation relay drained %d staged publication(s)", drained); + + pthread_mutex_lock(&RELAY.mutex); + int rc = pthread_create(&RELAY.thread, NULL, relay_main, NULL); + if (rc == 0) { + RELAY.running = true; + RELAY.stats.running = true; + } + pthread_mutex_unlock(&RELAY.mutex); + if (rc != 0) { + log_error("Failed to start observation relay thread: %s", strerror(rc)); + return -1; + } + log_info("Observation publication relay started"); + return 0; +} + +void observation_relay_stop(void) { + pthread_mutex_lock(&RELAY.mutex); + if (!RELAY.running) { + pthread_mutex_unlock(&RELAY.mutex); + return; + } + RELAY.stop_requested = true; + pthread_cond_broadcast(&RELAY.wake); + pthread_t thread = RELAY.thread; + pthread_mutex_unlock(&RELAY.mutex); + pthread_join(thread, NULL); + pthread_mutex_lock(&RELAY.mutex); + RELAY.running = false; + RELAY.stats.running = false; + RELAY.stop_requested = false; + pthread_mutex_unlock(&RELAY.mutex); + log_info("Observation publication relay stopped"); +} + +void observation_relay_notify(void) { + pthread_mutex_lock(&RELAY.mutex); + RELAY.notified = true; + if (RELAY.cond_ready) pthread_cond_signal(&RELAY.wake); + pthread_mutex_unlock(&RELAY.mutex); +} + +void observation_relay_get_stats(observation_relay_stats_t *stats) { + if (!stats) return; + pthread_mutex_lock(&RELAY.mutex); + *stats = RELAY.stats; + pthread_mutex_unlock(&RELAY.mutex); +} + +void observation_relay_reset_stats(void) { + pthread_mutex_lock(&RELAY.run_mutex); + RELAY.sidecar_dirty = true; + RELAY.sidecar_last_write_ms = 0; + pthread_mutex_unlock(&RELAY.run_mutex); + pthread_mutex_lock(&RELAY.mutex); + bool running = RELAY.stats.running; + memset(&RELAY.stats, 0, sizeof(RELAY.stats)); + RELAY.stats.running = running; + pthread_mutex_unlock(&RELAY.mutex); +} diff --git a/src/core/observation_retention.c b/src/core/observation_retention.c new file mode 100644 index 000000000..36ede390e --- /dev/null +++ b/src/core/observation_retention.c @@ -0,0 +1,834 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 L4: startup reconciliation, retention and eviction of observation + * evidence. See include/core/observation_retention.h and + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 4.2, 7.6 and 14. + * + * State changes go through the L2 compare-and-set transitions, so every + * referencing observation gets an evidence revision and a publication. Reads + * use a private read-only connection and never hold the global DB mutex; the + * only raw write here (deleting an unreferenced asset row) follows the + * db_auth.c pattern with a single statement. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "core/observation_retention.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/config.h" +#include "core/logger.h" +#include "core/path_utils.h" +#include "database/db_core.h" +#include "database/db_system_settings.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +#define DAY_MS 86400000LL +#define OBS_PATH_SIZE 1024 +#define OBS_WALK_MAX_DEPTH 4 /* observations/// */ +#define OBS_PAGE 256 + +/* ---- module state ------------------------------------------------------ */ + +static pthread_mutex_t g_run_mutex = PTHREAD_MUTEX_INITIALIZER; /* startup vs periodic */ +static atomic_int g_startup_done = 0; /* 0/1: atomic_bool breaks the C->Rust conversion */ + +static pthread_mutex_t g_status_mutex = PTHREAD_MUTEX_INITIALIZER; +static observation_retention_camera_status_t *g_status = NULL; +static int g_status_count = 0; +static atomic_int g_exhausted_count = 0; + +static time_t g_last_tick = 0; +static bool g_more_work = false; + +/* ---- paths --------------------------------------------------------------- */ + +static int root_dir(const char *storage_path, char *out, size_t size) { + if (!storage_path || !*storage_path) return -1; + size_t len = strlen(storage_path); + while (len > 1 && storage_path[len - 1] == '/') --len; + int n = snprintf(out, size, "%.*s/" OBS_RETENTION_DIR, (int)len, storage_path); + return (n > 0 && (size_t)n < size) ? 0 : -1; +} + +static bool has_suffix(const char *value, const char *suffix) { + size_t a = strlen(value), b = strlen(suffix); + return a >= b && strcmp(value + a - b, suffix) == 0; +} + +/* No empty, "." or ".." segment: a stored path can never escape the tree. */ +static bool safe_relative(const char *rel) { + if (!*rel || *rel == '/') return false; + const char *segment = rel; + for (;;) { + const char *end = strchr(segment, '/'); + size_t len = end ? (size_t)(end - segment) : strlen(segment); + if (len == 0 || (len == 1 && segment[0] == '.') || + (len == 2 && segment[0] == '.' && segment[1] == '.')) + return false; + if (!end) return true; + segment = end + 1; + } +} + +/* Map observation_assets.path to an absolute file inside + * /observations, or fail. Only "<...>.jpg" files qualify. */ +static int resolve_asset_path(const char *root, const char *stored, char *out, size_t size) { + if (!stored || !*stored || !has_suffix(stored, ".jpg")) return -1; + const char *rel = stored; + size_t root_len = strlen(root); + if (stored[0] == '/') { + if (strncmp(stored, root, root_len) != 0 || stored[root_len] != '/') return -1; + rel = stored + root_len + 1; + } else if (strncmp(stored, OBS_RETENTION_DIR "/", sizeof(OBS_RETENTION_DIR)) == 0) { + rel = stored + sizeof(OBS_RETENTION_DIR); + } + if (!safe_relative(rel)) return -1; + int n = snprintf(out, size, "%s/%s", root, rel); + return (n > 0 && (size_t)n < size) ? 0 : -1; +} + +/* Unlink a regular file; a missing file is success. Never follows a + * symlink to its target (unlink removes the link itself). */ +static int remove_file(const char *path, int *deleted) { + if (unlink(path) == 0) { + if (deleted) ++*deleted; + return 0; + } + if (errno == ENOENT || errno == ENOTDIR) return 0; + log_warn("Failed to delete observation evidence file %s: %s", path, strerror(errno)); + return -1; +} + +static void remove_asset_file(const char *root, const char *stored, int *deleted) { + char path[OBS_PATH_SIZE]; + if (resolve_asset_path(root, stored, path, sizeof(path)) != 0) { + if (stored && *stored) + log_warn("Observation asset path is outside the evidence tree; not deleting"); + return; + } + (void)remove_file(path, deleted); +} + +static int fsync_dir(const char *dir) { + int fd = open(dir, O_RDONLY | O_DIRECTORY); + if (fd < 0) return -1; + int rc = fsync(fd); + close(fd); + return rc; +} + +static int64_t wall_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +static void copy_text(sqlite3_stmt *stmt, int column, char *out, size_t size) { + const unsigned char *text = sqlite3_column_text(stmt, column); + safe_strcpy(out, text ? (const char *)text : "", size, 0); +} + +/* ---- cursor head sidecar (D9) ------------------------------------------ */ + +int observation_cursor_head_write(const char *storage_path, const char *epoch_uuid, + int64_t head_seq) { + char root[OBS_PATH_SIZE], path[OBS_PATH_SIZE], tmp[OBS_PATH_SIZE]; + if (!lightnvr_uuid_is_valid(epoch_uuid) || head_seq < 0 || + root_dir(storage_path, root, sizeof(root)) != 0) + return -1; + if (snprintf(path, sizeof(path), "%s/" OBS_CURSOR_HEAD_FILE, root) >= (int)sizeof(path) || + snprintf(tmp, sizeof(tmp), "%s/" OBS_CURSOR_HEAD_FILE ".tmp", root) >= (int)sizeof(tmp)) + return -1; + if (mkdir_recursive(root) != 0) return -1; + + char body[160]; + int len = snprintf(body, sizeof(body), "{\"epoch\":\"%s\",\"head_seq\":%lld}\n", + epoch_uuid, (long long)head_seq); + int fd = open(tmp, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0640); + if (fd < 0) return -1; + int rc = (write(fd, body, (size_t)len) == len && fsync(fd) == 0) ? 0 : -1; + if (close(fd) != 0) rc = -1; + if (rc == 0 && rename(tmp, path) != 0) rc = -1; + if (rc != 0) { + unlink(tmp); + log_warn("Failed to write observation cursor head sidecar"); + return -1; + } + (void)fsync_dir(root); + return 0; +} + +int observation_cursor_head_read(const char *storage_path, char epoch_uuid[OBS_UUID_SIZE], + int64_t *head_seq) { + char root[OBS_PATH_SIZE], path[OBS_PATH_SIZE]; + if (!epoch_uuid || !head_seq || root_dir(storage_path, root, sizeof(root)) != 0) return -1; + if (snprintf(path, sizeof(path), "%s/" OBS_CURSOR_HEAD_FILE, root) >= (int)sizeof(path)) + return -1; + FILE *f = fopen(path, "rb"); + if (!f) return errno == ENOENT ? 1 : -1; + char body[512]; + size_t n = fread(body, 1, sizeof(body) - 1, f); + fclose(f); + body[n] = '\0'; + cJSON *json = cJSON_Parse(body); + int rc = -1; + if (json) { + const cJSON *epoch = cJSON_GetObjectItemCaseSensitive(json, "epoch"); + const cJSON *head = cJSON_GetObjectItemCaseSensitive(json, "head_seq"); + if (cJSON_IsString(epoch) && lightnvr_uuid_is_valid(epoch->valuestring) && + cJSON_IsNumber(head) && head->valuedouble >= 0 && + head->valuedouble <= 9007199254740991.0 && + head->valuedouble == (double)(int64_t)head->valuedouble) { + safe_strcpy(epoch_uuid, epoch->valuestring, OBS_UUID_SIZE, 0); + *head_seq = (int64_t)head->valuedouble; + rc = 0; + } + cJSON_Delete(json); + } + return rc; +} + +int observation_cursor_head_reconcile(const char *storage_path, bool *regenerated) { + if (regenerated) *regenerated = false; + observation_meta_t meta; + if (db_observation_meta_get(&meta) != DB_OBS_OK) return -1; + + char epoch[OBS_UUID_SIZE]; + int64_t head = 0; + int rc = observation_cursor_head_read(storage_path, epoch, &head); + if (rc < 0) log_warn("Observation cursor head sidecar is malformed; recreating it"); + if (rc == 0 && strcmp(epoch, meta.epoch_uuid) == 0 && meta.head_seq < head) { + log_warn("Observation history rewound (DB head %lld < recorded head %lld): " + "database was restored; regenerating cursor epoch", + (long long)meta.head_seq, (long long)head); + if (db_observation_meta_regenerate_epoch() != DB_OBS_OK || + db_observation_meta_get(&meta) != DB_OBS_OK) + return -1; + if (regenerated) *regenerated = true; + } + return observation_cursor_head_write(storage_path, meta.epoch_uuid, meta.head_seq); +} + +/* ---- startup sweep ----------------------------------------------------- */ + +typedef struct { + sqlite3 *db; + sqlite3_stmt *lookup; + const char *root; + observation_reconcile_result_t *result; + int errors; +} sweep_t; + +/* Remove dir_fd/name, counting it. dir_fd-relative so a renamed or swapped + * parent directory cannot redirect the delete outside the evidence root. */ +static void remove_at(int dir_fd, const char *name, const char *path, int *deleted) { + if (unlinkat(dir_fd, name, 0) == 0) { + if (deleted) ++*deleted; + return; + } + if (errno == ENOENT || errno == ENOTDIR) return; + log_warn("Failed to delete observation evidence file %s: %s", path, strerror(errno)); +} + +/* .jpg: keep only the file a ready asset row points at. No worker runs + * yet, so a final file of a pending asset is an unfinished write. */ +static void sweep_final(sweep_t *s, int dir_fd, const char *path, const char *name) { + char uuid[OBS_UUID_SIZE]; + size_t len = strlen(name) - 4; /* ".jpg" */ + if (len != OBS_UUID_SIZE - 1) return; + memcpy(uuid, name, len); + uuid[len] = '\0'; + if (!lightnvr_uuid_is_valid(uuid)) return; + + sqlite3_reset(s->lookup); + sqlite3_bind_text(s->lookup, 1, uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(s->lookup); + if (step == SQLITE_ROW) { + char state[16], stored[MAX_PATH_LENGTH], expected[OBS_PATH_SIZE]; + copy_text(s->lookup, 0, state, sizeof(state)); + copy_text(s->lookup, 1, stored, sizeof(stored)); + if (strcmp(state, "ready") == 0 && + resolve_asset_path(s->root, stored, expected, sizeof(expected)) == 0 && + strcmp(expected, path) == 0) + return; + } else if (step != SQLITE_DONE) { + ++s->errors; + return; + } + remove_at(dir_fd, name, path, &s->result->stale_files_deleted); +} + +/* Walk an already-open directory. Every check and action is relative to the + * directory's fd and never follows symlinks, so an entry swapped for a + * symlink between the check and the action cannot escape the evidence root. + * Takes ownership of dir_fd. `dir` is used for logging and path comparison. */ +static void sweep_dir_fd(sweep_t *s, int dir_fd, const char *dir, int depth) { + DIR *d = fdopendir(dir_fd); + if (!d) { + log_warn("Cannot scan evidence directory %s: %s", dir, strerror(errno)); + close(dir_fd); + return; + } + int fd = dirfd(d); + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + const char *name = entry->d_name; + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; + char path[OBS_PATH_SIZE]; + if (snprintf(path, sizeof(path), "%s/%s", dir, name) >= (int)sizeof(path)) continue; + struct stat st; + if (fstatat(fd, name, &st, AT_SYMLINK_NOFOLLOW) != 0) continue; + if (S_ISDIR(st.st_mode)) { + if (depth < OBS_WALK_MAX_DEPTH) { + int child = openat(fd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + if (child < 0) continue; + sweep_dir_fd(s, child, path, depth + 1); + if (depth > 1) (void)unlinkat(fd, name, AT_REMOVEDIR); /* only succeeds when empty */ + } + continue; + } + if (!S_ISREG(st.st_mode)) continue; /* symlinks and specials are left alone */ + if (has_suffix(name, ".jpg.tmp")) { + remove_at(fd, name, path, &s->result->tmp_files_deleted); + } else if (has_suffix(name, ".jpg")) { + sweep_final(s, fd, path, name); + } + } + closedir(d); +} + +/* The evidence root comes from trusted configuration and may itself be a + * symlink (for example a mounted storage path), so it is followed; nothing + * below it is. */ +static void sweep_dir(sweep_t *s, const char *dir, int depth) { + int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (fd < 0) { + if (errno != ENOENT) log_warn("Cannot scan evidence directory %s: %s", dir, strerror(errno)); + return; + } + sweep_dir_fd(s, fd, dir, depth); +} + +static int sweep_files(const char *root, observation_reconcile_result_t *result) { + sweep_t s = {.root = root, .result = result}; + if (db_open_readonly_connection(&s.db) != 0) return -1; + if (sqlite3_prepare_v2(s.db, "SELECT state, path FROM observation_assets WHERE uuid = ?;", + -1, &s.lookup, NULL) != SQLITE_OK) { + db_close_readonly_connection(s.db); + return -1; + } + sweep_dir(&s, root, 1); + sqlite3_finalize(s.lookup); + db_close_readonly_connection(s.db); + return s.errors ? -1 : 0; +} + +typedef struct { + char uuid[OBS_UUID_SIZE]; + char path[MAX_PATH_LENGTH]; + int64_t bytes; + int64_t ref_ms; + char state[16]; +} asset_row_t; + +/* Keyset page of assets in one state, uuid > after. Returns the count or -1. */ +static int page_by_state(const char *state, const char *after, asset_row_t *rows, int max) { + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) return -1; + sqlite3_stmt *stmt = NULL; + int count = -1; + if (sqlite3_prepare_v2(db, + "SELECT uuid, COALESCE(path, '') FROM observation_assets " + "WHERE state = ?1 AND uuid > ?2 ORDER BY uuid LIMIT ?3;", -1, &stmt, NULL) == + SQLITE_OK) { + sqlite3_bind_text(stmt, 1, state, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, after, -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 3, max); + count = 0; + int step; + while (count < max && (step = sqlite3_step(stmt)) == SQLITE_ROW) { + memset(&rows[count], 0, sizeof(rows[count])); + copy_text(stmt, 0, rows[count].uuid, sizeof(rows[count].uuid)); + copy_text(stmt, 1, rows[count].path, sizeof(rows[count].path)); + ++count; + } + if (count < max && step != SQLITE_DONE) count = -1; + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return count; +} + +static int reconcile_state(const char *root, observation_asset_state_t from, + observation_reconcile_result_t *result) { + asset_row_t *rows = calloc(OBS_PAGE, sizeof(*rows)); + if (!rows) return -1; + char after[OBS_UUID_SIZE] = ""; + int errors = 0; + for (;;) { + int n = page_by_state(observation_asset_state_name(from), after, rows, OBS_PAGE); + if (n < 0) { ++errors; break; } + for (int i = 0; i < n; ++i) { + const char *reason = NULL; + if (from == OBS_ASSET_PENDING) { + reason = "interrupted"; + } else { + char path[OBS_PATH_SIZE]; + struct stat st; + if (resolve_asset_path(root, rows[i].path, path, sizeof(path)) != 0 || + lstat(path, &st) != 0 || !S_ISREG(st.st_mode)) + reason = "write_failed"; + } + if (!reason) continue; + int rc = db_observation_asset_transition(rows[i].uuid, from, OBS_ASSET_UNAVAILABLE, + reason, NULL, NULL); + if (rc == DB_OBS_OK) { + if (from == OBS_ASSET_PENDING) ++result->pending_interrupted; + else ++result->ready_missing; + } else if (rc != DB_OBS_CAS_LOST && rc != DB_OBS_NOT_FOUND) { + ++errors; + } + } + if (n < OBS_PAGE) break; + safe_strcpy(after, rows[n - 1].uuid, sizeof(after), 0); + } + free(rows); + return errors ? -1 : 0; +} + +int observation_retention_startup(const char *storage_path, + observation_reconcile_result_t *result) { + observation_reconcile_result_t local; + if (!result) result = &local; + memset(result, 0, sizeof(*result)); + char root[OBS_PATH_SIZE]; + if (root_dir(storage_path, root, sizeof(root)) != 0) return -1; + + pthread_mutex_lock(&g_run_mutex); + int rc = 0; + /* Before any transition here advances the head (section 7.6). */ + if (observation_cursor_head_reconcile(storage_path, &result->epoch_regenerated) != 0) { + log_warn("Observation cursor head reconciliation failed"); + rc = -1; + } + if (sweep_files(root, result) != 0) rc = -1; + if (reconcile_state(root, OBS_ASSET_PENDING, result) != 0) rc = -1; + if (reconcile_state(root, OBS_ASSET_READY, result) != 0) rc = -1; + if (rc == 0) { + observation_meta_t meta; + if (db_observation_meta_get(&meta) == DB_OBS_OK) + (void)observation_cursor_head_write(storage_path, meta.epoch_uuid, meta.head_seq); + atomic_store(&g_startup_done, 1); + } + pthread_mutex_unlock(&g_run_mutex); + + if (result->tmp_files_deleted || result->stale_files_deleted || + result->pending_interrupted || result->ready_missing || result->epoch_regenerated) + log_info("Observation reconciliation: %d temp and %d stale files deleted, %d pending " + "interrupted, %d ready missing%s", result->tmp_files_deleted, + result->stale_files_deleted, result->pending_interrupted, + result->ready_missing, result->epoch_regenerated ? ", epoch regenerated" : ""); + if (rc != 0) log_error("Observation startup reconciliation incomplete; eviction disabled"); + return rc; +} + +bool observation_retention_startup_done(void) { + return atomic_load(&g_startup_done); +} + +/* ---- periodic pass ----------------------------------------------------- */ + +static int history_days(void) { + char value[32]; + if (db_get_system_setting("observation_history_retention_days", value, sizeof(value)) != 0) + return OBS_RETENTION_DEFAULT_HISTORY_DAYS; + char *end = NULL; + long days = strtol(value, &end, 10); + if (end == value || days < 1 || days > 365) return OBS_RETENTION_DEFAULT_HISTORY_DAYS; + return (int)days; +} + +static int prune_history(int64_t now_ms, observation_retention_result_t *result) { + int64_t before_ms = now_ms - (int64_t)history_days() * DAY_MS; + while (result->changes_pruned < OBS_RETENTION_MAX_PRUNED_CHANGES) { + observation_prune_result_t prune; + if (db_observations_prune(before_ms, OBS_RETENTION_PRUNE_BATCH, &prune) != DB_OBS_OK) + return -1; + result->changes_pruned += prune.changes_deleted; + result->observations_pruned += prune.observations_deleted; + result->unreferenced_deleted += prune.assets_deleted; + if (prune.changes_deleted < OBS_RETENTION_PRUNE_BATCH) return 0; + } + result->more_work = true; + return 0; +} + +/* Single-statement delete of an asset row nobody references (db_auth.c + * locking: global mutex, then the connection mutex). */ +static int delete_asset_row(const char *uuid) { + pthread_mutex_t *mutex = get_db_mutex(); + if (!mutex) return -1; + pthread_mutex_lock(mutex); + sqlite3 *db = get_db_handle(); + int rc = -1; + if (db) { + sqlite3_mutex *connection = sqlite3_db_mutex(db); + sqlite3_mutex_enter(connection); + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, + "DELETE FROM observation_assets WHERE uuid = ?1 AND protected = 0 AND " + "state IN ('unavailable', 'expired') AND NOT EXISTS " + "(SELECT 1 FROM object_observations WHERE asset_uuid = ?1);", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, uuid, -1, SQLITE_TRANSIENT); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = sqlite3_changes(db); + } + sqlite3_finalize(stmt); + sqlite3_mutex_leave(connection); + } + pthread_mutex_unlock(mutex); + return rc; +} + +/* ready -> expired(retention_expired), then unlink. Returns 1 when expired, + * 0 when another writer moved the asset first, -1 on error. */ +static int expire_asset(const char *root, const asset_row_t *row, + observation_retention_result_t *result) { + int rc = db_observation_asset_transition(row->uuid, OBS_ASSET_READY, OBS_ASSET_EXPIRED, + "retention_expired", NULL, NULL); + if (rc == DB_OBS_CAS_LOST || rc == DB_OBS_NOT_FOUND) return 0; + if (rc != DB_OBS_OK) return -1; + ++result->assets_expired; + remove_asset_file(root, row->path, &result->files_deleted); + return 1; +} + +static int query_rows(const char *sql, const char *camera, int64_t bound, int max, + asset_row_t *rows) { + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) return -1; + sqlite3_stmt *stmt = NULL; + int count = -1; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { + int index = 1; + if (camera) sqlite3_bind_text(stmt, index++, camera, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, index++, bound); + sqlite3_bind_int(stmt, index, max); + count = 0; + int step = SQLITE_DONE; + while (count < max && (step = sqlite3_step(stmt)) == SQLITE_ROW) { + asset_row_t *row = &rows[count++]; + memset(row, 0, sizeof(*row)); + copy_text(stmt, 0, row->uuid, sizeof(row->uuid)); + copy_text(stmt, 1, row->path, sizeof(row->path)); + row->bytes = sqlite3_column_int64(stmt, 2); + row->ref_ms = sqlite3_column_int64(stmt, 3); + copy_text(stmt, 4, row->state, sizeof(row->state)); + } + if (count < max && step != SQLITE_DONE) count = -1; + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return count; +} + +static int reclaim_unreferenced(const char *root, int *budget, + observation_retention_result_t *result, asset_row_t *rows) { + if (*budget <= 0) return 0; + int n = query_rows( + "SELECT a.uuid, COALESCE(a.path, ''), COALESCE(a.bytes, 0), 0, a.state " + "FROM observation_assets a WHERE a.state <> 'pending' AND a.protected = 0 " + "AND ?1 > 0 AND NOT EXISTS " + "(SELECT 1 FROM object_observations o WHERE o.asset_uuid = a.uuid) " + "ORDER BY a.uuid LIMIT ?2;", NULL, 1, *budget, rows); + if (n < 0) return -1; + for (int i = 0; i < n; ++i) { + --*budget; + if (strcmp(rows[i].state, "ready") == 0) { + if (expire_asset(root, &rows[i], result) < 0) return -1; + } else { + remove_asset_file(root, rows[i].path, &result->files_deleted); + } + int deleted = delete_asset_row(rows[i].uuid); + if (deleted < 0) return -1; + result->unreferenced_deleted += deleted; + } + if (*budget <= 0) result->more_work = true; + return 0; +} + +/* An asset's age is its newest reference; captured/created time otherwise. */ +#define EVICTION_CANDIDATES \ + "SELECT uuid, path, bytes, ref_ms, 'ready' FROM (" \ + "SELECT a.uuid AS uuid, COALESCE(a.path, '') AS path, COALESCE(a.bytes, 0) AS bytes, " \ + "COALESCE((SELECT MAX(o.observed_at_ms) FROM object_observations o " \ + "WHERE o.asset_uuid = a.uuid), a.captured_at_ms, a.created_at_ms) AS ref_ms " \ + "FROM observation_assets a WHERE a.camera_uuid = ?1 AND a.state = 'ready' " \ + "AND a.protected = 0) WHERE ref_ms < ?2 ORDER BY ref_ms, uuid LIMIT ?3;" + +static int count_unprotected_ready(const char *camera, int64_t *count) { + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) return -1; + sqlite3_stmt *stmt = NULL; + int rc = -1; + if (sqlite3_prepare_v2(db, + "SELECT count(*) FROM observation_assets WHERE camera_uuid = ? AND " + "state = 'ready' AND protected = 0;", -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, camera, -1, SQLITE_TRANSIENT); + if (sqlite3_step(stmt) == SQLITE_ROW) { + *count = sqlite3_column_int64(stmt, 0); + rc = 0; + } + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return rc; +} + +static int enforce_camera(const char *root, int64_t now_ms, + observation_retention_camera_status_t *camera, int *budget, + observation_retention_result_t *result, asset_row_t *rows) { + /* Age budget. */ + int64_t cutoff = now_ms - (int64_t)camera->max_age_days * DAY_MS; + if (*budget > 0) { + int n = query_rows(EVICTION_CANDIDATES, camera->camera_uuid, cutoff, + *budget < OBS_PAGE ? *budget : OBS_PAGE, rows); + if (n < 0) return -1; + for (int i = 0; i < n; ++i) { + --*budget; + int rc = expire_asset(root, &rows[i], result); + if (rc < 0) return -1; + if (rc > 0) camera->retained_bytes -= rows[i].bytes; + } + if (n == OBS_PAGE) result->more_work = true; + } + + /* Byte budget: oldest unprotected first. */ + while (camera->retained_bytes > camera->max_bytes && *budget > 0) { + int want = *budget < OBS_PAGE ? *budget : OBS_PAGE; + int n = query_rows(EVICTION_CANDIDATES, camera->camera_uuid, INT64_MAX, want, rows); + if (n < 0) return -1; + if (n == 0) break; + for (int i = 0; i < n && camera->retained_bytes > camera->max_bytes; ++i) { + --*budget; + int rc = expire_asset(root, &rows[i], result); + if (rc < 0) return -1; + if (rc > 0) camera->retained_bytes -= rows[i].bytes; + } + if (n < want) break; + } + + camera->budget_exhausted = false; + if (camera->retained_bytes > camera->max_bytes) { + int64_t evictable = 0; + if (count_unprotected_ready(camera->camera_uuid, &evictable) != 0) return -1; + if (evictable == 0) { + camera->budget_exhausted = true; + ++result->cameras_budget_exhausted; + } else { + result->more_work = true; + } + } + return 0; +} + +static int load_cameras(observation_retention_camera_status_t **out, int *count) { + *out = NULL; + *count = 0; + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) return -1; + sqlite3_stmt *stmt = NULL; + int rc = -1, capacity = 0; + if (sqlite3_prepare_v2(db, + "SELECT c.camera_uuid, s.observation_max_age_days, s.observation_max_bytes, " + "(SELECT COALESCE(SUM(x.bytes), 0) FROM observation_assets x " + " WHERE x.camera_uuid = c.camera_uuid AND x.state = 'ready') " + "FROM (SELECT DISTINCT camera_uuid FROM observation_assets WHERE state = 'ready') c " + "LEFT JOIN streams s ON s.camera_uuid = c.camera_uuid ORDER BY c.camera_uuid;", + -1, &stmt, NULL) == SQLITE_OK) { + rc = 0; + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + if (*count == capacity) { + int next = capacity ? capacity * 2 : 16; + void *grown = realloc(*out, (size_t)next * sizeof(**out)); + if (!grown) { rc = -1; break; } + *out = grown; + capacity = next; + } + observation_retention_camera_status_t *camera = &(*out)[(*count)++]; + memset(camera, 0, sizeof(*camera)); + copy_text(stmt, 0, camera->camera_uuid, sizeof(camera->camera_uuid)); + camera->max_age_days = sqlite3_column_int(stmt, 1); + camera->max_bytes = sqlite3_column_int64(stmt, 2); + camera->retained_bytes = sqlite3_column_int64(stmt, 3); + if (camera->max_age_days <= 0) + camera->max_age_days = OBS_RETENTION_DEFAULT_MAX_AGE_DAYS; + if (camera->max_bytes <= 0) camera->max_bytes = OBS_RETENTION_DEFAULT_MAX_BYTES; + } + if (rc == 0 && step != SQLITE_DONE) rc = -1; + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + if (rc != 0) { + free(*out); + *out = NULL; + *count = 0; + } + return rc; +} + +/* L3 admission flag (include/core/observation_evidence.h). Weak so this + * module links without the evidence worker (tools, tests); once L3's object + * is linked the flags are pushed to it on every pass. */ +extern void observation_evidence_set_budget_exhausted(const char *camera_uuid, bool exhausted) + __attribute__((weak)); + +static const observation_retention_camera_status_t *find_status( + const observation_retention_camera_status_t *cameras, int count, const char *uuid) { + for (int i = 0; i < count; ++i) + if (strcmp(cameras[i].camera_uuid, uuid) == 0) return &cameras[i]; + return NULL; +} + +static void publish_status(observation_retention_camera_status_t *cameras, int count) { + int exhausted = 0; + for (int i = 0; i < count; ++i) + if (cameras[i].budget_exhausted) ++exhausted; + pthread_mutex_lock(&g_status_mutex); + observation_retention_camera_status_t *old = g_status; + int old_count = g_status_count; + g_status = cameras; + g_status_count = count; + atomic_store(&g_exhausted_count, exhausted); + pthread_mutex_unlock(&g_status_mutex); + + /* Only this thread replaces g_status, so cameras stays valid here. */ + if (observation_evidence_set_budget_exhausted) { + for (int i = 0; i < count; ++i) { + const observation_retention_camera_status_t *before = + find_status(old, old_count, cameras[i].camera_uuid); + if (cameras[i].budget_exhausted || (before && before->budget_exhausted)) + observation_evidence_set_budget_exhausted(cameras[i].camera_uuid, + cameras[i].budget_exhausted); + } + for (int i = 0; i < old_count; ++i) + if (old[i].budget_exhausted && !find_status(cameras, count, old[i].camera_uuid)) + observation_evidence_set_budget_exhausted(old[i].camera_uuid, false); + } + free(old); +} + +int observation_retention_run(const char *storage_path, int64_t now_ms, + observation_retention_result_t *result) { + observation_retention_result_t local; + if (!result) result = &local; + memset(result, 0, sizeof(*result)); + char root[OBS_PATH_SIZE]; + if (root_dir(storage_path, root, sizeof(root)) != 0) return -1; + if (!atomic_load(&g_startup_done)) return 1; + + asset_row_t *rows = calloc(OBS_PAGE, sizeof(*rows)); + if (!rows) return -1; + pthread_mutex_lock(&g_run_mutex); + int budget = OBS_RETENTION_MAX_EVICTIONS; + int rc = prune_history(now_ms, result); + if (rc == 0) rc = reclaim_unreferenced(root, &budget, result, rows); + + observation_retention_camera_status_t *cameras = NULL; + int camera_count = 0; + if (rc == 0) rc = load_cameras(&cameras, &camera_count); + for (int i = 0; rc == 0 && i < camera_count; ++i) + rc = enforce_camera(root, now_ms, &cameras[i], &budget, result, rows); + if (rc == 0) { + publish_status(cameras, camera_count); + cameras = NULL; + } + free(cameras); + pthread_mutex_unlock(&g_run_mutex); + free(rows); + + observation_meta_t meta; + if (db_observation_meta_get(&meta) == DB_OBS_OK) result->oldest_seq = meta.oldest_seq; + if (result->assets_expired || result->unreferenced_deleted || result->changes_pruned) + log_info("Observation retention: %d media expired, %d files deleted, %d unreferenced " + "assets reclaimed, %d changes and %d observations pruned (oldest seq %lld)", + result->assets_expired, result->files_deleted, result->unreferenced_deleted, + result->changes_pruned, result->observations_pruned, + (long long)result->oldest_seq); + if (result->cameras_budget_exhausted) + log_warn("Observation evidence budget exhausted on %d camera(s): only protected media " + "remains", result->cameras_budget_exhausted); + if (rc != 0) log_error("Observation retention pass failed"); + return rc == 0 ? 0 : -1; +} + +void observation_retention_maintenance_tick(void) { + if (!atomic_load(&g_startup_done)) return; + time_t now = time(NULL); + if (!g_more_work && g_last_tick != 0 && now - g_last_tick < OBS_RETENTION_INTERVAL_SEC) + return; + g_last_tick = now; + observation_retention_result_t result; + int rc = observation_retention_run(g_config.storage_path, wall_ms(), &result); + g_more_work = rc == 0 && result.more_work; +} + +/* ---- budget flag -------------------------------------------------------- */ + +bool observation_retention_budget_exhausted(const char *camera_uuid) { + if (!camera_uuid || atomic_load(&g_exhausted_count) == 0) return false; + if (pthread_mutex_trylock(&g_status_mutex) != 0) return false; + bool exhausted = false; + for (int i = 0; i < g_status_count; ++i) { + if (strcmp(g_status[i].camera_uuid, camera_uuid) == 0) { + exhausted = g_status[i].budget_exhausted; + break; + } + } + pthread_mutex_unlock(&g_status_mutex); + return exhausted; +} + +int observation_retention_budget_exhausted_count(void) { + return atomic_load(&g_exhausted_count); +} + +int observation_retention_camera_status(observation_retention_camera_status_t *out, int max) { + if (!out || max <= 0) return 0; + pthread_mutex_lock(&g_status_mutex); + int n = g_status_count < max ? g_status_count : max; + if (n > 0) memcpy(out, g_status, (size_t)n * sizeof(*out)); + pthread_mutex_unlock(&g_status_mutex); + return n; +} + +void observation_retention_reset_for_testing(void) { + pthread_mutex_lock(&g_run_mutex); + atomic_store(&g_startup_done, 0); + publish_status(NULL, 0); + g_last_tick = 0; + g_more_work = false; + pthread_mutex_unlock(&g_run_mutex); +} diff --git a/src/database/db_core.c b/src/database/db_core.c index 60c98ebb1..1325271f2 100644 --- a/src/database/db_core.c +++ b/src/database/db_core.c @@ -972,6 +972,27 @@ static int private_existing_database_files(const char *path) { return 0; } +/* + * INT_01 D9: same effect as db_observation_meta_regenerate_epoch(), done with + * raw SQL on the init connection because nothing else can use the database + * yet, and so db_core.c does not pull the observation layer (and its + * event-identity dependencies) into tools such as rebuild_recordings. + */ +static int regenerate_observation_epoch_after_restore(void) { + char epoch[LIGHTNVR_UUID_STRING_SIZE]; + if (!db || lightnvr_uuid_generate_v4(epoch) != 0) return -1; + sqlite3_stmt *stmt = NULL; + int rc = -1; + if (sqlite3_prepare_v2(db, "UPDATE observation_meta SET epoch_uuid = ? WHERE id = 1;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, epoch, -1, SQLITE_TRANSIENT); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = 0; + } + sqlite3_finalize(stmt); + if (rc == 0) log_info("Regenerated observation cursor epoch after database restore"); + return rc; +} + int init_database(const char *db_path) { return init_database_ex(db_path, DB_INIT_DEFAULT); } @@ -980,6 +1001,8 @@ int init_database_ex(const char *db_path, unsigned flags) { int rc; char *err_msg = NULL; bool is_new_database = false; + /* D9: a restored backup carries the old observation cursor epoch. */ + bool restored_from_backup = false; sqlite3 *test_db = NULL; sqlite3_stmt *stmt = NULL; @@ -1134,6 +1157,7 @@ int init_database_ex(const char *db_path, unsigned flags) { // Continue anyway, we'll try to create a new database } else { log_info("Successfully restored database from backup"); + restored_from_backup = true; } } else { log_warn("No backup database file found, will create a new database"); @@ -1194,6 +1218,7 @@ int init_database_ex(const char *db_path, unsigned flags) { // Continue anyway, we'll try to repair the database } else { log_info("Successfully restored database from backup"); + restored_from_backup = true; } } else { log_warn("No backup database file found, will attempt to repair"); @@ -1420,6 +1445,15 @@ int init_database_ex(const char *db_path, unsigned flags) { return -1; } + /* INT_01 D9 (contract section 7.6): the restored backup carries the old + * observation_meta.epoch_uuid while its change seq rewinds, so cursors + * issued against the lost history must stop validating. Runs after the + * migrations so observation_meta exists. */ + if (restored_from_backup && !(db_init_flags & DB_INIT_NO_MIGRATE) && + regenerate_observation_epoch_after_restore() != 0) { + log_error("Failed to regenerate the observation cursor epoch after restore"); + } + /* Normalize legacy comma-separated camera tags after the schema exists. * This rewrites every assignment, so it runs once rather than on every * start; both tag writers keep streams.tags and the normalized tables in diff --git a/src/database/db_event_outbox.c b/src/database/db_event_outbox.c index ba0136c82..b25ad8829 100644 --- a/src/database/db_event_outbox.c +++ b/src/database/db_event_outbox.c @@ -498,6 +498,28 @@ int db_event_outbox_prune_terminal(int64_t updated_before, int limit, return rc == SQLITE_DONE ? 0 : -1; } +int db_event_outbox_count_undelivered_type(const char *event_type, + int64_t *count) { + if (!count || !valid_text(event_type, EVENT_TYPE_MAX, true)) return -1; + *count = 0; + sqlite3 *db = get_db_handle(); + pthread_mutex_t *mutex = get_db_mutex(); + if (!db || !mutex) return -1; + pthread_mutex_lock(mutex); + sqlite3_stmt *stmt = NULL; + int rc = sqlite3_prepare_v2( + db, "SELECT count(*) FROM event_outbox WHERE event_type=? AND " + "state IN ('pending','delivering');", -1, &stmt, NULL); + if (rc == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, event_type, -1, SQLITE_TRANSIENT); + rc = sqlite3_step(stmt); + if (rc == SQLITE_ROW) *count = sqlite3_column_int64(stmt, 0); + } + if (stmt) sqlite3_finalize(stmt); + pthread_mutex_unlock(mutex); + return rc == SQLITE_ROW ? 0 : -1; +} + int db_event_outbox_get_stats(const char *destination, int64_t now, event_outbox_stats_t *stats) { if (!stats || (destination && diff --git a/src/database/db_observation_classifications.c b/src/database/db_observation_classifications.c new file mode 100644 index 000000000..d0c19a063 --- /dev/null +++ b/src/database/db_observation_classifications.c @@ -0,0 +1,587 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 P2 classification annotations. See + * include/database/db_observation_classifications.h and contract section 10. + * Writes follow the db_auth.c pattern used by db_observations.c: global DB + * mutex, SQLite connection mutex, BEGIN IMMEDIATE, raw statements plus + * db_observation_bump_revision_locked(), COMMIT or ROLLBACK. Nothing here + * calls a db_* helper that takes the global mutex while it is held. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "database/db_observation_classifications.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "core/event_identity.h" +#include "core/logger.h" +#include "core/observation_relay.h" +#include "database/db_core.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +#define CLS_SOURCE_SIZE 160 + +static const char *const KIND_NAMES[] = {"automated", "owner_confirmed"}; + +const char *observation_classification_kind_name(observation_classification_kind_t kind) { + return (kind == OBS_CLS_KIND_AUTOMATED || kind == OBS_CLS_KIND_OWNER_CONFIRMED) + ? KIND_NAMES[kind] : "automated"; +} + +int observation_classification_kind_parse(const char *name, + observation_classification_kind_t *kind) { + if (!name || !kind) return -1; + for (int i = 0; i < 2; ++i) { + if (strcmp(name, KIND_NAMES[i]) == 0) { + *kind = (observation_classification_kind_t)i; + return 0; + } + } + return -1; +} + +bool observation_classification_segment_is_valid(const char *segment) { + if (!segment) return false; + size_t length = strnlen(segment, OBS_CLS_SEGMENT_MAX + 1); + if (length == 0 || length > OBS_CLS_SEGMENT_MAX) return false; + for (size_t i = 0; i < length; ++i) { + unsigned char c = (unsigned char)segment[i]; + bool alnum = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); + if (i == 0 ? !alnum : !(alnum || c == '_' || c == '.' || c == '-')) return false; + } + return true; +} + +/* ---- validation -------------------------------------------------------- */ + +static bool empty_text(const char *value) { return !value || !value[0]; } + +/* Optional text: NULL/"" or 1..max bytes. */ +static bool optional_len_ok(const char *value, size_t max) { + return empty_text(value) || strnlen(value, max + 1) <= max; +} + +static bool required_len_ok(const char *value, size_t max) { + if (empty_text(value)) return false; + return strnlen(value, max + 1) <= max; +} + +static bool valid_input(const observation_classification_input_t *in) { + return in && + observation_classification_segment_is_valid(in->writer_namespace) && + observation_classification_segment_is_valid(in->name) && + in->writer_user_id > 0 && + required_len_ok(in->classifier, OBS_CLS_CLASSIFIER_SIZE - 1) && + optional_len_ok(in->model_version, OBS_CLS_MODEL_VERSION_SIZE - 1) && + required_len_ok(in->label, OBS_CLS_LABEL_SIZE - 1) && + optional_len_ok(in->taxonomy_id, OBS_CLS_TAXONOMY_SIZE - 1) && + isfinite(in->confidence) && in->confidence >= 0.0 && in->confidence <= 1.0 && + in->classified_at_ms >= 0 && + lightnvr_uuid_is_valid(in->input_asset_uuid) && + in->input_revision >= 1 && + (in->kind == OBS_CLS_KIND_AUTOMATED || in->kind == OBS_CLS_KIND_OWNER_CONFIRMED) && + in->expected_revision >= 0; +} + +/* ---- small helpers ----------------------------------------------------- */ + +static int64_t current_time_ms(void) { + struct timespec now; + if (clock_gettime(CLOCK_REALTIME, &now) != 0) return (int64_t)time(NULL) * 1000; + return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + +static void copy_column(sqlite3_stmt *stmt, int column, char *out, size_t size) { + const unsigned char *value = sqlite3_column_text(stmt, column); + safe_strcpy(out, value ? (const char *)value : "", size, 0); +} + +static void bind_text_or_null(sqlite3_stmt *stmt, int index, const char *value) { + if (!empty_text(value)) sqlite3_bind_text(stmt, index, value, -1, SQLITE_TRANSIENT); + else sqlite3_bind_null(stmt, index); +} + +static bool same_optional(const char *stored, const char *requested) { + return strcmp(stored, requested ? requested : "") == 0; +} + +#define CLS_COLUMNS \ + "key, writer_namespace, writer_user_id, classifier, model_version, label, taxonomy_id, " \ + "confidence, classified_at_ms, input_asset_uuid, input_revision, kind, " \ + "annotation_revision, created_at_ms, updated_at_ms" + +static void read_annotation(sqlite3_stmt *stmt, observation_classification_t *out) { + memset(out, 0, sizeof(*out)); + copy_column(stmt, 0, out->key, sizeof(out->key)); + copy_column(stmt, 1, out->writer_namespace, sizeof(out->writer_namespace)); + out->writer_user_id = sqlite3_column_int64(stmt, 2); + copy_column(stmt, 3, out->classifier, sizeof(out->classifier)); + copy_column(stmt, 4, out->model_version, sizeof(out->model_version)); + copy_column(stmt, 5, out->label, sizeof(out->label)); + copy_column(stmt, 6, out->taxonomy_id, sizeof(out->taxonomy_id)); + out->confidence = sqlite3_column_double(stmt, 7); + out->classified_at_ms = sqlite3_column_int64(stmt, 8); + copy_column(stmt, 9, out->input_asset_uuid, sizeof(out->input_asset_uuid)); + out->input_revision = sqlite3_column_int(stmt, 10); + char kind[24]; + copy_column(stmt, 11, kind, sizeof(kind)); + if (observation_classification_kind_parse(kind, &out->kind) != 0) + out->kind = OBS_CLS_KIND_AUTOMATED; + out->annotation_revision = sqlite3_column_int(stmt, 12); + out->created_at_ms = sqlite3_column_int64(stmt, 13); + out->updated_at_ms = sqlite3_column_int64(stmt, 14); +} + +/* 1 when found, 0 when absent, -1 on error. */ +static int load_annotation(sqlite3 *db, const char *observation_uuid, const char *key, + observation_classification_t *out) { + sqlite3_stmt *stmt = NULL; + int found = -1; + if (sqlite3_prepare_v2(db, "SELECT " CLS_COLUMNS " FROM observation_classifications " + "WHERE observation_uuid = ? AND key = ?;", -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, key, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) { + read_annotation(stmt, out); + found = 1; + } else if (step == SQLITE_DONE) { + found = 0; + } + } + if (found < 0) log_error("Failed to read classification: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return found; +} + +static bool same_payload(const observation_classification_t *stored, + const observation_classification_input_t *in) { + return strcmp(stored->classifier, in->classifier) == 0 && + same_optional(stored->model_version, in->model_version) && + strcmp(stored->label, in->label) == 0 && + same_optional(stored->taxonomy_id, in->taxonomy_id) && + fabs(stored->confidence - in->confidence) <= OBS_CLS_CONFIDENCE_EPSILON && + stored->classified_at_ms == in->classified_at_ms && + strcasecmp(stored->input_asset_uuid, in->input_asset_uuid) == 0 && + stored->input_revision == in->input_revision && + stored->kind == in->kind; +} + +typedef struct { + int revision; + char asset_uuid[OBS_UUID_SIZE]; + observation_asset_state_t state; +} observation_state_t; + +static int load_observation(sqlite3 *db, const char *observation_uuid, observation_state_t *out) { + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_CLS_ERROR; + if (sqlite3_prepare_v2(db, + "SELECT o.revision, a.uuid, a.state FROM object_observations o " + "JOIN observation_assets a ON a.uuid = o.asset_uuid WHERE o.uuid = ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) { + out->revision = sqlite3_column_int(stmt, 0); + copy_column(stmt, 1, out->asset_uuid, sizeof(out->asset_uuid)); + char state[16]; + copy_column(stmt, 2, state, sizeof(state)); + if (observation_asset_state_parse(state, &out->state) != 0) + out->state = OBS_ASSET_UNAVAILABLE; + rc = DB_OBS_CLS_CREATED; /* found */ + } else if (step == SQLITE_DONE) { + rc = DB_OBS_CLS_NOT_FOUND; + } + } + if (rc == DB_OBS_CLS_ERROR) log_error("Failed to read observation: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return rc; +} + +static int count_keys(sqlite3 *db, const char *observation_uuid) { + sqlite3_stmt *stmt = NULL; + int count = -1; + if (sqlite3_prepare_v2(db, "SELECT count(*) FROM observation_classifications " + "WHERE observation_uuid = ?;", -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_uuid, -1, SQLITE_TRANSIENT); + if (sqlite3_step(stmt) == SQLITE_ROW) count = sqlite3_column_int(stmt, 0); + } + sqlite3_finalize(stmt); + return count; +} + +/* Insert (existing == NULL) or replace the stored values of one key. */ +static int store_annotation(sqlite3 *db, const char *observation_uuid, const char *key, + const observation_classification_input_t *in, + const char *asset_uuid, + const observation_classification_t *existing, int64_t now_ms) { + static const char *const INSERT_SQL = + "INSERT INTO observation_classifications (classifier, model_version, label, " + "taxonomy_id, confidence, classified_at_ms, input_asset_uuid, input_revision, kind, " + "updated_at_ms, observation_uuid, key, writer_namespace, writer_user_id, " + "annotation_revision, created_at_ms) " + "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, 1, ?10);"; + static const char *const UPDATE_SQL = + "UPDATE observation_classifications SET classifier = ?1, model_version = ?2, " + "label = ?3, taxonomy_id = ?4, confidence = ?5, classified_at_ms = ?6, " + "input_asset_uuid = ?7, input_revision = ?8, kind = ?9, updated_at_ms = ?10, " + "annotation_revision = annotation_revision + 1 " + "WHERE observation_uuid = ?11 AND key = ?12 AND writer_user_id = ?14 " + "AND annotation_revision = ?15;"; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_CLS_ERROR; + if (sqlite3_prepare_v2(db, existing ? UPDATE_SQL : INSERT_SQL, -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, in->classifier, -1, SQLITE_TRANSIENT); + bind_text_or_null(stmt, 2, in->model_version); + sqlite3_bind_text(stmt, 3, in->label, -1, SQLITE_TRANSIENT); + bind_text_or_null(stmt, 4, in->taxonomy_id); + sqlite3_bind_double(stmt, 5, in->confidence); + sqlite3_bind_int64(stmt, 6, in->classified_at_ms); + sqlite3_bind_text(stmt, 7, asset_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 8, in->input_revision); + sqlite3_bind_text(stmt, 9, observation_classification_kind_name(in->kind), -1, + SQLITE_STATIC); + sqlite3_bind_int64(stmt, 10, now_ms); + sqlite3_bind_text(stmt, 11, observation_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 12, key, -1, SQLITE_TRANSIENT); + if (existing) { + sqlite3_bind_int64(stmt, 14, existing->writer_user_id); + sqlite3_bind_int(stmt, 15, existing->annotation_revision); + } else { + sqlite3_bind_text(stmt, 13, in->writer_namespace, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, 14, in->writer_user_id); + } + if (sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(db) == 1) + rc = existing ? DB_OBS_CLS_UPDATED : DB_OBS_CLS_CREATED; + } + if (rc == DB_OBS_CLS_ERROR) + log_error("Failed to store classification: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return rc; +} + +/* ---- write transaction (db_auth.c pattern) ------------------------------ */ + +typedef struct { + pthread_mutex_t *mutex; + sqlite3 *db; + sqlite3_mutex *connection_mutex; +} cls_tx_t; + +static int tx_begin(cls_tx_t *tx) { + tx->mutex = get_db_mutex(); + if (!tx->mutex) return -1; + pthread_mutex_lock(tx->mutex); + tx->db = get_db_handle(); + if (!tx->db) { + pthread_mutex_unlock(tx->mutex); + return -1; + } + tx->connection_mutex = sqlite3_db_mutex(tx->db); + sqlite3_mutex_enter(tx->connection_mutex); + if (sqlite3_exec(tx->db, "BEGIN IMMEDIATE;", NULL, NULL, NULL) != SQLITE_OK) { + log_error("Failed to begin classification transaction: %s", sqlite3_errmsg(tx->db)); + sqlite3_mutex_leave(tx->connection_mutex); + pthread_mutex_unlock(tx->mutex); + return -1; + } + return 0; +} + +/* Commits when commit is true; otherwise rolls back. Returns false when the + * commit failed (and was rolled back). */ +static bool tx_end(cls_tx_t *tx, bool commit) { + bool ok = true; + if (commit && sqlite3_exec(tx->db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) { + log_error("Failed to commit classification transaction: %s", sqlite3_errmsg(tx->db)); + ok = false; + } + if (!commit || !ok) sqlite3_exec(tx->db, "ROLLBACK;", NULL, NULL, NULL); + sqlite3_mutex_leave(tx->connection_mutex); + pthread_mutex_unlock(tx->mutex); + return ok; +} + +/* Rules 2 and 4-8 of contract section 10.4, in order, on the open + * transaction. Returns the outcome; only CREATED/UPDATED leave writes. */ +static int put_locked(sqlite3 *db, const char *source, const char *observation_uuid, + const char *key, const observation_classification_input_t *in, + observation_classification_result_t *result) { + observation_state_t obs; + int rc = load_observation(db, observation_uuid, &obs); + if (rc != DB_OBS_CLS_CREATED) return rc; + result->observation_revision = obs.revision; + safe_strcpy(result->observation_asset_uuid, obs.asset_uuid, + sizeof(result->observation_asset_uuid), 0); + result->evidence_state = obs.state; + + observation_classification_t existing; + int found = load_annotation(db, observation_uuid, key, &existing); + if (found < 0) return DB_OBS_CLS_ERROR; + + /* 2: authority is pinned to the user that created the key (D3). */ + if (found && (existing.writer_user_id != in->writer_user_id || + strcmp(existing.writer_namespace, in->writer_namespace) != 0)) + return DB_OBS_CLS_WRITER_MISMATCH; + + /* 4: the annotation must describe this observation's evidence. */ + if (strcasecmp(in->input_asset_uuid, obs.asset_uuid) != 0 || + in->input_revision > obs.revision || + (obs.state != OBS_ASSET_READY && obs.state != OBS_ASSET_EXPIRED)) + return DB_OBS_CLS_EVIDENCE_MISMATCH; + + if (found) { + result->has_annotation = true; + result->annotation = existing; + /* 5: identical retry, regardless of expected_revision. */ + if (same_payload(&existing, in)) return DB_OBS_CLS_NOOP; + /* 6: owner decisions are not replaced by automated results. */ + if (existing.kind == OBS_CLS_KIND_OWNER_CONFIRMED && in->kind == OBS_CLS_KIND_AUTOMATED) + return DB_OBS_CLS_OWNER_LOCKED; + /* 7: compare-and-set on the annotation revision. */ + if (in->expected_revision != existing.annotation_revision) + return DB_OBS_CLS_REVISION_CONFLICT; + } else { + if (in->expected_revision != 0) return DB_OBS_CLS_REVISION_CONFLICT; + /* Section 10.5 limit, checked at step 8 before the insert. */ + int keys = count_keys(db, observation_uuid); + if (keys < 0) return DB_OBS_CLS_ERROR; + if (keys >= OBS_CLS_MAX_PER_OBSERVATION) return DB_OBS_CLS_LIMIT; + } + + /* 8: store, then bump the observation revision (change + publication). */ + int64_t now_ms = current_time_ms(); + int stored = store_annotation(db, observation_uuid, key, in, obs.asset_uuid, + found ? &existing : NULL, now_ms); + if (stored != DB_OBS_CLS_CREATED && stored != DB_OBS_CLS_UPDATED) return DB_OBS_CLS_ERROR; + + int64_t seq = 0; + int revision = 0; + if (db_observation_bump_revision_locked(db, source, observation_uuid, + OBS_CAUSE_CLASSIFICATION, now_ms, + &seq, &revision) != DB_OBS_OK) + return DB_OBS_CLS_ERROR; + + observation_classification_t written; + if (load_annotation(db, observation_uuid, key, &written) != 1) return DB_OBS_CLS_ERROR; + result->has_annotation = true; + result->annotation = written; + result->observation_revision = revision; + result->change_seq = seq; + return stored; +} + +int db_observation_classification_put(const char *observation_uuid, + const observation_classification_input_t *input, + observation_classification_result_t *result) { + if (!result) return DB_OBS_CLS_INVALID; + memset(result, 0, sizeof(*result)); + result->limit = OBS_CLS_MAX_PER_OBSERVATION; + if (!lightnvr_uuid_is_valid(observation_uuid) || !valid_input(input)) + return DB_OBS_CLS_INVALID; + + char key[OBS_CLS_KEY_SIZE]; + int written = snprintf(key, sizeof(key), "%s/%s", input->writer_namespace, input->name); + if (written < 0 || (size_t)written >= sizeof(key)) return DB_OBS_CLS_INVALID; + + char source[CLS_SOURCE_SIZE]; + if (event_identity_get_source(source, sizeof(source)) != 0) { + log_error("Classification write needs the event installation identity"); + return DB_OBS_CLS_ERROR; + } + + cls_tx_t tx; + if (tx_begin(&tx) != 0) return DB_OBS_CLS_ERROR; + int rc = put_locked(tx.db, source, observation_uuid, key, input, result); + bool changed = rc == DB_OBS_CLS_CREATED || rc == DB_OBS_CLS_UPDATED; + if (!tx_end(&tx, changed)) rc = DB_OBS_CLS_ERROR; + + if (rc == DB_OBS_CLS_CREATED || rc == DB_OBS_CLS_UPDATED) { + observation_relay_notify(); + } else if (rc < 0 && rc != DB_OBS_CLS_NOT_FOUND) { + /* Nothing was written; never report values from a rolled-back write. */ + memset(result, 0, sizeof(*result)); + result->limit = OBS_CLS_MAX_PER_OBSERVATION; + } else if (rc == DB_OBS_CLS_WRITER_MISMATCH) { + result->has_annotation = false; + memset(&result->annotation, 0, sizeof(result->annotation)); + } + return rc; +} + +/* ---- reads ------------------------------------------------------------- */ + +#define CLS_LIST_SQL \ + "SELECT " CLS_COLUMNS " FROM observation_classifications " \ + "WHERE observation_uuid = ? ORDER BY updated_at_ms DESC, key ASC;" + +/* One observation's annotations through an already prepared CLS_LIST_SQL. */ +static int list_with(sqlite3_stmt *stmt, const char *observation_uuid, + observation_classification_set_t *out) { + memset(out, 0, sizeof(*out)); + sqlite3_reset(stmt); + sqlite3_clear_bindings(stmt); + sqlite3_bind_text(stmt, 1, observation_uuid, -1, SQLITE_TRANSIENT); + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + observation_classification_t row; + read_annotation(stmt, &row); + if (row.kind == OBS_CLS_KIND_OWNER_CONFIRMED) { + if (out->owner_confirmed_count < OBS_CLS_MAX_PER_OBSERVATION) + out->owner_confirmed[out->owner_confirmed_count++] = row; + } else if (out->automated_count < OBS_CLS_MAX_PER_OBSERVATION) { + out->automated[out->automated_count++] = row; + } + } + if (step == SQLITE_DONE) return DB_OBS_OK; + memset(out, 0, sizeof(*out)); + return DB_OBS_ERROR; +} + +int db_observation_classifications_list(const char *observation_uuid, + observation_classification_set_t *out) { + if (!out || !lightnvr_uuid_is_valid(observation_uuid)) return DB_OBS_INVALID; + memset(out, 0, sizeof(*out)); + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, CLS_LIST_SQL, -1, &stmt, NULL) == SQLITE_OK) + rc = list_with(stmt, observation_uuid, out); + if (rc != DB_OBS_OK) log_error("Failed to list classifications: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + return rc; +} + +int db_observation_classifications_list_many(const char *const *observation_uuids, size_t count, + observation_classification_visit_fn visit, + void *context) { + if (!visit || (count > 0 && !observation_uuids)) return DB_OBS_INVALID; + for (size_t i = 0; i < count; ++i) + if (!lightnvr_uuid_is_valid(observation_uuids[i])) return DB_OBS_INVALID; + if (count == 0) return DB_OBS_OK; + observation_classification_set_t *set = malloc(sizeof(*set)); + if (!set) return DB_OBS_ERROR; + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) { + free(set); + return DB_OBS_ERROR; + } + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, CLS_LIST_SQL, -1, &stmt, NULL) == SQLITE_OK) { + rc = DB_OBS_OK; + for (size_t i = 0; rc == DB_OBS_OK && i < count; ++i) { + if (list_with(stmt, observation_uuids[i], set) != DB_OBS_OK) { + log_error("Failed to list classifications: %s", sqlite3_errmsg(db)); + rc = DB_OBS_ERROR; + break; + } + int visited = visit(i, set, context); + if (visited != 0) rc = visited; + } + } else { + log_error("Failed to list classifications: %s", sqlite3_errmsg(db)); + } + sqlite3_finalize(stmt); + db_close_readonly_connection(db); + free(set); + return rc; +} + +/* ---- detection -> observation link (L15) -------------------------------- */ + +/* ?1 camera, ?2/?3 observed_at_ms bounds of the +/-1 s window (so the + * (camera_uuid, observed_at_ms) index serves the range), ?4 label, ?5 the + * detection second. Nearest second first, then earliest, then UUID. */ +#define LINK_MATCH_SQL \ + "SELECT uuid FROM object_observations " \ + "WHERE camera_uuid = ?1 AND observed_at_ms BETWEEN ?2 AND ?3 " \ + "AND label = ?4 COLLATE NOCASE " \ + "ORDER BY abs((observed_at_ms / 1000) - ?5), observed_at_ms, uuid LIMIT 1;" + +static int link_one(sqlite3_stmt *match, sqlite3_stmt *list, const char *camera_uuid, + const observation_detection_ref_t *ref, observation_classification_set_t *set, + observation_detection_link_t *out) { + memset(out, 0, sizeof(*out)); + if (!ref->label || !ref->label[0]) return DB_OBS_OK; + /* Keep the arithmetic far from int64 overflow; such timestamps never match. */ + if (ref->timestamp < 0 || ref->timestamp > INT64_MAX / 2000) return DB_OBS_OK; + int64_t window = OBS_DETECTION_LINK_WINDOW_SECONDS; + sqlite3_reset(match); + sqlite3_clear_bindings(match); + sqlite3_bind_text(match, 1, camera_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(match, 2, (ref->timestamp - window) * 1000); + sqlite3_bind_int64(match, 3, (ref->timestamp + window) * 1000 + 999); + sqlite3_bind_text(match, 4, ref->label, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(match, 5, ref->timestamp); + int step = sqlite3_step(match); + if (step == SQLITE_DONE) return DB_OBS_OK; + if (step != SQLITE_ROW) return DB_OBS_ERROR; + copy_column(match, 0, out->observation_uuid, sizeof(out->observation_uuid)); + if (!lightnvr_uuid_is_valid(out->observation_uuid)) { + memset(out, 0, sizeof(*out)); + return DB_OBS_OK; + } + out->linked = true; + if (list_with(list, out->observation_uuid, set) != DB_OBS_OK) return DB_OBS_ERROR; + if (set->owner_confirmed_count > 0) { + out->owner_confirmed = true; + out->has_classification = true; + out->classification = set->owner_confirmed[0]; + } else if (set->automated_count > 0) { + out->has_classification = true; + out->classification = set->automated[0]; + } + return DB_OBS_OK; +} + +int db_observation_link_detections(const char *camera_uuid, + const observation_detection_ref_t *refs, size_t count, + observation_detection_link_t *out) { + if (count == 0) return DB_OBS_OK; + if (!refs || !out) return DB_OBS_INVALID; + memset(out, 0, count * sizeof(*out)); + if (!lightnvr_uuid_is_valid(camera_uuid)) return DB_OBS_INVALID; + observation_classification_set_t *set = malloc(sizeof(*set)); + if (!set) return DB_OBS_ERROR; + sqlite3 *db = NULL; + if (db_open_readonly_connection(&db) != 0) { + free(set); + return DB_OBS_ERROR; + } + sqlite3_stmt *match = NULL; + sqlite3_stmt *list = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, LINK_MATCH_SQL, -1, &match, NULL) == SQLITE_OK && + sqlite3_prepare_v2(db, CLS_LIST_SQL, -1, &list, NULL) == SQLITE_OK) { + rc = DB_OBS_OK; + for (size_t i = 0; rc == DB_OBS_OK && i < count; ++i) + rc = link_one(match, list, camera_uuid, &refs[i], set, &out[i]); + } + if (rc != DB_OBS_OK) { + log_error("Failed to link detections to observations: %s", sqlite3_errmsg(db)); + memset(out, 0, count * sizeof(*out)); + } + sqlite3_finalize(match); + sqlite3_finalize(list); + db_close_readonly_connection(db); + free(set); + return rc; +} diff --git a/src/database/db_observations.c b/src/database/db_observations.c new file mode 100644 index 000000000..65fcb62c0 --- /dev/null +++ b/src/database/db_observations.c @@ -0,0 +1,1421 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* + * INT_01 observation DB layer. See include/database/db_observations.h and + * docs/integrations/OBSERVATIONS_CONTRACT.md. Writes follow the db_auth.c + * pattern: global DB mutex, SQLite connection mutex, BEGIN IMMEDIATE, raw + * statements only, COMMIT or ROLLBACK. Nothing here calls a db_* helper that + * takes the global mutex while it is held. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "database/db_observations.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/event_identity.h" +#include "core/logger.h" +#include "database/db_core.h" +#include "utils/base64.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +#define OBS_SOURCE_SIZE 160 +#define OBS_PRUNE_MAX_BATCH 10000 +#define OBS_MAX_SAFE_SEQ 9007199254740991LL /* 2^53 - 1: exact in JSON numbers */ + +/* ---- enums and validation --------------------------------------------- */ + +static const char *const STATE_NAMES[] = {"pending", "ready", "unavailable", "expired"}; +static const char *const CAUSE_NAMES[] = {"created", "evidence", "classification"}; +static const char *const PUBLICATION_NAMES[] = { + "pending", "relayed", "relayed_no_route", "expired", "failed" +}; + +const char *observation_asset_state_name(observation_asset_state_t state) { + return (unsigned)state < 4 ? STATE_NAMES[state] : NULL; +} + +int observation_asset_state_parse(const char *name, observation_asset_state_t *state) { + if (!name || !state) return -1; + for (int i = 0; i < 4; ++i) { + if (strcmp(name, STATE_NAMES[i]) == 0) { + *state = (observation_asset_state_t)i; + return 0; + } + } + return -1; +} + +const char *observation_change_cause_name(observation_change_cause_t cause) { + return (unsigned)cause < 3 ? CAUSE_NAMES[cause] : NULL; +} + +static int cause_parse(const char *name, observation_change_cause_t *cause) { + for (int i = 0; name && i < 3; ++i) { + if (strcmp(name, CAUSE_NAMES[i]) == 0) { + *cause = (observation_change_cause_t)i; + return 0; + } + } + return -1; +} + +const char *observation_publication_state_name(observation_publication_state_t state) { + return (unsigned)state < 5 ? PUBLICATION_NAMES[state] : NULL; +} + +/* Contract section 4.3. */ +static const struct { + const char *reason; + observation_asset_state_t state; +} REASONS[] = { + {"queue_full", OBS_ASSET_UNAVAILABLE}, + {"budget_exhausted", OBS_ASSET_UNAVAILABLE}, + {"encode_failed", OBS_ASSET_UNAVAILABLE}, + {"write_failed", OBS_ASSET_UNAVAILABLE}, + {"interrupted", OBS_ASSET_UNAVAILABLE}, + {"no_matching_media", OBS_ASSET_UNAVAILABLE}, + {"disabled", OBS_ASSET_UNAVAILABLE}, + {"retention_expired", OBS_ASSET_EXPIRED}, + {"deleted", OBS_ASSET_EXPIRED}, +}; + +static bool empty_text(const char *value) { return !value || !value[0]; } + +bool observation_reason_is_valid(observation_asset_state_t state, const char *reason) { + if (state == OBS_ASSET_PENDING || state == OBS_ASSET_READY) return empty_text(reason); + if (state != OBS_ASSET_UNAVAILABLE && state != OBS_ASSET_EXPIRED) return false; + if (empty_text(reason)) return false; + for (size_t i = 0; i < sizeof(REASONS) / sizeof(REASONS[0]); ++i) { + if (strcmp(reason, REASONS[i].reason) == 0) return REASONS[i].state == state; + } + return false; +} + +static bool reason_in(const char *reason, const char *const *allowed, size_t count) { + for (size_t i = 0; reason && i < count; ++i) + if (strcmp(reason, allowed[i]) == 0) return true; + return false; +} + +/* Section 4.2 edges. Direct-creation reasons (queue_full, budget_exhausted, + * no_matching_media) are never transition targets. */ +static bool transition_allowed(observation_asset_state_t from, observation_asset_state_t to, + const char *reason) { + static const char *const pending_unavailable[] = { + "encode_failed", "write_failed", "interrupted", "disabled" + }; + static const char *const ready_unavailable[] = {"write_failed"}; + static const char *const ready_expired[] = {"retention_expired", "deleted"}; + if (from == OBS_ASSET_PENDING && to == OBS_ASSET_READY) return empty_text(reason); + if (from == OBS_ASSET_PENDING && to == OBS_ASSET_UNAVAILABLE) + return reason_in(reason, pending_unavailable, 4); + if (from == OBS_ASSET_READY && to == OBS_ASSET_UNAVAILABLE) + return reason_in(reason, ready_unavailable, 1); + if (from == OBS_ASSET_READY && to == OBS_ASSET_EXPIRED) + return reason_in(reason, ready_expired, 2); + return false; +} + +static bool terminated(const char *value, size_t size) { + return value && memchr(value, '\0', size) != NULL; +} + +static bool text_len_between(const char *value, size_t size, size_t min, size_t max) { + if (!terminated(value, size)) return false; + size_t length = strlen(value); + return length >= min && length <= max; +} + +static int64_t current_time_ms(void) { + struct timespec now; + if (clock_gettime(CLOCK_REALTIME, &now) != 0) return (int64_t)time(NULL) * 1000; + return (int64_t)now.tv_sec * 1000 + now.tv_nsec / 1000000; +} + +static void copy_column(sqlite3_stmt *stmt, int column, char *out, size_t size) { + const unsigned char *value = sqlite3_column_text(stmt, column); + safe_strcpy(out, value ? (const char *)value : "", size, 0); +} + +static void bind_text_or_null(sqlite3_stmt *stmt, int index, const char *value) { + if (value && value[0]) sqlite3_bind_text(stmt, index, value, -1, SQLITE_TRANSIENT); + else sqlite3_bind_null(stmt, index); +} + +static void bind_positive_or_null(sqlite3_stmt *stmt, int index, int64_t value) { + if (value > 0) sqlite3_bind_int64(stmt, index, value); + else sqlite3_bind_null(stmt, index); +} + +/* ---- identifiers and cursor ------------------------------------------- */ + +int observation_message_id(const char *source, const char *observation_uuid, + int revision, char out[OBS_UUID_SIZE]) { + if (!out || empty_text(source) || !lightnvr_uuid_is_valid(observation_uuid) || + revision < 1) + return -1; + char name[OBS_SOURCE_SIZE + OBS_UUID_SIZE + 24]; + int written = snprintf(name, sizeof(name), "%s|%s|%d", source, observation_uuid, revision); + if (written < 0 || (size_t)written >= sizeof(name)) return -1; + return lightnvr_uuid_generate_v5(OBS_MESSAGE_ID_NAMESPACE, name, (size_t)written, out); +} + +int observation_cursor_encode(int64_t seq, const char *epoch_uuid, char *out, size_t out_size) { + if (!out || out_size == 0) return -1; + out[0] = '\0'; + if (seq < 0 || seq > OBS_MAX_SAFE_SEQ || !lightnvr_uuid_is_valid(epoch_uuid)) return -1; + char json[128]; + int written = snprintf(json, sizeof(json), "{\"v\":1,\"seq\":%lld,\"epoch\":\"%s\"}", + (long long)seq, epoch_uuid); + if (written < 0 || (size_t)written >= sizeof(json)) return -1; + return base64url_encode((const unsigned char *)json, (size_t)written, out, out_size, NULL); +} + +static bool json_integer(const cJSON *item, double min, double max, int64_t *value) { + if (!cJSON_IsNumber(item)) return false; + double number = item->valuedouble; + if (!isfinite(number) || number < min || number > max || floor(number) != number) + return false; + *value = (int64_t)number; + return true; +} + +int observation_cursor_decode(const char *cursor, int64_t *seq, char epoch_uuid[OBS_UUID_SIZE]) { + if (!cursor || !seq || !epoch_uuid) return DB_OBS_INVALID_CURSOR; + size_t length = strnlen(cursor, OBS_CURSOR_SIZE); + if (length == 0 || length > OBS_CURSOR_SIZE - 1) return DB_OBS_INVALID_CURSOR; + unsigned char json[OBS_CURSOR_SIZE]; + size_t json_length = 0; + if (base64url_decode(cursor, length, json, sizeof(json) - 1, &json_length) != 0 || + memchr(json, '\0', json_length) != NULL) + return DB_OBS_INVALID_CURSOR; + json[json_length] = '\0'; + + cJSON *root = cJSON_ParseWithLength((const char *)json, json_length); + int result = DB_OBS_INVALID_CURSOR; + int64_t version = 0, value = 0; + const cJSON *epoch = root ? cJSON_GetObjectItemCaseSensitive(root, "epoch") : NULL; + if (cJSON_IsObject(root) && + json_integer(cJSON_GetObjectItemCaseSensitive(root, "v"), 1, 1, &version) && + json_integer(cJSON_GetObjectItemCaseSensitive(root, "seq"), 0, + (double)OBS_MAX_SAFE_SEQ, &value) && + cJSON_IsString(epoch) && lightnvr_uuid_is_valid(epoch->valuestring)) { + *seq = value; + safe_strcpy(epoch_uuid, epoch->valuestring, OBS_UUID_SIZE, 0); + result = DB_OBS_OK; + } + cJSON_Delete(root); + return result; +} + +/* ---- post-commit publication hook (L5) ------------------------------- */ + +static db_observation_publication_hook_t publication_hook = NULL; + +void db_observations_set_publication_hook(db_observation_publication_hook_t hook) { + __atomic_store_n(&publication_hook, hook, __ATOMIC_RELEASE); +} + +static void notify_publications_committed(void) { + db_observation_publication_hook_t hook = + __atomic_load_n(&publication_hook, __ATOMIC_ACQUIRE); + if (hook) hook(); +} + +/* ---- write transactions (db_auth.c pattern) ---------------------------- */ + +typedef struct { + pthread_mutex_t *mutex; + sqlite3 *db; + sqlite3_mutex *connection_mutex; +} obs_tx_t; + +static int tx_begin(obs_tx_t *tx) { + tx->mutex = get_db_mutex(); + if (!tx->mutex) return -1; + pthread_mutex_lock(tx->mutex); + tx->db = get_db_handle(); + if (!tx->db) { + pthread_mutex_unlock(tx->mutex); + return -1; + } + /* Legacy helpers use the shared connection without get_db_mutex(); the + * connection mutex keeps their statements out of this transaction. */ + tx->connection_mutex = sqlite3_db_mutex(tx->db); + sqlite3_mutex_enter(tx->connection_mutex); + if (sqlite3_exec(tx->db, "BEGIN IMMEDIATE;", NULL, NULL, NULL) != SQLITE_OK) { + log_error("Failed to begin observation transaction: %s", sqlite3_errmsg(tx->db)); + sqlite3_mutex_leave(tx->connection_mutex); + pthread_mutex_unlock(tx->mutex); + return -1; + } + return 0; +} + +/* Commits when rc is DB_OBS_OK; otherwise rolls back. Returns the final rc. */ +static int tx_end(obs_tx_t *tx, int rc) { + if (rc == DB_OBS_OK && sqlite3_exec(tx->db, "COMMIT;", NULL, NULL, NULL) != SQLITE_OK) { + log_error("Failed to commit observation transaction: %s", sqlite3_errmsg(tx->db)); + rc = DB_OBS_ERROR; + } + if (rc != DB_OBS_OK) sqlite3_exec(tx->db, "ROLLBACK;", NULL, NULL, NULL); + sqlite3_mutex_leave(tx->connection_mutex); + pthread_mutex_unlock(tx->mutex); + return rc; +} + +static int get_source(char source[OBS_SOURCE_SIZE]) { + if (event_identity_get_source(source, OBS_SOURCE_SIZE) != 0) { + log_error("Observation write needs the event installation identity"); + return -1; + } + return 0; +} + +/* Insert one change row (state snapshot at this revision) and its pending + * publication with the deterministic message ID. */ +static int write_change_locked(sqlite3 *db, const char *source, const char *observation_uuid, + int revision, observation_change_cause_t cause, + const char *evidence_state, const char *evidence_reason, + int64_t now_ms, int64_t *seq_out) { + char message_id[OBS_UUID_SIZE]; + if (observation_message_id(source, observation_uuid, revision, message_id) != 0) + return DB_OBS_ERROR; + + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "INSERT INTO observation_changes (observation_uuid, revision, cause, " + "evidence_state, evidence_reason, created_at_ms) VALUES (?, ?, ?, ?, ?, ?);", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 2, revision); + sqlite3_bind_text(stmt, 3, observation_change_cause_name(cause), -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, evidence_state, -1, SQLITE_TRANSIENT); + bind_text_or_null(stmt, 5, evidence_reason); + sqlite3_bind_int64(stmt, 6, now_ms); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + if (rc != DB_OBS_OK) { + log_error("Failed to insert observation change: %s", sqlite3_errmsg(db)); + return rc; + } + int64_t seq = sqlite3_last_insert_rowid(db); + + rc = DB_OBS_ERROR; + stmt = NULL; + if (sqlite3_prepare_v2(db, + "INSERT INTO observation_publications (change_seq, message_id, state, attempts, " + "updated_at_ms) VALUES (?, ?, 'pending', 0, ?);", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, seq); + sqlite3_bind_text(stmt, 2, message_id, -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, 3, now_ms); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + if (rc != DB_OBS_OK) { + log_error("Failed to insert observation publication: %s", sqlite3_errmsg(db)); + return rc; + } + if (seq_out) *seq_out = seq; + return DB_OBS_OK; +} + +int db_observation_bump_revision_locked(sqlite3 *db, const char *source, + const char *observation_uuid, + observation_change_cause_t cause, + int64_t now_ms, + int64_t *seq_out, int *revision_out) { + if (!db || empty_text(source) || !lightnvr_uuid_is_valid(observation_uuid) || + (cause != OBS_CAUSE_EVIDENCE && cause != OBS_CAUSE_CLASSIFICATION)) + return DB_OBS_INVALID; + + sqlite3_stmt *stmt = NULL; + int revision = 0; + char state[OBS_ENGINE_TYPE_SIZE] = ""; + char reason[OBS_REASON_SIZE] = ""; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "SELECT o.revision, a.state, a.reason FROM object_observations o " + "JOIN observation_assets a ON a.uuid = o.asset_uuid WHERE o.uuid = ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) { + revision = sqlite3_column_int(stmt, 0); + copy_column(stmt, 1, state, sizeof(state)); + copy_column(stmt, 2, reason, sizeof(reason)); + rc = DB_OBS_OK; + } else if (step == SQLITE_DONE) { + rc = DB_OBS_NOT_FOUND; + } + } + sqlite3_finalize(stmt); + if (rc != DB_OBS_OK) return rc; + + rc = DB_OBS_ERROR; + stmt = NULL; + if (sqlite3_prepare_v2(db, + "UPDATE object_observations SET revision = ? WHERE uuid = ? AND revision = ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int(stmt, 1, revision + 1); + sqlite3_bind_text(stmt, 2, observation_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 3, revision); + if (sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(db) == 1) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + if (rc != DB_OBS_OK) return rc; + + rc = write_change_locked(db, source, observation_uuid, revision + 1, cause, state, + reason, now_ms, seq_out); + if (rc == DB_OBS_OK && revision_out) *revision_out = revision + 1; + return rc; +} + +/* ---- insert ------------------------------------------------------------ */ + +static double round6(double value) { return round(value * 1e6) / 1e6; } + +static bool valid_box(const observation_object_input_t *object) { + if (!object->has_box) return true; + double x = object->box_x, y = object->box_y, w = object->box_width, h = object->box_height; + return isfinite(x) && isfinite(y) && isfinite(w) && isfinite(h) && + x >= 0.0 && y >= 0.0 && w > 0.0 && h > 0.0 && + x + w <= 1.000001 && y + h <= 1.000001; +} + +static bool valid_object(const observation_object_input_t *object) { + if (object->object_index < 0 || object->object_index >= OBS_MAX_BATCH) return false; + if (!text_len_between(object->label, sizeof(object->label), 1, OBS_LABEL_SIZE - 1)) + return false; + if (!isfinite(object->confidence) || object->confidence < 0.0 || object->confidence > 1.0) + return false; + if (!valid_box(object)) return false; + if (object->has_track && + (!text_len_between(object->track_id, sizeof(object->track_id), 1, 64) || + !text_len_between(object->track_namespace, sizeof(object->track_namespace), 1, 64) || + !lightnvr_uuid_is_valid(object->track_session))) + return false; + return true; +} + +static bool valid_engine_type(const char *type) { + static const char *const types[] = {"object", "api", "onvif", "external"}; + return reason_in(type, types, 4); +} + +static bool valid_batch(const observation_batch_input_t *batch) { + if (!batch || !terminated(batch->batch_id, sizeof(batch->batch_id)) || + !lightnvr_uuid_is_valid(batch->batch_id) || + !terminated(batch->camera_uuid, sizeof(batch->camera_uuid)) || + !lightnvr_uuid_is_valid(batch->camera_uuid) || + !text_len_between(batch->stream_name, sizeof(batch->stream_name), 0, 63) || + batch->observed_at_ms <= 0 || + !text_len_between(batch->engine_key, sizeof(batch->engine_key), 1, 64) || + !terminated(batch->engine_type, sizeof(batch->engine_type)) || + !valid_engine_type(batch->engine_type) || + !text_len_between(batch->model, sizeof(batch->model), 0, 128) || + strchr(batch->model, '/') || strchr(batch->model, '\\') || + !text_len_between(batch->model_version, sizeof(batch->model_version), 0, 64) || + batch->recording_id < 0 || + batch->object_count < 1 || batch->object_count > OBS_MAX_BATCH) + return false; + bool used[OBS_MAX_BATCH] = {false}; + for (int i = 0; i < batch->object_count; ++i) { + const observation_object_input_t *object = &batch->objects[i]; + if (!valid_object(object) || used[object->object_index]) return false; + used[object->object_index] = true; + } + return true; +} + +static bool valid_asset_input(const observation_asset_input_t *asset) { + static const char *const origins[] = {"inference_input", "submitted_image"}; + if (!asset || !terminated(asset->asset_uuid, sizeof(asset->asset_uuid)) || + (asset->asset_uuid[0] && !lightnvr_uuid_is_valid(asset->asset_uuid)) || + !terminated(asset->reason, sizeof(asset->reason)) || + !terminated(asset->origin, sizeof(asset->origin)) || + !terminated(asset->media_type, sizeof(asset->media_type)) || + !terminated(asset->transform_json, sizeof(asset->transform_json)) || + asset->captured_at_ms < 0 || asset->expires_at_ms < 0) + return false; + if (asset->state != OBS_ASSET_PENDING && asset->state != OBS_ASSET_UNAVAILABLE) return false; + if (!observation_reason_is_valid(asset->state, asset->reason)) return false; + if (asset->origin[0] && !reason_in(asset->origin, origins, 2)) return false; + if (asset->media_type[0] && strcmp(asset->media_type, "image/jpeg") != 0) return false; + return true; +} + +/* Returns DB_OBS_DUPLICATE with result filled, DB_OBS_OK when the batch is + * new, DB_OBS_CONFLICT when another camera owns the batch ID. */ +static int find_existing_batch(sqlite3 *db, const observation_batch_input_t *batch, + observation_insert_result_t *result) { + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, + "SELECT uuid, asset_uuid, camera_uuid FROM object_observations " + "WHERE batch_id = ? ORDER BY object_index;", -1, &stmt, NULL) != SQLITE_OK) + return DB_OBS_ERROR; + sqlite3_bind_text(stmt, 1, batch->batch_id, -1, SQLITE_TRANSIENT); + int rc = DB_OBS_OK; + int count = 0; + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + char camera[OBS_UUID_SIZE]; + copy_column(stmt, 2, camera, sizeof(camera)); + if (strcmp(camera, batch->camera_uuid) != 0) { + rc = DB_OBS_CONFLICT; + break; + } + if (count < OBS_MAX_BATCH) { + copy_column(stmt, 0, result->observation_uuids[count], OBS_UUID_SIZE); + if (count == 0) copy_column(stmt, 1, result->asset_uuid, OBS_UUID_SIZE); + count++; + } + rc = DB_OBS_DUPLICATE; + } + if (step != SQLITE_ROW && step != SQLITE_DONE) rc = DB_OBS_ERROR; + sqlite3_finalize(stmt); + if (rc == DB_OBS_DUPLICATE) result->object_count = count; + return rc; +} + +static int resolve_stream_name(sqlite3 *db, const char *camera_uuid, char *name, size_t size) { + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, "SELECT name FROM streams WHERE camera_uuid = ?;", + -1, &stmt, NULL) != SQLITE_OK) + return DB_OBS_ERROR; + sqlite3_bind_text(stmt, 1, camera_uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(stmt); + int rc = step == SQLITE_ROW ? DB_OBS_OK : step == SQLITE_DONE ? DB_OBS_NOT_FOUND : DB_OBS_ERROR; + if (rc == DB_OBS_OK) copy_column(stmt, 0, name, size); + sqlite3_finalize(stmt); + return rc; +} + +static int insert_asset_row(sqlite3 *db, const char *asset_uuid, + const observation_batch_input_t *batch, + const observation_asset_input_t *asset, int64_t now_ms) { + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "INSERT INTO observation_assets (uuid, camera_uuid, state, reason, origin, " + "media_type, captured_at_ms, transform_json, protected, expires_at_ms, " + "created_at_ms, updated_at_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?);", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, asset_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, batch->camera_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, observation_asset_state_name(asset->state), -1, SQLITE_STATIC); + bind_text_or_null(stmt, 4, asset->reason); + bind_text_or_null(stmt, 5, asset->origin); + bind_text_or_null(stmt, 6, asset->media_type); + sqlite3_bind_int64(stmt, 7, asset->captured_at_ms > 0 ? asset->captured_at_ms + : batch->observed_at_ms); + bind_text_or_null(stmt, 8, asset->transform_json); + bind_positive_or_null(stmt, 9, asset->expires_at_ms); + sqlite3_bind_int64(stmt, 10, now_ms); + sqlite3_bind_int64(stmt, 11, now_ms); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = DB_OBS_OK; + } + if (rc != DB_OBS_OK) log_error("Failed to insert observation asset: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return rc; +} + +static int insert_observation_row(sqlite3 *db, const char *uuid, const char *asset_uuid, + const char *stream_name, + const observation_batch_input_t *batch, + const observation_object_input_t *object, int64_t now_ms) { + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "INSERT OR IGNORE INTO object_observations (uuid, camera_uuid, " + "stream_name_at_capture, batch_id, object_index, observed_at_ms, created_at_ms, " + "engine_key, engine_type, model, model_version, label, confidence, box_x, box_y, " + "box_width, box_height, track_id, track_namespace, track_session, recording_id, " + "asset_uuid, revision) VALUES " + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1);", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, batch->camera_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, stream_name, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 4, batch->batch_id, -1, SQLITE_TRANSIENT); + sqlite3_bind_int(stmt, 5, object->object_index); + sqlite3_bind_int64(stmt, 6, batch->observed_at_ms); + sqlite3_bind_int64(stmt, 7, now_ms); + sqlite3_bind_text(stmt, 8, batch->engine_key, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 9, batch->engine_type, -1, SQLITE_TRANSIENT); + bind_text_or_null(stmt, 10, batch->model); + bind_text_or_null(stmt, 11, batch->model_version); + sqlite3_bind_text(stmt, 12, object->label, -1, SQLITE_TRANSIENT); + sqlite3_bind_double(stmt, 13, object->confidence); + if (object->has_box) { + sqlite3_bind_double(stmt, 14, round6(object->box_x)); + sqlite3_bind_double(stmt, 15, round6(object->box_y)); + sqlite3_bind_double(stmt, 16, round6(object->box_width)); + sqlite3_bind_double(stmt, 17, round6(object->box_height)); + } else { + for (int i = 14; i <= 17; ++i) sqlite3_bind_null(stmt, i); + } + if (object->has_track) { + sqlite3_bind_text(stmt, 18, object->track_id, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 19, object->track_namespace, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 20, object->track_session, -1, SQLITE_TRANSIENT); + } else { + for (int i = 18; i <= 20; ++i) sqlite3_bind_null(stmt, i); + } + bind_positive_or_null(stmt, 21, batch->recording_id); + sqlite3_bind_text(stmt, 22, asset_uuid, -1, SQLITE_TRANSIENT); + /* Under BEGIN IMMEDIATE and the mutex the batch was checked absent, + * so an ignored insert means a broken invariant, not a retry. */ + if (sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(db) == 1) rc = DB_OBS_OK; + } + if (rc != DB_OBS_OK) + log_error("Failed to insert observation: %s", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + return rc; +} + +int db_observation_insert_batch(const observation_batch_input_t *batch, + const observation_asset_input_t *asset, + observation_insert_result_t *result) { + if (!result) return DB_OBS_INVALID; + memset(result, 0, sizeof(*result)); + if (!valid_batch(batch) || !valid_asset_input(asset)) return DB_OBS_INVALID; + + char source[OBS_SOURCE_SIZE]; + if (get_source(source) != 0) return DB_OBS_ERROR; + + /* Generate IDs before taking the mutex. */ + char asset_uuid[OBS_UUID_SIZE]; + char uuids[OBS_MAX_BATCH][OBS_UUID_SIZE]; + if (asset->asset_uuid[0]) { + safe_strcpy(asset_uuid, asset->asset_uuid, sizeof(asset_uuid), 0); + for (char *c = asset_uuid; *c; ++c) + if (*c >= 'A' && *c <= 'F') *c = (char)(*c - 'A' + 'a'); + } else if (lightnvr_uuid_generate_v4(asset_uuid) != 0) { + return DB_OBS_ERROR; + } + for (int i = 0; i < batch->object_count; ++i) + if (lightnvr_uuid_generate_v4(uuids[i]) != 0) return DB_OBS_ERROR; + + /* Process objects in object_index order so results and seq follow it. */ + int order[OBS_MAX_BATCH]; + int ordered = 0; + for (int index = 0; index < OBS_MAX_BATCH; ++index) + for (int i = 0; i < batch->object_count; ++i) + if (batch->objects[i].object_index == index) order[ordered++] = i; + + int64_t now_ms = current_time_ms(); + obs_tx_t tx; + if (tx_begin(&tx) != 0) return DB_OBS_ERROR; + int rc = find_existing_batch(tx.db, batch, result); + if (rc != DB_OBS_OK) { + /* Duplicate or conflict: nothing written. */ + int final_rc = rc; + tx_end(&tx, DB_OBS_ERROR); + if (final_rc != DB_OBS_DUPLICATE) memset(result, 0, sizeof(*result)); + return final_rc; + } + + char stream_name[OBS_STREAM_NAME_SIZE]; + rc = resolve_stream_name(tx.db, batch->camera_uuid, stream_name, sizeof(stream_name)); + if (rc == DB_OBS_OK && batch->stream_name[0]) + safe_strcpy(stream_name, batch->stream_name, sizeof(stream_name), 0); + if (rc == DB_OBS_OK) rc = insert_asset_row(tx.db, asset_uuid, batch, asset, now_ms); + + const char *state = observation_asset_state_name(asset->state); + for (int n = 0; rc == DB_OBS_OK && n < ordered; ++n) { + int i = order[n]; + rc = insert_observation_row(tx.db, uuids[i], asset_uuid, stream_name, batch, + &batch->objects[i], now_ms); + if (rc == DB_OBS_OK) + rc = write_change_locked(tx.db, source, uuids[i], 1, OBS_CAUSE_CREATED, state, + asset->reason, now_ms, &result->last_seq); + if (rc == DB_OBS_OK) + safe_strcpy(result->observation_uuids[n], uuids[i], OBS_UUID_SIZE, 0); + } + rc = tx_end(&tx, rc); + if (rc == DB_OBS_OK) { + safe_strcpy(result->asset_uuid, asset_uuid, sizeof(result->asset_uuid), 0); + result->object_count = ordered; + if (ordered > 0) notify_publications_committed(); + } else { + memset(result, 0, sizeof(*result)); + } + return rc; +} + +/* ---- transitions ------------------------------------------------------- */ + +int db_observation_asset_transition(const char *asset_uuid, + observation_asset_state_t from, + observation_asset_state_t to, + const char *reason, + const observation_asset_media_t *media, + observation_transition_result_t *result) { + if (result) memset(result, 0, sizeof(*result)); + if (!lightnvr_uuid_is_valid(asset_uuid) || !transition_allowed(from, to, reason)) + return DB_OBS_INVALID; + if (to == OBS_ASSET_READY && + (!media || empty_text(media->path) || strlen(media->path) >= MAX_PATH_LENGTH || + media->bytes < 0 || media->width <= 0 || media->height <= 0)) + return DB_OBS_INVALID; + if (media && (media->bytes < 0 || media->width < 0 || media->height < 0 || + media->expires_at_ms < 0)) + return DB_OBS_INVALID; + + char source[OBS_SOURCE_SIZE]; + if (get_source(source) != 0) return DB_OBS_ERROR; + int64_t now_ms = current_time_ms(); + + obs_tx_t tx; + if (tx_begin(&tx) != 0) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(tx.db, + "UPDATE observation_assets SET state = ?, reason = ?, " + "path = COALESCE(?, path), bytes = COALESCE(?, bytes), " + "width = COALESCE(?, width), height = COALESCE(?, height), " + "expires_at_ms = COALESCE(?, expires_at_ms), updated_at_ms = ? " + "WHERE uuid = ? AND state = ?;", -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_asset_state_name(to), -1, SQLITE_STATIC); + bind_text_or_null(stmt, 2, reason); + bind_text_or_null(stmt, 3, media ? media->path : NULL); + if (media && to == OBS_ASSET_READY) sqlite3_bind_int64(stmt, 4, media->bytes); + else sqlite3_bind_null(stmt, 4); + bind_positive_or_null(stmt, 5, media ? media->width : 0); + bind_positive_or_null(stmt, 6, media ? media->height : 0); + bind_positive_or_null(stmt, 7, media ? media->expires_at_ms : 0); + sqlite3_bind_int64(stmt, 8, now_ms); + sqlite3_bind_text(stmt, 9, asset_uuid, -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 10, observation_asset_state_name(from), -1, SQLITE_STATIC); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + + if (rc == DB_OBS_OK && sqlite3_changes(tx.db) == 0) { + stmt = NULL; + rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(tx.db, "SELECT 1 FROM observation_assets WHERE uuid = ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, asset_uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) rc = DB_OBS_CAS_LOST; + else if (step == SQLITE_DONE) rc = DB_OBS_NOT_FOUND; + } + sqlite3_finalize(stmt); + tx_end(&tx, DB_OBS_ERROR); + return rc; + } + + /* Collect referencing observations before mutating the same table. */ + char (*observations)[OBS_UUID_SIZE] = NULL; + size_t count = 0, capacity = 0; + stmt = NULL; + if (rc == DB_OBS_OK) { + rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(tx.db, + "SELECT uuid FROM object_observations WHERE asset_uuid = ? ORDER BY " + "batch_id, object_index;", -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, asset_uuid, -1, SQLITE_TRANSIENT); + int step; + rc = DB_OBS_OK; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + if (count == capacity) { + size_t next = capacity ? capacity * 2 : OBS_MAX_BATCH; + void *grown = realloc(observations, next * sizeof(*observations)); + if (!grown) { rc = DB_OBS_ERROR; break; } + observations = grown; + capacity = next; + } + copy_column(stmt, 0, observations[count++], OBS_UUID_SIZE); + } + if (rc == DB_OBS_OK && step != SQLITE_DONE) rc = DB_OBS_ERROR; + } + sqlite3_finalize(stmt); + } + + int64_t last_seq = 0; + for (size_t i = 0; rc == DB_OBS_OK && i < count; ++i) + rc = db_observation_bump_revision_locked(tx.db, source, observations[i], + OBS_CAUSE_EVIDENCE, now_ms, &last_seq, NULL); + free(observations); + rc = tx_end(&tx, rc); + if (rc == DB_OBS_OK && result) { + result->observations_bumped = (int)count; + result->last_seq = last_seq; + } + if (rc == DB_OBS_OK && count > 0) notify_publications_committed(); + return rc; +} + +/* ---- reads ------------------------------------------------------------- */ + +static int ro_begin(sqlite3 **db) { + if (db_open_readonly_connection(db) != 0) return DB_OBS_ERROR; + /* One read transaction = one WAL snapshot for the whole call. */ + if (sqlite3_exec(*db, "BEGIN;", NULL, NULL, NULL) != SQLITE_OK) { + db_close_readonly_connection(*db); + *db = NULL; + return DB_OBS_ERROR; + } + return DB_OBS_OK; +} + +static void ro_end(sqlite3 *db) { + if (!db) return; + sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL); + db_close_readonly_connection(db); +} + +#define ASSET_COLUMNS \ + "a.uuid, a.camera_uuid, a.state, a.reason, a.origin, a.media_type, a.path, a.bytes, " \ + "a.width, a.height, a.captured_at_ms, a.transform_json, a.protected, a.expires_at_ms, " \ + "a.created_at_ms, a.updated_at_ms" + +#define RECORD_SELECT \ + "SELECT o.uuid, o.camera_uuid, o.stream_name_at_capture, o.batch_id, o.object_index, " \ + "o.observed_at_ms, o.created_at_ms, o.engine_key, o.engine_type, o.model, " \ + "o.model_version, o.label, o.confidence, o.box_x, o.box_y, o.box_width, o.box_height, " \ + "o.track_id, o.track_namespace, o.track_session, o.recording_id, o.revision, " \ + ASSET_COLUMNS " FROM object_observations o " \ + "JOIN observation_assets a ON a.uuid = o.asset_uuid " \ + "WHERE o.uuid = ? AND EXISTS (SELECT 1 FROM streams s WHERE s.camera_uuid = o.camera_uuid);" + +static void read_asset(sqlite3_stmt *stmt, int c, observation_asset_record_t *asset) { + memset(asset, 0, sizeof(*asset)); + copy_column(stmt, c + 0, asset->uuid, sizeof(asset->uuid)); + copy_column(stmt, c + 1, asset->camera_uuid, sizeof(asset->camera_uuid)); + char state[OBS_ENGINE_TYPE_SIZE]; + copy_column(stmt, c + 2, state, sizeof(state)); + if (observation_asset_state_parse(state, &asset->state) != 0) + asset->state = OBS_ASSET_UNAVAILABLE; + copy_column(stmt, c + 3, asset->reason, sizeof(asset->reason)); + copy_column(stmt, c + 4, asset->origin, sizeof(asset->origin)); + copy_column(stmt, c + 5, asset->media_type, sizeof(asset->media_type)); + copy_column(stmt, c + 6, asset->path, sizeof(asset->path)); + asset->has_bytes = sqlite3_column_type(stmt, c + 7) != SQLITE_NULL; + asset->bytes = sqlite3_column_int64(stmt, c + 7); + asset->width = sqlite3_column_int(stmt, c + 8); + asset->height = sqlite3_column_int(stmt, c + 9); + asset->captured_at_ms = sqlite3_column_int64(stmt, c + 10); + copy_column(stmt, c + 11, asset->transform_json, sizeof(asset->transform_json)); + asset->is_protected = sqlite3_column_int(stmt, c + 12) != 0; + asset->expires_at_ms = sqlite3_column_int64(stmt, c + 13); + asset->created_at_ms = sqlite3_column_int64(stmt, c + 14); + asset->updated_at_ms = sqlite3_column_int64(stmt, c + 15); +} + +static void read_record(sqlite3_stmt *stmt, observation_record_t *out) { + memset(out, 0, sizeof(*out)); + copy_column(stmt, 0, out->uuid, sizeof(out->uuid)); + copy_column(stmt, 1, out->camera_uuid, sizeof(out->camera_uuid)); + copy_column(stmt, 2, out->stream_name_at_capture, sizeof(out->stream_name_at_capture)); + copy_column(stmt, 3, out->batch_id, sizeof(out->batch_id)); + out->object_index = sqlite3_column_int(stmt, 4); + out->observed_at_ms = sqlite3_column_int64(stmt, 5); + out->created_at_ms = sqlite3_column_int64(stmt, 6); + copy_column(stmt, 7, out->engine_key, sizeof(out->engine_key)); + copy_column(stmt, 8, out->engine_type, sizeof(out->engine_type)); + copy_column(stmt, 9, out->model, sizeof(out->model)); + copy_column(stmt, 10, out->model_version, sizeof(out->model_version)); + copy_column(stmt, 11, out->label, sizeof(out->label)); + out->confidence = sqlite3_column_double(stmt, 12); + out->has_box = sqlite3_column_type(stmt, 13) != SQLITE_NULL; + if (out->has_box) { + out->box_x = sqlite3_column_double(stmt, 13); + out->box_y = sqlite3_column_double(stmt, 14); + out->box_width = sqlite3_column_double(stmt, 15); + out->box_height = sqlite3_column_double(stmt, 16); + } + out->has_track = sqlite3_column_type(stmt, 17) != SQLITE_NULL; + copy_column(stmt, 17, out->track_id, sizeof(out->track_id)); + copy_column(stmt, 18, out->track_namespace, sizeof(out->track_namespace)); + copy_column(stmt, 19, out->track_session, sizeof(out->track_session)); + out->recording_id = sqlite3_column_int64(stmt, 20); + out->revision = sqlite3_column_int(stmt, 21); + read_asset(stmt, 22, &out->asset); +} + +static int get_record_on(sqlite3 *db, sqlite3_stmt **cached, const char *uuid, + observation_record_t *out) { + if (!*cached && sqlite3_prepare_v2(db, RECORD_SELECT, -1, cached, NULL) != SQLITE_OK) { + log_error("Failed to prepare observation read: %s", sqlite3_errmsg(db)); + return DB_OBS_ERROR; + } + sqlite3_reset(*cached); + sqlite3_clear_bindings(*cached); + sqlite3_bind_text(*cached, 1, uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(*cached); + if (step == SQLITE_ROW) { + read_record(*cached, out); + return DB_OBS_OK; + } + return step == SQLITE_DONE ? DB_OBS_NOT_FOUND : DB_OBS_ERROR; +} + +int db_observation_get(const char *observation_uuid, observation_record_t *out) { + if (!out || !lightnvr_uuid_is_valid(observation_uuid)) return DB_OBS_INVALID; + sqlite3 *db = NULL; + if (ro_begin(&db) != DB_OBS_OK) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = get_record_on(db, &stmt, observation_uuid, out); + sqlite3_finalize(stmt); + ro_end(db); + return rc; +} + +int db_observation_asset_get(const char *asset_uuid, observation_asset_record_t *out) { + if (!out || !lightnvr_uuid_is_valid(asset_uuid)) return DB_OBS_INVALID; + sqlite3 *db = NULL; + if (ro_begin(&db) != DB_OBS_OK) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, "SELECT " ASSET_COLUMNS " FROM observation_assets a " + "WHERE a.uuid = ?;", -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, asset_uuid, -1, SQLITE_TRANSIENT); + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) { + read_asset(stmt, 0, out); + rc = DB_OBS_OK; + } else if (step == SQLITE_DONE) { + rc = DB_OBS_NOT_FOUND; + } + } + sqlite3_finalize(stmt); + ro_end(db); + return rc; +} + +static int read_meta_on(sqlite3 *db, observation_meta_t *meta) { + memset(meta, 0, sizeof(*meta)); + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "SELECT m.epoch_uuid, m.oldest_seq, (SELECT MAX(seq) FROM observation_changes) " + "FROM observation_meta m WHERE m.id = 1;", -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { + copy_column(stmt, 0, meta->epoch_uuid, sizeof(meta->epoch_uuid)); + meta->oldest_seq = sqlite3_column_int64(stmt, 1); + int64_t max_seq = sqlite3_column_int64(stmt, 2); /* NULL -> 0 */ + meta->head_seq = max_seq > meta->oldest_seq - 1 ? max_seq : meta->oldest_seq - 1; + rc = lightnvr_uuid_is_valid(meta->epoch_uuid) ? DB_OBS_OK : DB_OBS_ERROR; + } + sqlite3_finalize(stmt); + return rc; +} + +int db_observation_meta_get(observation_meta_t *out) { + if (!out) return DB_OBS_INVALID; + sqlite3 *db = NULL; + if (ro_begin(&db) != DB_OBS_OK) return DB_OBS_ERROR; + int rc = read_meta_on(db, out); + ro_end(db); + return rc; +} + +/* L10: one bounded statement per figure. The pending figures use the partial + * pending index; failed/expired scan the publication table, which is bounded + * by history retention. */ +static int diag_scalar(sqlite3 *db, const char *sql, int64_t *value, bool *is_null) { + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK) { + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) { + if (is_null) *is_null = sqlite3_column_type(stmt, 0) == SQLITE_NULL; + *value = sqlite3_column_int64(stmt, 0); + rc = DB_OBS_OK; + } else if (step == SQLITE_DONE) { + if (is_null) *is_null = true; + *value = 0; + rc = DB_OBS_OK; + } + } + sqlite3_finalize(stmt); + return rc; +} + +int db_observation_diagnostics_get(observation_db_diagnostics_t *out) { + if (!out) return DB_OBS_INVALID; + memset(out, 0, sizeof(*out)); + sqlite3 *db = NULL; + if (ro_begin(&db) != DB_OBS_OK) return DB_OBS_ERROR; + bool oldest_null = true; + int rc = read_meta_on(db, &out->meta); + if (rc == DB_OBS_OK) + rc = diag_scalar(db, "SELECT MIN(observed_at_ms) FROM object_observations;", + &out->oldest_observed_at_ms, &oldest_null); + if (rc == DB_OBS_OK) + rc = diag_scalar(db, "SELECT COUNT(*) FROM object_observations;", + &out->observations, NULL); + if (rc == DB_OBS_OK) + rc = diag_scalar(db, "SELECT COUNT(*) FROM observation_publications " + "WHERE state = 'pending';", + &out->publications_pending, NULL); + if (rc == DB_OBS_OK) + rc = diag_scalar(db, "SELECT COUNT(*) FROM observation_publications " + "WHERE state = 'failed';", + &out->publications_failed, NULL); + if (rc == DB_OBS_OK) + rc = diag_scalar(db, "SELECT COUNT(*) FROM observation_publications " + "WHERE state = 'expired';", + &out->publications_expired, NULL); + if (rc == DB_OBS_OK) + rc = diag_scalar(db, "SELECT c.created_at_ms FROM observation_publications p " + "JOIN observation_changes c ON c.seq = p.change_seq " + "WHERE p.state = 'pending' ORDER BY p.change_seq LIMIT 1;", + &out->oldest_pending_created_at_ms, NULL); + ro_end(db); + if (rc != DB_OBS_OK) { + memset(out, 0, sizeof(*out)); + return DB_OBS_ERROR; + } + out->has_oldest_observed_at = !oldest_null; + if (oldest_null) out->oldest_observed_at_ms = 0; + return DB_OBS_OK; +} + +int db_observation_meta_regenerate_epoch(void) { + char epoch[OBS_UUID_SIZE]; + if (lightnvr_uuid_generate_v4(epoch) != 0) return DB_OBS_ERROR; + obs_tx_t tx; + if (tx_begin(&tx) != 0) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(tx.db, "UPDATE observation_meta SET epoch_uuid = ? WHERE id = 1;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, epoch, -1, SQLITE_TRANSIENT); + if (sqlite3_step(stmt) == SQLITE_DONE && sqlite3_changes(tx.db) == 1) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + rc = tx_end(&tx, rc); + if (rc == DB_OBS_OK) log_info("Regenerated observation cursor epoch"); + return rc; +} + +static bool valid_query(const observation_list_query_t *query) { + if (!query) return false; + int limit = query->limit == 0 ? OBS_LIST_DEFAULT_LIMIT : query->limit; + if (limit < 1 || limit > OBS_LIST_MAX_LIMIT) return false; + if (query->label_count > OBS_QUERY_MAX_LABELS) return false; + for (size_t i = 0; i < query->label_count; ++i) + if (!text_len_between(query->labels[i], OBS_LABEL_SIZE, 1, OBS_LABEL_SIZE - 1)) + return false; + if (query->has_since && query->has_until && query->since_ms >= query->until_ms) + return false; + if (!query->all_cameras && query->camera_count > 0 && !query->camera_uuids) return false; + return true; +} + +static bool camera_allowed(const observation_list_query_t *query, const char *camera) { + if (query->all_cameras) return true; + for (size_t i = 0; i < query->camera_count; ++i) + if (strcmp(query->camera_uuids[i], camera) == 0) return true; + return false; +} + +static bool change_matches(const observation_list_query_t *query, sqlite3_stmt *stmt) { + if (sqlite3_column_int(stmt, 5) == 0) return false; /* capture camera deleted */ + const char *camera = (const char *)sqlite3_column_text(stmt, 2); + if (!camera || !camera_allowed(query, camera)) return false; + int64_t observed = sqlite3_column_int64(stmt, 4); + if (query->has_since && observed < query->since_ms) return false; + if (query->has_until && observed >= query->until_ms) return false; + if (query->label_count > 0) { + const char *label = (const char *)sqlite3_column_text(stmt, 3); + bool found = false; + for (size_t i = 0; label && i < query->label_count && !found; ++i) + found = strcasecmp(label, query->labels[i]) == 0; + if (!found) return false; + } + return true; +} + +static int fill_expired(sqlite3 *db, const observation_meta_t *meta, observation_page_t *page) { + if (observation_cursor_encode(meta->oldest_seq - 1, meta->epoch_uuid, page->oldest_cursor, + sizeof(page->oldest_cursor)) != 0) + return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, "SELECT MIN(observed_at_ms) FROM object_observations;", + -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { + page->has_oldest_observed_at = sqlite3_column_type(stmt, 0) != SQLITE_NULL; + page->oldest_observed_at_ms = sqlite3_column_int64(stmt, 0); + rc = DB_OBS_CURSOR_EXPIRED; + } + sqlite3_finalize(stmt); + return rc; +} + +static int scan_page(sqlite3 *db, const observation_list_query_t *query, int64_t start, + int64_t watermark, int limit, char (*selected)[OBS_UUID_SIZE], + size_t *selected_count, int64_t *last_scanned) { + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, + "SELECT c.seq, o.uuid, o.camera_uuid, o.label, o.observed_at_ms, " + "EXISTS (SELECT 1 FROM streams s WHERE s.camera_uuid = o.camera_uuid) " + "FROM observation_changes c JOIN object_observations o " + "ON o.uuid = c.observation_uuid WHERE c.seq > ? AND c.seq <= ? " + "ORDER BY c.seq LIMIT ?;", -1, &stmt, NULL) != SQLITE_OK) { + log_error("Failed to prepare observation scan: %s", sqlite3_errmsg(db)); + return DB_OBS_ERROR; + } + sqlite3_bind_int64(stmt, 1, start); + sqlite3_bind_int64(stmt, 2, watermark); + sqlite3_bind_int(stmt, 3, OBS_LIST_SCAN_CAP); + *last_scanned = start; + *selected_count = 0; + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + if (change_matches(query, stmt)) { + const char *uuid = (const char *)sqlite3_column_text(stmt, 1); + bool present = false; + for (size_t i = 0; uuid && i < *selected_count && !present; ++i) + present = strcmp(selected[i], uuid) == 0; + if (!present) { + /* Stop before the change that would add observation limit + 1. */ + if (*selected_count == (size_t)limit) break; + safe_strcpy(selected[(*selected_count)++], uuid ? uuid : "", OBS_UUID_SIZE, 0); + } + } + *last_scanned = sqlite3_column_int64(stmt, 0); + } + int rc = (step == SQLITE_ROW || step == SQLITE_DONE) ? DB_OBS_OK : DB_OBS_ERROR; + sqlite3_finalize(stmt); + return rc; +} + +static int changes_exist(sqlite3 *db, int64_t after, int64_t watermark, bool *exists) { + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "SELECT EXISTS (SELECT 1 FROM observation_changes WHERE seq > ? AND seq <= ?);", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, after); + sqlite3_bind_int64(stmt, 2, watermark); + if (sqlite3_step(stmt) == SQLITE_ROW) { + *exists = sqlite3_column_int(stmt, 0) != 0; + rc = DB_OBS_OK; + } + } + sqlite3_finalize(stmt); + return rc; +} + +int db_observations_list(const observation_list_query_t *query, const char *cursor, + observation_page_t *page) { + if (!page) return DB_OBS_INVALID; + memset(page, 0, sizeof(*page)); + if (!valid_query(query)) return DB_OBS_INVALID; + int limit = query->limit == 0 ? OBS_LIST_DEFAULT_LIMIT : query->limit; + + bool has_cursor = cursor && cursor[0]; + bool head = has_cursor && strcmp(cursor, "head") == 0; + int64_t start = 0; + char epoch[OBS_UUID_SIZE] = ""; + if (has_cursor && !head && observation_cursor_decode(cursor, &start, epoch) != DB_OBS_OK) + return DB_OBS_INVALID_CURSOR; + + sqlite3 *db = NULL; + if (ro_begin(&db) != DB_OBS_OK) return DB_OBS_ERROR; + observation_meta_t meta; + int rc = read_meta_on(db, &meta); + int64_t watermark = meta.head_seq; + if (rc == DB_OBS_OK && has_cursor && !head && + (strcmp(epoch, meta.epoch_uuid) != 0 || start < meta.oldest_seq - 1 || + start > watermark)) { + rc = fill_expired(db, &meta, page); + ro_end(db); + return rc; + } + if (rc == DB_OBS_OK && head) { + rc = observation_cursor_encode(watermark, meta.epoch_uuid, page->next_cursor, + sizeof(page->next_cursor)) == 0 ? DB_OBS_OK : DB_OBS_ERROR; + ro_end(db); + return rc; + } + if (!has_cursor) start = meta.oldest_seq - 1; + + char (*selected)[OBS_UUID_SIZE] = NULL; + size_t selected_count = 0; + int64_t last_scanned = start; + if (rc == DB_OBS_OK) { + selected = calloc((size_t)limit, sizeof(*selected)); + if (!selected) rc = DB_OBS_ERROR; + } + if (rc == DB_OBS_OK) + rc = scan_page(db, query, start, watermark, limit, selected, &selected_count, + &last_scanned); + if (rc == DB_OBS_OK) + rc = changes_exist(db, last_scanned, watermark, &page->has_more); + if (rc == DB_OBS_OK && + observation_cursor_encode(last_scanned, meta.epoch_uuid, page->next_cursor, + sizeof(page->next_cursor)) != 0) + rc = DB_OBS_ERROR; + if (rc == DB_OBS_OK && selected_count > 0) { + page->items = calloc(selected_count, sizeof(*page->items)); + if (!page->items) rc = DB_OBS_ERROR; + } + sqlite3_stmt *record_stmt = NULL; + for (size_t i = 0; rc == DB_OBS_OK && i < selected_count; ++i) { + int got = get_record_on(db, &record_stmt, selected[i], &page->items[page->count]); + if (got == DB_OBS_OK) page->count++; + else if (got != DB_OBS_NOT_FOUND) rc = got; + } + sqlite3_finalize(record_stmt); + free(selected); + ro_end(db); + if (rc != DB_OBS_OK) db_observation_page_free(page); + return rc; +} + +void db_observation_page_free(observation_page_t *page) { + if (!page) return; + free(page->items); + page->items = NULL; + page->count = 0; +} + +/* ---- publications ------------------------------------------------------ */ + +int db_observation_pending_publications(observation_publication_t *out, int max) { + if (!out || max <= 0) return DB_OBS_INVALID; + sqlite3 *db = NULL; + if (ro_begin(&db) != DB_OBS_OK) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int count = 0; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(db, + "SELECT p.change_seq, p.message_id, p.attempts, c.observation_uuid, c.revision, " + "c.cause, c.evidence_state, c.evidence_reason, c.created_at_ms, o.camera_uuid, " + "o.label, o.observed_at_ms, o.asset_uuid FROM observation_publications p " + "JOIN observation_changes c ON c.seq = p.change_seq " + "JOIN object_observations o ON o.uuid = c.observation_uuid " + "WHERE p.state = 'pending' ORDER BY p.change_seq LIMIT ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int(stmt, 1, max); + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + observation_publication_t *pub = &out[count]; + memset(pub, 0, sizeof(*pub)); + pub->change_seq = sqlite3_column_int64(stmt, 0); + copy_column(stmt, 1, pub->message_id, sizeof(pub->message_id)); + pub->attempts = sqlite3_column_int(stmt, 2); + copy_column(stmt, 3, pub->observation_uuid, sizeof(pub->observation_uuid)); + pub->revision = sqlite3_column_int(stmt, 4); + char text[OBS_ENGINE_TYPE_SIZE]; + copy_column(stmt, 5, text, sizeof(text)); + if (cause_parse(text, &pub->cause) != 0) continue; + copy_column(stmt, 6, text, sizeof(text)); + if (observation_asset_state_parse(text, &pub->evidence_state) != 0) continue; + copy_column(stmt, 7, pub->evidence_reason, sizeof(pub->evidence_reason)); + pub->change_created_at_ms = sqlite3_column_int64(stmt, 8); + copy_column(stmt, 9, pub->camera_uuid, sizeof(pub->camera_uuid)); + copy_column(stmt, 10, pub->label, sizeof(pub->label)); + pub->observed_at_ms = sqlite3_column_int64(stmt, 11); + copy_column(stmt, 12, pub->asset_uuid, sizeof(pub->asset_uuid)); + count++; + } + if (step == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + ro_end(db); + return rc == DB_OBS_OK ? count : rc; +} + +int db_observation_publication_mark(int64_t change_seq, + observation_publication_state_t state, + const char *last_error) { + if (change_seq <= 0 || !observation_publication_state_name(state)) return DB_OBS_INVALID; + char error_text[OBS_ERROR_TEXT_SIZE] = ""; + if (last_error) safe_strcpy(error_text, last_error, sizeof(error_text), 0); + int64_t now_ms = current_time_ms(); + + obs_tx_t tx; + if (tx_begin(&tx) != 0) return DB_OBS_ERROR; + sqlite3_stmt *stmt = NULL; + int rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(tx.db, + "UPDATE observation_publications SET state = ?, attempts = attempts + 1, " + "last_error = ?, updated_at_ms = ? WHERE change_seq = ? AND state = 'pending';", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_text(stmt, 1, observation_publication_state_name(state), -1, SQLITE_STATIC); + bind_text_or_null(stmt, 2, error_text); + sqlite3_bind_int64(stmt, 3, now_ms); + sqlite3_bind_int64(stmt, 4, change_seq); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + if (rc == DB_OBS_OK && sqlite3_changes(tx.db) == 0) { + rc = DB_OBS_ERROR; + stmt = NULL; + if (sqlite3_prepare_v2(tx.db, + "SELECT 1 FROM observation_publications WHERE change_seq = ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, change_seq); + int step = sqlite3_step(stmt); + if (step == SQLITE_ROW) rc = DB_OBS_CAS_LOST; + else if (step == SQLITE_DONE) rc = DB_OBS_NOT_FOUND; + } + sqlite3_finalize(stmt); + tx_end(&tx, DB_OBS_ERROR); + return rc; + } + return tx_end(&tx, rc); +} + +/* ---- prune ------------------------------------------------------------- */ + +static int collect_texts(sqlite3 *db, const char *sql, int64_t bound, + char (**out)[OBS_UUID_SIZE], size_t *count) { + *out = NULL; + *count = 0; + size_t capacity = 0; + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return DB_OBS_ERROR; + sqlite3_bind_int64(stmt, 1, bound); + int rc = DB_OBS_OK; + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW) { + if (sqlite3_column_type(stmt, 0) == SQLITE_NULL) continue; + if (*count == capacity) { + size_t next = capacity ? capacity * 2 : 64; + void *grown = realloc(*out, next * sizeof(**out)); + if (!grown) { rc = DB_OBS_ERROR; break; } + *out = grown; + capacity = next; + } + copy_column(stmt, 0, (*out)[(*count)++], OBS_UUID_SIZE); + } + if (rc == DB_OBS_OK && step != SQLITE_DONE) rc = DB_OBS_ERROR; + sqlite3_finalize(stmt); + return rc; +} + +static int delete_each(sqlite3 *db, const char *sql, char (*uuids)[OBS_UUID_SIZE], + size_t count, int *deleted) { + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) return DB_OBS_ERROR; + int rc = DB_OBS_OK; + for (size_t i = 0; i < count && rc == DB_OBS_OK; ++i) { + sqlite3_reset(stmt); + sqlite3_bind_text(stmt, 1, uuids[i], -1, SQLITE_TRANSIENT); + if (sqlite3_step(stmt) != SQLITE_DONE) rc = DB_OBS_ERROR; + else *deleted += sqlite3_changes(db); + } + sqlite3_finalize(stmt); + return rc; +} + +int db_observations_prune(int64_t before_ms, int batch, observation_prune_result_t *result) { + if (!result) return DB_OBS_INVALID; + memset(result, 0, sizeof(*result)); + if (batch <= 0 || batch > OBS_PRUNE_MAX_BATCH) return DB_OBS_INVALID; + + obs_tx_t tx; + if (tx_begin(&tx) != 0) return DB_OBS_ERROR; + int rc = DB_OBS_ERROR; + int64_t oldest_seq = 1; + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(tx.db, "SELECT oldest_seq FROM observation_meta WHERE id = 1;", + -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) { + oldest_seq = sqlite3_column_int64(stmt, 0); + rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + + /* Ascending seq while created_at is older than the boundary. */ + int64_t last_pruned = 0; + stmt = NULL; + if (rc == DB_OBS_OK) { + rc = DB_OBS_ERROR; + if (sqlite3_prepare_v2(tx.db, + "SELECT seq, created_at_ms FROM observation_changes ORDER BY seq LIMIT ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int(stmt, 1, batch); + int step; + while ((step = sqlite3_step(stmt)) == SQLITE_ROW && + sqlite3_column_int64(stmt, 1) < before_ms) + last_pruned = sqlite3_column_int64(stmt, 0); + if (step == SQLITE_ROW || step == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + } + + char (*observations)[OBS_UUID_SIZE] = NULL; + char (*assets)[OBS_UUID_SIZE] = NULL; + size_t observation_count = 0, asset_count = 0; + if (rc == DB_OBS_OK && last_pruned > 0) { + rc = collect_texts(tx.db, + "SELECT DISTINCT observation_uuid FROM observation_changes WHERE seq <= ?;", + last_pruned, &observations, &observation_count); + if (rc == DB_OBS_OK) + rc = collect_texts(tx.db, + "SELECT DISTINCT o.asset_uuid FROM object_observations o JOIN " + "observation_changes c ON c.observation_uuid = o.uuid WHERE c.seq <= ?;", + last_pruned, &assets, &asset_count); + if (rc == DB_OBS_OK) { + rc = DB_OBS_ERROR; + stmt = NULL; + if (sqlite3_prepare_v2(tx.db, "DELETE FROM observation_changes WHERE seq <= ?;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, last_pruned); + if (sqlite3_step(stmt) == SQLITE_DONE) { + result->changes_deleted = sqlite3_changes(tx.db); + rc = DB_OBS_OK; + } + } + sqlite3_finalize(stmt); + } + /* Observations before assets: asset_uuid is a foreign key. */ + if (rc == DB_OBS_OK) + rc = delete_each(tx.db, + "DELETE FROM object_observations WHERE uuid = ?1 AND NOT EXISTS " + "(SELECT 1 FROM observation_changes WHERE observation_uuid = ?1);", + observations, observation_count, &result->observations_deleted); + if (rc == DB_OBS_OK) + rc = delete_each(tx.db, + "DELETE FROM observation_assets WHERE uuid = ?1 AND state IN " + "('unavailable', 'expired') AND NOT EXISTS " + "(SELECT 1 FROM object_observations WHERE asset_uuid = ?1);", + assets, asset_count, &result->assets_deleted); + if (rc == DB_OBS_OK && last_pruned + 1 > oldest_seq) { + rc = DB_OBS_ERROR; + stmt = NULL; + if (sqlite3_prepare_v2(tx.db, + "UPDATE observation_meta SET oldest_seq = ? WHERE id = 1;", + -1, &stmt, NULL) == SQLITE_OK) { + sqlite3_bind_int64(stmt, 1, last_pruned + 1); + if (sqlite3_step(stmt) == SQLITE_DONE) rc = DB_OBS_OK; + } + sqlite3_finalize(stmt); + if (rc == DB_OBS_OK) oldest_seq = last_pruned + 1; + } + } + free(observations); + free(assets); + rc = tx_end(&tx, rc); + if (rc == DB_OBS_OK) result->oldest_seq = oldest_seq; + else memset(result, 0, sizeof(*result)); + return rc; +} diff --git a/src/database/db_streams.c b/src/database/db_streams.c index 261abcd82..154045b0c 100644 --- a/src/database/db_streams.c +++ b/src/database/db_streams.c @@ -75,6 +75,38 @@ static const char *normalized_playback_transport(const char *value) { return playback_transport_is_valid(value) ? value : "auto"; } +static int normalized_observation_max_age_days(int value) { + return value > 0 ? value : OBSERVATION_DEFAULT_MAX_AGE_DAYS; +} + +static sqlite3_int64 normalized_observation_max_bytes(int64_t value) { + return value > 0 ? (sqlite3_int64)value : (sqlite3_int64)OBSERVATION_DEFAULT_MAX_BYTES; +} + +/* Bind the four migration-0095 evidence columns starting at index first. */ +static void bind_observation_settings(sqlite3_stmt *stmt, int first, + const stream_config_t *stream) { + sqlite3_bind_int(stmt, first, stream->observation_evidence_enabled ? 1 : 0); + sqlite3_bind_text(stmt, first + 1, stream->observation_labels, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, first + 2, + normalized_observation_max_age_days(stream->observation_max_age_days)); + sqlite3_bind_int64(stmt, first + 3, + normalized_observation_max_bytes(stream->observation_max_bytes)); +} + +/* Load the evidence columns starting at column first. */ +static void load_observation_settings(sqlite3_stmt *stmt, int first, + stream_config_t *stream) { + stream->observation_evidence_enabled = sqlite3_column_int(stmt, first) != 0; + const char *labels = (const char *)sqlite3_column_text(stmt, first + 1); + safe_strcpy(stream->observation_labels, labels ? labels : "", + sizeof(stream->observation_labels), 0); + stream->observation_max_age_days = + normalized_observation_max_age_days(sqlite3_column_int(stmt, first + 2)); + stream->observation_max_bytes = + (int64_t)normalized_observation_max_bytes(sqlite3_column_int64(stmt, first + 3)); +} + static const char *normalized_go2rtc_source_override(const char *value) { return string_has_non_whitespace(value) ? value : ""; } @@ -172,7 +204,9 @@ uint64_t add_stream_config(const stream_config_t *stream) { "sub_stream_url = ?, audio_voice_enhancement = ?, " "detection_url = ?, publish_url = ?, " "detection_record_on_schedule = ?, detection_recording_schedule = ?, " - "playback_transport = ?, eptz_config = ? " + "playback_transport = ?, eptz_config = ?, " + "observation_evidence_enabled = ?, observation_labels = ?, " + "observation_max_age_days = ?, observation_max_bytes = ? " "WHERE id = ?;"; rc = sqlite3_prepare_v2(db, update_sql, -1, &stmt, NULL); @@ -270,9 +304,10 @@ uint64_t add_stream_config(const stream_config_t *stream) { normalized_playback_transport(stream->playback_transport), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 54, stream->eptz_config, -1, SQLITE_STATIC); + bind_observation_settings(stmt, 55, stream); // Bind ID parameter - sqlite3_bind_int64(stmt, 55, (sqlite3_int64)existing_id); + sqlite3_bind_int64(stmt, 59, (sqlite3_int64)existing_id); bool owns_transaction = false; if (!stream_transaction_begin(db, &owns_transaction)) { @@ -347,8 +382,11 @@ uint64_t add_stream_config(const stream_config_t *stream) { "onvif_username, onvif_password, onvif_profile, onvif_port, " "record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, " "go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, " - "detection_record_on_schedule, detection_recording_schedule, playback_transport, eptz_config, camera_uuid, location_uuid) " + "detection_record_on_schedule, detection_recording_schedule, playback_transport, eptz_config, " + "observation_evidence_enabled, observation_labels, observation_max_age_days, observation_max_bytes, " + "camera_uuid, location_uuid) " "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, " "lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || " "substr(hex(randomblob(2)), 2) || '-' || " "substr('89ab', (abs(random()) % 4) + 1, 1) || " @@ -451,6 +489,7 @@ uint64_t add_stream_config(const stream_config_t *stream) { normalized_playback_transport(stream->playback_transport), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 55, stream->eptz_config, -1, SQLITE_STATIC); + bind_observation_settings(stmt, 56, stream); bool owns_transaction = false; if (!stream_transaction_begin(db, &owns_transaction)) { @@ -585,7 +624,9 @@ int update_stream_config(const char *name, const stream_config_t *stream) { "sub_stream_url = ?, audio_voice_enhancement = ?, " "detection_url = ?, publish_url = ?, " "detection_record_on_schedule = ?, detection_recording_schedule = ?, " - "playback_transport = ?, eptz_config = ? " + "playback_transport = ?, eptz_config = ?, " + "observation_evidence_enabled = ?, observation_labels = ?, " + "observation_max_age_days = ?, observation_max_bytes = ? " "WHERE name = ?;"; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); @@ -684,9 +725,10 @@ int update_stream_config(const char *name, const stream_config_t *stream) { normalized_playback_transport(stream->playback_transport), -1, SQLITE_STATIC); sqlite3_bind_text(stmt, 55, stream->eptz_config, -1, SQLITE_STATIC); + bind_observation_settings(stmt, 56, stream); // Bind the WHERE clause parameter - sqlite3_bind_text(stmt, 56, name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 60, name, -1, SQLITE_STATIC); bool owns_transaction = false; if (!stream_transaction_begin(db, &owns_transaction)) { @@ -984,7 +1026,8 @@ int get_stream_config_by_name(const char *name, stream_config_t *stream) { "onvif_username, onvif_password, onvif_profile, onvif_port, " "record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, " "go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, " - "detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config " + "detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config, " + "observation_evidence_enabled, observation_labels, observation_max_age_days, observation_max_bytes " "FROM streams WHERE name = ?;"; // Column index constants for readability @@ -1003,7 +1046,8 @@ int get_stream_config_by_name(const char *name, stream_config_t *stream) { COL_MOTION_TRIGGER_SOURCE, COL_GO2RTC_SOURCE_OVERRIDE, COL_SUB_STREAM_URL, COL_AUDIO_VOICE_ENHANCEMENT, COL_DETECTION_URL, COL_PUBLISH_URL, COL_DETECTION_RECORD_ON_SCHEDULE, COL_DETECTION_RECORDING_SCHEDULE, - COL_CAMERA_UUID, COL_LOCATION_UUID, COL_PLAYBACK_TRANSPORT, COL_EPTZ_CONFIG + COL_CAMERA_UUID, COL_LOCATION_UUID, COL_PLAYBACK_TRANSPORT, COL_EPTZ_CONFIG, + COL_OBSERVATION_EVIDENCE_ENABLED }; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); @@ -1229,6 +1273,7 @@ int get_stream_config_by_name(const char *name, stream_config_t *stream) { (const char *)sqlite3_column_text(stmt, COL_EPTZ_CONFIG); safe_strcpy(stream->eptz_config, eptz_config ? eptz_config : "", sizeof(stream->eptz_config), 0); + load_observation_settings(stmt, COL_OBSERVATION_EVIDENCE_ENABLED, stream); result = 0; } @@ -1326,7 +1371,8 @@ static int get_all_stream_configs_on_connection(sqlite3 *db, "onvif_username, onvif_password, onvif_profile, onvif_port, " "record_on_schedule, recording_schedule, tags, admin_url, privacy_mode, motion_trigger_source, " "go2rtc_source_override, sub_stream_url, audio_voice_enhancement, detection_url, publish_url, " - "detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config " + "detection_record_on_schedule, detection_recording_schedule, camera_uuid, location_uuid, playback_transport, eptz_config, " + "observation_evidence_enabled, observation_labels, observation_max_age_days, observation_max_bytes " "FROM streams ORDER BY name;"; // Column index constants (same as get_stream_config_by_name) @@ -1345,7 +1391,8 @@ static int get_all_stream_configs_on_connection(sqlite3 *db, COL_MOTION_TRIGGER_SOURCE, COL_GO2RTC_SOURCE_OVERRIDE, COL_SUB_STREAM_URL, COL_AUDIO_VOICE_ENHANCEMENT, COL_DETECTION_URL, COL_PUBLISH_URL, COL_DETECTION_RECORD_ON_SCHEDULE, COL_DETECTION_RECORDING_SCHEDULE, - COL_CAMERA_UUID, COL_LOCATION_UUID, COL_PLAYBACK_TRANSPORT, COL_EPTZ_CONFIG + COL_CAMERA_UUID, COL_LOCATION_UUID, COL_PLAYBACK_TRANSPORT, COL_EPTZ_CONFIG, + COL_OBSERVATION_EVIDENCE_ENABLED }; rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); @@ -1569,6 +1616,7 @@ static int get_all_stream_configs_on_connection(sqlite3 *db, (const char *)sqlite3_column_text(stmt, COL_EPTZ_CONFIG); safe_strcpy(s->eptz_config, eptz_config ? eptz_config : "", sizeof(s->eptz_config), 0); + load_observation_settings(stmt, COL_OBSERVATION_EVIDENCE_ENABLED, s); count++; } diff --git a/src/storage/storage_manager.c b/src/storage/storage_manager.c index 43e085eab..0156fd02e 100644 --- a/src/storage/storage_manager.c +++ b/src/storage/storage_manager.c @@ -40,6 +40,7 @@ #include "core/event_producers.h" #include "core/logger.h" #include "core/mqtt_client.h" +#include "core/observation_retention.h" #include "core/path_utils.h" #include "utils/strings.h" @@ -1816,6 +1817,10 @@ static void* unified_storage_controller_func(void *arg) { log_warn("Storage target health refresh failed"); } cleanup_pressured_targets(); + // INT_01 evidence age/byte budgets and history prune. Bounded and + // rate-limited internally; a no-op until startup reconciliation ran. + // Touches only /observations, never recordings. + observation_retention_maintenance_tick(); unified_ctrl.last_heartbeat = now; // Check if forced cleanup was requested diff --git a/src/utils/base64.c b/src/utils/base64.c index 07bff4f9e..23dd3ec3a 100644 --- a/src/utils/base64.c +++ b/src/utils/base64.c @@ -85,3 +85,73 @@ char *base64_encode_alloc(const unsigned char *input, size_t input_len) { } return encoded; } + +static const char BASE64URL_ALPHABET[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +int base64url_encode(const unsigned char *input, size_t input_len, + char *dst, size_t dst_size, size_t *out_len) { + if (out_len) *out_len = 0; + if (!dst || dst_size == 0 || (!input && input_len > 0)) return -1; + if (input_len > (SIZE_MAX - 3) / 4) return -1; + size_t needed = (input_len * 4 + 2) / 3 + 1; + if (dst_size < needed) return -1; + + size_t written = 0; + size_t i = 0; + for (; i + 2 < input_len; i += 3) { + uint32_t triple = ((uint32_t)input[i] << 16) | + ((uint32_t)input[i + 1] << 8) | (uint32_t)input[i + 2]; + dst[written++] = BASE64URL_ALPHABET[(triple >> 18) & 0x3F]; + dst[written++] = BASE64URL_ALPHABET[(triple >> 12) & 0x3F]; + dst[written++] = BASE64URL_ALPHABET[(triple >> 6) & 0x3F]; + dst[written++] = BASE64URL_ALPHABET[triple & 0x3F]; + } + size_t remaining = input_len - i; + if (remaining > 0) { + uint32_t triple = (uint32_t)input[i] << 16; + if (remaining == 2) triple |= (uint32_t)input[i + 1] << 8; + dst[written++] = BASE64URL_ALPHABET[(triple >> 18) & 0x3F]; + dst[written++] = BASE64URL_ALPHABET[(triple >> 12) & 0x3F]; + if (remaining == 2) dst[written++] = BASE64URL_ALPHABET[(triple >> 6) & 0x3F]; + } + dst[written] = '\0'; + if (out_len) *out_len = written; + return 0; +} + +static int base64url_value(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '-') return 62; + if (c == '_') return 63; + return -1; +} + +int base64url_decode(const char *src, size_t src_len, + unsigned char *dst, size_t dst_size, size_t *out_len) { + if (out_len) *out_len = 0; + if ((!src && src_len > 0) || (!dst && src_len > 0)) return -1; + if (src_len % 4 == 1) return -1; + size_t needed = src_len / 4 * 3 + (src_len % 4 ? src_len % 4 - 1 : 0); + if (needed > dst_size) return -1; + + size_t written = 0; + uint32_t buffer = 0; + int bits = 0; + for (size_t i = 0; i < src_len; ++i) { + int value = base64url_value(src[i]); + if (value < 0) return -1; + buffer = (buffer << 6) | (uint32_t)value; + bits += 6; + if (bits >= 8) { + bits -= 8; + dst[written++] = (unsigned char)((buffer >> bits) & 0xFF); + } + } + /* Leftover bits must be zero so each value has exactly one encoding. */ + if (bits > 0 && (buffer & ((1U << bits) - 1U)) != 0) return -1; + if (out_len) *out_len = written; + return 0; +} diff --git a/src/utils/uuid.c b/src/utils/uuid.c index ba20c49f0..bb9e63846 100644 --- a/src/utils/uuid.c +++ b/src/utils/uuid.c @@ -15,6 +15,8 @@ #include #include +#include + bool lightnvr_uuid_is_valid(const char *value) { if (!value || strnlen(value, LIGHTNVR_UUID_STRING_SIZE) != 36) { return false; @@ -73,3 +75,50 @@ int lightnvr_uuid_generate_v4(char output[LIGHTNVR_UUID_STRING_SIZE]) { output[position] = '\0'; return 0; } + +static int hex_value(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +int lightnvr_uuid_generate_v5(const char *namespace_uuid, const char *name, + size_t name_len, + char output[LIGHTNVR_UUID_STRING_SIZE]) { + if (!output || !name || !lightnvr_uuid_is_valid(namespace_uuid)) return -1; + + unsigned char namespace_bytes[16]; + size_t byte = 0; + for (int index = 0; index < 36; ++index) { + if (index == 8 || index == 13 || index == 18 || index == 23) continue; + int high = hex_value(namespace_uuid[index]); + int low = hex_value(namespace_uuid[++index]); + namespace_bytes[byte++] = (unsigned char)((high << 4) | low); + } + + const mbedtls_md_info_t *sha1 = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + if (!sha1) return -1; + mbedtls_md_context_t context; + mbedtls_md_init(&context); + unsigned char digest[20]; + int rc = mbedtls_md_setup(&context, sha1, 0); + if (rc == 0) rc = mbedtls_md_starts(&context); + if (rc == 0) rc = mbedtls_md_update(&context, namespace_bytes, sizeof(namespace_bytes)); + if (rc == 0) rc = mbedtls_md_update(&context, (const unsigned char *)name, name_len); + if (rc == 0) rc = mbedtls_md_finish(&context, digest); + mbedtls_md_free(&context); + if (rc != 0) return -1; + + digest[6] = (unsigned char)((digest[6] & 0x0fU) | 0x50U); + digest[8] = (unsigned char)((digest[8] & 0x3fU) | 0x80U); + size_t position = 0; + for (size_t index = 0; index < 16; ++index) { + if (index == 4 || index == 6 || index == 8 || index == 10) + output[position++] = '-'; + output[position++] = hex_digit((unsigned char)(digest[index] >> 4)); + output[position++] = hex_digit(digest[index]); + } + output[position] = '\0'; + return 0; +} diff --git a/src/utils/webrtc_candidates.c b/src/utils/webrtc_candidates.c new file mode 100644 index 000000000..cd3b8d1be --- /dev/null +++ b/src/utils/webrtc_candidates.c @@ -0,0 +1,143 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +#include "utils/webrtc_candidates.h" + +#include +#include +#include +#include + +#include "utils/strings.h" + +static bool candidate_port_ok(const char *p, size_t n) { + if (n == 0 || n > 5) return false; + long v = 0; + for (size_t i = 0; i < n; i++) { + if (p[i] < '0' || p[i] > '9') return false; + v = v * 10 + (p[i] - '0'); + } + return v >= 1 && v <= 65535; +} + +/* Hostname or IPv4: dot-separated labels of [A-Za-z0-9-], no edge hyphens. */ +static bool candidate_host_ok(const char *h, size_t n) { + if (n == 0 || n > 253) return false; + size_t label = 0; + for (size_t i = 0; i < n; i++) { + char c = h[i]; + if (c == '.') { + if (label == 0 || h[i - 1] == '-') return false; + label = 0; + continue; + } + bool alnum = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'); + if (!alnum && c != '-') return false; + if (c == '-' && label == 0) return false; + if (++label > 63) return false; + } + return label > 0 && h[n - 1] != '-'; +} + +static bool candidate_ipv6_ok(const char *h, size_t n) { + if (n < 2 || n > 45) return false; + int colons = 0; + for (size_t i = 0; i < n; i++) { + char c = h[i]; + if (c == ':') { colons++; continue; } + bool hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); + if (!hex && c != '.') return false; + } + return colons >= 2; +} + +/* host[:port] with an optional bracketed IPv6 host; writes ":". */ +static bool candidate_address(const char *e, size_t n, int default_port, bool port_optional_out, + char *out, size_t out_size) { + const char *host = e, *port = NULL; + size_t host_len = n, port_len = 0; + bool v6 = false; + if (n > 0 && e[0] == '[') { + const char *close = memchr(e, ']', n); + if (!close) return false; + host = e + 1; + host_len = (size_t)(close - host); + size_t rest = n - (size_t)(close - e) - 1; + if (rest > 0) { + if (close[1] != ':') return false; + port = close + 2; + port_len = rest - 1; + if (port_len == 0) return false; + } + if (!candidate_ipv6_ok(host, host_len)) return false; + v6 = true; + } else { + const char *first = memchr(e, ':', n); + if (first && memchr(first + 1, ':', n - (size_t)(first - e) - 1)) { + if (!candidate_ipv6_ok(e, n)) return false; /* bare IPv6, no port */ + v6 = true; + } else if (first) { + host_len = (size_t)(first - e); + port = first + 1; + port_len = n - host_len - 1; + if (port_len == 0) return false; + } + if (!v6 && !(host_len == 1 && host[0] == '*') && !candidate_host_ok(host, host_len)) return false; + } + if (port && !candidate_port_ok(port, port_len)) return false; + + int w; + if (port) { + w = snprintf(out, out_size, v6 ? "[%.*s]:%.*s" : "%.*s:%.*s", + (int)host_len, host, (int)port_len, port); + } else if (port_optional_out) { + w = snprintf(out, out_size, v6 ? "[%.*s]" : "%.*s", (int)host_len, host); + } else { + w = snprintf(out, out_size, v6 ? "[%.*s]:%d" : "%.*s:%d", (int)host_len, host, default_port); + } + return w > 0 && (size_t)w < out_size; +} + +int go2rtc_parse_external_candidates(const char *list, int port, + char out[][GO2RTC_CANDIDATE_SIZE], int max) { + if (!list) return 0; + if (port <= 0 || port > 65535) port = 8555; + int count = 0; + const char *p = list; + for (;;) { + while (*p == ',' || *p == ';' || *p == ' ' || *p == '\t' || *p == '\n' || *p == '\r') p++; + if (!*p) break; + const char *start = p; + while (*p && *p != ',' && *p != ';' && *p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') p++; + size_t n = (size_t)(p - start); + + char candidate[GO2RTC_CANDIDATE_SIZE]; + bool ok; + if ((n == 1 && start[0] == '*') || (n == 4 && strncasecmp(start, "auto", 4) == 0)) { + ok = snprintf(candidate, sizeof(candidate), "*:%d", port) > 0; + } else if (n > 5 && strncasecmp(start, "stun:", 5) == 0) { + char addr[GO2RTC_CANDIDATE_SIZE]; + ok = candidate_address(start + 5, n - 5, port, true, addr, sizeof(addr)) && + addr[0] != '*' && + snprintf(candidate, sizeof(candidate), "stun:%s", addr) < (int)sizeof(candidate); + } else { + ok = candidate_address(start, n, port, false, candidate, sizeof(candidate)); + } + if (!ok) return -1; + + bool duplicate = false; + for (int i = 0; i < count; i++) { + if (strcmp(out[i], candidate) == 0) { duplicate = true; break; } + } + if (duplicate) continue; + if (count >= max) return -1; + safe_strcpy(out[count], candidate, GO2RTC_CANDIDATE_SIZE, 0); + count++; + } + return count; +} + diff --git a/src/video/api_detection.c b/src/video/api_detection.c index 1e42403b8..6ab3e8810 100644 --- a/src/video/api_detection.c +++ b/src/video/api_detection.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -23,9 +24,11 @@ #include "core/curl_init.h" #include "core/shutdown_coordinator.h" #include "core/event_producers.h" +#include "core/observation_evidence.h" #include "utils/base64.h" #include "utils/strings.h" #include "video/api_detection.h" +#include "video/detection_evidence.h" #include "video/detection_result.h" #include "video/stream_manager.h" #include "video/stream_state.h" @@ -436,9 +439,60 @@ static void log_json_item(const char *message, const cJSON *item, bool as_error) free(json_str); } +/* INT_01 L7: optional "letterbox" / "model" / "model_version" (see header). */ +static void parse_response_meta(const cJSON *root, api_detection_response_meta_t *meta) { + memset(meta, 0, sizeof(*meta)); + meta->pad_x = meta->pad_y = -1.0; + + const cJSON *model = cJSON_GetObjectItemCaseSensitive(root, "model"); + if (cJSON_IsString(model) && model->valuestring[0] != '\0' && + strlen(model->valuestring) < sizeof(meta->model)) { + safe_strcpy(meta->model, model->valuestring, sizeof(meta->model), 0); + } + const cJSON *version = cJSON_GetObjectItemCaseSensitive(root, "model_version"); + if (cJSON_IsString(version) && version->valuestring[0] != '\0' && + strlen(version->valuestring) < sizeof(meta->model_version)) { + safe_strcpy(meta->model_version, version->valuestring, sizeof(meta->model_version), 0); + } + + const cJSON *lb = cJSON_GetObjectItemCaseSensitive(root, "letterbox"); + if (!lb || !cJSON_IsObject(lb)) { + return; + } + double w = 0.0, h = 0.0, v = 0.0; + if (!number_item(lb, "width", &w) || !number_item(lb, "height", &h) || + !(w >= 1.0) || !(h >= 1.0) || w > 65535.0 || h > 65535.0) { + log_warn("API Detection: Ignoring letterbox declaration without a valid width/height"); + return; + } + meta->inference_width = (int)w; + meta->inference_height = (int)h; + if (number_item(lb, "scale", &v) && v > 0.0) { + meta->scale_x = meta->scale_y = v; + } + if (number_item(lb, "scale_x", &v) && v > 0.0) meta->scale_x = v; + if (number_item(lb, "scale_y", &v) && v > 0.0) meta->scale_y = v; + if (number_item(lb, "pad_x", &v) && v >= 0.0) meta->pad_x = v; + if (number_item(lb, "pad_y", &v) && v >= 0.0) meta->pad_y = v; + const cJSON *units = cJSON_GetObjectItemCaseSensitive(lb, "box_units"); + meta->pixel_units = cJSON_IsString(units) && strcasecmp(units->valuestring, "pixels") == 0; + meta->letterbox = true; +} + int api_detection_parse_response(const char *json, const api_detection_options_t *options, detection_result_t *result) { + return api_detection_parse_response_meta(json, options, result, NULL); +} + +int api_detection_parse_response_meta(const char *json, + const api_detection_options_t *options, + detection_result_t *result, + api_detection_response_meta_t *meta) { + if (meta) { + memset(meta, 0, sizeof(*meta)); + meta->pad_x = meta->pad_y = -1.0; + } if (!json || !result) { return -1; } @@ -455,6 +509,10 @@ int api_detection_parse_response(const char *json, return -1; } + if (meta && cJSON_IsObject(root)) { + parse_response_meta(root, meta); + } + if (doods2) { const cJSON *error = cJSON_GetObjectItem(root, "error"); if (error && cJSON_IsString(error) && error->valuestring[0] != '\0') { @@ -535,6 +593,120 @@ int api_detection_parse_response(const char *json, return 0; } +/* ------------------------------------------------------------------------- */ +/* INT_01 L7: response geometry and submitted-image size */ +/* ------------------------------------------------------------------------- */ + +int api_detection_apply_response_geometry(const api_detection_response_meta_t *meta, + int image_w, int image_h, + int source_w, int source_h, + detection_result_t *result, + observation_evidence_transform_t *transform) { + if (image_w <= 0 || image_h <= 0 || !result) { + return -1; + } + if (source_w <= 0 || source_h <= 0) { + source_w = image_w; + source_h = image_h; + } + + observation_evidence_transform_t t; + memset(&t, 0, sizeof(t)); + t.mode = OBS_TRANSFORM_NONE; + t.source_width = source_w; + t.source_height = source_h; + t.inference_width = image_w; + t.inference_height = image_h; + t.scale_x = t.scale_y = 1.0; + + if (meta && meta->letterbox && meta->inference_width > 0 && meta->inference_height > 0) { + const double iw = (double)meta->inference_width; + const double ih = (double)meta->inference_height; + double sx = meta->scale_x, sy = meta->scale_y; + if (!(sx > 0.0) || !(sy > 0.0)) { + double uniform = iw / image_w < ih / image_h ? iw / image_w : ih / image_h; + if (!(sx > 0.0)) sx = uniform; + if (!(sy > 0.0)) sy = uniform; + } + double px = meta->pad_x, py = meta->pad_y; + if (px < 0.0) px = floor((iw - round(image_w * sx)) / 2.0); + if (py < 0.0) py = floor((ih - round(image_h * sy)) / 2.0); + if (px < 0.0) px = 0.0; + if (py < 0.0) py = 0.0; + + for (int i = 0; i < result->count; ++i) { + detection_t *d = &result->detections[i]; + double u = d->x, v = d->y, du = d->width, dv = d->height; + if (!meta->pixel_units) { + u *= iw; du *= iw; + v *= ih; dv *= ih; + } + d->x = (float)(((u - px) / sx) / image_w); + d->y = (float)(((v - py) / sy) / image_h); + d->width = (float)((du / sx) / image_w); + d->height = (float)((dv / sy) / image_h); + } + + t.mode = OBS_TRANSFORM_LETTERBOX; + t.inference_width = meta->inference_width; + t.inference_height = meta->inference_height; + t.scale_x = sx; + t.scale_y = sy; + t.pad_x = px; + t.pad_y = py; + } + + if (transform) { + *transform = t; + } + return 0; +} + +int api_detection_jpeg_dimensions(const uint8_t *jpeg, size_t size, int *width, int *height) { + if (!jpeg || size < 4 || !width || !height || jpeg[0] != 0xFF || jpeg[1] != 0xD8) { + return -1; + } + size_t pos = 2; + while (pos + 4 <= size) { + if (jpeg[pos] != 0xFF) { + return -1; + } + uint8_t marker = jpeg[pos + 1]; + if (marker == 0xFF) { /* fill byte */ + pos++; + continue; + } + if (marker == 0xD8 || marker == 0x01 || (marker >= 0xD0 && marker <= 0xD7)) { + pos += 2; /* standalone markers */ + continue; + } + if (marker == 0xD9 || marker == 0xDA) { + return -1; /* EOI / SOS before any SOFn */ + } + size_t seg = ((size_t)jpeg[pos + 2] << 8) | jpeg[pos + 3]; + if (seg < 2 || pos + 2 + seg > size) { + return -1; + } + bool sof = marker >= 0xC0 && marker <= 0xCF && + marker != 0xC4 && marker != 0xC8 && marker != 0xCC; + if (sof) { + if (seg < 7) { + return -1; + } + int h = ((int)jpeg[pos + 5] << 8) | jpeg[pos + 6]; + int w = ((int)jpeg[pos + 7] << 8) | jpeg[pos + 8]; + if (w <= 0 || h <= 0) { + return -1; + } + *width = w; + *height = h; + return 0; + } + pos += 2 + seg; + } + return -1; +} + /* ------------------------------------------------------------------------- */ /* HTTP transport */ /* ------------------------------------------------------------------------- */ @@ -682,7 +854,8 @@ static int perform_detection_request(const char *context, const char *base_url, const unsigned char *jpeg_data, size_t jpeg_size, float threshold, const char *request_id, const api_detection_options_t *options, - detection_result_t *result) { + detection_result_t *result, + api_detection_response_meta_t *meta) { CURL *curl = NULL; curl_mime *mime = NULL; struct curl_slist *headers = NULL; @@ -777,7 +950,7 @@ static int perform_detection_request(const char *context, const char *base_url, make_response_preview(&chunk, preview, sizeof(preview)); log_info("%s: Response preview: %s", context, preview); - ret = api_detection_parse_response(chunk.memory, options, result); + ret = api_detection_parse_response_meta(chunk.memory, options, result, meta); if (ret != 0) { log_error("%s: Response size: %zu bytes", context, chunk.size); } @@ -791,15 +964,120 @@ static int perform_detection_request(const char *context, const char *base_url, return ret; } +/* INT_01 L7: the exact image sent to the detection server. */ +typedef struct { + uint8_t *jpeg; /* owned; consumed by finalize_detections() */ + size_t size; + int width, height; /* JPEG pixel size (0 = unknown) */ + observation_evidence_transform_t transform; +} submitted_image_t; + +static bool has_camera_uuid(const api_detection_evidence_t *evidence) { + return evidence && evidence->camera_uuid && evidence->camera_uuid[0] != '\0'; +} + +/* Resolve the JPEG's size and map any server-declared letterbox onto it + * before zone filtering, so every consumer sees boxes normalized to the + * submitted image (contract section 3.2). source_w/h <= 0 = the JPEG is the + * source (go2rtc snapshot). */ +static void apply_submitted_geometry(const char *context, const char *stream_name, + const api_detection_response_meta_t *meta, + int source_w, int source_h, + detection_result_t *result, submitted_image_t *image) { + if (image->width <= 0 || image->height <= 0) { + if (api_detection_jpeg_dimensions(image->jpeg, image->size, + &image->width, &image->height) != 0) { + image->width = image->height = 0; + } + } + if (image->width <= 0 || image->height <= 0) { + if (meta->letterbox) { + log_warn("%s: Cannot map the declared letterbox for %s: submitted JPEG size unknown", + context, stream_name ? stream_name : "(none)"); + } + return; + } + api_detection_apply_response_geometry(meta, image->width, image->height, source_w, source_h, + result, &image->transform); +} + +/* Hand the submitted JPEG and the surviving detections to the L3 queue when + * the camera wants evidence. Always consumes image->jpeg. */ +static void submit_api_evidence(const char *context, const char *stream_name, + const detection_result_t *result, float threshold, + uint64_t recording_id, + const api_detection_options_t *options, + const api_detection_evidence_t *evidence, + const api_detection_response_meta_t *meta, + submitted_image_t *image) { + uint8_t *jpeg = image->jpeg; + image->jpeg = NULL; + if (!has_camera_uuid(evidence) || !evidence->evidence_enabled || result->count <= 0 || + !jpeg || image->width <= 0 || image->height <= 0) { + free(jpeg); + return; + } + + /* Same survivors as the detection thread keeps: confidence >= threshold. */ + detection_t survivors[MAX_DETECTIONS]; + int n = 0; + for (int i = 0; i < result->count && n < MAX_DETECTIONS; ++i) { + if (threshold > 0.0f && result->detections[i].confidence < threshold) continue; + survivors[n++] = result->detections[i]; + } + if (n == 0) { + free(jpeg); + return; + } + + const char *model = NULL; + if (meta->model[0] != '\0') { + model = meta->model; + } else if (options->format == API_DETECTION_FORMAT_DOODS2 && + options->detector_name[0] != '\0' && + strcmp(options->detector_name, "default") != 0) { + model = options->detector_name; + } + detection_evidence_camera_t camera = { + .camera_uuid = evidence->camera_uuid, + .stream_name = stream_name, + .evidence_enabled = true, + .labels_csv = evidence->labels_csv, + .recording_id = (int64_t)recording_id, + }; + detection_evidence_engine_t engine = { + .engine_key = evidence->engine_key, + .engine_type = "api", + .model_path = model, + .model_version = meta->model_version[0] ? meta->model_version : NULL, + }; + observation_evidence_result_t submitted = OBS_EVIDENCE_INVALID; + detection_evidence_status_t status = detection_evidence_capture_jpeg( + &camera, &engine, survivors, n, evidence->observed_at_ms, + jpeg, image->size, image->width, image->height, &image->transform, + &submitted, NULL); + if (status == DETECTION_EVIDENCE_SUBMITTED && submitted != OBS_EVIDENCE_ACCEPTED) { + log_debug("%s: Observation evidence for %s not queued (%d)", context, + stream_name, (int)submitted); + } +} + /** - * Apply zone and object filters, persist, and publish detections for a stream. - * Returns 0 on success, -1 when zone filtering fails. + * Apply zone and object filters, persist, and publish detections for a stream, + * then hand the submitted image to the evidence queue (INT_01 L7). Always + * consumes image->jpeg. Returns 0 on success, -1 when zone filtering fails. */ static int finalize_detections(const char *context, const char *stream_name, detection_result_t *result, time_t frame_timestamp, - uint64_t recording_id) { + uint64_t recording_id, float threshold, + const api_detection_options_t *options, + const api_detection_evidence_t *evidence, + const api_detection_response_meta_t *meta, + submitted_image_t *image) { if (!stream_name || stream_name[0] == '\0') { log_warn("%s: No stream name provided, skipping database storage", context); + free(image->jpeg); + image->jpeg = NULL; return 0; } @@ -807,26 +1085,54 @@ static int finalize_detections(const char *context, const char *stream_name, if (filter_detections_by_zones(stream_name, result) != 0) { log_error("%s: Failed to filter detections by zones for stream %s, aborting detection pipeline for this frame", context, stream_name); + free(image->jpeg); + image->jpeg = NULL; return -1; } filter_detections_by_stream_objects(stream_name, result); time_t timestamp = (frame_timestamp != 0) ? frame_timestamp : time(NULL); - store_detections_in_db(stream_name, result, timestamp, recording_id); + const bool by_camera = has_camera_uuid(evidence); + if (by_camera) { + store_detections_in_db_for_camera(evidence->camera_uuid, stream_name, result, + timestamp, recording_id); + } else { + store_detections_in_db(stream_name, result, timestamp, recording_id); + } if (result->count > 0) { char event_error[256] = {0}; - if (event_producer_publish_detection_for_stream( - stream_name, result, timestamp, - event_error, sizeof(event_error)) != 0) { + int rc = by_camera + ? event_producer_publish_detection(evidence->camera_uuid, stream_name, result, + timestamp, event_error, sizeof(event_error)) + : event_producer_publish_detection_for_stream(stream_name, result, timestamp, + event_error, sizeof(event_error)); + if (rc != 0) { log_debug("%s: Event enqueue failed for %s: %s", context, stream_name, event_error); } } + submit_api_evidence(context, stream_name, result, threshold, recording_id, + options, evidence, meta, image); return 0; } +/* Copy the caller's evidence settings, stamping the capture time now (before + * the up-to-10 s request) when the caller did not. */ +static const api_detection_evidence_t *stamp_evidence(const api_detection_evidence_t *in, + api_detection_evidence_t *storage, + time_t frame_timestamp) { + if (!in) { + return NULL; + } + *storage = *in; + if (storage->observed_at_ms <= 0) { + storage->observed_at_ms = detection_evidence_observed_at_ms(frame_timestamp); + } + return storage; +} + /* ------------------------------------------------------------------------- */ /* Lifecycle */ /* ------------------------------------------------------------------------- */ @@ -902,7 +1208,22 @@ int detect_objects_api_with_options(const char *api_url, const unsigned char *fr float threshold, uint64_t recording_id, time_t frame_timestamp, const api_detection_options_t *options) { + return detect_objects_api_with_evidence(api_url, frame_data, width, height, channels, + result, stream_name, threshold, recording_id, + frame_timestamp, options, NULL); +} + +int detect_objects_api_with_evidence(const char *api_url, const unsigned char *frame_data, + int width, int height, int channels, + detection_result_t *result, const char *stream_name, + float threshold, uint64_t recording_id, + time_t frame_timestamp, + const api_detection_options_t *options, + const api_detection_evidence_t *evidence_in) { static const char *context = "API Detection"; + api_detection_evidence_t evidence_storage; + const api_detection_evidence_t *evidence = + stamp_evidence(evidence_in, &evidence_storage, frame_timestamp); // Check if we're in shutdown mode or if the stream has been stopped. if (is_shutdown_initiated()) { @@ -991,13 +1312,26 @@ int detect_objects_api_with_options(const char *api_url, const unsigned char *fr return -1; } + api_detection_response_meta_t meta; int ret = perform_detection_request(context, actual_api_url, jpeg_data, jpeg_size, - threshold, stream_name, options, result); - free(jpeg_data); + threshold, stream_name, options, result, &meta); if (ret == 0) { - ret = finalize_detections(context, stream_name, result, frame_timestamp, recording_id); + /* The bytes just sent ARE the evidence; never fetch a second image. + * A locally encoded frame keeps its size; a go2rtc snapshot is + * measured from its SOF marker and is its own source. */ + submitted_image_t image = { + .jpeg = jpeg_data, .size = jpeg_size, + .width = snapshot_ok ? 0 : width, .height = snapshot_ok ? 0 : height, + }; + jpeg_data = NULL; + apply_submitted_geometry(context, stream_name, &meta, + snapshot_ok ? 0 : width, snapshot_ok ? 0 : height, + result, &image); + ret = finalize_detections(context, stream_name, result, frame_timestamp, recording_id, + threshold, options, evidence, &meta, &image); } + free(jpeg_data); if (ret != 0) { result->count = 0; @@ -1030,7 +1364,20 @@ int detect_objects_api_snapshot_with_options(const char *api_url, const char *st detection_result_t *result, float threshold, uint64_t recording_id, time_t frame_timestamp, const api_detection_options_t *options) { + return detect_objects_api_snapshot_with_evidence(api_url, stream_name, result, threshold, + recording_id, frame_timestamp, options, + NULL); +} + +int detect_objects_api_snapshot_with_evidence(const char *api_url, const char *stream_name, + detection_result_t *result, float threshold, + uint64_t recording_id, time_t frame_timestamp, + const api_detection_options_t *options, + const api_detection_evidence_t *evidence_in) { static const char *context = "API Detection (snapshot)"; + api_detection_evidence_t evidence_storage; + const api_detection_evidence_t *evidence = + stamp_evidence(evidence_in, &evidence_storage, frame_timestamp); // Check if we're in shutdown mode if (is_shutdown_initiated()) { @@ -1088,13 +1435,19 @@ int detect_objects_api_snapshot_with_options(const char *api_url, const char *st return API_DETECTION_SNAPSHOT_UNAVAILABLE; } + api_detection_response_meta_t meta; int ret = perform_detection_request(context, actual_api_url, jpeg_data, jpeg_size, - threshold, stream_name, options, result); - free(jpeg_data); + threshold, stream_name, options, result, &meta); if (ret == 0) { - ret = finalize_detections(context, stream_name, result, frame_timestamp, recording_id); + /* The go2rtc snapshot was the inference input, so it is the evidence. */ + submitted_image_t image = { .jpeg = jpeg_data, .size = jpeg_size }; + jpeg_data = NULL; + apply_submitted_geometry(context, stream_name, &meta, 0, 0, result, &image); + ret = finalize_detections(context, stream_name, result, frame_timestamp, recording_id, + threshold, options, evidence, &meta, &image); } + free(jpeg_data); if (ret != 0) { result->count = 0; diff --git a/src/video/detection_evidence.c b/src/video/detection_evidence.c new file mode 100644 index 000000000..b1db77798 --- /dev/null +++ b/src/video/detection_evidence.c @@ -0,0 +1,414 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/* INT_01 L6: local detector -> observation evidence glue. See + * include/video/detection_evidence.h. No DB access and no blocking. */ + +#include "video/detection_evidence.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/logger.h" +#include "utils/strings.h" +#include "utils/uuid.h" +#include "video/detection_model_internal.h" +#include "video/sod_detection.h" +#ifdef HAVE_LITERT +#include "video/detection/litert_engine.h" +#endif + +#define DEFAULT_ENGINE_KEY "legacy-primary" + +/* ---- boot session ----------------------------------------------------- */ + +static pthread_once_t boot_once = PTHREAD_ONCE_INIT; +static char boot_session[OBS_UUID_SIZE]; + +static void boot_session_init(void) { + if (lightnvr_uuid_generate_v4(boot_session) != 0) boot_session[0] = '\0'; +} + +const char *detection_evidence_boot_session(void) { + pthread_once(&boot_once, boot_session_init); + return boot_session; +} + +/* ---- eligibility ------------------------------------------------------ */ + +bool detection_evidence_label_eligible(const char *labels_csv, const char *label) { + if (!label || !label[0]) return false; + if (!labels_csv) return true; + + const char *scan = labels_csv; + while (*scan == ' ' || *scan == '\t') scan++; + if (*scan == '\0') return true; + + size_t label_len = strlen(label); + const char *cursor = labels_csv; + for (;;) { + const char *end = strchr(cursor, ','); + const char *stop = end ? end : cursor + strlen(cursor); + const char *first = cursor; + const char *last = stop; + while (first < last && (*first == ' ' || *first == '\t')) first++; + while (last > first && (last[-1] == ' ' || last[-1] == '\t')) last--; + if ((size_t)(last - first) == label_len && + strncasecmp(first, label, label_len) == 0) { + return true; + } + if (!end) return false; + cursor = end + 1; + } +} + +/* ---- boxes -------------------------------------------------------------- */ + +static double clamp01(double v) { + if (!(v > 0.0)) return 0.0; /* also maps NaN to 0 */ + return v > 1.0 ? 1.0 : v; +} + +bool detection_evidence_clamp_box(double x, double y, double w, double h, + double *ox, double *oy, double *ow, double *oh) { + if (!isfinite(x) || !isfinite(y) || !isfinite(w) || !isfinite(h)) return false; + double x0 = clamp01(x), y0 = clamp01(y); + double x1 = clamp01(x + w), y1 = clamp01(y + h); + /* L2 rounds to 6 decimals; anything that would round to zero area is no box. */ + if (x1 - x0 < 1e-6 || y1 - y0 < 1e-6) return false; + if (ox) *ox = x0; + if (oy) *oy = y0; + if (ow) *ow = x1 - x0; + if (oh) *oh = y1 - y0; + return true; +} + +/* ---- batch ---------------------------------------------------------------- */ + +static const char *basename_of(const char *path) { + if (!path) return ""; + const char *slash = strrchr(path, '/'); + return slash ? slash + 1 : path; +} + +int detection_evidence_build_batch(const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, + observation_batch_input_t *out) { + if (!camera || !engine || !out || count < 0 || (count > 0 && !detections) || + !camera->camera_uuid || !camera->camera_uuid[0] || observed_at_ms <= 0) { + return -1; + } + + memset(out, 0, sizeof(*out)); + safe_strcpy(out->camera_uuid, camera->camera_uuid, sizeof(out->camera_uuid), 0); + if (camera->stream_name) { + safe_strcpy(out->stream_name, camera->stream_name, sizeof(out->stream_name), 0); + } + out->observed_at_ms = observed_at_ms; + out->recording_id = camera->recording_id > 0 ? camera->recording_id : 0; + const char *key = (engine->engine_key && engine->engine_key[0]) + ? engine->engine_key : DEFAULT_ENGINE_KEY; + safe_strcpy(out->engine_key, key, sizeof(out->engine_key), 0); + safe_strcpy(out->engine_type, + engine->engine_type && engine->engine_type[0] ? engine->engine_type : "object", + sizeof(out->engine_type), 0); + const char *model = basename_of(engine->model_path); + if (strlen(model) < sizeof(out->model)) { + safe_strcpy(out->model, model, sizeof(out->model), 0); + } + if (engine->model_version && + strlen(engine->model_version) < sizeof(out->model_version)) { + safe_strcpy(out->model_version, engine->model_version, sizeof(out->model_version), 0); + } + + const char *session = NULL; + int n = 0; + for (int i = 0; i < count && n < OBS_MAX_BATCH; ++i) { + const detection_t *d = &detections[i]; + size_t label_len = strnlen(d->label, sizeof(d->label)); + if (label_len == 0 || label_len >= OBS_LABEL_SIZE) continue; + if (!detection_evidence_label_eligible(camera->labels_csv, d->label)) continue; + if (!isfinite(d->confidence)) continue; + + observation_object_input_t *o = &out->objects[n]; + o->object_index = n; + memcpy(o->label, d->label, label_len); + o->label[label_len] = '\0'; + o->confidence = clamp01((double)d->confidence); + o->has_box = detection_evidence_clamp_box(d->x, d->y, d->width, d->height, + &o->box_x, &o->box_y, + &o->box_width, &o->box_height); + if (d->track_id >= 0) { + if (!session) session = detection_evidence_boot_session(); + if (session[0]) { + o->has_track = true; + snprintf(o->track_id, sizeof(o->track_id), "%d", d->track_id); + safe_strcpy(o->track_namespace, out->engine_key, sizeof(o->track_namespace), 0); + safe_strcpy(o->track_session, session, sizeof(o->track_session), 0); + } + } + ++n; + } + out->object_count = n; + if (n == 0) return 0; + + if (lightnvr_uuid_generate_v4(out->batch_id) != 0) return -1; + return n; +} + +/* ---- transform ------------------------------------------------------------ */ + +void detection_evidence_transform_compute(int frame_w, int frame_h, + int net_w, int net_h, bool letterbox, + observation_evidence_transform_t *out) { + if (!out) return; + memset(out, 0, sizeof(*out)); + out->mode = OBS_TRANSFORM_NONE; + out->source_width = out->inference_width = frame_w; + out->source_height = out->inference_height = frame_h; + out->scale_x = out->scale_y = 1.0; + if (frame_w <= 0 || frame_h <= 0 || net_w <= 0 || net_h <= 0 || + (net_w == frame_w && net_h == frame_h)) { + return; + } + + out->inference_width = net_w; + out->inference_height = net_h; + if (!letterbox) { + out->mode = OBS_TRANSFORM_RESIZE; + out->scale_x = (double)net_w / (double)frame_w; + out->scale_y = (double)net_h / (double)frame_h; + return; + } + + /* Mirror litert_engine.cc letterbox_rgb24() exactly (float math). */ + float sx = (float)net_w / (float)frame_w; + float sy = (float)net_h / (float)frame_h; + float scale = sx < sy ? sx : sy; + long new_w = lroundf((float)frame_w * scale); + long new_h = lroundf((float)frame_h * scale); + if (new_w < 1) new_w = 1; + if (new_h < 1) new_h = 1; + out->mode = OBS_TRANSFORM_LETTERBOX; + out->scale_x = out->scale_y = (double)scale; + out->pad_x = (double)((net_w - (int)new_w) / 2); + out->pad_y = (double)((net_h - (int)new_h) / 2); + if (out->pad_x < 0) out->pad_x = 0; + if (out->pad_y < 0) out->pad_y = 0; +} + +int detection_evidence_model_geometry(detection_model_t model, int *net_w, int *net_h, + bool *letterbox) { + if (!model || !net_w || !net_h || !letterbox) return -1; + const model_t *m = (const model_t *)model; + if (strcmp(m->type, MODEL_TYPE_SOD) == 0) { + if (sod_model_input_size(model, net_w, net_h) != 0) return -1; + *letterbox = false; + return 0; + } +#ifdef HAVE_LITERT + if (strcmp(m->type, MODEL_TYPE_TFLITE) == 0 && m->tflite.engine) { + const litert_engine_t *engine = (const litert_engine_t *)m->tflite.engine; + *net_w = litert_engine_input_width(engine); + *net_h = litert_engine_input_height(engine); + if (*net_w <= 0 || *net_h <= 0) return -1; + *letterbox = true; + return 0; + } +#endif + /* RealNet scans the frame at its own resolution; others are unknown. */ + return -1; +} + +/* ---- time ------------------------------------------------------------------ */ + +int64_t detection_evidence_observed_at_ms(time_t frame_ts) { + struct timeval tv; + gettimeofday(&tv, NULL); + if (frame_ts <= 0) return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; + if ((time_t)tv.tv_sec == frame_ts) { + return (int64_t)tv.tv_sec * 1000 + tv.tv_usec / 1000; + } + return (int64_t)frame_ts * 1000; +} + +/* ---- capture ---------------------------------------------------------------- */ + +detection_evidence_status_t detection_evidence_capture( + const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, + uint8_t *rgb24, int width, int height, + const observation_evidence_transform_t *transform, + observation_evidence_result_t *submit_result, + char asset_uuid_out[OBS_UUID_SIZE]) { + if (asset_uuid_out) asset_uuid_out[0] = '\0'; + if (!camera || !engine) { + free(rgb24); + return DETECTION_EVIDENCE_ERROR; + } + if (!camera->evidence_enabled) { + free(rgb24); + return DETECTION_EVIDENCE_NOT_ENABLED; + } + /* Contract section 3: only object engines observe here. Motion never + * creates observations; API/ONVIF have their own paths (L7). */ + if (!engine->engine_type || strcmp(engine->engine_type, "object") != 0) { + free(rgb24); + return DETECTION_EVIDENCE_NOT_OBJECT_ENGINE; + } + if (!rgb24 || width <= 0 || height <= 0) { + free(rgb24); + return DETECTION_EVIDENCE_ERROR; + } + + observation_batch_input_t *batch = malloc(sizeof(*batch)); + if (!batch) { + free(rgb24); + return DETECTION_EVIDENCE_ERROR; + } + int n = detection_evidence_build_batch(camera, engine, detections, count, + observed_at_ms, batch); + if (n <= 0) { + free(batch); + free(rgb24); + return n == 0 ? DETECTION_EVIDENCE_NOT_ELIGIBLE : DETECTION_EVIDENCE_ERROR; + } + + observation_evidence_result_t r = observation_evidence_submit( + batch, rgb24, width, height, transform, asset_uuid_out); + if (submit_result) *submit_result = r; + if (r != OBS_EVIDENCE_ACCEPTED) { + log_debug("[%s] Observation evidence not accepted (result %d, %d object(s))", + camera->stream_name ? camera->stream_name : "?", (int)r, n); + } + free(batch); + return DETECTION_EVIDENCE_SUBMITTED; +} + +/* ---- INT_01 L7: HTTP API (submitted JPEG) -------------------------------- */ + +detection_evidence_status_t detection_evidence_capture_jpeg( + const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, + uint8_t *jpeg, size_t jpeg_size, int width, int height, + const observation_evidence_transform_t *transform, + observation_evidence_result_t *submit_result, + char asset_uuid_out[OBS_UUID_SIZE]) { + if (asset_uuid_out) asset_uuid_out[0] = '\0'; + if (!camera || !engine) { + free(jpeg); + return DETECTION_EVIDENCE_ERROR; + } + if (!camera->evidence_enabled) { + free(jpeg); + return DETECTION_EVIDENCE_NOT_ENABLED; + } + if (!engine->engine_type || strcmp(engine->engine_type, "api") != 0) { + free(jpeg); + return DETECTION_EVIDENCE_NOT_OBJECT_ENGINE; + } + if (!jpeg || jpeg_size == 0 || width <= 0 || height <= 0) { + free(jpeg); + return DETECTION_EVIDENCE_ERROR; + } + + observation_batch_input_t *batch = malloc(sizeof(*batch)); + if (!batch) { + free(jpeg); + return DETECTION_EVIDENCE_ERROR; + } + int n = detection_evidence_build_batch(camera, engine, detections, count, + observed_at_ms, batch); + if (n <= 0) { + free(batch); + free(jpeg); + return n == 0 ? DETECTION_EVIDENCE_NOT_ELIGIBLE : DETECTION_EVIDENCE_ERROR; + } + + observation_evidence_result_t r = observation_evidence_submit_jpeg( + batch, jpeg, jpeg_size, width, height, transform, asset_uuid_out); + if (submit_result) *submit_result = r; + if (r != OBS_EVIDENCE_ACCEPTED) { + log_debug("[%s] API observation evidence not accepted (result %d, %d object(s))", + camera->stream_name ? camera->stream_name : "?", (int)r, n); + } + free(batch); + return DETECTION_EVIDENCE_SUBMITTED; +} + +/* ---- INT_01 L7: ONVIF / external (no matching media) --------------------- */ + +bool detection_evidence_is_motion_label(const char *label) { + return label && strcasecmp(label, "motion") == 0; +} + +detection_evidence_status_t detection_evidence_record_no_media( + const detection_evidence_camera_t *camera, + const detection_evidence_engine_t *engine, + const detection_t *detections, int count, + int64_t observed_at_ms, bool keep_boxes, + observation_evidence_result_t *submit_result, + char asset_uuid_out[OBS_UUID_SIZE]) { + if (asset_uuid_out) asset_uuid_out[0] = '\0'; + if (!camera || !engine || count < 0 || (count > 0 && !detections)) { + return DETECTION_EVIDENCE_ERROR; + } + if (!camera->evidence_enabled) return DETECTION_EVIDENCE_NOT_ENABLED; + if (!engine->engine_type || (strcmp(engine->engine_type, "onvif") != 0 && + strcmp(engine->engine_type, "external") != 0)) { + return DETECTION_EVIDENCE_NOT_OBJECT_ENGINE; + } + + /* Object-class events only: drop the generic motion label first so the + * object_index stays the position among the stored objects. */ + detection_t objects[MAX_DETECTIONS]; + int kept = 0; + for (int i = 0; i < count && kept < MAX_DETECTIONS; ++i) { + if (detection_evidence_is_motion_label(detections[i].label)) continue; + objects[kept++] = detections[i]; + } + if (kept == 0) return DETECTION_EVIDENCE_NOT_ELIGIBLE; + + observation_batch_input_t *batch = malloc(sizeof(*batch)); + if (!batch) return DETECTION_EVIDENCE_ERROR; + int n = detection_evidence_build_batch(camera, engine, objects, kept, + observed_at_ms, batch); + if (n <= 0) { + free(batch); + return n == 0 ? DETECTION_EVIDENCE_NOT_ELIGIBLE : DETECTION_EVIDENCE_ERROR; + } + if (!keep_boxes) { + for (int i = 0; i < n; ++i) { + observation_object_input_t *o = &batch->objects[i]; + o->has_box = false; + o->box_x = o->box_y = o->box_width = o->box_height = 0.0; + } + } + + observation_evidence_result_t r = observation_evidence_submit_unavailable( + batch, "no_matching_media", asset_uuid_out); + if (submit_result) *submit_result = r; + if (r != OBS_EVIDENCE_ACCEPTED) { + log_debug("[%s] %s observation not recorded (result %d, %d object(s))", + camera->stream_name ? camera->stream_name : "?", engine->engine_type, + (int)r, n); + } + free(batch); + return DETECTION_EVIDENCE_SUBMITTED; +} diff --git a/src/video/go2rtc/go2rtc_process.c b/src/video/go2rtc/go2rtc_process.c index dedeec52a..ff3668d90 100644 --- a/src/video/go2rtc/go2rtc_process.c +++ b/src/video/go2rtc/go2rtc_process.c @@ -1095,16 +1095,21 @@ static bool go2rtc_process_generate_config_locked(const char *config_path, int a // Candidates configuration for NAT traversal fprintf(config_file, " candidates:\n"); - // If external IP is specified, use it - if (global_config->go2rtc_external_ip[0] != '\0') { - fprintf(config_file, " - \"%s:%d\"\n", - global_config->go2rtc_external_ip, - global_config->go2rtc_webrtc_listen_port > 0 ? global_config->go2rtc_webrtc_listen_port : 8555); - } else { - // Auto-detect external IP using wildcard - // Use separate entries for IPv4 and IPv6 to handle both - fprintf(config_file, " - \"*:%d\"\n", - global_config->go2rtc_webrtc_listen_port > 0 ? global_config->go2rtc_webrtc_listen_port : 8555); + // External IP is a list: one candidate per entry, so LAN, VPN and + // public clients can each reach go2rtc. Empty (or invalid, which the + // settings API refuses to save) falls back to auto-detect. + int webrtc_port = global_config->go2rtc_webrtc_listen_port > 0 ? global_config->go2rtc_webrtc_listen_port : 8555; + char candidates[GO2RTC_MAX_EXTERNAL_CANDIDATES][GO2RTC_CANDIDATE_SIZE]; + int candidate_count = go2rtc_parse_external_candidates(global_config->go2rtc_external_ip, webrtc_port, + candidates, GO2RTC_MAX_EXTERNAL_CANDIDATES); + if (candidate_count < 0) { + log_warn("go2rtc config: ignoring invalid external_ip list; using auto-detect"); + } + if (candidate_count <= 0) { + fprintf(config_file, " - \"*:%d\"\n", webrtc_port); + } + for (int i = 0; i < candidate_count; i++) { + fprintf(config_file, " - \"%s\"\n", candidates[i]); } // Add STUN server as candidate for ICE gathering diff --git a/src/video/onvif_device_management.c b/src/video/onvif_device_management.c index 56ed7f8fe..3f518e527 100644 --- a/src/video/onvif_device_management.c +++ b/src/video/onvif_device_management.c @@ -5,12 +5,15 @@ * This file is part of LightNVR . */ +#include #include #include #include +#include #include #include #include +#include #include "ezxml.h" #include "video/onvif_device_management.h" @@ -48,6 +51,11 @@ static size_t write_callback(void *contents, size_t size, size_t nmemb, void *us } // Send a SOAP request to the ONVIF device +/* Why the last send_soap_request() on this thread returned NULL, so the + * connection test can tell the user what failed. */ +enum { SOAP_FAIL_NONE = 0, SOAP_FAIL_TRANSPORT, SOAP_FAIL_AUTH, SOAP_FAIL_OTHER }; +static __thread int g_soap_failure; + static char* send_soap_request(const char *device_url, const char *soap_action, const char *request_body, const char *username, const char *password) { CURL *curl; @@ -171,6 +179,10 @@ static char* send_soap_request(const char *device_url, const char *soap_action, "", security_header, request_body); + // The previous envelope was freed: point curl at the new one before + // retrying, or it would send freed memory. + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, soap_envelope); + // Retry the request res = curl_easy_perform(curl); if (res != CURLE_OK) { @@ -183,11 +195,15 @@ static char* send_soap_request(const char *device_url, const char *soap_action, } // Check HTTP response code and parse any SOAP fault + g_soap_failure = (res == CURLE_OK) ? SOAP_FAIL_NONE : SOAP_FAIL_TRANSPORT; if (res == CURLE_OK) { long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); if (http_code != 200) { + bool not_authorized = http_code == 401 || http_code == 403 || + (chunk.size > 0 && strstr(chunk.memory, "NotAuthorized") != NULL); + g_soap_failure = not_authorized ? SOAP_FAIL_AUTH : SOAP_FAIL_OTHER; log_error("ONVIF device management request failed with HTTP code %ld", http_code); if (chunk.size > 0) { onvif_log_soap_fault(chunk.memory, chunk.size, "Device Management"); @@ -854,71 +870,138 @@ int add_onvif_device_as_stream(const onvif_device_info_t *device_info, } // Test connection to an ONVIF device -int test_onvif_connection(const char *url, const char *username, const char *password) { - // Attempt to get device profiles as a way to test the connection +int onvif_plain_rtsp_fallback_url(const char *uri, char *out, size_t out_size) { + if (!uri || !out || out_size == 0 || strncasecmp(uri, "rtsps://", 8) != 0) return -1; + const char *authority = uri + 8; + const char *path = authority + strcspn(authority, "/?#"); + /* userinfo ends at the last '@' inside the authority */ + const char *host = authority; + for (const char *p = authority; p < path; p++) { + if (*p == '@') host = p + 1; + } + const char *host_end; + if (*host == '[') { + const char *close = memchr(host, ']', (size_t)(path - host)); + if (!close) return -1; + host_end = close + 1; + } else { + const char *colon = memchr(host, ':', (size_t)(path - host)); + host_end = colon ? colon : path; + } + if (host_end == host) return -1; + int written = snprintf(out, out_size, "rtsp://%.*s:554%s", + (int)(host_end - authority), authority, path); + return (written > 0 && (size_t)written < out_size) ? 0 : -1; +} + +/* Open and close a stream (TCP, then UDP). Fills `error` with a reason. */ +static bool probe_stream(const char *stream_url, const char *safe_url, char *error, size_t error_size) { + static const int protocols[] = {STREAM_PROTOCOL_TCP, STREAM_PROTOCOL_UDP}; + int ret = -1; + for (size_t i = 0; i < sizeof(protocols) / sizeof(protocols[0]); i++) { + AVFormatContext *input_ctx = NULL; + ret = open_input_stream(&input_ctx, stream_url, protocols[i]); + if (ret >= 0) { + avformat_close_input(&input_ctx); + log_info("Successfully connected to stream: %s", safe_url); + return true; + } + log_warn("Failed to open stream %s over %s", safe_url, protocols[i] == STREAM_PROTOCOL_TCP ? "TCP" : "UDP"); + } + char reason[AV_ERROR_MAX_STRING_SIZE] = {0}; + av_strerror(ret, reason, sizeof(reason)); + snprintf(error, error_size, "%s", reason[0] ? reason : "could not open stream"); + return false; +} + +int test_onvif_connection_detail(const char *url, const char *username, const char *password, + char *detail, size_t detail_size) { + char scratch[1]; + if (!detail || detail_size == 0) { + detail = scratch; + detail_size = sizeof(scratch); + } + detail[0] = '\0'; + char safe_device_url[MAX_URL_LENGTH]; if (url_redact_for_logging(url, safe_device_url, sizeof(safe_device_url)) != 0) { safe_strcpy(safe_device_url, "[invalid-url]", sizeof(safe_device_url), 0); } log_info("Testing connection to ONVIF device: %s", safe_device_url); - + + g_soap_failure = SOAP_FAIL_NONE; onvif_profile_t profiles[1]; int count = get_onvif_device_profiles(url, username, password, profiles, 1); - if (count <= 0) { log_error("Failed to connect to ONVIF device: %s", safe_device_url); - return -1; + switch (g_soap_failure) { + case SOAP_FAIL_AUTH: + snprintf(detail, detail_size, + "The camera rejected the ONVIF credentials. Check the username and password, " + "that this ONVIF user may read media profiles, and that the camera's clock is correct."); + return ONVIF_TEST_AUTH_FAILED; + case SOAP_FAIL_TRANSPORT: + snprintf(detail, detail_size, "Could not reach the camera's ONVIF service at %s.", safe_device_url); + return ONVIF_TEST_UNREACHABLE; + default: + snprintf(detail, detail_size, + "The camera's ONVIF service at %s answered, but did not return a usable media profile.", + safe_device_url); + return ONVIF_TEST_DEVICE_ERROR; + } } - - log_info("Successfully connected to ONVIF device: %s", safe_device_url); - - // Now test the stream connection - if (strlen(profiles[0].stream_uri) > 0) { - char stream_url[MAX_URL_LENGTH]; - char safe_stream_url[MAX_URL_LENGTH]; - if (url_apply_credentials(profiles[0].stream_uri, - (username && strlen(username) > 0) ? username : NULL, - (password && strlen(password) > 0) ? password : NULL, - stream_url, sizeof(stream_url)) != 0) { - safe_strcpy(stream_url, profiles[0].stream_uri, sizeof(stream_url), 0); - } + log_info("Successfully connected to ONVIF device: %s", safe_device_url); + if (profiles[0].stream_uri[0] == '\0') { + snprintf(detail, detail_size, "Connected to the camera's ONVIF service."); + return ONVIF_TEST_OK; + } - if (url_redact_for_logging(stream_url, safe_stream_url, sizeof(safe_stream_url)) != 0) { - safe_strcpy(safe_stream_url, "[invalid-url]", sizeof(safe_stream_url), 0); - } + const char *user = (username && username[0]) ? username : NULL; + const char *pass = (password && password[0]) ? password : NULL; + char stream_url[MAX_URL_LENGTH]; + char safe_stream_url[MAX_URL_LENGTH]; + if (url_apply_credentials(profiles[0].stream_uri, user, pass, stream_url, sizeof(stream_url)) != 0) { + safe_strcpy(stream_url, profiles[0].stream_uri, sizeof(stream_url), 0); + } + if (url_redact_for_logging(stream_url, safe_stream_url, sizeof(safe_stream_url)) != 0) { + safe_strcpy(safe_stream_url, "[invalid-url]", sizeof(safe_stream_url), 0); + } + log_info("Testing stream connection for profile: %s, URI: %s", profiles[0].token, safe_stream_url); - log_info("Testing stream connection for profile: %s, URI: %s", - profiles[0].token, safe_stream_url); - - // Try to open the stream with TCP protocol first - AVFormatContext *input_ctx = NULL; - int ret = open_input_stream(&input_ctx, stream_url, STREAM_PROTOCOL_TCP); - - if (ret < 0) { - log_warn("Failed to connect to stream with TCP protocol, trying UDP: %s", safe_stream_url); - - // Try UDP protocol as fallback - ret = open_input_stream(&input_ctx, stream_url, STREAM_PROTOCOL_UDP); - - if (ret < 0) { - // Try with a direct RTSP URL without any modifications - log_warn("Failed with UDP protocol too, trying direct RTSP connection"); + char error[AV_ERROR_MAX_STRING_SIZE + 32]; + if (probe_stream(stream_url, safe_stream_url, error, sizeof(error))) { + snprintf(detail, detail_size, "Connected to the camera and opened its stream at %s.", safe_stream_url); + return ONVIF_TEST_OK; + } - log_info("Trying direct RTSP URL: %s", safe_stream_url); - ret = open_input_stream(&input_ctx, stream_url, STREAM_PROTOCOL_TCP); - - if (ret < 0) { - log_error("All connection attempts failed for stream: %s", safe_stream_url); - return -1; - } - } + /* Some cameras advertise rtsps:// on a port where secure RTSP is + * disabled while plain RTSP on 554 works; try that before giving up. */ + char fallback_url[MAX_URL_LENGTH]; + if (onvif_plain_rtsp_fallback_url(stream_url, fallback_url, sizeof(fallback_url)) == 0) { + char safe_fallback[MAX_URL_LENGTH]; + if (url_redact_for_logging(fallback_url, safe_fallback, sizeof(safe_fallback)) != 0) { + safe_strcpy(safe_fallback, "[invalid-url]", sizeof(safe_fallback), 0); } - - // Close the stream - avformat_close_input(&input_ctx); - log_info("Successfully connected to stream: %s", safe_stream_url); + log_info("Advertised secure stream failed; trying plain RTSP: %s", safe_fallback); + char fallback_error[AV_ERROR_MAX_STRING_SIZE + 32]; + if (probe_stream(fallback_url, safe_fallback, fallback_error, sizeof(fallback_error))) { + snprintf(detail, detail_size, + "Connected to the camera. Its advertised stream %s failed (%s), " + "but plain RTSP at %s works.", safe_stream_url, error, safe_fallback); + return ONVIF_TEST_OK; + } + snprintf(detail, detail_size, + "ONVIF works, but the camera's stream could not be opened: %s (%s); also tried %s (%s).", + safe_stream_url, error, safe_fallback, fallback_error); + } else { + snprintf(detail, detail_size, "ONVIF works, but the camera's stream could not be opened: %s (%s).", + safe_stream_url, error); } - - return 0; + log_error("All connection attempts failed for stream: %s", safe_stream_url); + return ONVIF_TEST_STREAM_FAILED; +} + +int test_onvif_connection(const char *url, const char *username, const char *password) { + return test_onvif_connection_detail(url, username, password, NULL, 0); } diff --git a/src/video/sod_detection.c b/src/video/sod_detection.c index c04b6dbf3..77974f439 100644 --- a/src/video/sod_detection.c +++ b/src/video/sod_detection.c @@ -197,6 +197,24 @@ bool is_sod_available(void) { return sod_available; } +int sod_model_input_size(detection_model_t model, int *width, int *height) { + if (!model || !width || !height) return -1; +#ifdef SOD_ENABLED + model_t *m = (model_t *)model; + if (strcmp(m->type, MODEL_TYPE_SOD) != 0 || !m->sod) return -1; + int net_w = 0, net_h = 0, net_c = 0; + if (sod_cnn_get_network_size((sod_cnn *)m->sod, &net_w, &net_h, &net_c) != 0 || + net_w <= 0 || net_h <= 0) { + return -1; + } + *width = net_w; + *height = net_h; + return 0; +#else + return -1; +#endif +} + /** * Load a SOD model */ diff --git a/src/video/unified_detection_thread.c b/src/video/unified_detection_thread.c index 0cd0c9523..e23e2a32a 100644 --- a/src/video/unified_detection_thread.c +++ b/src/video/unified_detection_thread.c @@ -56,6 +56,7 @@ #include "video/detection.h" #include "video/detection_model.h" #include "video/detection_result.h" +#include "video/detection_evidence.h" #include "video/api_detection.h" #include "video/motion_detection.h" #include "video/onvif_detection.h" @@ -137,6 +138,9 @@ static uint64_t detection_link_recording_id(unified_detection_ctx_t *ctx); static bool should_annotate_continuous(unified_detection_ctx_t *ctx); // ONVIF async detection thread helpers (defined before start_unified_detection_thread) static void *onvif_detection_thread_func(void *arg); +static void udt_record_onvif_observations(unified_detection_ctx_t *ctx, + const detection_result_t *result, + int64_t observed_at_ms); static int start_onvif_detection_thread(unified_detection_ctx_t *ctx); static void stop_onvif_detection_thread(unified_detection_ctx_t *ctx); static bool runtime_has_engine_type(const unified_detection_ctx_t *ctx, @@ -722,6 +726,8 @@ static void *onvif_detection_thread_func(void *arg) { struct timespec poll_start; clock_gettime(CLOCK_MONOTONIC, &poll_start); + /* detect_motion_onvif stamps the event before its PullMessages call. */ + int64_t poll_wall_ms = detection_evidence_observed_at_ms(0); int ret = detect_motion_onvif(ctx->onvif_url_cached, ctx->onvif_username_cached, @@ -735,6 +741,9 @@ static void *onvif_detection_thread_func(void *arg) { if (!atomic_load(&ctx->onvif_thread_running)) break; if (ret == 0 && result.count > 0) { + /* INT_01 L7: object-class events (not plain motion) become + * observations with evidence unavailable(no_matching_media). */ + udt_record_onvif_observations(ctx, &result, poll_wall_ms); long long seen = (long long)time(NULL); if (atomic_exchange(&ctx->onvif_motion_detected, 1) == 0) { // Start of a motion episode: the pre-roll is anchored here @@ -1279,6 +1288,9 @@ int start_unified_detection_thread(const char *stream_name, const char *model_pa ctx->record_audio = detection_should_record_audio(&config, global_cfg); ctx->audio_voice_enhancement = config.audio_voice_enhancement; ctx->annotation_only = annotation_only; + atomic_store(&ctx->observation_evidence_enabled, config.observation_evidence_enabled ? 1 : 0); + safe_strcpy(ctx->observation_labels, config.observation_labels, + sizeof(ctx->observation_labels), 0); atomic_store(&ctx->external_motion_trigger, 0); // no pending external trigger // Replay-detection: will be set properly on first successful connect @@ -1663,6 +1675,23 @@ void unified_detection_notify_motion(const char *stream_name, bool motion_active pthread_mutex_unlock(&contexts_mutex); } +int unified_detection_set_observation_settings(const char *stream_name, bool enabled, + const char *labels_csv) { + if (!stream_name) return -1; + + pthread_mutex_lock(&contexts_mutex); + unified_detection_ctx_t *ctx = find_context_by_name(stream_name); + if (ctx) { + pthread_mutex_lock(&ctx->mutex); + safe_strcpy(ctx->observation_labels, labels_csv ? labels_csv : "", + sizeof(ctx->observation_labels), 0); + pthread_mutex_unlock(&ctx->mutex); + atomic_store(&ctx->observation_evidence_enabled, enabled ? 1 : 0); + } + pthread_mutex_unlock(&contexts_mutex); + return ctx ? 0 : -1; +} + /** * Connect to RTSP stream @@ -3025,6 +3054,130 @@ static uint8_t *frame_to_rgb24(const AVFrame *frame) { return ffmpeg_frame_to_rgb24_padded(frame, NULL); } +/* ---- INT_01 L6: observation evidence capture -------------------------- + * + * Runs on the thread that did the inference (UDT packet thread for the + * main-stream keyframe path, the detection-stream thread otherwise) while the + * RGB24 inference input is still alive, so the evidence is exactly the frame + * the model saw, at the resolution it was decoded at (the detection stream's + * when one is configured). Settings come from the ctx cache; nothing here + * reads the DB or blocks (observation_evidence_submit is non-blocking). + * store_detections_in_db_for_camera / event_producer_publish_detection in + * report_detections are unaffected. + */ + +/* Copy the cached settings when evidence is on and at least one of the + * detections is eligible. Fast path (evidence off) is one atomic load. */ +static bool udt_evidence_wanted(unified_detection_ctx_t *ctx, + const detection_t *detections, int count, + char labels[OBSERVATION_LABELS_MAX]) { + if (count <= 0 || !atomic_load(&ctx->observation_evidence_enabled)) return false; + pthread_mutex_lock(&ctx->mutex); + memcpy(labels, ctx->observation_labels, OBSERVATION_LABELS_MAX); + pthread_mutex_unlock(&ctx->mutex); + labels[OBSERVATION_LABELS_MAX - 1] = '\0'; + for (int i = 0; i < count; ++i) { + if (detection_evidence_label_eligible(labels, detections[i].label)) return true; + } + return false; +} + +/* Copy the cached settings (labels always NUL-terminated). Returns the + * enabled flag. Used by the API and ONVIF paths, which learn the labels only + * after the call. */ +static bool udt_evidence_settings(unified_detection_ctx_t *ctx, + char labels[OBSERVATION_LABELS_MAX]) { + labels[0] = '\0'; + if (!atomic_load(&ctx->observation_evidence_enabled)) return false; + pthread_mutex_lock(&ctx->mutex); + memcpy(labels, ctx->observation_labels, OBSERVATION_LABELS_MAX); + pthread_mutex_unlock(&ctx->mutex); + labels[OBSERVATION_LABELS_MAX - 1] = '\0'; + return true; +} + +/* Hand one engine's surviving detections and the owned inference frame to the + * L3 queue. Always consumes rgb. */ +static void udt_submit_evidence(unified_detection_ctx_t *ctx, const char *engine_key, + const char *model_path, detection_model_t model, + const detection_t *detections, int count, + const char *labels, time_t now, + uint8_t *rgb, int width, int height) { + int net_w = 0, net_h = 0; + bool letterbox = false; + if (detection_evidence_model_geometry(model, &net_w, &net_h, &letterbox) != 0) { + net_w = net_h = 0; + } + observation_evidence_transform_t transform; + detection_evidence_transform_compute(width, height, net_w, net_h, letterbox, &transform); + + detection_evidence_camera_t camera = { + .camera_uuid = ctx->camera_uuid, + .stream_name = ctx->stream_name, + .evidence_enabled = true, + .labels_csv = labels, + .recording_id = (int64_t)detection_link_recording_id(ctx), + }; + detection_evidence_engine_t engine = { + .engine_key = engine_key, + .engine_type = "object", + .model_path = model_path, + .model_version = NULL, + }; + observation_evidence_result_t submitted = OBS_EVIDENCE_INVALID; + detection_evidence_status_t status = detection_evidence_capture( + &camera, &engine, detections, count, detection_evidence_observed_at_ms(now), + rgb, width, height, &transform, &submitted, NULL); + if (status == DETECTION_EVIDENCE_SUBMITTED && submitted != OBS_EVIDENCE_ACCEPTED) { + log_debug("[%s] Observation evidence for engine %s not queued (%d)", + ctx->stream_name, engine_key ? engine_key : "legacy-primary", + (int)submitted); + } +} + +static const char *udt_single_engine_key(const unified_detection_ctx_t *ctx) { + return (ctx->engine_count == 1 && ctx->engines[0].engine_key[0]) + ? ctx->engines[0].engine_key : "legacy-primary"; +} + +/* INT_01 L7: ONVIF events carry a class but no frame (contract reason + * no_matching_media; a separately timed snapshot is never substituted). Runs + * on the ONVIF polling thread; settings come from the ctx cache and the + * hand-off goes through the L3 drop ring, so no DB access here. */ +static void udt_record_onvif_observations(unified_detection_ctx_t *ctx, + const detection_result_t *result, + int64_t observed_at_ms) { + if (!ctx || !result || result->count <= 0 || !ctx->camera_uuid[0]) return; + char labels[OBSERVATION_LABELS_MAX]; + if (!udt_evidence_settings(ctx, labels)) return; + + const char *engine_key = "legacy-primary"; + for (int i = 0; i < ctx->engine_count; ++i) { + if (strcmp(ctx->engines[i].engine_type, "onvif") == 0 && ctx->engines[i].engine_key[0]) { + engine_key = ctx->engines[i].engine_key; + break; + } + } + detection_evidence_camera_t camera = { + .camera_uuid = ctx->camera_uuid, + .stream_name = ctx->stream_name, + .evidence_enabled = true, + .labels_csv = labels, + .recording_id = (int64_t)detection_link_recording_id(ctx), + }; + detection_evidence_engine_t engine = { + .engine_key = engine_key, + .engine_type = "onvif", + }; + observation_evidence_result_t submitted = OBS_EVIDENCE_INVALID; + detection_evidence_status_t status = detection_evidence_record_no_media( + &camera, &engine, result->detections, result->count, + observed_at_ms, false, &submitted, NULL); + if (status == DETECTION_EVIDENCE_SUBMITTED && submitted != OBS_EVIDENCE_ACCEPTED) { + log_debug("[%s] ONVIF observation not queued (%d)", ctx->stream_name, (int)submitted); + } +} + static void append_engine_result(unified_detection_ctx_t *ctx, const stream_detection_engine_t *engine, detection_result_t *combined, @@ -3045,7 +3198,10 @@ static void append_engine_result(unified_detection_ctx_t *ctx, /* Multi-engine frame fan-out. A decoded frame is converted to RGB once and * shared by every due local engine. ONVIF remains an asynchronous event * source. API engines are deliberately isolated until their legacy helper no - * longer persists internally; running them here would duplicate rows/events. */ + * longer persists internally; running them here would duplicate rows/events. + * (INT_01 L7: an isolated API engine therefore never produces observation + * evidence here; the single-engine API branch of detect_on_decoded_frame is + * the only API evidence producer.) */ static bool detect_with_runtime_engines(unified_detection_ctx_t *ctx, AVFrame *frame, time_t now, detection_result_t *combined) { @@ -3076,6 +3232,10 @@ static bool detect_with_runtime_engines(unified_detection_ctx_t *ctx, uint8_t *rgb = any_due_frame_engine ? frame_to_rgb24(frame) : NULL; if (any_due_frame_engine && !rgb) return onvif_triggered; + /* Surviving detections of engine i are combined->detections[start, +n). */ + int slice_start[MAX_DETECTION_ENGINES_PER_STREAM] = {0}; + int slice_count[MAX_DETECTION_ENGINES_PER_STREAM] = {0}; + for (int i = 0; i < ctx->engine_count; ++i) { stream_detection_engine_t *engine = &ctx->engines[i]; if (!engine->enabled || @@ -3106,10 +3266,47 @@ static bool detect_with_runtime_engines(unified_detection_ctx_t *ctx, continue; } ctx->engine_last_run[i] = now; - if (result == 0) append_engine_result(ctx, engine, combined, &candidate); - else if (!ctx->engine_load_failed[i]) + if (result == 0) { + int before = combined->count; + append_engine_result(ctx, engine, combined, &candidate); + slice_start[i] = before; + slice_count[i] = combined->count - before; + } else if (!ctx->engine_load_failed[i]) { log_warn("[%s] Detection engine %s failed", ctx->stream_name, engine->engine_key); + } + } + + /* One observation batch (and one evidence asset) per object engine with + * eligible survivors: a batch has a single detector provenance. Motion + * results never become observations. The shared frame goes to the last + * such engine; earlier ones get a padded copy. */ + char labels[OBSERVATION_LABELS_MAX]; + int wanted[MAX_DETECTION_ENGINES_PER_STREAM]; + int wanted_count = 0; + for (int i = 0; rgb && i < ctx->engine_count; ++i) { + if (slice_count[i] > 0 && strcmp(ctx->engines[i].engine_type, "object") == 0 && + udt_evidence_wanted(ctx, &combined->detections[slice_start[i]], + slice_count[i], labels)) { + wanted[wanted_count++] = i; + } + } + for (int w = 0; w < wanted_count; ++w) { + int i = wanted[w]; + uint8_t *owned = rgb; + if (w + 1 < wanted_count) { + size_t size = (size_t)frame->width * (size_t)frame->height * 3; + owned = malloc(size + RGB24_SWS_TAIL_PADDING); + if (!owned) continue; + memcpy(owned, rgb, size); + memset(owned + size, 0, RGB24_SWS_TAIL_PADDING); + } else { + rgb = NULL; /* ownership moves to the evidence queue */ + } + udt_submit_evidence(ctx, ctx->engines[i].engine_key, ctx->engines[i].model_path, + ctx->engine_models[i], &combined->detections[slice_start[i]], + slice_count[i], labels, now, owned, + frame->width, frame->height); } free(rgb); return combined->count > 0 || onvif_triggered; @@ -3128,6 +3325,7 @@ static bool detect_on_decoded_frame(unified_detection_ctx_t *ctx, int width = frame->width; int height = frame->height; + uint8_t *evidence_rgb = NULL; /* embedded-model inference input (L6) */ /* ---- model dispatch ---- */ @@ -3141,11 +3339,21 @@ static bool detect_on_decoded_frame(unified_detection_ctx_t *ctx, const char *api_url = get_actual_api_url(ctx->stream_name, ctx->model_path); api_detection_options_t api_options; resolve_api_detection_options(ctx, &api_options); + /* INT_01 L7: capture-time identity plus the cached evidence settings; + * the exact JPEG submitted to the server becomes the evidence. */ + char labels[OBSERVATION_LABELS_MAX]; + api_detection_evidence_t evidence = { + .camera_uuid = ctx->camera_uuid, + .engine_key = udt_single_engine_key(ctx), + .evidence_enabled = udt_evidence_settings(ctx, labels), + .labels_csv = labels, + .observed_at_ms = detection_evidence_observed_at_ms(now), + }; int ret = api_url - ? detect_objects_api_with_options(api_url, rgb_buf, width, height, 3, - result, ctx->stream_name, - ctx->detection_threshold, rec_id, now, - &api_options) + ? detect_objects_api_with_evidence(api_url, rgb_buf, width, height, 3, + result, ctx->stream_name, + ctx->detection_threshold, rec_id, now, + &api_options, &evidence) : -1; free(rgb_buf); @@ -3205,18 +3413,27 @@ static bool detect_on_decoded_frame(unified_detection_ctx_t *ctx, } int ret = detect_objects(ctx->model, rgb_buf, width, height, 3, result); - free(rgb_buf); if (ret != 0) { + free(rgb_buf); log_warn("[%s] Embedded model detection failed with error %d", ctx->stream_name, ret); return false; } + /* Keep the inference input alive for evidence when it may be needed. */ + if (result->count > 0 && atomic_load(&ctx->observation_evidence_enabled)) { + evidence_rgb = rgb_buf; + } else { + free(rgb_buf); + } } /* ---- post-processing: common to all model types ---- */ - if (result->count == 0) return false; + if (result->count == 0) { + free(evidence_rgb); + return false; + } filter_detections_by_zones(ctx->stream_name, result); filter_detections_by_stream_objects(ctx->stream_name, result); @@ -3242,6 +3459,17 @@ static bool detect_on_decoded_frame(unified_detection_ctx_t *ctx, } } result->count = kept; + + /* INT_01 L6: the surviving (zone, object-filter and threshold filtered) + * detections plus the exact frame the model saw. */ + char labels[OBSERVATION_LABELS_MAX]; + if (evidence_rgb && udt_evidence_wanted(ctx, result->detections, result->count, labels)) { + udt_submit_evidence(ctx, udt_single_engine_key(ctx), ctx->model_path, ctx->model, + result->detections, result->count, labels, now, + evidence_rgb, width, height); + } else { + free(evidence_rgb); + } return result->count > 0; } diff --git a/src/web/api_handlers_detection.c b/src/web/api_handlers_detection.c index 6155b6d43..95403e143 100644 --- a/src/web/api_handlers_detection.c +++ b/src/web/api_handlers_detection.c @@ -29,6 +29,9 @@ #include "video/go2rtc/go2rtc_integration.h" #include "video/unified_detection_thread.h" #include "database/database_manager.h" +#include "database/db_observation_classifications.h" +#include "web/api_handlers_observations.h" +#include "utils/uuid.h" // Minimum/default window for live-view detection queries (seconds). // The actual window is max(MIN_DETECTION_AGE, stream_detection_interval * 2) so @@ -188,6 +191,28 @@ void handle_get_detection_results(const http_request_t *req, http_response_t *re cJSON_AddStringToObject(response, "stream_status", status_str); } + // INT_01 L15: link each detection to the observation captured by this + // already-authorized camera (live.view above) in the same second +/-1 with + // the same label, so the UI can show a classification without one request + // per detection. Additive: unlinked detections carry no "observation". + // Heap, not stack: each link embeds a full annotation (~0.6 KiB). + observation_detection_link_t *links = NULL; + if (result.count > 0 && lightnvr_uuid_is_valid(camera.camera_uuid)) + links = calloc((size_t)result.count, sizeof(*links)); + if (links) { + observation_detection_ref_t refs[MAX_DETECTIONS]; + for (int i = 0; i < result.count; i++) { + refs[i].label = result.detections[i].label; + refs[i].timestamp = (int64_t)timestamps[i]; + } + phase = api_phase_begin(req, "detection observation link"); + if (db_observation_link_detections(camera.camera_uuid, refs, (size_t)result.count, + links) != DB_OBS_OK) { + log_warn("Observation links unavailable for stream %s", stream_name); + } + api_phase_end(&phase); + } + // Add each detection to the array for (int i = 0; i < result.count; i++) { cJSON *detection = cJSON_CreateObject(); @@ -205,10 +230,14 @@ void handle_get_detection_results(const http_request_t *req, http_response_t *re cJSON_AddNumberToObject(detection, "height", result.detections[i].height); cJSON_AddNumberToObject(detection, "timestamp", (double)timestamps[i]); cJSON_AddNumberToObject(detection, "end_timestamp", (double)end_timestamps[i]); + cJSON *observation = links ? observation_detection_link_to_json(&links[i]) : NULL; + if (observation) cJSON_AddItemToObject(detection, "observation", observation); cJSON_AddItemToArray(detections_array, detection); } + free(links); + // Convert to string char *json_str = cJSON_PrintUnformatted(response); if (!json_str) { diff --git a/src/web/api_handlers_metrics.c b/src/web/api_handlers_metrics.c index 0c4899d06..67eb566d7 100644 --- a/src/web/api_handlers_metrics.c +++ b/src/web/api_handlers_metrics.c @@ -35,6 +35,7 @@ #include "core/mqtt_delivery_worker.h" #include "core/mqtt_destination_client.h" #include "core/mqtt_presence.h" +#include "core/observation_diagnostics.h" #include "database/db_event_destinations.h" #include "database/db_event_outbox.h" #include "video/stream_manager.h" @@ -1055,6 +1056,214 @@ char *api_metrics_render_system_health( return buffer.data; } +/* ------------------------------------------------------------------ */ +/* INT_01 L10: observation pipeline (contract section 15) */ +/* ------------------------------------------------------------------ */ + +static void prom_family(prom_buf_t *b, const char *name, const char *type, + const char *help) { + prom_buf_append(b, "# HELP %s %s\n", name, help); + prom_buf_append(b, "# TYPE %s %s\n", name, type); +} + +static void prom_u64(prom_buf_t *b, const char *name, const char *type, + const char *help, uint64_t value) { + prom_family(b, name, type, help); + prom_buf_append(b, "%s %llu\n", name, (unsigned long long)value); +} + +char *api_metrics_render_observations(const observation_diagnostics_t *d) { + if (!d) return NULL; + const observation_evidence_stats_t *e = &d->evidence; + const observation_relay_stats_t *r = &d->relay; + prom_buf_t b; + prom_buf_init(&b, 8192U); + if (!b.data) return NULL; + + prom_u64(&b, "lightnvr_observation_accepted_total", "counter", + "Observation batches whose evidence frame was queued", e->accepted); + prom_family(&b, "lightnvr_observation_dropped_total", "counter", + "Observation evidence submissions dropped, by reason"); + const struct { const char *reason; uint64_t value; } drops[] = { + {"queue_full", e->dropped_queue_full}, + {"budget_exhausted", e->dropped_budget}, + {"disabled", e->dropped_disabled}, + {"untracked", e->dropped_untracked}, + }; + for (size_t i = 0; i < sizeof(drops) / sizeof(drops[0]); ++i) + prom_buf_append(&b, "lightnvr_observation_dropped_total{reason=\"%s\"} %llu\n", + drops[i].reason, (unsigned long long)drops[i].value); + + prom_u64(&b, "lightnvr_observation_evidence_unavailable_total", "counter", + "Observation batches recorded without media (no_matching_media)", + e->unavailable_submitted); + prom_u64(&b, "lightnvr_observation_evidence_ready_total", "counter", + "Evidence assets committed as ready", e->ready); + prom_family(&b, "lightnvr_observation_evidence_failures_total", "counter", + "Evidence assets that became unavailable after admission, by reason"); + const struct { const char *reason; uint64_t value; } failures[] = { + {"encode_failed", e->failed_encode}, + {"write_failed", e->failed_write}, + {"db_error", e->failed_db}, + {"disabled", e->disabled_at_encode}, + {"interrupted", e->interrupted}, + }; + for (size_t i = 0; i < sizeof(failures) / sizeof(failures[0]); ++i) + prom_buf_append(&b, + "lightnvr_observation_evidence_failures_total{reason=\"%s\"} %llu\n", + failures[i].reason, (unsigned long long)failures[i].value); + prom_u64(&b, "lightnvr_observation_evidence_bytes_written_total", "counter", + "Evidence JPEG bytes written", e->bytes_written); + prom_u64(&b, "lightnvr_observation_evidence_backlog_items", "gauge", + "Evidence frames queued for encoding", e->backlog_items); + prom_u64(&b, "lightnvr_observation_evidence_backlog_bytes", "gauge", + "Bytes held by queued evidence frames", e->backlog_bytes); + prom_u64(&b, "lightnvr_observation_evidence_drop_ring_depth", "gauge", + "Drop records waiting to be written", e->drop_ring_depth); + prom_u64(&b, "lightnvr_observation_evidence_high_water_items", "gauge", + "Highest evidence backlog in items this process run", e->high_water_items); + prom_u64(&b, "lightnvr_observation_evidence_high_water_bytes", "gauge", + "Highest evidence backlog in bytes this process run", e->high_water_bytes); + prom_u64(&b, "lightnvr_observation_evidence_queue_allocated", "gauge", + "Whether the evidence queue or worker exists (0 while evidence is unused)", + e->allocated ? 1U : 0U); + prom_u64(&b, "lightnvr_observation_evidence_worker_running", "gauge", + "Whether the evidence encoder worker is running", e->worker_running ? 1U : 0U); + prom_u64(&b, "lightnvr_observation_evidence_camera_high_water_items", "gauge", + "Highest evidence backlog held by one camera this process run", + e->camera_high_water_items); + prom_family(&b, "lightnvr_observation_evidence_limit_items", "gauge", + "Configured evidence queue depth bound, global and per camera"); + prom_buf_append(&b, "lightnvr_observation_evidence_limit_items{scope=\"global\"} %u\n", + (unsigned)e->limit_items); + prom_buf_append(&b, "lightnvr_observation_evidence_limit_items{scope=\"camera\"} %u\n", + (unsigned)e->limit_camera_items); + prom_family(&b, "lightnvr_observation_evidence_limit_bytes", "gauge", + "Configured evidence queue byte bound, global and per camera"); + prom_buf_append(&b, "lightnvr_observation_evidence_limit_bytes{scope=\"global\"} %llu\n", + (unsigned long long)e->limit_bytes); + prom_buf_append(&b, "lightnvr_observation_evidence_limit_bytes{scope=\"camera\"} %llu\n", + (unsigned long long)e->limit_camera_bytes); + prom_buf_append(&b, "lightnvr_observation_evidence_limit_bytes{scope=\"discard\"} %llu\n", + (unsigned long long)e->limit_discard_bytes); + prom_family(&b, "lightnvr_observation_evidence_discards_total", "counter", + "Rejected evidence frames released by the worker (deferred) or by the producer (inline)"); + prom_buf_append(&b, "lightnvr_observation_evidence_discards_total{mode=\"deferred\"} %llu\n", + (unsigned long long)e->discard_deferred); + prom_buf_append(&b, "lightnvr_observation_evidence_discards_total{mode=\"inline\"} %llu\n", + (unsigned long long)e->discard_inline); + prom_u64(&b, "lightnvr_observation_evidence_discard_pending_bytes", "gauge", + "Rejected evidence frame bytes waiting for the worker to release them", + e->discard_pending_bytes); + + /* Producer submit latency (X2, A11: admission never waits on I/O). The + * bucket strings mirror observation_evidence_submit_latency_bounds_ns. */ + static const char *const submit_le[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS] = { + "0.000001", "0.000005", "0.00001", "0.00005", "0.0001", + "0.00025", "0.0005", "0.001", "0.01", "+Inf", + }; + prom_family(&b, "lightnvr_observation_submit_latency_seconds", "histogram", + "Producer time inside evidence admission this process run"); + unsigned long long cumulative = 0; + for (int i = 0; i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS; ++i) { + cumulative += (unsigned long long)e->submit_latency_bucket[i]; + prom_buf_append(&b, "lightnvr_observation_submit_latency_seconds_bucket{le=\"%s\"} %llu\n", + submit_le[i], cumulative); + } + prom_buf_append(&b, "lightnvr_observation_submit_latency_seconds_sum %.9f\n", + (double)e->submit_latency_sum_ns / 1e9); + prom_buf_append(&b, "lightnvr_observation_submit_latency_seconds_count %llu\n", + (unsigned long long)e->submit_latency_count); + prom_family(&b, "lightnvr_observation_submit_latency_max_seconds", "gauge", + "Slowest producer evidence admission this process run"); + prom_buf_append(&b, "lightnvr_observation_submit_latency_max_seconds %.9f\n", + (double)e->submit_latency_max_ns / 1e9); + + prom_family(&b, "lightnvr_observation_evidence_retained_bytes", "gauge", + "Ready evidence bytes retained per camera as of the last retention pass"); + for (int i = 0; i < d->camera_count; ++i) { + char uuid[2U * sizeof(d->cameras[i].camera_uuid) + 1U]; + prom_label_escape(d->cameras[i].camera_uuid, uuid, sizeof(uuid)); + prom_buf_append(&b, "lightnvr_observation_evidence_retained_bytes{camera_uuid=\"%s\"} %lld\n", + uuid, (long long)d->cameras[i].retained_bytes); + } + prom_family(&b, "lightnvr_observation_evidence_budget_bytes", "gauge", + "Per-camera evidence byte budget"); + for (int i = 0; i < d->camera_count; ++i) { + char uuid[2U * sizeof(d->cameras[i].camera_uuid) + 1U]; + prom_label_escape(d->cameras[i].camera_uuid, uuid, sizeof(uuid)); + prom_buf_append(&b, "lightnvr_observation_evidence_budget_bytes{camera_uuid=\"%s\"} %lld\n", + uuid, (long long)d->cameras[i].max_bytes); + } + prom_family(&b, "lightnvr_observation_evidence_budget_exhausted", "gauge", + "Whether a camera is over budget with only protected evidence left"); + for (int i = 0; i < d->camera_count; ++i) { + char uuid[2U * sizeof(d->cameras[i].camera_uuid) + 1U]; + prom_label_escape(d->cameras[i].camera_uuid, uuid, sizeof(uuid)); + prom_buf_append(&b, "lightnvr_observation_evidence_budget_exhausted{camera_uuid=\"%s\"} %d\n", + uuid, d->cameras[i].budget_exhausted ? 1 : 0); + } + prom_u64(&b, "lightnvr_observation_evidence_budget_exhausted_cameras", "gauge", + "Cameras whose evidence admission is paused by the byte budget", + d->budget_exhausted_cameras > 0 ? (uint64_t)d->budget_exhausted_cameras : 0U); + + if (d->db_available) { + prom_u64(&b, "lightnvr_observation_history_observations", "gauge", + "Retained observation rows", (uint64_t)d->db.observations); + prom_family(&b, "lightnvr_observation_history_oldest_seq", "gauge", + "Oldest retained change seq (oldest recoverable cursor is seq - 1)"); + prom_buf_append(&b, "lightnvr_observation_history_oldest_seq %lld\n", + (long long)d->db.meta.oldest_seq); + prom_family(&b, "lightnvr_observation_history_head_seq", "gauge", + "Newest change seq"); + prom_buf_append(&b, "lightnvr_observation_history_head_seq %lld\n", + (long long)d->db.meta.head_seq); + prom_family(&b, "lightnvr_observation_history_oldest_observed_timestamp_seconds", "gauge", + "observed_at of the oldest retained observation (Unix seconds)"); + if (d->db.has_oldest_observed_at) + prom_buf_append(&b, + "lightnvr_observation_history_oldest_observed_timestamp_seconds %.3f\n", + (double)d->db.oldest_observed_at_ms / 1000.0); + prom_family(&b, "lightnvr_observation_publications", "gauge", + "Observation change publications by state"); + prom_buf_append(&b, "lightnvr_observation_publications{state=\"pending\"} %lld\n", + (long long)d->db.publications_pending); + prom_buf_append(&b, "lightnvr_observation_publications{state=\"failed\"} %lld\n", + (long long)d->db.publications_failed); + prom_buf_append(&b, "lightnvr_observation_publications{state=\"expired\"} %lld\n", + (long long)d->db.publications_expired); + } + prom_family(&b, "lightnvr_observation_relay_lag_seconds", "gauge", + "Age of the oldest pending observation publication"); + prom_buf_append(&b, "lightnvr_observation_relay_lag_seconds %.3f\n", + observation_diagnostics_relay_lag_seconds(d)); + prom_u64(&b, "lightnvr_observation_relay_running", "gauge", + "Whether the observation publication relay is running", r->running ? 1U : 0U); + prom_family(&b, "lightnvr_observation_relay_events_total", "counter", + "Observation publication relay outcomes this process run"); + const struct { const char *outcome; uint64_t value; } relay[] = { + {"relayed", r->relayed}, + {"duplicate", r->duplicates}, + {"no_route", r->relayed_no_route}, + {"expired", r->expired}, + {"failed", r->failed}, + {"outbox_full", r->enqueue_full}, + {"enqueue_error", r->enqueue_errors}, + {"mark_error", r->mark_errors}, + {"read_error", r->read_errors}, + {"cap_pause", r->cap_pauses}, + {"backoff", r->backoffs}, + {"sidecar_error", r->sidecar_errors}, + }; + for (size_t i = 0; i < sizeof(relay) / sizeof(relay[0]); ++i) + prom_buf_append(&b, "lightnvr_observation_relay_events_total{outcome=\"%s\"} %llu\n", + relay[i].outcome, (unsigned long long)relay[i].value); + prom_u64(&b, "lightnvr_observation_history_epoch_regenerations_total", "counter", + "Cursor epoch regenerations after a detected database rewind", + r->epoch_regenerations); + return b.data; +} + /* ------------------------------------------------------------------ */ /* Instance-level helpers */ /* ------------------------------------------------------------------ */ @@ -1243,6 +1452,16 @@ void handle_get_metrics(const http_request_t *req, http_response_t *res) { } } + observation_diagnostics_t observations; + if (observation_diagnostics_collect(&observations, 0) == 0) { + char *observation_metrics = api_metrics_render_observations(&observations); + if (observation_metrics) { + prom_buf_append(&buf, "%s", observation_metrics); + free(observation_metrics); + } + observation_diagnostics_free(&observations); + } + char *self_metrics = api_metrics_render_self_observability(); if (self_metrics) { prom_buf_append(&buf, "%s", self_metrics); diff --git a/src/web/api_handlers_observations.c b/src/web/api_handlers_observations.c new file mode 100644 index 000000000..c62a553e9 --- /dev/null +++ b/src/web/api_handlers_observations.c @@ -0,0 +1,1658 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (C) 2026 OpenSensor Engineering and LightNVR contributors + * + * This file is part of LightNVR . + */ + +/** + * INT_01 observation and integration API. + * + * Contract: docs/integrations/OBSERVATIONS_CONTRACT.md (sections 7-9, 11 and + * 12). Every coded error body is {"error", "code", ...} (D2). + * + * Authorization always resolves the observation's capture-time camera_uuid + * and evaluates it with httpd_evaluate_camera_identity_action(), never the + * httpd_authorize_* helpers: those write 403 themselves, which would let an + * opaque observation ID reveal that it exists. Unknown and unauthorized IDs + * are therefore byte-identical 404s. The only 403 for a single observation is + * the D4 snapshot case, where the caller can already see the metadata, and + * the matching PUT case (live.view without observations.annotate). + * + * The P2 classification PUT (section 10) authorizes observations.annotate on + * the capture camera before it looks at the namespace or the body, so neither + * leaks anything about an observation the caller cannot see. The writer + * namespace is derived from the authenticated user (D3), never the request. + */ + +#define _GNU_SOURCE + +#include "web/api_handlers_observations.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core/authorization.h" +#include "core/config.h" +#include "core/event_identity.h" +#include "core/observation_diagnostics.h" +#include "core/version.h" +#include "database/db_auth.h" +#include "database/db_fleet_query.h" +#include "database/db_streams.h" +#include "database/db_system_settings.h" +#include "utils/strings.h" +#include "utils/uuid.h" +#include "web/audit_log.h" +#include "web/httpd_utils.h" + +#define LOG_COMPONENT "ObservationsAPI" +#include "core/logger.h" + +#define OBS_SOURCE_TEXT_SIZE 128 +#define OBS_QUERY_VALUE_SIZE 1024 /* query_string is 1,024 bytes (D1) */ +#define OBS_FILTER_CAMERAS_MAX 10 +#define OBS_CAPABILITY_CAMERAS_MAX 1024 +#define OBS_SNAPSHOT_RETRY_MAX OBS_SNAPSHOT_RETRY_AFTER_MAX +#define OBS_SNAPSHOT_CACHE_CONTROL "private, max-age=300" +#define OBS_HISTORY_RETENTION_DEFAULT 30 +#define OBS_EVENT_TYPE "io.lightnvr.detection.observation_changed.v1" +#define OBS_ANNOTATIONS_MAX 8 +#define URN_PREFIX "urn:lightnvr:" + +/* ---- time, namespace and serialization helpers ----------------------- */ + +int observation_format_time(int64_t ms, char *out, size_t out_size) { + if (!out || out_size < 25) return -1; + int64_t seconds = ms / 1000; + int millis = (int)(ms % 1000); + if (millis < 0) { + millis += 1000; + seconds--; + } + time_t t = (time_t)seconds; + struct tm tm; + if (!gmtime_r(&t, &tm)) return -1; + int n = snprintf(out, out_size, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec, millis); + return n > 0 && (size_t)n < out_size ? 0 : -1; +} + +static bool digits(const char *text, int count, int *value) { + int result = 0; + for (int i = 0; i < count; i++) { + if (!isdigit((unsigned char)text[i])) return false; + result = result * 10 + (text[i] - '0'); + } + *value = result; + return true; +} + +int observation_parse_time(const char *text, int64_t *ms) { + if (!text || !ms) return -1; + int year, month, day, hour, minute, second; + if (strnlen(text, 20) < 20 || !digits(text, 4, &year) || text[4] != '-' || + !digits(text + 5, 2, &month) || text[7] != '-' || !digits(text + 8, 2, &day) || + (text[10] != 'T' && text[10] != 't') || !digits(text + 11, 2, &hour) || + text[13] != ':' || !digits(text + 14, 2, &minute) || text[16] != ':' || + !digits(text + 17, 2, &second)) + return -1; + const char *p = text + 19; + int millis = 0; + if (*p == '.') { + p++; + int count = 0; + while (isdigit((unsigned char)*p)) { + if (count < 3) millis = millis * 10 + (*p - '0'); + count++; + p++; + } + if (count == 0 || count > 9) return -1; + for (int i = count; i < 3; i++) millis *= 10; + } + int offset_minutes = 0; + if (*p == 'Z' || *p == 'z') { + p++; + } else if (*p == '+' || *p == '-') { + int oh, om; + if (!digits(p + 1, 2, &oh) || p[3] != ':' || !digits(p + 4, 2, &om) || oh > 23 || + om > 59) + return -1; + offset_minutes = (oh * 60 + om) * (*p == '-' ? -1 : 1); + p += 6; + } else { + return -1; + } + if (*p != '\0') return -1; + if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || + minute > 59 || second > 59) + return -1; + struct tm tm = {0}; + tm.tm_year = year - 1900; + tm.tm_mon = month - 1; + tm.tm_mday = day; + tm.tm_hour = hour; + tm.tm_min = minute; + tm.tm_sec = second; + time_t t = timegm(&tm); + struct tm check; + if (t == (time_t)-1 || !gmtime_r(&t, &check) || check.tm_mday != day || + check.tm_mon != month - 1) + return -1; /* e.g. 2026-02-30 */ + *ms = ((int64_t)t - (int64_t)offset_minutes * 60) * 1000 + millis; + return 0; +} + +int observation_writer_namespace(const char *username, char out[OBS_WRITER_NAMESPACE_SIZE]) { + static const char *const reserved[] = {"lightnvr", "system", "owner", "local"}; + if (!out) return -1; + out[0] = '\0'; + if (!username) return -1; + size_t length = strlen(username); + if (length < 1 || length > OBS_WRITER_NAMESPACE_SIZE - 1) return -1; + char normalized[OBS_WRITER_NAMESPACE_SIZE]; + for (size_t i = 0; i < length; i++) { + unsigned char c = (unsigned char)username[i]; + if (c >= 'A' && c <= 'Z') c = (unsigned char)(c - 'A' + 'a'); + bool allowed = (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || + c == '.' || c == '-'; + normalized[i] = allowed ? (char)c : '-'; + } + normalized[length] = '\0'; + char first = normalized[0]; + if (!((first >= 'a' && first <= 'z') || (first >= '0' && first <= '9'))) return -1; + for (size_t i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++) + if (strcmp(normalized, reserved[i]) == 0) return -2; + memcpy(out, normalized, length + 1); + return 0; +} + +static cJSON *time_or_null(int64_t ms) { + char text[OBS_TIME_TEXT_SIZE]; + if (ms <= 0 || observation_format_time(ms, text, sizeof(text)) != 0) return cJSON_CreateNull(); + return cJSON_CreateString(text); +} + +static cJSON *text_or_null(const char *value) { + return value && value[0] ? cJSON_CreateString(value) : cJSON_CreateNull(); +} + +/* Unlike time_or_null, 0 (the epoch) is a valid classifier time. */ +static cJSON *time_text(int64_t ms) { + char text[OBS_TIME_TEXT_SIZE]; + if (ms < 0 || observation_format_time(ms, text, sizeof(text)) != 0) return cJSON_CreateNull(); + return cJSON_CreateString(text); +} + +cJSON *observation_classification_to_json(const observation_classification_t *annotation) { + if (!annotation) return NULL; + cJSON *json = cJSON_CreateObject(); + if (!json) return NULL; + cJSON_AddStringToObject(json, "key", annotation->key); + cJSON_AddStringToObject(json, "writer_namespace", annotation->writer_namespace); + cJSON_AddStringToObject(json, "classifier", annotation->classifier); + cJSON_AddItemToObject(json, "model_version", text_or_null(annotation->model_version)); + cJSON_AddStringToObject(json, "label", annotation->label); + cJSON_AddItemToObject(json, "taxonomy_id", text_or_null(annotation->taxonomy_id)); + cJSON_AddNumberToObject(json, "confidence", annotation->confidence); + cJSON_AddItemToObject(json, "classified_at", time_text(annotation->classified_at_ms)); + cJSON_AddStringToObject(json, "input_asset_id", annotation->input_asset_uuid); + cJSON_AddNumberToObject(json, "input_revision", annotation->input_revision); + cJSON_AddStringToObject(json, "kind", observation_classification_kind_name(annotation->kind)); + cJSON_AddNumberToObject(json, "annotation_revision", annotation->annotation_revision); + cJSON_AddItemToObject(json, "created_at", time_text(annotation->created_at_ms)); + cJSON_AddItemToObject(json, "updated_at", time_text(annotation->updated_at_ms)); + /* writer_user_id is internal (D3) and never serialized. */ + return json; +} + +cJSON *observation_detection_link_to_json(const observation_detection_link_t *link) { + if (!link || !link->linked) return NULL; + cJSON *json = cJSON_CreateObject(); + if (!json) return NULL; + cJSON_AddStringToObject(json, "id", link->observation_uuid); + cJSON_AddBoolToObject(json, "owner_confirmed", link->owner_confirmed); + cJSON *classification = link->has_classification + ? observation_classification_to_json(&link->classification) : cJSON_CreateNull(); + if (!classification || !cJSON_AddItemToObject(json, "classification", classification)) { + cJSON_Delete(classification); + cJSON_Delete(json); + return NULL; + } + return json; +} + +static bool add_annotations(cJSON *list, const observation_classification_t *items, int count) { + for (int i = 0; i < count; i++) { + cJSON *item = observation_classification_to_json(&items[i]); + if (!item) return false; + cJSON_AddItemToArray(list, item); + } + return true; +} + +static cJSON *evidence_to_json(const observation_record_t *record) { + const observation_asset_record_t *asset = &record->asset; + cJSON *evidence = cJSON_CreateObject(); + if (!evidence) return NULL; + /* C3 conventions: evidence that never had media (no origin) has every + * media field null; pending keeps origin/media_type/captured_at/transform + * and nulls the size and expiry, which are only known once ready. */ + bool has_media = asset->origin[0] != '\0'; + bool sized = has_media && asset->state != OBS_ASSET_PENDING; + bool reasoned = asset->state == OBS_ASSET_UNAVAILABLE || asset->state == OBS_ASSET_EXPIRED; + cJSON_AddStringToObject(evidence, "asset_id", asset->uuid); + cJSON_AddStringToObject(evidence, "state", observation_asset_state_name(asset->state)); + cJSON_AddItemToObject(evidence, "reason", + text_or_null(reasoned ? asset->reason : NULL)); + cJSON_AddItemToObject(evidence, "origin", text_or_null(has_media ? asset->origin : NULL)); + cJSON_AddItemToObject(evidence, "media_type", + text_or_null(has_media ? asset->media_type : NULL)); + if (sized && asset->width > 0) cJSON_AddNumberToObject(evidence, "width", asset->width); + else cJSON_AddNullToObject(evidence, "width"); + if (sized && asset->height > 0) cJSON_AddNumberToObject(evidence, "height", asset->height); + else cJSON_AddNullToObject(evidence, "height"); + if (sized && asset->has_bytes) cJSON_AddNumberToObject(evidence, "bytes", (double)asset->bytes); + else cJSON_AddNullToObject(evidence, "bytes"); + cJSON_AddItemToObject(evidence, "captured_at", + has_media ? time_or_null(asset->captured_at_ms) : cJSON_CreateNull()); + cJSON_AddItemToObject(evidence, "expires_at", + sized ? time_or_null(asset->expires_at_ms) : cJSON_CreateNull()); + cJSON *transform = has_media && asset->transform_json[0] + ? cJSON_Parse(asset->transform_json) : NULL; + if (transform && !cJSON_IsObject(transform)) { + cJSON_Delete(transform); + transform = NULL; + } + cJSON_AddItemToObject(evidence, "transform", transform ? transform : cJSON_CreateNull()); + if (asset->state == OBS_ASSET_READY) { + char url[96]; + snprintf(url, sizeof(url), "/api/observations/%s/snapshot", record->uuid); + cJSON_AddStringToObject(evidence, "snapshot_url", url); + } + /* asset->path is internal and never serialized (contract section 2). */ + return evidence; +} + +cJSON *observation_record_to_json(const observation_record_t *record, const char *source, + const observation_classification_set_t *classifications) { + if (!record || !source) return NULL; + cJSON *root = cJSON_CreateObject(); + if (!root) return NULL; + cJSON_AddStringToObject(root, "id", record->uuid); + cJSON_AddStringToObject(root, "source", source); + cJSON_AddStringToObject(root, "camera_uuid", record->camera_uuid); + cJSON_AddStringToObject(root, "stream_name_at_capture", record->stream_name_at_capture); + cJSON_AddStringToObject(root, "batch_id", record->batch_id); + cJSON_AddNumberToObject(root, "object_index", record->object_index); + cJSON_AddItemToObject(root, "observed_at", time_or_null(record->observed_at_ms)); + cJSON_AddItemToObject(root, "created_at", time_or_null(record->created_at_ms)); + + cJSON *detector = cJSON_AddObjectToObject(root, "detector"); + if (detector) { + cJSON_AddStringToObject(detector, "engine_key", record->engine_key); + cJSON_AddStringToObject(detector, "engine_type", record->engine_type); + cJSON_AddItemToObject(detector, "model", text_or_null(record->model)); + cJSON_AddItemToObject(detector, "model_version", text_or_null(record->model_version)); + } + cJSON_AddStringToObject(root, "label", record->label); + cJSON_AddNumberToObject(root, "confidence", record->confidence); + if (record->has_box) { + cJSON *box = cJSON_AddObjectToObject(root, "box"); + if (box) { + cJSON_AddStringToObject(box, "format", "xywh_normalized"); + cJSON_AddNumberToObject(box, "x", record->box_x); + cJSON_AddNumberToObject(box, "y", record->box_y); + cJSON_AddNumberToObject(box, "width", record->box_width); + cJSON_AddNumberToObject(box, "height", record->box_height); + } + } else { + cJSON_AddNullToObject(root, "box"); + } + cJSON *evidence = evidence_to_json(record); + if (evidence) cJSON_AddItemToObject(root, "evidence", evidence); + if (record->has_track && record->track_id[0]) { + cJSON *track = cJSON_AddObjectToObject(root, "track"); + if (track) { + cJSON_AddStringToObject(track, "id", record->track_id); + cJSON_AddStringToObject(track, "namespace", record->track_namespace); + cJSON_AddStringToObject(track, "session", record->track_session); + } + } else { + cJSON_AddNullToObject(root, "track"); + } + if (record->recording_id > 0) { + cJSON *recording = cJSON_AddObjectToObject(root, "recording_reference"); + if (recording) + cJSON_AddNumberToObject(recording, "recording_id", (double)record->recording_id); + } else { + cJSON_AddNullToObject(root, "recording_reference"); + } + cJSON_AddNumberToObject(root, "revision", record->revision); + /* Contract section 10.5: split by kind, each ordered by updated_at desc + * (the DB read's order). */ + cJSON *lists = cJSON_AddObjectToObject(root, "classifications"); + bool listed = false; + if (lists) { + cJSON *automated = cJSON_AddArrayToObject(lists, "automated"); + cJSON *owner = cJSON_AddArrayToObject(lists, "owner_confirmed"); + listed = automated && owner; + if (listed && classifications) + listed = add_annotations(automated, classifications->automated, + classifications->automated_count) && + add_annotations(owner, classifications->owner_confirmed, + classifications->owner_confirmed_count); + } + if (!detector || !evidence || !listed) { + cJSON_Delete(root); + return NULL; + } + return root; +} + +/* ---- response helpers -------------------------------------------------- */ + +static void send_json(http_response_t *res, int status, cJSON *body) { + char *json = body ? cJSON_PrintUnformatted(body) : NULL; + cJSON_Delete(body); + if (!json) { + http_response_set_json_error(res, 500, "Internal server error"); + return; + } + http_response_set_json(res, status, json); + cJSON_free(json); +} + +static cJSON *error_body(const char *message, const char *code) { + cJSON *body = cJSON_CreateObject(); + if (body) { + cJSON_AddStringToObject(body, "error", message); + cJSON_AddStringToObject(body, "code", code); + } + return body; +} + +static void send_error(http_response_t *res, int status, const char *code, const char *message) { + send_json(res, status, error_body(message, code)); +} + +static void send_invalid(http_response_t *res, const char *field, const char *message) { + cJSON *body = error_body(message, "invalid_request"); + if (body && field) cJSON_AddStringToObject(body, "field", field); + send_json(res, 400, body); +} + +static void send_internal(http_response_t *res) { + send_error(res, 500, "internal_error", "Internal server error"); +} + +static void send_not_found(http_response_t *res) { + send_error(res, 404, "observation_not_found", "Observation not found"); +} + +static void send_unavailable(http_response_t *res) { + http_response_add_header(res, "Retry-After", "5"); + send_error(res, 503, "service_unavailable", "Service temporarily unavailable"); +} + +static bool authenticate(const http_request_t *req, http_response_t *res, user_t *user) { + memset(user, 0, sizeof(*user)); + if (!httpd_check_action_access(req, user)) { + http_response_set_json_error(res, 401, "Unauthorized"); + return false; + } + return true; +} + +static bool installation_source(char source[OBS_SOURCE_TEXT_SIZE]) { + return event_identity_get_source(source, OBS_SOURCE_TEXT_SIZE) == 0 && + strncmp(source, URN_PREFIX, strlen(URN_PREFIX)) == 0; +} + +static void lowercase(char *text) { + for (; *text; text++) + if (*text >= 'A' && *text <= 'Z') *text = (char)(*text - 'A' + 'a'); +} + +/* Observation ID from /api/observations/{id}[suffix]; lowercased. */ +static bool path_observation_id(const http_request_t *req, const char *suffix, + char id[OBS_UUID_SIZE]) { + char param[256]; + if (http_request_extract_path_param(req, "/api/observations/", param, sizeof(param)) != 0) + return false; + if (suffix) { + size_t length = strlen(param), suffix_length = strlen(suffix); + if (length <= suffix_length || strcmp(param + length - suffix_length, suffix) != 0) + return false; + param[length - suffix_length] = '\0'; + } + lowercase(param); + if (!lightnvr_uuid_is_valid(param) || strlen(param) != OBS_UUID_SIZE - 1) return false; + memcpy(id, param, OBS_UUID_SIZE); + return true; +} + +/* Audit a camera-scoped decision with the existing read-decision modes. Only + * the camera UUID of the stub is read by the audit path. */ +static void audit_camera_decision(const http_request_t *req, const user_t *user, + authorization_action_t action, const char *camera_uuid, + const authorization_evaluation_t *evaluation, + const char *outcome) { + fleet_camera_t *camera = calloc(1, sizeof(*camera)); + if (!camera) return; + safe_strcpy(camera->camera_uuid, camera_uuid, sizeof(camera->camera_uuid), 0); + audit_log_authorization(req, user, action, camera, evaluation, outcome); + free(camera); +} + +/* Contract section 12.2: snapshot fetches are audited as snapshot.create + * decisions on the observation (target_type "observation"), recording the + * camera, asset and outcome. Allowed reads honour the per-action decision + * mode: off records nothing, summarize folds into the camera summary (a + * summary row cannot carry an observation ID), record writes the full row. + * Never records bytes, filesystem paths or credentials. */ +static void audit_snapshot(const http_request_t *req, const user_t *user, + const observation_record_t *record, + const authorization_evaluation_t *evaluation, + const char *outcome) { + if (strcmp(outcome, "allowed") == 0) { + audit_decision_mode_t mode = audit_log_get_decision_mode(AUTHZ_SNAPSHOT_CREATE); + if (mode == AUDIT_DECISION_MODE_OFF) return; + if (mode == AUDIT_DECISION_MODE_SUMMARIZE) { + audit_camera_decision(req, user, AUTHZ_SNAPSHOT_CREATE, record->camera_uuid, + evaluation, outcome); + return; + } + } + const authorization_action_metadata_t *metadata = + authorization_action_metadata(AUTHZ_SNAPSHOT_CREATE); + cJSON *details = cJSON_CreateObject(); + if (details) { + cJSON_AddStringToObject(details, "event_type", "authorization.decision"); + cJSON_AddStringToObject(details, "method", req->method_str); + cJSON_AddStringToObject(details, "path", req->path); + cJSON_AddStringToObject(details, "camera_uuid", record->camera_uuid); + cJSON_AddStringToObject(details, "asset_id", record->asset.uuid); + cJSON_AddStringToObject(details, "evidence_state", + observation_asset_state_name(record->asset.state)); + if (evaluation) { + cJSON_AddStringToObject(details, "decision_source", + authorization_decision_source_name(evaluation->source)); + cJSON_AddStringToObject(details, "explanation", evaluation->explanation); + } + } + audit_log_append(req, user, metadata ? metadata->key : "snapshot.create", "observation", + record->uuid, outcome, details); + cJSON_Delete(details); +} + +/* Load the fleet and reduce it to the cameras where the user holds action. + * The caller frees *cameras. */ +static int authorized_cameras(const user_t *user, authorization_action_t action, + fleet_camera_t **cameras, int *count) { + *cameras = NULL; + *count = 0; + if (db_fleet_camera_load_readonly(cameras, count) != 0) return -1; + if (*count > 0 && authorization_filter_cameras(user, action, *cameras, count) != 0) { + free(*cameras); + *cameras = NULL; + *count = 0; + return -1; + } + return 0; +} + +static bool camera_in(const fleet_camera_t *cameras, int count, const char *uuid) { + for (int i = 0; i < count; i++) + if (strcmp(cameras[i].camera_uuid, uuid) == 0) return true; + return false; +} + +/* ---- GET /api/integrations/capabilities ------------------------------ */ + +static void add_operation(cJSON *operations, const char *name, bool supported, + bool authorized, const char *reason) { + cJSON *op = cJSON_AddObjectToObject(operations, name); + if (!op) return; + cJSON_AddBoolToObject(op, "supported", supported); + cJSON_AddBoolToObject(op, "authorized", authorized); + if (reason && (!supported || !authorized)) cJSON_AddStringToObject(op, "reason", reason); +} + +static int history_retention_days(void) { + char value[32] = {0}; + if (db_get_system_setting("observation_history_retention_days", value, sizeof(value)) != 0) + return OBS_HISTORY_RETENTION_DEFAULT; + char *end = NULL; + errno = 0; + long days = strtol(value, &end, 10); + if (errno != 0 || end == value || *end != '\0' || days < 1 || days > 365) + return OBS_HISTORY_RETENTION_DEFAULT; + return (int)days; +} + +/* oldest_observed_at is reported by the cursor_expired path; ask for it with + * a cursor from a deliberately different DB generation and no cameras, which + * expires before any change row is scanned. */ +static int history_bounds(cJSON *history) { + observation_meta_t meta; + if (db_observation_meta_get(&meta) != DB_OBS_OK) return -1; + char oldest[OBS_CURSOR_SIZE]; + if (observation_cursor_encode(meta.oldest_seq - 1, meta.epoch_uuid, oldest, + sizeof(oldest)) != 0) + return -1; + char other_epoch[OBS_UUID_SIZE]; + do { + if (lightnvr_uuid_generate_v4(other_epoch) != 0) return -1; + } while (strcmp(other_epoch, meta.epoch_uuid) == 0); + char probe[OBS_CURSOR_SIZE]; + if (observation_cursor_encode(0, other_epoch, probe, sizeof(probe)) != 0) return -1; + observation_list_query_t query; + memset(&query, 0, sizeof(query)); + observation_page_t page; + if (db_observations_list(&query, probe, &page) != DB_OBS_CURSOR_EXPIRED) { + db_observation_page_free(&page); + return -1; + } + cJSON_AddStringToObject(history, "oldest_cursor", oldest); + cJSON_AddItemToObject(history, "oldest_observed_at", + page.has_oldest_observed_at ? time_or_null(page.oldest_observed_at_ms) + : cJSON_CreateNull()); + db_observation_page_free(&page); + return 0; +} + +static bool camera_evidence_enabled(const stream_config_t *streams, int count, const char *uuid) { + for (int i = 0; streams && i < count; i++) + if (strcmp(streams[i].camera_uuid, uuid) == 0) return streams[i].observation_evidence_enabled; + return false; +} + +void handle_get_integration_capabilities(const http_request_t *req, http_response_t *res) { + user_t user; + if (!authenticate(req, res, &user)) return; + char source[OBS_SOURCE_TEXT_SIZE]; + if (!installation_source(source)) { + send_unavailable(res); + return; + } + + fleet_camera_t *viewable = NULL, *snapshot = NULL, *annotatable = NULL; + int viewable_count = 0, snapshot_count = 0, annotatable_count = 0; + if (authorized_cameras(&user, AUTHZ_LIVE_VIEW, &viewable, &viewable_count) != 0 || + authorized_cameras(&user, AUTHZ_SNAPSHOT_CREATE, &snapshot, &snapshot_count) != 0 || + authorized_cameras(&user, AUTHZ_OBSERVATIONS_ANNOTATE, &annotatable, + &annotatable_count) != 0) { + free(viewable); + free(snapshot); + free(annotatable); + send_error(res, 500, "internal_error", "Authorization policy evaluation failed"); + return; + } + int slots = configured_stream_slots(); + stream_config_t *streams = calloc((size_t)slots, sizeof(*streams)); + int stream_count = streams ? get_all_stream_configs_readonly(streams, slots) : -1; + + cJSON *root = cJSON_CreateObject(); + cJSON *history = NULL; + bool ok = root && stream_count >= 0; + if (ok) { + cJSON *versions = cJSON_AddObjectToObject(root, "contract_versions"); + cJSON_AddNumberToObject(versions, "observations", 1); + cJSON_AddNumberToObject(versions, "classifications", 1); + cJSON_AddStringToObject(versions, "observation_event", OBS_EVENT_TYPE); + + cJSON *src = cJSON_AddObjectToObject(root, "source"); + cJSON_AddStringToObject(src, "id", source); + cJSON_AddStringToObject(src, "kind", "lightnvr"); + cJSON_AddStringToObject(src, "installation_uuid", source + strlen(URN_PREFIX)); + cJSON_AddStringToObject(src, "product_version", LIGHTNVR_VERSION_STRING); + + char writer[OBS_WRITER_NAMESPACE_SIZE]; + int namespace_rc = observation_writer_namespace(user.username, writer); + cJSON *principal = cJSON_AddObjectToObject(root, "principal"); + cJSON_AddItemToObject(principal, "writer_namespace", + namespace_rc == 0 ? cJSON_CreateString(writer) : cJSON_CreateNull()); + + cJSON *operations = cJSON_AddObjectToObject(root, "operations"); + add_operation(operations, "observations.history", true, viewable_count > 0, + "not_authorized"); + add_operation(operations, "observations.snapshot", true, snapshot_count > 0, + "not_authorized"); + /* A principal without a writer namespace can never annotate, so that + * reason wins over a missing grant. */ + add_operation(operations, "observations.annotate", true, + namespace_rc == 0 && annotatable_count > 0, + namespace_rc == -2 ? "reserved_writer_namespace" + : namespace_rc != 0 ? "invalid_writer_namespace" : "not_authorized"); + add_operation(operations, "track_lifecycle", false, false, "not_implemented"); + add_operation(operations, "recorded_frame", false, false, "not_implemented"); + add_operation(operations, "event_clip", false, false, "not_implemented"); + add_operation(operations, "range_clip", false, false, "not_implemented"); + + cJSON *limits = cJSON_AddObjectToObject(root, "limits"); + cJSON_AddNumberToObject(limits, "history_page_default", OBS_LIST_DEFAULT_LIMIT); + cJSON_AddNumberToObject(limits, "history_page_max", OBS_LIST_MAX_LIMIT); + cJSON_AddNumberToObject(limits, "history_filter_cameras_max", OBS_FILTER_CAMERAS_MAX); + cJSON_AddNumberToObject(limits, "history_filter_labels_max", OBS_QUERY_MAX_LABELS); + cJSON_AddNumberToObject(limits, "max_objects_per_batch", OBS_MAX_BATCH); + cJSON_AddNumberToObject(limits, "label_max_bytes", OBS_LABEL_SIZE - 1); + cJSON_AddNumberToObject(limits, "snapshot_retry_after_max_seconds", + OBS_SNAPSHOT_RETRY_MAX); + cJSON_AddNumberToObject(limits, "annotations_per_observation_max", OBS_ANNOTATIONS_MAX); + + history = cJSON_AddObjectToObject(root, "history"); + cJSON_AddNumberToObject(history, "retention_days", history_retention_days()); + ok = history && history_bounds(history) == 0; + + cJSON *delivery = cJSON_AddObjectToObject(root, "delivery"); + cJSON_AddBoolToObject(delivery, "mqtt_enabled", g_config.mqtt_enabled); + if (g_config.mqtt_topic_prefix[0]) { + char topic[256]; + snprintf(topic, sizeof(topic), "%s/v1/events/%s/{camera_uuid}", + g_config.mqtt_topic_prefix, OBS_EVENT_TYPE); + cJSON_AddStringToObject(delivery, "topic", topic); + } else { + cJSON_AddNullToObject(delivery, "topic"); + } + + /* Cameras with live.view, snapshot.create or observations.annotate, in + * fleet order. */ + cJSON *cameras = cJSON_AddArrayToObject(root, "cameras"); + fleet_camera_t *all = NULL; + int all_count = 0; + if (db_fleet_camera_load_readonly(&all, &all_count) != 0) ok = false; + int listed = 0; + for (int i = 0; ok && i < all_count && listed < OBS_CAPABILITY_CAMERAS_MAX; i++) { + const char *uuid = all[i].camera_uuid; + bool live = camera_in(viewable, viewable_count, uuid); + bool snap = camera_in(snapshot, snapshot_count, uuid); + bool annotate = camera_in(annotatable, annotatable_count, uuid); + if (!live && !snap && !annotate) continue; + cJSON *camera = cJSON_CreateObject(); + cJSON_AddStringToObject(camera, "camera_uuid", uuid); + cJSON_AddBoolToObject(camera, "evidence_enabled", + camera_evidence_enabled(streams, stream_count, uuid)); + cJSON *actions = cJSON_AddArrayToObject(camera, "actions"); + if (live) cJSON_AddItemToArray(actions, cJSON_CreateString("live.view")); + if (snap) cJSON_AddItemToArray(actions, cJSON_CreateString("snapshot.create")); + if (annotate) + cJSON_AddItemToArray(actions, cJSON_CreateString("observations.annotate")); + cJSON_AddItemToArray(cameras, camera); + listed++; + } + free(all); + } + free(streams); + free(viewable); + free(snapshot); + free(annotatable); + if (!ok) { + cJSON_Delete(root); + send_internal(res); + return; + } + send_json(res, 200, root); +} + +/* ---- GET /api/observations ------------------------------------------- */ + +static bool query_has(const char *query, const char *name) { + size_t length = strlen(name); + for (const char *p = query; p && *p; ) { + if (strncmp(p, name, length) == 0 && p[length] == '=') return true; + p = strchr(p, '&'); + if (p) p++; + } + return false; +} + +/* Returns the decoded length, -1 when absent and -2 when present but not + * decodable. The query string is bounded at 1,024 bytes, so value (1,024 + * bytes) never truncates. Only the first occurrence is read (D1). */ +static int query_value(const http_request_t *req, const char *name, char *value, size_t size) { + value[0] = '\0'; + if (!query_has(req->query_string, name)) return -1; + int n = http_request_get_query_param(req, name, value, size); + return n < 0 ? -2 : n; +} + +static bool parse_limit(const char *text, int *limit) { + char *end = NULL; + errno = 0; + long value = strtol(text, &end, 10); + if (errno != 0 || end == text || *end != '\0' || !isdigit((unsigned char)text[0])) + return false; + if (value < 1 || value > OBS_LIST_MAX_LIMIT) return false; + *limit = (int)value; + return true; +} + +static bool parse_labels(char *text, observation_list_query_t *query) { + size_t count = 0; + char *start = text; + for (;;) { + char *comma = strchr(start, ','); + size_t length = comma ? (size_t)(comma - start) : strlen(start); + if (length < 1 || length > OBS_LABEL_SIZE - 1 || count >= OBS_QUERY_MAX_LABELS) + return false; + for (size_t i = 0; i < length; i++) + if ((unsigned char)start[i] < 0x20 || start[i] == 0x7f) return false; + memcpy(query->labels[count], start, length); + query->labels[count][length] = '\0'; + count++; + if (!comma) break; + start = comma + 1; + } + query->label_count = count; + return true; +} + +static bool parse_cameras(char *text, char (*cameras)[OBS_UUID_SIZE], size_t *count) { + *count = 0; + char *start = text; + for (;;) { + char *comma = strchr(start, ','); + size_t length = comma ? (size_t)(comma - start) : strlen(start); + if (length != OBS_UUID_SIZE - 1 || *count >= OBS_FILTER_CAMERAS_MAX) return false; + memcpy(cameras[*count], start, length); + cameras[*count][length] = '\0'; + lowercase(cameras[*count]); + if (!lightnvr_uuid_is_valid(cameras[*count])) return false; + (*count)++; + if (!comma) break; + start = comma + 1; + } + return true; +} + +static void send_cursor_expired(http_response_t *res, const observation_page_t *page) { + cJSON *body = error_body("Cursor is older than retained observation history", + "cursor_expired"); + if (body) { + cJSON_AddStringToObject(body, "oldest_cursor", page->oldest_cursor); + cJSON_AddItemToObject(body, "oldest_observed_at", + page->has_oldest_observed_at + ? time_or_null(page->oldest_observed_at_ms) + : cJSON_CreateNull()); + } + send_json(res, 410, body); +} + +typedef struct { + const observation_page_t *page; + const char *source; + cJSON *items; +} page_item_context_t; + +static int add_page_item(size_t index, const observation_classification_set_t *set, + void *context) { + page_item_context_t *page = context; + cJSON *item = observation_record_to_json(&page->page->items[index], page->source, set); + if (!item) return 1; + cJSON_AddItemToArray(page->items, item); + return 0; +} + +void handle_get_observations(const http_request_t *req, http_response_t *res) { + user_t user; + if (!authenticate(req, res, &user)) return; + char source[OBS_SOURCE_TEXT_SIZE]; + if (!installation_source(source)) { + send_unavailable(res); + return; + } + + observation_list_query_t query; + memset(&query, 0, sizeof(query)); + char value[OBS_QUERY_VALUE_SIZE]; + int n; + + if ((n = query_value(req, "limit", value, sizeof(value))) != -1 && + (n <= 0 || !parse_limit(value, &query.limit))) { + send_invalid(res, "limit", "limit must be an integer from 1 to 500"); + return; + } + if ((n = query_value(req, "label", value, sizeof(value))) != -1 && + (n <= 0 || !parse_labels(value, &query))) { + send_invalid(res, "label", "label must list 1 to 8 labels of 1 to 31 bytes"); + return; + } + if ((n = query_value(req, "since", value, sizeof(value))) != -1) { + if (n <= 0 || observation_parse_time(value, &query.since_ms) != 0) { + send_invalid(res, "since", "since must be an RFC 3339 time"); + return; + } + query.has_since = true; + } + if ((n = query_value(req, "until", value, sizeof(value))) != -1) { + if (n <= 0 || observation_parse_time(value, &query.until_ms) != 0) { + send_invalid(res, "until", "until must be an RFC 3339 time"); + return; + } + query.has_until = true; + } + if (query.has_since && query.has_until && query.since_ms >= query.until_ms) { + send_invalid(res, "until", "since must be before until"); + return; + } + char cursor[OBS_QUERY_VALUE_SIZE]; + bool has_cursor = false; + if ((n = query_value(req, "cursor", cursor, sizeof(cursor))) != -1) { + if (n <= 0) { + send_invalid(res, "cursor", "cursor must not be empty"); + return; + } + has_cursor = true; + } + char requested[OBS_FILTER_CAMERAS_MAX][OBS_UUID_SIZE]; + size_t requested_count = 0; + if ((n = query_value(req, "camera_uuid", value, sizeof(value))) != -1 && + (n <= 0 || !parse_cameras(value, requested, &requested_count))) { + send_invalid(res, "camera_uuid", "camera_uuid must list 1 to 10 camera UUIDs"); + return; + } + + /* Authorization: intersect with the cameras where the principal holds + * live.view. An explicit camera outside that set (unknown included) is a + * 403, matching the investigation-scope precedent. */ + fleet_camera_t *cameras = NULL; + int camera_count = 0; + if (authorized_cameras(&user, AUTHZ_LIVE_VIEW, &cameras, &camera_count) != 0) { + send_error(res, 500, "internal_error", "Authorization policy evaluation failed"); + return; + } + char (*scope)[OBS_UUID_SIZE] = NULL; + if (requested_count > 0) { + for (size_t i = 0; i < requested_count; i++) { + if (!camera_in(cameras, camera_count, requested[i])) { + audit_camera_decision(req, &user, AUTHZ_LIVE_VIEW, requested[i], NULL, "denied"); + free(cameras); + send_error(res, 403, "forbidden", "Forbidden"); + return; + } + } + query.camera_uuids = (const char (*)[OBS_UUID_SIZE])requested; + query.camera_count = requested_count; + } else if (camera_count > 0) { + scope = calloc((size_t)camera_count, sizeof(*scope)); + if (!scope) { + free(cameras); + send_internal(res); + return; + } + for (int i = 0; i < camera_count; i++) + safe_strcpy(scope[i], cameras[i].camera_uuid, OBS_UUID_SIZE, 0); + query.camera_uuids = (const char (*)[OBS_UUID_SIZE])scope; + query.camera_count = (size_t)camera_count; + } + free(cameras); + + observation_page_t page; + int rc = db_observations_list(&query, has_cursor ? cursor : NULL, &page); + free(scope); + switch (rc) { + case DB_OBS_OK: + break; + case DB_OBS_INVALID_CURSOR: + send_error(res, 400, "invalid_cursor", "Malformed cursor"); + return; + case DB_OBS_CURSOR_EXPIRED: + send_cursor_expired(res, &page); + db_observation_page_free(&page); + return; + case DB_OBS_INVALID: + send_invalid(res, NULL, "Invalid observation query"); + return; + default: + send_internal(res); + return; + } + + cJSON *body = cJSON_CreateObject(); + cJSON *items = body ? cJSON_AddArrayToObject(body, "items") : NULL; + bool ok = items != NULL; + if (ok && page.count > 0) { + /* One read-only connection for the whole page's annotations. */ + const char **ids = calloc(page.count, sizeof(*ids)); + ok = ids != NULL; + for (size_t i = 0; ok && i < page.count; i++) ids[i] = page.items[i].uuid; + page_item_context_t context = {.page = &page, .source = source, .items = items}; + ok = ok && db_observation_classifications_list_many(ids, page.count, add_page_item, + &context) == DB_OBS_OK; + free(ids); + } + if (ok) { + cJSON_AddStringToObject(body, "next_cursor", page.next_cursor); + cJSON_AddBoolToObject(body, "has_more", page.has_more); + } + db_observation_page_free(&page); + if (!ok) { + cJSON_Delete(body); + send_internal(res); + return; + } + send_json(res, 200, body); +} + +/* ---- GET /api/observations/{id} --------------------------------------- */ + +/* Load the observation named by the path. Returns 1 when found, 0 when + * unknown (or its capture camera was deleted), and -1 after writing an error + * response. Authorization is the caller's. */ +static int load_observation(const http_request_t *req, http_response_t *res, + const char *suffix, observation_record_t *record) { + char id[OBS_UUID_SIZE]; + if (!path_observation_id(req, suffix, id)) { + send_invalid(res, "id", "Observation ID must be a UUID"); + return -1; + } + int rc = db_observation_get(id, record); + if (rc == DB_OBS_NOT_FOUND) return 0; + if (rc != DB_OBS_OK) { + send_internal(res); + return -1; + } + return 1; +} + +void handle_get_observation(const http_request_t *req, http_response_t *res) { + user_t user; + if (!authenticate(req, res, &user)) return; + char source[OBS_SOURCE_TEXT_SIZE]; + if (!installation_source(source)) { + send_unavailable(res); + return; + } + observation_record_t *record = calloc(1, sizeof(*record)); + if (!record) { + send_internal(res); + return; + } + int found = load_observation(req, res, NULL, record); + if (found <= 0) { + if (found == 0) send_not_found(res); + free(record); + return; + } + authorization_evaluation_t evaluation; + int rc = httpd_evaluate_camera_identity_action(&user, AUTHZ_LIVE_VIEW, record->camera_uuid, + NULL, &evaluation); + if (rc < 0) { + audit_camera_decision(req, &user, AUTHZ_LIVE_VIEW, record->camera_uuid, NULL, "error"); + send_internal(res); + free(record); + return; + } + if (rc > 0 || evaluation.decision != AUTHZ_DECISION_ALLOW) { + if (rc == 0) + audit_camera_decision(req, &user, AUTHZ_LIVE_VIEW, record->camera_uuid, &evaluation, + "denied"); + send_not_found(res); + free(record); + return; + } + audit_camera_decision(req, &user, AUTHZ_LIVE_VIEW, record->camera_uuid, &evaluation, + "allowed"); + observation_classification_set_t *set = calloc(1, sizeof(*set)); + cJSON *body = set && db_observation_classifications_list(record->uuid, set) == DB_OBS_OK + ? observation_record_to_json(record, source, set) : NULL; + free(set); + free(record); + if (!body) { + send_internal(res); + return; + } + send_json(res, 200, body); +} + +/* ---- GET /api/observations/{id}/snapshot ------------------------------ */ + +/* Resolve the stored asset path (relative to storage_path, or absolute under + * it) to a file under /observations/. Rejects anything else so a + * corrupt row can never serve an arbitrary file. */ +static int resolve_asset_file(const char *stored, char *out, size_t size) { + char root[MAX_PATH_LENGTH]; + safe_strcpy(root, g_config.storage_path, sizeof(root), 0); + size_t root_length = strlen(root); + while (root_length > 1 && root[root_length - 1] == '/') root[--root_length] = '\0'; + if (!stored || !stored[0] || root_length == 0) return -1; + const char *relative = stored; + if (stored[0] == '/') { + if (strncmp(stored, root, root_length) != 0 || stored[root_length] != '/') return -1; + relative = stored + root_length + 1; + } + if (strncmp(relative, "observations/", strlen("observations/")) != 0) return -1; + if (strchr(relative, '\\') || strstr(relative, "//")) return -1; + for (const char *segment = relative; segment; ) { + if (strncmp(segment, "..", 2) == 0 && (segment[2] == '/' || segment[2] == '\0')) + return -1; + if (segment[0] == '.' && (segment[1] == '/' || segment[1] == '\0')) return -1; + segment = strchr(segment, '/'); + if (segment) segment++; + } + size_t length = strlen(relative); + if (length < 5 || strcmp(relative + length - 4, ".jpg") != 0) return -1; + int n = snprintf(out, size, "%s/%s", root, relative); + return n > 0 && (size_t)n < size ? 0 : -1; +} + +static int64_t now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* Contract section 8.2: 1 s while the asset is under 5 s old, otherwise + * min(30, 2 x queue items ahead). L3 exposes no per-asset queue position, so + * the whole evidence backlog (frames queued for encoding) stands in as an + * upper bound on the items ahead of this asset. */ +static int retry_after_seconds(const observation_asset_record_t *asset) { + observation_evidence_stats_t stats; + observation_evidence_get_stats(&stats); + return observation_snapshot_retry_after_seconds(now_ms() - asset->created_at_ms, + stats.backlog_items); +} + +static void send_evidence_unavailable(http_response_t *res, const char *reason) { + cJSON *body = error_body("Evidence is unavailable", "evidence_unavailable"); + if (body) cJSON_AddStringToObject(body, "reason", reason && reason[0] ? reason : "write_failed"); + send_json(res, 404, body); +} + +static void send_evidence_expired(http_response_t *res, const observation_asset_record_t *asset) { + cJSON *body = error_body("Evidence media has expired", "evidence_expired"); + if (body) { + cJSON_AddStringToObject(body, "reason", + asset->reason[0] ? asset->reason : "retention_expired"); + cJSON_AddItemToObject(body, "expired_at", time_or_null(asset->updated_at_ms)); + } + send_json(res, 410, body); +} + +static bool etag_matches(const http_request_t *req, const char *etag) { + const char *header = http_request_get_header(req, "If-None-Match"); + if (!header) return false; + while (*header == ' ') header++; + if (strcmp(header, "*") == 0) return true; + return strstr(header, etag) != NULL; +} + +static void serve_ready_snapshot(const http_request_t *req, http_response_t *res, + const observation_record_t *record) { + char file[MAX_PATH_LENGTH]; + if (resolve_asset_file(record->asset.path, file, sizeof(file)) != 0) { + log_error("Observation asset %s has a path outside observation storage", + record->asset.uuid); + send_internal(res); + return; + } + struct stat st; + if (stat(file, &st) != 0 || !S_ISREG(st.st_mode)) { + /* Retention may have expired it since the row was read. */ + observation_asset_record_t current; + if (db_observation_asset_get(record->asset.uuid, ¤t) == DB_OBS_OK && + current.state == OBS_ASSET_EXPIRED) { + send_evidence_expired(res, ¤t); + return; + } + log_warn("Ready observation asset %s has no media file", record->asset.uuid); + send_evidence_unavailable(res, "write_failed"); + return; + } + char etag[OBS_UUID_SIZE + 2]; + snprintf(etag, sizeof(etag), "\"%s\"", record->asset.uuid); + if (etag_matches(req, etag)) { + res->status_code = 304; + http_response_add_header(res, "ETag", etag); + http_response_add_header(res, "Cache-Control", OBS_SNAPSHOT_CACHE_CONTROL); + return; + } + char headers[160]; + snprintf(headers, sizeof(headers), "Cache-Control: %s\r\nETag: %s\r\n", + OBS_SNAPSHOT_CACHE_CONTROL, etag); + if (http_serve_file(req, res, file, "image/jpeg", headers) != 0) + send_internal(res); +} + +void handle_get_observation_snapshot(const http_request_t *req, http_response_t *res) { + user_t user; + if (!authenticate(req, res, &user)) return; + observation_record_t *record = calloc(1, sizeof(*record)); + if (!record) { + send_internal(res); + return; + } + int found = load_observation(req, res, "/snapshot", record); + if (found <= 0) { + if (found == 0) send_not_found(res); + free(record); + return; + } + + authorization_evaluation_t evaluation; + int rc = httpd_evaluate_camera_identity_action(&user, AUTHZ_SNAPSHOT_CREATE, + record->camera_uuid, NULL, &evaluation); + if (rc < 0) { + audit_snapshot(req, &user, record, NULL, "error"); + send_internal(res); + free(record); + return; + } + if (rc > 0) { /* capture camera vanished between the read and the check */ + send_not_found(res); + free(record); + return; + } + if (evaluation.decision != AUTHZ_DECISION_ALLOW) { + audit_snapshot(req, &user, record, &evaluation, "denied"); + /* D4: a caller who can read the metadata learns nothing from a 403. */ + authorization_evaluation_t view; + int view_rc = httpd_evaluate_camera_identity_action(&user, AUTHZ_LIVE_VIEW, + record->camera_uuid, NULL, &view); + if (view_rc < 0) send_internal(res); + else if (view_rc == 0 && view.decision == AUTHZ_DECISION_ALLOW) + send_error(res, 403, "forbidden", "Forbidden"); + else send_not_found(res); + free(record); + return; + } + audit_snapshot(req, &user, record, &evaluation, "allowed"); + + /* Only this observation's own asset is ever served; there is no fallback + * to a latest or go2rtc frame. */ + switch (record->asset.state) { + case OBS_ASSET_READY: + serve_ready_snapshot(req, res, record); + break; + case OBS_ASSET_PENDING: { + int seconds = retry_after_seconds(&record->asset); + char header[16]; + snprintf(header, sizeof(header), "%d", seconds); + http_response_add_header(res, "Retry-After", header); + cJSON *body = error_body("Evidence is not ready yet", "evidence_pending"); + if (body) cJSON_AddNumberToObject(body, "retry_after_seconds", seconds); + send_json(res, 202, body); + break; + } + case OBS_ASSET_EXPIRED: + send_evidence_expired(res, &record->asset); + break; + case OBS_ASSET_UNAVAILABLE: + default: + send_evidence_unavailable(res, record->asset.reason); + break; + } + free(record); +} + +/* ---- PUT /api/observations/{id}/classifications/{namespace}/{name} ------ */ + +#define OBS_PUT_PREFIX "/api/observations/" +#define OBS_PUT_INFIX "/classifications/" +#define OBS_ANNOTATE_ACTION "observations.annotate" + +typedef struct { + char id[OBS_UUID_SIZE]; + char ns[OBS_CLS_NAMESPACE_SIZE]; /* "" when not a valid segment */ + char name[OBS_CLS_NAMESPACE_SIZE]; /* "" when not a valid segment */ +} put_target_t; + +static void copy_segment(const char *start, size_t length, char out[OBS_CLS_NAMESPACE_SIZE]) { + out[0] = '\0'; + if (length == 0 || length > OBS_CLS_SEGMENT_MAX) return; + memcpy(out, start, length); + out[length] = '\0'; + if (!observation_classification_segment_is_valid(out)) out[0] = '\0'; +} + +/* Split the raw request path. False when the observation ID is not a UUID or + * the route shape is wrong; invalid key segments are left empty for the + * namespace (403) and name (400) checks. Segments are never percent-decoded, + * so an encoded '/' can only make a segment invalid. */ +static bool parse_put_path(const char *path, put_target_t *target) { + memset(target, 0, sizeof(*target)); + size_t prefix = strlen(OBS_PUT_PREFIX), infix = strlen(OBS_PUT_INFIX); + if (!path || strncmp(path, OBS_PUT_PREFIX, prefix) != 0) return false; + const char *id = path + prefix; + const char *slash = strchr(id, '/'); + if (!slash || slash - id != OBS_UUID_SIZE - 1 || strncmp(slash, OBS_PUT_INFIX, infix) != 0) + return false; + memcpy(target->id, id, OBS_UUID_SIZE - 1); + target->id[OBS_UUID_SIZE - 1] = '\0'; + lowercase(target->id); + if (!lightnvr_uuid_is_valid(target->id)) return false; + const char *ns = slash + infix; + const char *separator = strchr(ns, '/'); + if (!separator) return false; + copy_segment(ns, (size_t)(separator - ns), target->ns); + copy_segment(separator + 1, strlen(separator + 1), target->name); + return true; +} + +typedef struct { + char classifier[OBS_CLS_CLASSIFIER_SIZE]; + char model_version[OBS_CLS_MODEL_VERSION_SIZE]; + char label[OBS_CLS_LABEL_SIZE]; + char taxonomy_id[OBS_CLS_TAXONOMY_SIZE]; + double confidence; + int64_t classified_at_ms; + char input_asset_uuid[OBS_UUID_SIZE]; + int input_revision; + observation_classification_kind_t kind; + bool has_kind; + int expected_revision; +} put_body_t; + +/* Well-formed UTF-8 without control characters. */ +static bool clean_utf8(const char *text) { + const unsigned char *p = (const unsigned char *)text; + while (*p) { + unsigned char c = *p; + int extra; + uint32_t code; + if (c < 0x80) { + if (c < 0x20 || c == 0x7f) return false; + p++; + continue; + } else if ((c & 0xe0) == 0xc0) { + extra = 1; code = c & 0x1f; + } else if ((c & 0xf0) == 0xe0) { + extra = 2; code = c & 0x0f; + } else if ((c & 0xf8) == 0xf0) { + extra = 3; code = c & 0x07; + } else { + return false; + } + for (int i = 1; i <= extra; i++) { + if ((p[i] & 0xc0) != 0x80) return false; + code = (code << 6) | (p[i] & 0x3f); + } + static const uint32_t minimum[] = {0, 0x80, 0x800, 0x10000}; + if (code < minimum[extra] || code > 0x10ffff || (code >= 0xd800 && code <= 0xdfff)) + return false; + p += extra + 1; + } + return true; +} + +/* Text field: required (1..max bytes) or optional (absent, null or 0..max; + * "" is stored as null). */ +static bool body_text(const cJSON *body, const char *name, bool required, size_t max, + char *out, size_t size) { + out[0] = '\0'; + const cJSON *item = cJSON_GetObjectItemCaseSensitive(body, name); + if (!item) return !required; + if (!required && cJSON_IsNull(item)) return true; + if (!cJSON_IsString(item) || !item->valuestring) return false; + size_t length = strlen(item->valuestring); + if (length > max || (required && length == 0) || length >= size) return false; + if (!clean_utf8(item->valuestring)) return false; + memcpy(out, item->valuestring, length + 1); + return true; +} + +static bool body_integer(const cJSON *body, const char *name, int minimum, int *out) { + const cJSON *item = cJSON_GetObjectItemCaseSensitive(body, name); + if (!cJSON_IsNumber(item)) return false; + double value = item->valuedouble; + if (!isfinite(value) || value != floor(value) || value < minimum || value > INT_MAX) + return false; + *out = (int)value; + return true; +} + +/* Contract section 10.3. Returns NULL when valid, else the offending field. */ +static const char *parse_put_body(const cJSON *body, put_body_t *out) { + static const char *const allowed[] = { + "classifier", "model_version", "label", "taxonomy_id", "confidence", "classified_at", + "input_asset_id", "input_revision", "kind", "expected_revision", "writer_namespace", + "writer", + }; + memset(out, 0, sizeof(*out)); + const cJSON *item; + cJSON_ArrayForEach(item, body) { + bool known = false; + for (size_t i = 0; !known && i < sizeof(allowed) / sizeof(allowed[0]); i++) + known = item->string && strcmp(item->string, allowed[i]) == 0; + if (!known) return item->string ? item->string : "body"; + } + if (!body_text(body, "classifier", true, OBS_CLS_CLASSIFIER_SIZE - 1, out->classifier, + sizeof(out->classifier))) + return "classifier"; + if (!body_text(body, "model_version", false, OBS_CLS_MODEL_VERSION_SIZE - 1, + out->model_version, sizeof(out->model_version))) + return "model_version"; + if (!body_text(body, "label", true, OBS_CLS_LABEL_SIZE - 1, out->label, sizeof(out->label))) + return "label"; + if (!body_text(body, "taxonomy_id", false, OBS_CLS_TAXONOMY_SIZE - 1, out->taxonomy_id, + sizeof(out->taxonomy_id))) + return "taxonomy_id"; + item = cJSON_GetObjectItemCaseSensitive(body, "confidence"); + if (!cJSON_IsNumber(item) || !isfinite(item->valuedouble) || item->valuedouble < 0.0 || + item->valuedouble > 1.0) + return "confidence"; + out->confidence = item->valuedouble; + item = cJSON_GetObjectItemCaseSensitive(body, "classified_at"); + if (!cJSON_IsString(item) || observation_parse_time(item->valuestring, + &out->classified_at_ms) != 0) + return "classified_at"; + item = cJSON_GetObjectItemCaseSensitive(body, "input_asset_id"); + if (!cJSON_IsString(item) || strlen(item->valuestring) != OBS_UUID_SIZE - 1) + return "input_asset_id"; + memcpy(out->input_asset_uuid, item->valuestring, OBS_UUID_SIZE); + lowercase(out->input_asset_uuid); + if (!lightnvr_uuid_is_valid(out->input_asset_uuid)) return "input_asset_id"; + if (!body_integer(body, "input_revision", 1, &out->input_revision)) return "input_revision"; + item = cJSON_GetObjectItemCaseSensitive(body, "kind"); + if (!cJSON_IsString(item) || observation_classification_kind_parse(item->valuestring, + &out->kind) != 0) + return "kind"; + out->has_kind = true; + if (cJSON_GetObjectItemCaseSensitive(body, "expected_revision") && + !body_integer(body, "expected_revision", 0, &out->expected_revision)) + return "expected_revision"; + return NULL; +} + +typedef struct { + const http_request_t *req; + const user_t *user; + const char *observation_uuid; + const char *camera_uuid; /* NULL before the observation is known */ + char key[OBS_CLS_KEY_SIZE]; /* "" until both segments are valid */ + const char *kind; /* NULL until the body is parsed */ +} annotate_audit_t; + +/* Contract section 12.2: every outcome, with camera, key, kind, the result + * and the annotation revision before/after (-1 = unknown). Never the body, + * credentials or bytes; the principal and token come from the standard + * audit columns. */ +static void audit_annotation(const annotate_audit_t *audit, const char *outcome, + const char *result, int before, int after) { + cJSON *context = cJSON_CreateObject(); + if (context) { + cJSON_AddItemToObject(context, "camera_uuid", text_or_null(audit->camera_uuid)); + cJSON_AddItemToObject(context, "key", text_or_null(audit->key)); + cJSON_AddItemToObject(context, "kind", text_or_null(audit->kind)); + cJSON_AddStringToObject(context, "result", result); + if (before >= 0) cJSON_AddNumberToObject(context, "annotation_revision_before", before); + else cJSON_AddNullToObject(context, "annotation_revision_before"); + if (after >= 0) cJSON_AddNumberToObject(context, "annotation_revision_after", after); + else cJSON_AddNullToObject(context, "annotation_revision_after"); + } + audit_log_operation(audit->req, audit->user, OBS_ANNOTATE_ACTION, "observation", + audit->observation_uuid, "annotate", outcome, context); + cJSON_Delete(context); +} + +static void send_annotation_error(http_response_t *res, int status, const char *code, + const char *message, + const observation_classification_result_t *result) { + cJSON *body = error_body(message, code); + if (body && result && result->has_annotation) { + cJSON *current = observation_classification_to_json(&result->annotation); + if (current) cJSON_AddItemToObject(body, "current", current); + } + send_json(res, status, body); +} + +static void respond_to_put(const annotate_audit_t *audit, http_response_t *res, int rc, + const observation_classification_result_t *result) { + int stored = result->has_annotation ? result->annotation.annotation_revision : -1; + switch (rc) { + case DB_OBS_CLS_CREATED: + case DB_OBS_CLS_UPDATED: + case DB_OBS_CLS_NOOP: { + bool changed = rc != DB_OBS_CLS_NOOP; + cJSON *body = cJSON_CreateObject(); + cJSON *annotation = observation_classification_to_json(&result->annotation); + if (!body || !annotation) { + cJSON_Delete(body); + cJSON_Delete(annotation); + audit_annotation(audit, "error", "internal_error", -1, -1); + send_internal(res); + return; + } + cJSON_AddBoolToObject(body, "changed", changed); + cJSON_AddNumberToObject(body, "observation_revision", result->observation_revision); + cJSON_AddItemToObject(body, "annotation", annotation); + const char *name = rc == DB_OBS_CLS_CREATED ? "created" + : rc == DB_OBS_CLS_UPDATED ? "updated" : "noop"; + int before = rc == DB_OBS_CLS_CREATED ? -1 + : rc == DB_OBS_CLS_UPDATED ? stored - 1 : stored; + audit_annotation(audit, "success", name, before, stored); + send_json(res, rc == DB_OBS_CLS_CREATED ? 201 : 200, body); + return; + } + case DB_OBS_CLS_REVISION_CONFLICT: + audit_annotation(audit, "failure", "revision_conflict", stored, stored); + send_annotation_error(res, 409, "revision_conflict", "Annotation revision does not match", + result); + return; + case DB_OBS_CLS_OWNER_LOCKED: + audit_annotation(audit, "failure", "owner_decision_locked", stored, stored); + send_annotation_error(res, 409, "owner_decision_locked", + "An owner-confirmed annotation cannot be replaced by an automated one", + result); + return; + case DB_OBS_CLS_EVIDENCE_MISMATCH: { + audit_annotation(audit, "failure", "evidence_mismatch", -1, -1); + cJSON *body = error_body("Annotation input does not match the observation evidence", + "evidence_mismatch"); + if (body) { + cJSON_AddStringToObject(body, "observation_asset_id", result->observation_asset_uuid); + cJSON_AddStringToObject(body, "evidence_state", + observation_asset_state_name(result->evidence_state)); + } + send_json(res, 422, body); + return; + } + case DB_OBS_CLS_WRITER_MISMATCH: + /* D3: another user created this key; its values are withheld. */ + audit_annotation(audit, "denied", "namespace_forbidden", -1, -1); + send_error(res, 403, "namespace_forbidden", "Writer namespace not permitted"); + return; + case DB_OBS_CLS_LIMIT: { + audit_annotation(audit, "failure", "annotation_limit", -1, -1); + cJSON *body = error_body("Annotation limit reached for this observation", + "annotation_limit"); + if (body) cJSON_AddNumberToObject(body, "limit", result->limit); + send_json(res, 409, body); + return; + } + case DB_OBS_CLS_INVALID: + audit_annotation(audit, "failure", "invalid_request", -1, -1); + send_invalid(res, NULL, "Invalid annotation"); + return; + case DB_OBS_CLS_NOT_FOUND: /* pruned between the read and the write */ + audit_annotation(audit, "failure", "observation_not_found", -1, -1); + send_not_found(res); + return; + default: + audit_annotation(audit, "error", "internal_error", -1, -1); + send_internal(res); + return; + } +} + +/* Contract section 10.4 rule 1. Returns true when observations.annotate is + * held on the capture camera; otherwise writes 404 (nothing visible), 403 + * (live.view without annotate) or 500, and audits the denial. */ +static bool authorize_annotation(const annotate_audit_t *audit, http_response_t *res, + const user_t *user, const char *camera_uuid) { + authorization_evaluation_t evaluation; + int rc = httpd_evaluate_camera_identity_action(user, AUTHZ_OBSERVATIONS_ANNOTATE, + camera_uuid, NULL, &evaluation); + if (rc == 0 && evaluation.decision == AUTHZ_DECISION_ALLOW) return true; + if (rc < 0) { + audit_annotation(audit, "error", "internal_error", -1, -1); + send_internal(res); + return false; + } + if (rc > 0) { /* capture camera deleted */ + audit_annotation(audit, "denied", "observation_not_found", -1, -1); + send_not_found(res); + return false; + } + authorization_evaluation_t view; + int view_rc = httpd_evaluate_camera_identity_action(user, AUTHZ_LIVE_VIEW, camera_uuid, NULL, + &view); + if (view_rc < 0) { + audit_annotation(audit, "error", "internal_error", -1, -1); + send_internal(res); + } else if (view_rc == 0 && view.decision == AUTHZ_DECISION_ALLOW) { + audit_annotation(audit, "denied", "forbidden", -1, -1); + send_error(res, 403, "forbidden", "Forbidden"); + } else { + audit_annotation(audit, "denied", "observation_not_found", -1, -1); + send_not_found(res); + } + return false; +} + +/* Rule 2 for an optional body field naming a writer. 0 = absent or the + * caller's own namespace, 1 = another namespace, -1 = not a string. */ +static int body_names_other_writer(const cJSON *body, const char *field, const char *writer) { + const cJSON *item = cJSON_GetObjectItemCaseSensitive(body, field); + if (!item) return 0; + if (!cJSON_IsString(item) || !item->valuestring) return -1; + return strcmp(item->valuestring, writer) == 0 ? 0 : 1; +} + +void handle_put_observation_classification(const http_request_t *req, http_response_t *res) { + user_t user; + if (!authenticate(req, res, &user)) return; + char source[OBS_SOURCE_TEXT_SIZE]; + if (!installation_source(source)) { + send_unavailable(res); + return; + } + put_target_t target; + if (!parse_put_path(req->path, &target)) { + send_invalid(res, "id", "Observation ID must be a UUID"); + return; + } + annotate_audit_t audit = {.req = req, .user = &user, .observation_uuid = target.id}; + if (target.ns[0] && target.name[0]) + snprintf(audit.key, sizeof(audit.key), "%s/%s", target.ns, target.name); + + observation_record_t *record = calloc(1, sizeof(*record)); + if (!record) { + send_internal(res); + return; + } + int rc = db_observation_get(target.id, record); + if (rc != DB_OBS_OK) { + if (rc == DB_OBS_NOT_FOUND) { + audit_annotation(&audit, "failure", "observation_not_found", -1, -1); + send_not_found(res); + } else { + audit_annotation(&audit, "error", "internal_error", -1, -1); + send_internal(res); + } + free(record); + return; + } + char camera_uuid[OBS_UUID_SIZE]; + safe_strcpy(camera_uuid, record->camera_uuid, sizeof(camera_uuid), 0); + free(record); + audit.camera_uuid = camera_uuid; + + /* 1: authorization on the capture camera, before anything else is read. */ + if (!authorize_annotation(&audit, res, &user, camera_uuid)) return; + + /* 2: the key namespace must be the caller's derived namespace (D3). */ + char writer[OBS_WRITER_NAMESPACE_SIZE]; + if (observation_writer_namespace(user.username, writer) != 0 || !target.ns[0] || + strcmp(target.ns, writer) != 0) { + audit_annotation(&audit, "denied", "namespace_forbidden", -1, -1); + send_error(res, 403, "namespace_forbidden", "Writer namespace not permitted"); + return; + } + + /* 3: the closed, bounded body. */ + if (!req->body || req->body_len == 0 || req->body_len > OBS_PUT_BODY_MAX) { + audit_annotation(&audit, "failure", "invalid_request", -1, -1); + send_invalid(res, "body", req->body_len > OBS_PUT_BODY_MAX + ? "Request body is too large" : "Request body is required"); + return; + } + cJSON *body = httpd_parse_json_body(req); + if (!cJSON_IsObject(body)) { + cJSON_Delete(body); + audit_annotation(&audit, "failure", "invalid_request", -1, -1); + send_invalid(res, "body", "Request body must be a JSON object"); + return; + } + const char *writer_fields[] = {"writer_namespace", "writer"}; + for (int i = 0; i < 2; i++) { + int other = body_names_other_writer(body, writer_fields[i], writer); + if (other == 1) { + cJSON_Delete(body); + audit_annotation(&audit, "denied", "namespace_forbidden", -1, -1); + send_error(res, 403, "namespace_forbidden", "Writer namespace not permitted"); + return; + } + if (other < 0) { + cJSON_Delete(body); + audit_annotation(&audit, "failure", "invalid_request", -1, -1); + send_invalid(res, writer_fields[i], "Writer fields must be strings"); + return; + } + } + if (!target.name[0]) { + cJSON_Delete(body); + audit_annotation(&audit, "failure", "invalid_request", -1, -1); + send_invalid(res, "name", "Annotation name must match ^[a-z0-9][a-z0-9_.-]{0,31}$"); + return; + } + put_body_t fields; + const char *invalid = parse_put_body(body, &fields); + if (fields.has_kind) audit.kind = observation_classification_kind_name(fields.kind); + if (invalid) { + char field[64], message[128]; + safe_strcpy(field, invalid, sizeof(field), 0); + cJSON_Delete(body); + snprintf(message, sizeof(message), "Invalid or unknown field: %s", field); + audit_annotation(&audit, "failure", "invalid_request", -1, -1); + send_invalid(res, field, message); + return; + } + cJSON_Delete(body); + + /* 4-8 run in one DB transaction (L13). */ + observation_classification_input_t input = { + .writer_namespace = writer, + .writer_user_id = user.id, + .name = target.name, + .classifier = fields.classifier, + .model_version = fields.model_version, + .label = fields.label, + .taxonomy_id = fields.taxonomy_id, + .confidence = fields.confidence, + .classified_at_ms = fields.classified_at_ms, + .input_asset_uuid = fields.input_asset_uuid, + .input_revision = fields.input_revision, + .kind = fields.kind, + .expected_revision = fields.expected_revision, + }; + observation_classification_result_t *result = calloc(1, sizeof(*result)); + if (!result) { + audit_annotation(&audit, "error", "internal_error", -1, -1); + send_internal(res); + return; + } + rc = db_observation_classification_put(target.id, &input, result); + respond_to_put(&audit, res, rc, result); + free(result); +} diff --git a/src/web/api_handlers_onvif_backend_agnostic.c b/src/web/api_handlers_onvif_backend_agnostic.c index 1eed5c1a9..5d37fdd8c 100644 --- a/src/web/api_handlers_onvif_backend_agnostic.c +++ b/src/web/api_handlers_onvif_backend_agnostic.c @@ -9,6 +9,7 @@ #define _GNU_SOURCE #include "web/device_request.h" +#include "video/onvif_device_management.h" #include #include #include @@ -629,12 +630,14 @@ void handle_post_test_onvif_connection(const http_request_t *req, http_response_ } // Test ONVIF connection - int result = test_onvif_connection(url, username, password); + char detail[1024]; + int result = test_onvif_connection_detail(url, username, password, detail, sizeof(detail)); cJSON_Delete(root); - if (result != 0) { - log_error("Failed to connect to ONVIF device"); - http_response_set_json_error(res, 500, "Failed to connect to ONVIF device"); + if (result != ONVIF_TEST_OK) { + /* The camera, not this server, failed: 502 with what went wrong. */ + log_error("[ONVIFAPI] ONVIF connection test failed (%d): %s", result, detail); + http_response_set_json_error(res, 502, detail[0] ? detail : "Failed to connect to ONVIF device"); return; } @@ -647,7 +650,8 @@ void handle_post_test_onvif_connection(const http_request_t *req, http_response_ } cJSON_AddBoolToObject(response_json, "success", true); - cJSON_AddStringToObject(response_json, "message", "Successfully connected to ONVIF device"); + cJSON_AddStringToObject(response_json, "message", + detail[0] ? detail : "Successfully connected to ONVIF device"); // Convert to string char *json_str = cJSON_PrintUnformatted(response_json); diff --git a/src/web/api_handlers_settings.c b/src/web/api_handlers_settings.c index 545a0d1f0..a3f3556ff 100644 --- a/src/web/api_handlers_settings.c +++ b/src/web/api_handlers_settings.c @@ -1717,6 +1717,17 @@ void handle_post_settings(const http_request_t *req, http_response_t *res) { cJSON *go2rtc_external_ip = cJSON_GetObjectItem(settings, "go2rtc_external_ip"); if (go2rtc_external_ip && cJSON_IsString(go2rtc_external_ip)) { + char candidates[GO2RTC_MAX_EXTERNAL_CANDIDATES][GO2RTC_CANDIDATE_SIZE]; + if (strlen(go2rtc_external_ip->valuestring) >= sizeof(g_config.go2rtc_external_ip) || + go2rtc_parse_external_candidates(go2rtc_external_ip->valuestring, 8555, candidates, + GO2RTC_MAX_EXTERNAL_CANDIDATES) < 0) { + log_warn("Rejected invalid go2rtc_external_ip setting"); + cJSON_Delete(settings); + http_response_set_json_error(res, 400, + "Invalid go2rtc_external_ip: expected up to 8 comma-separated IPs or hostnames " + "(optionally :port), '*' for auto-detect, or stun:host[:port]"); + return; + } safe_strcpy(g_config.go2rtc_external_ip, go2rtc_external_ip->valuestring, sizeof(g_config.go2rtc_external_ip), 0); settings_changed = true; log_info("Updated go2rtc_external_ip: %s", g_config.go2rtc_external_ip); diff --git a/src/web/api_handlers_streams_get.c b/src/web/api_handlers_streams_get.c index b6207a161..7ece35b85 100644 --- a/src/web/api_handlers_streams_get.c +++ b/src/web/api_handlers_streams_get.c @@ -16,6 +16,7 @@ #include "web/api_handlers.h" #include "web/request_response.h" #include "web/httpd_utils.h" +#include "web/stream_observation_settings.h" #include "web/fleet_health.h" #define LOG_COMPONENT "StreamsAPI" #include "core/logger.h" @@ -611,6 +612,7 @@ void handle_get_streams(const http_request_t *req, http_response_t *res) { cJSON_AddNumberToObject(stream_obj, "post_detection_buffer", db_streams[i].post_detection_buffer); cJSON_AddStringToObject(stream_obj, "detection_object_filter", db_streams[i].detection_object_filter); cJSON_AddStringToObject(stream_obj, "detection_object_filter_list", db_streams[i].detection_object_filter_list); + stream_observation_settings_to_json(stream_obj, &db_streams[i]); cJSON_AddNumberToObject(stream_obj, "protocol", (int)db_streams[i].protocol); cJSON_AddBoolToObject(stream_obj, "record_audio", db_streams[i].record_audio); cJSON_AddBoolToObject(stream_obj, "audio_voice_enhancement", db_streams[i].audio_voice_enhancement); @@ -831,6 +833,7 @@ void handle_get_stream(const http_request_t *req, http_response_t *res) { cJSON_AddNumberToObject(stream_obj, "post_detection_buffer", config.post_detection_buffer); cJSON_AddStringToObject(stream_obj, "detection_object_filter", config.detection_object_filter); cJSON_AddStringToObject(stream_obj, "detection_object_filter_list", config.detection_object_filter_list); + stream_observation_settings_to_json(stream_obj, &config); cJSON_AddNumberToObject(stream_obj, "protocol", (int)config.protocol); cJSON_AddBoolToObject(stream_obj, "record_audio", config.record_audio); cJSON_AddBoolToObject(stream_obj, "audio_voice_enhancement", config.audio_voice_enhancement); @@ -1037,6 +1040,7 @@ void handle_get_stream_full(const http_request_t *req, http_response_t *res) { cJSON_AddNumberToObject(stream_obj, "post_detection_buffer", config.post_detection_buffer); cJSON_AddStringToObject(stream_obj, "detection_object_filter", config.detection_object_filter); cJSON_AddStringToObject(stream_obj, "detection_object_filter_list", config.detection_object_filter_list); + stream_observation_settings_to_json(stream_obj, &config); cJSON_AddNumberToObject(stream_obj, "protocol", (int)config.protocol); cJSON_AddBoolToObject(stream_obj, "record_audio", config.record_audio); cJSON_AddBoolToObject(stream_obj, "audio_voice_enhancement", config.audio_voice_enhancement); diff --git a/src/web/api_handlers_streams_modify.c b/src/web/api_handlers_streams_modify.c index e04ac2875..b59793e6c 100644 --- a/src/web/api_handlers_streams_modify.c +++ b/src/web/api_handlers_streams_modify.c @@ -21,6 +21,7 @@ #include "web/api_handlers.h" #include "web/request_response.h" #include "web/httpd_utils.h" +#include "web/stream_observation_settings.h" #define LOG_COMPONENT "StreamsAPI" #include "core/logger.h" #include "core/config.h" @@ -224,6 +225,12 @@ static void put_stream_worker_locked(put_stream_task_t *task) { return; } + // INT_01 L6: a running detector caches the evidence settings; refresh them + // so the change applies without a restart. -1 = no detector running. + (void)unified_detection_set_observation_settings(task->stream_id, + updated_config.observation_evidence_enabled, + updated_config.observation_labels); + if (set_stream_detection_params(stream, task->config.detection_interval, task->config.detection_threshold, @@ -803,6 +810,16 @@ void handle_post_stream(const http_request_t *req, http_response_t *res) { } } + /* INT_01 L9: per-camera observation evidence opt-in and budgets. */ + char observation_error[160]; + if (stream_observation_settings_apply_json(stream_json, &config, NULL, + observation_error, + sizeof(observation_error)) != 0) { + cJSON_Delete(stream_json); + http_response_set_json_error(res, 400, observation_error); + return; + } + // Note: width, height, fps, and codec are auto-detected from the stream // and are not accepted from API input. They remain at their defaults (0/empty) // until populated by stream probing or ONVIF discovery. @@ -1589,6 +1606,22 @@ void handle_put_stream(const http_request_t *req, http_response_t *res) { } } + /* INT_01 L9: observation evidence settings never restart the stream or + * detector. The worker persists them; the evidence encoder re-reads the + * flag from the database, and a running detection thread's cached copy + * is refreshed by the INT_01 L6 hook (unified_detection_set_observation_settings). */ + char observation_error[160]; + bool observation_changed = false; + if (stream_observation_settings_apply_json(stream_json, &config, + &observation_changed, + observation_error, + sizeof(observation_error)) != 0) { + cJSON_Delete(stream_json); + http_response_set_json_error(res, 400, observation_error); + return; + } + if (observation_changed) config_changed = true; + // Note: width, height, fps, and codec are auto-detected from the stream // and are not accepted from API input on update either. diff --git a/src/web/api_handlers_system.c b/src/web/api_handlers_system.c index 044438408..d1751134b 100644 --- a/src/web/api_handlers_system.c +++ b/src/web/api_handlers_system.c @@ -50,6 +50,7 @@ #include "web/audit_log.h" #define LOG_COMPONENT "SystemAPI" #include "core/logger.h" +#include "core/observation_diagnostics.h" #include "core/config.h" #include "core/path_utils.h" #include "core/version.h" @@ -1083,6 +1084,15 @@ void handle_get_system_info(const http_request_t *req, http_response_t *res) { // Add stream storage usage information with caching add_cached_stream_storage_usage_to_json(info, 0); + // INT_01 L10: observation pipeline diagnostics (contract section 15). + // Read-only; never starts the evidence worker. + observation_diagnostics_t observation_diagnostics; + if (observation_diagnostics_collect(&observation_diagnostics, 0) == 0) { + cJSON *pipeline = observation_diagnostics_to_json(&observation_diagnostics); + if (pipeline) cJSON_AddItemToObject(info, "observation_pipeline", pipeline); + observation_diagnostics_free(&observation_diagnostics); + } + // Convert to string char *json_str = cJSON_PrintUnformatted(info); if (!json_str) { diff --git a/src/web/libuv_api_handlers.c b/src/web/libuv_api_handlers.c index 9cf0c944d..5a6c05e87 100644 --- a/src/web/libuv_api_handlers.c +++ b/src/web/libuv_api_handlers.c @@ -67,6 +67,7 @@ #include "web/api_handlers_operator_floor_plans.h" #include "web/api_handlers_lpr.h" #include "web/api_handlers_detection_engines.h" +#include "web/api_handlers_observations.h" #define LOG_COMPONENT "HTTP" #include "core/logger.h" #include "core/config.h" @@ -302,6 +303,17 @@ int register_all_libuv_handlers(http_server_handle_t server) { // Detection event snapshots (saved on MQTT publish, see issue #449) http_server_register_handler(server, "/api/snapshots/#/#", "GET", handle_get_detection_snapshot); + // INT_01 observations, evidence snapshots and integration capabilities + // (docs/integrations/OBSERVATIONS_CONTRACT.md) + http_server_register_handler(server, "/api/integrations/capabilities", "GET", + handle_get_integration_capabilities); + http_server_register_handler(server, "/api/observations", "GET", handle_get_observations); + http_server_register_handler(server, "/api/observations/#/snapshot", "GET", + handle_get_observation_snapshot); + http_server_register_handler(server, "/api/observations/#/classifications/#/#", "PUT", + handle_put_observation_classification); + http_server_register_handler(server, "/api/observations/#", "GET", handle_get_observation); + // Storage Management API http_server_register_handler(server, "/api/storage/health", "GET", handle_get_storage_health); http_server_register_handler(server, "/api/storage/cleanup", "POST", handle_post_storage_cleanup); diff --git a/src/web/stream_observation_settings.c b/src/web/stream_observation_settings.c new file mode 100644 index 000000000..335bfb96d --- /dev/null +++ b/src/web/stream_observation_settings.c @@ -0,0 +1,153 @@ +/* + * INT_01 L9: per-camera observation evidence settings (contract section 13). + * Shared by the stream GET/POST/PUT handlers so every path applies the same + * bounds; migration 0095 only checks that the budgets are positive. + */ + +#include "web/stream_observation_settings.h" + +#include +#include +#include + +#include "utils/strings.h" + +static int fail(char *error, size_t error_size, const char *message) { + if (error && error_size) snprintf(error, error_size, "%s", message); + return -1; +} + +static bool is_integer_in_range(const cJSON *value, double minimum, double maximum) { + return cJSON_IsNumber(value) && isfinite(value->valuedouble) && + value->valuedouble == floor(value->valuedouble) && + value->valuedouble >= minimum && value->valuedouble <= maximum; +} + +int stream_observation_labels_normalize(const char *input, char *out, size_t out_size, + char *error, size_t error_size) { + if (!input || !out || out_size == 0) return fail(error, error_size, "observation_labels is invalid"); + char result[OBSERVATION_LABELS_MAX] = ""; + size_t used = 0; + int count = 0; + + /* Whitespace only (or empty) means "every object label". */ + const char *scan = input; + while (*scan == ' ' || *scan == '\t') scan++; + if (*scan == '\0') { + out[0] = '\0'; + return 0; + } + + const char *cursor = input; + for (;;) { + const char *end = strchr(cursor, ','); + const char *stop = end ? end : cursor + strlen(cursor); + const char *first = cursor; + const char *last = stop; + while (first < last && (*first == ' ' || *first == '\t')) first++; + while (last > first && (last[-1] == ' ' || last[-1] == '\t')) last--; + size_t length = (size_t)(last - first); + if (length == 0) { + return fail(error, error_size, "observation_labels must not contain empty labels"); + } + if (length > OBSERVATION_LABEL_MAX_BYTES) { + return fail(error, error_size, "observation_labels entries must be 1..31 bytes"); + } + for (const char *c = first; c < last; c++) { + unsigned char ch = (unsigned char)*c; + if (ch < 0x20 || ch > 0x7e) { + return fail(error, error_size, + "observation_labels entries must be printable ASCII"); + } + } + if (++count > OBSERVATION_LABELS_MAX_COUNT) { + return fail(error, error_size, "observation_labels allows at most 16 labels"); + } + if (used + length + (count > 1 ? 1 : 0) >= sizeof(result)) { + return fail(error, error_size, "observation_labels is too long"); + } + if (count > 1) result[used++] = ','; + memcpy(result + used, first, length); + used += length; + result[used] = '\0'; + if (!end) break; + cursor = end + 1; + } + + if (used >= out_size) return fail(error, error_size, "observation_labels is too long"); + memcpy(out, result, used + 1); + return 0; +} + +void stream_observation_settings_to_json(cJSON *object, const stream_config_t *config) { + if (!object || !config) return; + cJSON_AddBoolToObject(object, "observation_evidence_enabled", + config->observation_evidence_enabled); + cJSON_AddStringToObject(object, "observation_labels", config->observation_labels); + cJSON_AddNumberToObject(object, "observation_max_age_days", + config->observation_max_age_days > 0 + ? config->observation_max_age_days + : OBSERVATION_DEFAULT_MAX_AGE_DAYS); + cJSON_AddNumberToObject(object, "observation_max_bytes", + (double)(config->observation_max_bytes > 0 + ? config->observation_max_bytes + : OBSERVATION_DEFAULT_MAX_BYTES)); +} + +int stream_observation_settings_apply_json(const cJSON *body, stream_config_t *config, + bool *changed, char *error, size_t error_size) { + if (changed) *changed = false; + if (!body || !config) return fail(error, error_size, "observation settings are invalid"); + + const cJSON *enabled = cJSON_GetObjectItemCaseSensitive(body, "observation_evidence_enabled"); + const cJSON *labels = cJSON_GetObjectItemCaseSensitive(body, "observation_labels"); + const cJSON *max_age = cJSON_GetObjectItemCaseSensitive(body, "observation_max_age_days"); + const cJSON *max_bytes = cJSON_GetObjectItemCaseSensitive(body, "observation_max_bytes"); + + if (enabled && !cJSON_IsBool(enabled)) { + return fail(error, error_size, "observation_evidence_enabled must be a boolean"); + } + char normalized[OBSERVATION_LABELS_MAX] = ""; + if (labels) { + if (!cJSON_IsString(labels) || !labels->valuestring) { + return fail(error, error_size, "observation_labels must be a string"); + } + if (stream_observation_labels_normalize(labels->valuestring, normalized, + sizeof(normalized), error, error_size) != 0) { + return -1; + } + } + if (max_age && !is_integer_in_range(max_age, OBSERVATION_MAX_AGE_DAYS_MIN, + OBSERVATION_MAX_AGE_DAYS_MAX)) { + return fail(error, error_size, "observation_max_age_days must be an integer between 1 and 90"); + } + if (max_bytes && !is_integer_in_range(max_bytes, (double)OBSERVATION_MAX_BYTES_MIN, + (double)OBSERVATION_MAX_BYTES_MAX)) { + return fail(error, error_size, + "observation_max_bytes must be an integer between 67108864 (64 MiB) " + "and 1099511627776 (1 TiB)"); + } + + bool any_change = false; + if (enabled) { + bool value = cJSON_IsTrue(enabled); + any_change |= value != config->observation_evidence_enabled; + config->observation_evidence_enabled = value; + } + if (labels) { + any_change |= strcmp(normalized, config->observation_labels) != 0; + safe_strcpy(config->observation_labels, normalized, sizeof(config->observation_labels), 0); + } + if (max_age) { + int value = (int)max_age->valuedouble; + any_change |= value != config->observation_max_age_days; + config->observation_max_age_days = value; + } + if (max_bytes) { + int64_t value = (int64_t)max_bytes->valuedouble; + any_change |= value != config->observation_max_bytes; + config->observation_max_bytes = value; + } + if (changed) *changed = any_change; + return 0; +} diff --git a/tests/contracts/device.api.spec.ts b/tests/contracts/device.api.spec.ts index 44079104c..65e95b172 100644 --- a/tests/contracts/device.api.spec.ts +++ b/tests/contracts/device.api.spec.ts @@ -82,7 +82,7 @@ test('owned ONVIF headers, empty inventory, profiles, connection test, and claim const profiles=await wire(server,'/api/onvif/device/profiles',identity);expect(profiles.response.status).toBe(200);valid('OnvifProfilesResponse',profiles.body);expect(profiles.body.profiles[0].video_source_token).toBe('VideoSource_1');expect(profiles.body.profiles[0].stream_uri).toBe('');projectedResponse(doc,'/api/onvif/device/profiles','get',200,profiles.body); const tested=await wire(server,'/api/onvif/device/test',admin,'POST',{url:device.base+'/onvif/device_service'});expect(tested.response.status).toBe(200);valid('DeviceCommandResult',tested.body); device.setEmpty(true);const empty=await wire(server,'/api/onvif/device/profiles',identity);expect(empty.response.status).toBe(200);expect(empty.body.profiles).toEqual([]);valid('OnvifProfilesResponse',empty.body); - expect((await wire(server,'/api/onvif/device/test',admin,'POST',{url:device.base+'/onvif/device_service'})).response.status).toBe(500); + const noProfile=await wire(server,'/api/onvif/device/test',admin,'POST',{url:device.base+'/onvif/device_service'});expect(noProfile.response.status).toBe(502);expect(noProfile.body.error).toContain('usable media profile'); for(const [route,body] of [['/api/onvif/discovery/discover',{network:4}],['/api/onvif/device/test',{url:device.base,username:false}],['/api/onvif/device/add',{device_url:device.base,profile_token:'x',stream_name:'A',password:4}],['/api/onvif/device/claim',{inventory_uuid:'bad',stream_name:'A'}]] as const){const before=device.requests.length;expect((await wire(server,route,admin,'POST',body)).response.status).toBe(400);expect(device.requests.slice(before)).toEqual([]);} }finally{await device.close();} }); diff --git a/tests/contracts/fixture-server.d.mts b/tests/contracts/fixture-server.d.mts index 16b5c91bb..c16449212 100644 --- a/tests/contracts/fixture-server.d.mts +++ b/tests/contracts/fixture-server.d.mts @@ -1,6 +1,6 @@ import type { APIRequest, APIRequestContext, Browser, BrowserContext } from '@playwright/test'; export interface FixtureCredentials { username: string; password: string } -export interface FixtureOptions { lprMasterKeyHex?: string; binary?: string; webRoot?: string; maxStreams?: number; port?: number; startupTimeoutMs?: number; seed?: boolean; retain?: boolean } +export interface FixtureOptions { lprMasterKeyHex?: string; extraConfig?: string; binary?: string; webRoot?: string; maxStreams?: number; port?: number; startupTimeoutMs?: number; seed?: boolean; retain?: boolean } export interface FixtureRequestOptions extends Omit { principal?: string; method?: string; body?: unknown; headers?: Record; } @@ -15,6 +15,10 @@ export interface ContractFixture { json(route: string, options?: FixtureRequestOptions): Promise; apiContext(request: APIRequest, principal?: string): Promise; browserContext(browser: Browser, principal?: string): Promise; + /** Test-only: end the current process (default SIGKILL), keeping the directory. */ + kill(signal?: 'SIGKILL' | 'SIGTERM'): Promise; + /** Test-only: start the next process generation on the same directory, config and port. */ + relaunch(): Promise; stop(options?: {failed?: boolean}): Promise; } export declare const credentials: Readonly>; diff --git a/tests/contracts/fixture-server.mjs b/tests/contracts/fixture-server.mjs index 9f8ec3f63..6e97cb3fe 100644 --- a/tests/contracts/fixture-server.mjs +++ b/tests/contracts/fixture-server.mjs @@ -24,7 +24,7 @@ export async function startFixture(options = {}) { throw new Error('Fixture maxStreams must be an integer from 1 to 1024'); } const directory = await mkdtemp(path.join(tmpdir(), 'lightnvr-contract-')); - let child, output, reservation, exit, stopped = false; + let child, output, reservation, exit, stopped = false, expectedSignal = null, configPath, env; const fixture = { directory, logPath: path.join(directory, 'process.log'), credentials, users: {}, cameras: {}, tokens: {}, async request(route, {principal = 'admin', method = 'GET', body, headers = {}, ...rest} = {}) { @@ -52,6 +52,23 @@ export async function startFixture(options = {}) { } else if (fixture.tokens[principal]) await context.setExtraHTTPHeaders({Authorization: `Bearer ${fixture.tokens[principal]}`}); return context; }, + /** Test-only: end the current process (SIGKILL by default) and keep the directory. */ + async kill(signal = 'SIGKILL') { + if (!child || child.exitCode !== null || child.signalCode !== null) throw new Error('Fixture process is not running'); + expectedSignal = signal; + child.kill(signal); + let timer; + const done = await Promise.race([exit.then(() => true), new Promise(resolve => { timer = setTimeout(() => resolve(false), 15000); })]); + clearTimeout(timer); + if (!done) { child.kill('SIGKILL'); await exit; throw new Error(`Fixture did not exit on ${signal}`); } + if (signal === 'SIGTERM' && child.exitCode !== 0) throw new Error(`Fixture exited ${child.exitCode}/${child.signalCode} on SIGTERM`); + }, + /** Test-only: start the next process generation on the same directory, config and port. */ + async relaunch() { + if (child && child.exitCode === null && child.signalCode === null) throw new Error('Fixture process is still running'); + expectedSignal = null; + await launch(); + }, async stop({failed = false} = {}) { if (stopped) return; stopped = true; @@ -68,7 +85,7 @@ export async function startFixture(options = {}) { if (output) await output.close(); const logs = await readFile(fixture.logPath, 'utf8').catch(() => ''); const nativeFailure = /ERROR: (AddressSanitizer|LeakSanitizer)|runtime error:/.test(logs) || (child && child.exitCode !== 0 && child.exitCode !== null) || shutdownTimedOut || - (child?.signalCode && !(shutdownRequested && child.signalCode === 'SIGTERM')); + (child?.signalCode && !(shutdownRequested && child.signalCode === 'SIGTERM') && child.signalCode !== expectedSignal); if (failed || nativeFailure || options.retain) console.error(`Contract fixture retained: ${directory}`); else await rm(directory, {recursive: true, force: true}); if (nativeFailure) throw new Error(`Native fixture failed; inspect ${fixture.logPath}`); @@ -79,13 +96,27 @@ export async function startFixture(options = {}) { fixture.port = reservation.port; fixture.baseURL = `http://127.0.0.1:${fixture.port}`; for (const dir of ['recordings', 'models']) await mkdir(path.join(directory, dir)); - const config = `[general]\npid_file = ${directory}/lightnvr.pid\nlog_file = ${directory}/server.log\nlog_level = 2\nsyslog_enabled = false\n[storage]\npath = ${directory}/recordings\nmax_size = 0\nrecord_mp4_directly = false\n[database]\npath = ${directory}/lightnvr.db\n[web]\nport = ${fixture.port}\nbind_ip = 127.0.0.1\nroot = ${options.webRoot || `${root}/web/dist`}\nauth_enabled = true\nusername = admin\npassword = admin\nweb_thread_pool_size = 4\n[streams]\nmax_streams = ${maxStreams}\n[models]\npath = ${directory}/models\n[go2rtc]\nenabled = false\nauto_start = false\n`; - const configPath = path.join(directory, 'lightnvr.ini'); + const config = `[general]\npid_file = ${directory}/lightnvr.pid\nlog_file = ${directory}/server.log\nlog_level = 2\nsyslog_enabled = false\n[storage]\npath = ${directory}/recordings\nmax_size = 0\nrecord_mp4_directly = false\n[database]\npath = ${directory}/lightnvr.db\n[web]\nport = ${fixture.port}\nbind_ip = 127.0.0.1\nroot = ${options.webRoot || `${root}/web/dist`}\nauth_enabled = true\nusername = admin\npassword = admin\nweb_thread_pool_size = 4\n[streams]\nmax_streams = ${maxStreams}\n[models]\npath = ${directory}/models\n[go2rtc]\nenabled = false\nauto_start = false\n` + (options.extraConfig ? `${options.extraConfig}\n` : ''); + configPath = path.join(directory, 'lightnvr.ini'); await writeFile(configPath, config); output = await open(fixture.logPath, 'w', 0o600); - await reservation.release(); reservation = null; - const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('LIGHTNVR_'))); + env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith('LIGHTNVR_'))); if(options.lprMasterKeyHex) env.LIGHTNVR_LPR_MASTER_KEY_HEX=options.lprMasterKeyHex; + await launch(); + await fixture.json('/api/auth/users/1/password', {method: 'PUT', principal: 'anonymous', headers: {Authorization: basic({username: 'admin', password: 'admin'})}, body: {old_password: 'admin', new_password: credentials.admin.password}}); + if (options.seed !== false) await seedFixture(fixture); + return fixture; + } catch (error) { + await fixture.stop({failed: true}).catch(stopError => { error.message += `; ${stopError.message}`; }); + error.message += `; fixture: ${directory}`; + throw error; + } + + /** Spawn one process generation; readiness is judged only on this generation's log output. */ + async function launch() { + if (!reservation) reservation = await reservePort(fixture.port); + const logOffset = (await readFile(path.join(directory, 'server.log')).catch(() => Buffer.alloc(0))).length; + await reservation.release(); reservation = null; child = spawn(path.resolve(root, options.binary || process.env.LIGHTNVR_CONTRACT_BIN || 'build/eng-api-T2/bin/lightnvr'), ['-c', configPath], {cwd: root, env, stdio: ['ignore', output.fd, output.fd]}); fixture.pid = child.pid; let spawnError; @@ -99,7 +130,7 @@ export async function startFixture(options = {}) { // Require our PID and successful bind in our own log before connecting. // A PID file alone does not prove ownership of a port lost after reservation. const pid = await readFile(path.join(directory, 'lightnvr.pid'), 'utf8').catch(() => ''); - const serverLog = await readFile(path.join(directory, 'server.log'), 'utf8').catch(() => ''); + const serverLog = (await readFile(path.join(directory, 'server.log')).catch(() => Buffer.alloc(0))).subarray(logOffset).toString('utf8'); if (Number(pid.trim()) === child.pid && serverLog.includes(`libuv_server_start: Listening on 127.0.0.1:${fixture.port}`)) { try { const r = await fetch(`${fixture.baseURL}/api/streams`, {signal: AbortSignal.timeout(500)}); ready = r.status === 401; } catch {} } @@ -107,13 +138,6 @@ export async function startFixture(options = {}) { await pause(100); } if (!ready) throw new Error('Server startup timed out (or port was taken after reservation)'); - await fixture.json('/api/auth/users/1/password', {method: 'PUT', principal: 'anonymous', headers: {Authorization: basic({username: 'admin', password: 'admin'})}, body: {old_password: 'admin', new_password: credentials.admin.password}}); - if (options.seed !== false) await seedFixture(fixture); - return fixture; - } catch (error) { - await fixture.stop({failed: true}).catch(stopError => { error.message += `; ${stopError.message}`; }); - error.message += `; fixture: ${directory}`; - throw error; } } diff --git a/tests/contracts/fixtures/observations/seed.ts b/tests/contracts/fixtures/observations/seed.ts new file mode 100644 index 000000000..eb83353fe --- /dev/null +++ b/tests/contracts/fixtures/observations/seed.ts @@ -0,0 +1,142 @@ +/** + * INT_01 L11 observation fixtures for the isolated contract server. + * + * Rows are written straight into the fixture DB in exactly the shape the L2 DB + * layer writes them: one observation_changes row per revision (cause created + * for revision 1, evidence afterwards) plus its observation_publications row + * keyed by the section 5.4 message ID. Seeded publications default to + * `relayed` so the live relay leaves them alone; pass `publication: 'pending'` + * to stage work the relay must pick up (the "committed, not yet relayed" + * boundary). Only the history, snapshot and relay paths under test read these + * rows; the encoder path itself is covered by the L3/L6/L7 C tests and the + * crash-boundary test. + */ +import {createHash, randomUUID} from 'node:crypto'; +import {mkdirSync, writeFileSync} from 'node:fs'; +import path from 'node:path'; +import {DatabaseSync, type SQLInputValue} from 'node:sqlite'; +import type {Server} from '../../permissionCases'; + +export const OBSERVATION_EVENT = 'io.lightnvr.detection.observation_changed.v1'; + +export function uuidv5(namespace: string, name: string): string { + const hash = createHash('sha1').update(Buffer.from(namespace.replaceAll('-', ''), 'hex')).update(Buffer.from(name, 'utf8')).digest(); + hash[6] = (hash[6] & 0x0f) | 0x50; hash[8] = (hash[8] & 0x3f) | 0x80; + const hex = hash.subarray(0, 16).toString('hex'); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} +/** Section 5.4 (also tests/fixtures/integrations/observations/message-ids.json). */ +const MESSAGE_ID_NAMESPACE = uuidv5('6ba7b811-9dad-11d1-80b4-00c04fd430c8', 'https://lightnvr.io/ns/observation-change/v1'); +export const messageId = (source: string, observation: string, revision: number) => uuidv5(MESSAGE_ID_NAMESPACE, `${source}|${observation}|${revision}`); + +/** Section 7.4: base64url, no padding, of the compact JSON. */ +export const encodeCursor = (seq: number, epoch: string) => Buffer.from(JSON.stringify({v: 1, seq, epoch})).toString('base64url'); +export const decodeCursor = (cursor: string): {v: number; seq: number; epoch: string} => JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')); + +/** A tiny, recognisable JPEG-framed payload; the snapshot path never decodes it. */ +export function jpegBytes(tag: string): Buffer { + return Buffer.concat([Buffer.from([0xff, 0xd8, 0xff, 0xe0]), Buffer.from(`lightnvr-contract-${tag}-${randomUUID()}`), Buffer.from([0xff, 0xd9])]); +} + +function open(server: Server): DatabaseSync { + if (!/^lightnvr-contract-/.test(path.basename(server.directory))) throw new Error('Observation seed requires an isolated contract fixture'); + const db = new DatabaseSync(path.join(server.directory, 'lightnvr.db')); + db.exec('PRAGMA busy_timeout=5000; PRAGMA foreign_keys=ON'); + return db; +} +export function sql>(server: Server, query: string, values: SQLInputValue[] = []): T[] { + const db = open(server); + try { return db.prepare(query).all(...values) as T[]; } finally { db.close(); } +} +export function meta(server: Server): {epoch: string; oldest: number; head: number} { + const row = sql(server, 'SELECT epoch_uuid AS epoch, oldest_seq AS oldest, (SELECT coalesce(max(seq), 0) FROM observation_changes) AS head FROM observation_meta WHERE id = 1')[0]; + return {epoch: String(row.epoch), oldest: Number(row.oldest), head: Number(row.head)}; +} + +export type EvidenceState = 'pending' | 'ready' | 'unavailable' | 'expired'; +export interface SeedOptions { + camera: string; // capture-time camera_uuid + stream?: string; // stream_name_at_capture + state?: EvidenceState; // final evidence state (default ready) + reason?: string; // unavailable/expired reason + label?: string; + observedAt?: number; // ms + recordingId?: number | null; + publication?: 'relayed' | 'pending'; + source?: string; // required when publication is pending (message IDs) +} +export interface SeededObservation {observation: string; asset: string; revision: number; bytes?: Buffer; relativePath: string; observedAt: number} + +const day = (ms: number) => new Date(ms).toISOString().slice(0, 10); + +/** Append one change + publication (the L2 bump), inside an open transaction. */ +function change(db: DatabaseSync, observation: string, revision: number, cause: string, state: EvidenceState, reason: string | null, now: number, publication: string, src: string) { + const info = db.prepare('INSERT INTO observation_changes (observation_uuid, revision, cause, evidence_state, evidence_reason, created_at_ms) VALUES (?, ?, ?, ?, ?, ?)') + .run(observation, revision, cause, state, reason, now); + db.prepare('INSERT INTO observation_publications (change_seq, message_id, state, attempts, updated_at_ms) VALUES (?, ?, ?, ?, ?)') + .run(Number(info.lastInsertRowid), messageId(src || 'urn:lightnvr:contract-seed', observation, revision), publication, publication === 'pending' ? 0 : 1, now); +} + +/** One observation and its asset, with the full revision history up to `state`. */ +export function seedObservation(server: Server, options: SeedOptions): SeededObservation { + const now = Date.now(), observedAt = options.observedAt ?? now, state = options.state ?? 'ready'; + const observation = randomUUID(), asset = randomUUID(); + const relativePath = `observations/${options.camera}/${day(observedAt)}/${asset}.jpg`; + const publication = options.publication ?? 'relayed', src = options.source ?? ''; + if (publication === 'pending' && !src) throw new Error('pending publications need the installation source'); + let bytes: Buffer | undefined; + if (state === 'ready') { + bytes = jpegBytes(observation); + const file = path.join(server.directory, 'recordings', relativePath); + mkdirSync(path.dirname(file), {recursive: true, mode: 0o750}); + writeFileSync(file, bytes, {mode: 0o640}); + } + const reason = state === 'unavailable' || state === 'expired' ? (options.reason ?? (state === 'expired' ? 'retention_expired' : 'queue_full')) : null; + const hasMedia = state === 'ready' || state === 'expired'; + const db = open(server); + try { + db.exec('BEGIN IMMEDIATE'); + db.prepare(`INSERT INTO observation_assets (uuid, camera_uuid, state, reason, origin, media_type, path, bytes, width, height, captured_at_ms, transform_json, expires_at_ms, created_at_ms, updated_at_ms) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(asset, options.camera, state, reason, + hasMedia ? 'inference_input' : null, hasMedia ? 'image/jpeg' : null, hasMedia ? relativePath : null, + hasMedia ? (bytes?.length ?? 64) : null, hasMedia ? 640 : null, hasMedia ? 360 : null, observedAt, + hasMedia ? '{"mode":"none","source_width":640,"source_height":360,"inference_width":640,"inference_height":360,"scale_x":1,"scale_y":1,"pad_x":0,"pad_y":0}' : null, + state === 'ready' ? now + 7 * 86400000 : null, now, now); + const finalRevision = state === 'pending' ? 1 : state === 'expired' ? 3 : 2; + db.prepare(`INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index, observed_at_ms, created_at_ms, engine_key, engine_type, model, label, confidence, box_x, box_y, box_width, box_height, recording_id, asset_uuid, revision) + VALUES (?, ?, ?, ?, 0, ?, ?, 'legacy-primary', 'object', 'contract-model', ?, 0.87, 0.1, 0.2, 0.3, 0.4, ?, ?, ?)`) + .run(observation, options.camera, options.stream ?? String((db.prepare('SELECT name FROM streams WHERE camera_uuid = ?').get(options.camera) as {name?: string} | undefined)?.name ?? 'unknown'), randomUUID(), observedAt, now, options.label ?? 'bird', options.recordingId ?? null, asset, finalRevision); + change(db, observation, 1, 'created', 'pending', null, now, publication, src); + if (state !== 'pending') change(db, observation, 2, 'evidence', state === 'expired' ? 'ready' : state, state === 'expired' ? null : reason, now, publication, src); + if (state === 'expired') change(db, observation, 3, 'evidence', 'expired', reason, now, publication, src); + db.exec('COMMIT'); + return {observation, asset, revision: finalRevision, bytes, relativePath, observedAt}; + } catch (error) { try { db.exec('ROLLBACK'); } catch {} throw error; } finally { db.close(); } +} + +/** The L3 pending -> ready commit (file first, then one transaction bumping every referencing observation). */ +export function completeEvidence(server: Server, seeded: SeededObservation, src = ''): Buffer { + const bytes = jpegBytes(seeded.observation); + const file = path.join(server.directory, 'recordings', seeded.relativePath); + mkdirSync(path.dirname(file), {recursive: true, mode: 0o750}); + writeFileSync(file, bytes, {mode: 0o640}); + transition(server, seeded, 'ready', null, src, bytes); + seeded.bytes = bytes; + return bytes; +} + +/** A compare-and-set evidence transition with its change + publication rows (relayed). */ +export function transition(server: Server, seeded: SeededObservation, to: EvidenceState, reason: string | null, src = '', bytes?: Buffer) { + const now = Date.now(), db = open(server); + try { + db.exec('BEGIN IMMEDIATE'); + const updated = db.prepare(`UPDATE observation_assets SET state = ?, reason = ?, origin = coalesce(origin, 'inference_input'), media_type = coalesce(media_type, 'image/jpeg'), + path = coalesce(path, ?), bytes = coalesce(?, bytes), width = coalesce(width, 640), height = coalesce(height, 360), updated_at_ms = ? WHERE uuid = ? AND state <> ?`) + .run(to, reason, seeded.relativePath, bytes?.length ?? null, now, seeded.asset, to); + if (Number(updated.changes) !== 1) throw new Error(`asset ${seeded.asset} already ${to}`); + seeded.revision += 1; + db.prepare('UPDATE object_observations SET revision = ? WHERE uuid = ?').run(seeded.revision, seeded.observation); + change(db, seeded.observation, seeded.revision, 'evidence', to, reason, now, 'relayed', src); + db.exec('COMMIT'); + } catch (error) { try { db.exec('ROLLBACK'); } catch {} throw error; } finally { db.close(); } +} diff --git a/tests/contracts/mqtt-sink.ts b/tests/contracts/mqtt-sink.ts index 96d5b8cf5..a14b84a04 100644 --- a/tests/contracts/mqtt-sink.ts +++ b/tests/contracts/mqtt-sink.ts @@ -1,6 +1,7 @@ /** Minimal owned MQTT 3.1.1 sink: observes real PUBLISH bytes, controls PUBACK. */ import net from 'node:net'; -export async function startMqttSink() { +/** port 0 = ephemeral; pass a reserved port to bring a "down" broker up later. */ +export async function startMqttSink(port = 0) { const sockets=new Set(); const publications:{topic:string;payload:Buffer;qos:number}[]=[]; let acknowledge=true; @@ -37,7 +38,7 @@ export async function startMqttSink() { } }); }); - await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(0,'127.0.0.1',resolve);}); + await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(port,'127.0.0.1',resolve);}); return {port:(server.address() as net.AddressInfo).port,publications, acknowledge(value:boolean){acknowledge=value;}, disconnect(){for(const socket of sockets)socket.destroy();}, diff --git a/tests/contracts/observations.api.spec.ts b/tests/contracts/observations.api.spec.ts new file mode 100644 index 000000000..62248935a --- /dev/null +++ b/tests/contracts/observations.api.spec.ts @@ -0,0 +1,209 @@ +/** + * INT_01 P2 classification PUT against the real native server (L14): + * A12 (retry no-op, stale revision 409, owner overwrite 409, evidence + * mismatch 422), A7-annotate (another camera's observation is 404 or 403 and + * nothing is written) and the classification change event on a real MQTT + * sink with its stable message ID. Contract: + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 5.4, 10, 11 and 12. + */ +import {createHash, randomUUID} from 'node:crypto'; +import path from 'node:path'; +import {DatabaseSync} from 'node:sqlite'; +import {startMqttSink} from './mqtt-sink'; +import {test, expect, ok, admin, viewer, wire, valid, createUser, role, policy, token, cameraScope, database, type Server, type Identity, type Json} from './permissionCases'; + +const EVENT = 'io.lightnvr.detection.observation_changed.v1'; + +function uuidv5(namespace: string, name: string): string { + const hash = createHash('sha1').update(Buffer.from(namespace.replaceAll('-', ''), 'hex')).update(Buffer.from(name, 'utf8')).digest(); + hash[6] = (hash[6] & 0x0f) | 0x50; hash[8] = (hash[8] & 0x3f) | 0x80; + const hex = hash.subarray(0, 16).toString('hex'); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} +// Section 5.4 (also tests/fixtures/integrations/observations/message-ids.json). +const MESSAGE_ID_NAMESPACE = uuidv5('6ba7b811-9dad-11d1-80b4-00c04fd430c8', 'https://lightnvr.io/ns/observation-change/v1'); +const messageId = (source: string, observation: string, revision: number) => uuidv5(MESSAGE_ID_NAMESPACE, `${source}|${observation}|${revision}`); + +type Seeded = {observation: string; asset: string}; +/** A ready observation at revision 2, written straight into the isolated fixture DB. */ +function seedReady(server: Server, camera: 'A' | 'B', observedAtMs?: number): Seeded { + const now = Date.now(), observation = randomUUID(), asset = randomUUID(); + const observedAt = observedAtMs ?? now; + const uuid = server.cameras[camera].camera_uuid; + database(server, `INSERT INTO observation_assets (uuid, camera_uuid, state, origin, media_type, path, bytes, width, height, captured_at_ms, expires_at_ms, created_at_ms, updated_at_ms) + VALUES (?, ?, 'ready', 'inference_input', 'image/jpeg', ?, 10, 640, 360, ?, ?, ?, ?)`, + [asset, uuid, `observations/${uuid}/2026-11-08/${asset}.jpg`, now, now + 86400000, now, now]); + database(server, `INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index, observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, asset_uuid, revision) + VALUES (?, ?, ?, ?, 0, ?, ?, 'legacy-primary', 'object', 'bird', 0.87, ?, 2)`, + [observation, uuid, camera, randomUUID(), observedAt, now, asset]); + return {observation, asset}; +} +function rows(server: Server, sql: string, value: string): number { + const db = new DatabaseSync(path.join(server.directory, 'lightnvr.db')); + try { db.exec('PRAGMA busy_timeout=3000'); return Number((db.prepare(sql).get(value) as Json).n); } finally { db.close(); } +} +const annotations = (server: Server, observation: string) => rows(server, 'SELECT count(*) AS n FROM observation_classifications WHERE observation_uuid = ?', observation); +const revision = (server: Server, observation: string) => rows(server, 'SELECT revision AS n FROM object_observations WHERE uuid = ?', observation); + +const route = (seeded: Seeded, namespace: string, name = 'species') => `/api/observations/${seeded.observation}/classifications/${namespace}/${name}`; +function body(seeded: Seeded, extra: Json = {}): Json { + return {classifier: 'yawamf', model_version: 'convnext-v2-2026.10', label: 'Black-capped Chickadee', taxonomy_id: 'inat:144815', + confidence: 0.93, classified_at: '2026-11-08T14:03:15.002Z', input_asset_id: seeded.asset, input_revision: 2, kind: 'automated', + expected_revision: 0, ...extra}; +} +async function put(server: Server, path: string, payload: Json, identity: Identity = admin) { + const result = await wire(server, path, identity, 'PUT', payload); + valid(result.response.ok ? 'ObservationClassificationResult' : 'ObservationError', result.body); + return result; +} + +test('A12: retries are no-ops, stale and automated-over-owner writes conflict, mismatched evidence is 422', async ({server}) => { + const capabilities = await ok(server, '/api/integrations/capabilities'); + valid('IntegrationCapabilities', capabilities); + expect(capabilities.operations['observations.annotate']).toEqual({supported: true, authorized: true}); + const namespace = capabilities.principal.writer_namespace; + expect(namespace).toBe('admin'); + const cameraA = capabilities.cameras.find((camera: Json) => camera.camera_uuid === server.cameras.A.camera_uuid); + expect(cameraA.actions).toContain('observations.annotate'); + + const seeded = seedReady(server, 'A'); + const created = await put(server, route(seeded, namespace), body(seeded)); + expect(created.response.status).toBe(201); + expect(created.body).toMatchObject({changed: true, observation_revision: 3, annotation: {key: `${namespace}/species`, writer_namespace: namespace, annotation_revision: 1, kind: 'automated'}}); + + const retried = await put(server, route(seeded, namespace), body(seeded)); + expect(retried.response.status).toBe(200); + expect(retried.body).toEqual({...created.body, changed: false}); + expect(revision(server, seeded.observation)).toBe(3); + + const stale = await put(server, route(seeded, namespace), body(seeded, {confidence: 0.95, expected_revision: 0})); + expect(stale.response.status).toBe(409); + expect(stale.body).toMatchObject({code: 'revision_conflict', current: created.body.annotation}); + + const owner = await put(server, route(seeded, namespace), body(seeded, {label: 'Carolina Chickadee', confidence: 1, model_version: null, taxonomy_id: null, kind: 'owner_confirmed', input_revision: 3, expected_revision: 1})); + expect(owner.response.status).toBe(200); + expect(owner.body).toMatchObject({changed: true, observation_revision: 4, annotation: {annotation_revision: 2, kind: 'owner_confirmed', model_version: null}}); + + const locked = await put(server, route(seeded, namespace), body(seeded, {confidence: 0.95, input_revision: 4, expected_revision: 2})); + expect(locked.response.status).toBe(409); + expect(locked.body).toMatchObject({code: 'owner_decision_locked', current: owner.body.annotation}); + + const mismatch = await put(server, route(seeded, namespace, 'other'), body(seeded, {input_asset_id: randomUUID()})); + expect(mismatch.response.status).toBe(422); + expect(mismatch.body).toMatchObject({code: 'evidence_mismatch', observation_asset_id: seeded.asset, evidence_state: 'ready'}); + + const unknown = await put(server, route(seeded, namespace, 'other'), body(seeded, {surprise: true})); + expect(unknown.response.status).toBe(400); + expect(unknown.body).toMatchObject({code: 'invalid_request', field: 'surprise'}); + + // Reads separate the owner decision from automated results and keep the detector label. + const observation = await ok(server, `/api/observations/${seeded.observation}`); + valid('Observation', observation); + expect(observation.label).toBe('bird'); + expect(observation.revision).toBe(4); + expect(observation.classifications).toEqual({automated: [], owner_confirmed: [owner.body.annotation]}); + const page = await ok(server, `/api/observations?camera_uuid=${server.cameras.A.camera_uuid}`); + valid('ObservationPage', page); + expect(page.items.find((item: Json) => item.id === seeded.observation).classifications).toEqual(observation.classifications); + expect(annotations(server, seeded.observation)).toBe(1); +}); + +test('A7-annotate: another camera is 404, view without annotate is 403, and denials write nothing', async ({server}) => { + const onA = seedReady(server, 'A'), onB = seedReady(server, 'B'); + const annotator = await role(server, 'Contract annotator', ['live.view', 'observations.annotate']); + const yawamf = await createUser(server, 'yawamf'); + await policy(server, yawamf, [{role_uuid: annotator, scope: cameraScope(server.cameras.A.camera_uuid)}]); + const capabilities = await ok(server, '/api/integrations/capabilities', yawamf); + expect(capabilities.principal.writer_namespace).toBe('yawamf'); + expect(capabilities.cameras.map((camera: Json) => camera.camera_uuid)).toEqual([server.cameras.A.camera_uuid]); + + const hidden = await put(server, route(onB, 'yawamf'), body(onB), yawamf); + expect(hidden.response.status).toBe(404); + expect(hidden.body.code).toBe('observation_not_found'); + const missing = await put(server, `/api/observations/${randomUUID()}/classifications/yawamf/species`, body(onB), yawamf); + expect(missing.body).toEqual(hidden.body); + // Authorization runs before the namespace and body checks. + const hiddenBad = await wire(server, route(onB, 'someone-else'), yawamf, 'PUT', {nonsense: 1}); + expect(hiddenBad.response.status).toBe(404); + expect(hiddenBad.body).toEqual(hidden.body); + + // The fixture viewer holds live.view on A, not observations.annotate. + const forbidden = await put(server, route(onA, 'contract_viewer'), body(onA), viewer); + expect(forbidden.response.status).toBe(403); + expect(forbidden.body.code).toBe('forbidden'); + const otherNamespace = await put(server, route(onA, 'admin'), body(onA), yawamf); + expect(otherNamespace.response.status).toBe(403); + expect(otherNamespace.body.code).toBe('namespace_forbidden'); + expect(annotations(server, onA.observation)).toBe(0); + expect(annotations(server, onB.observation)).toBe(0); + expect(revision(server, onA.observation)).toBe(2); + expect(revision(server, onB.observation)).toBe(2); + + const created = await put(server, route(onA, 'yawamf'), body(onA), yawamf); + expect(created.response.status).toBe(201); + // Scoped tokens carry the action explicitly; a read-only token cannot annotate. + const writeToken = await token(server, yawamf, cameraScope(server.cameras.A.camera_uuid), ['live.view', 'observations.annotate']); + const viaToken = await put(server, route(onA, 'yawamf', 'species-token'), body(onA, {input_revision: 3}), writeToken); + expect(viaToken.response.status).toBe(201); + const readToken = await token(server, yawamf, cameraScope(server.cameras.A.camera_uuid), ['live.view']); + const denied = await put(server, route(onA, 'yawamf', 'read-only'), body(onA, {input_revision: 3}), readToken); + expect(denied.response.status).toBe(403); + expect(denied.body.code).toBe('forbidden'); + expect(annotations(server, onA.observation)).toBe(2); +}); + +test('L15: detection results link the same-camera observation and show the owner decision first', async ({server}) => { + const second = Math.floor(Date.now() / 1000) - 3600; + const onA = seedReady(server, 'A', second * 1000 + 250); + const onB = seedReady(server, 'B', second * 1000 + 250); // same second and label, other camera + const detection = 'INSERT INTO detections (stream_name, camera_uuid, timestamp, label, confidence, x, y, width, height) VALUES (?, ?, ?, ?, 0.9, 0.1, 0.1, 0.2, 0.2)'; + database(server, detection, ['A', server.cameras.A.camera_uuid, second, 'Bird']); + database(server, detection, ['A', server.cameras.A.camera_uuid, second + 5, 'person']); + const namespace = (await ok(server, '/api/integrations/capabilities')).principal.writer_namespace; + const results = () => ok(server, `/api/detection/results/A?start=${second - 10}&end=${second + 10}`, viewer); + + let listed = await results(); + valid('EvidenceDetections', listed); + let bird = listed.detections.find((d: Json) => d.label === 'Bird'); + expect(bird.observation).toEqual({id: onA.observation, owner_confirmed: false, classification: null}); + expect(listed.detections.find((d: Json) => d.label === 'person')).not.toHaveProperty('observation'); + + const automated = await put(server, route(onA, namespace, 'auto'), body(onA)); + expect(automated.response.status).toBe(201); + const owner = await put(server, route(onA, namespace, 'owner'), body(onA, {label: 'Carolina Chickadee', confidence: 1, kind: 'owner_confirmed', input_revision: 3})); + expect(owner.response.status).toBe(201); + + listed = await results(); + valid('EvidenceDetections', listed); + bird = listed.detections.find((d: Json) => d.label === 'Bird'); + // The detector's label and confidence are unchanged beside the owner decision. + expect(bird).toMatchObject({label: 'Bird', confidence: expect.closeTo(0.9, 5)}); + expect(bird.observation).toEqual({id: onA.observation, owner_confirmed: true, classification: owner.body.annotation}); + expect(JSON.stringify(listed)).not.toContain(onB.observation); +}); + +test('a stored classification reaches a real MQTT sink once, with its stable message ID', async ({server}) => { + test.skip(process.env.LIGHTNVR_CONTRACT_MQTT !== '1', 'Requires the explicitly MQTT-enabled contract build (enabled in CI).'); + const sink = await startMqttSink(); + try { + const destination = await ok(server, '/api/event-destinations', admin, 'POST', {name: 'Observation sink', broker: {host: '127.0.0.1', port: sink.port, client_id: 'observation-contract-sink', topic_template: 'fixture/{type}/{subject_id}', qos: 1, keepalive_seconds: 5}, tls: {mode: 'disabled'}}); + await ok(server, '/api/event-routes', admin, 'POST', {name: 'Observation route', destination: destination.key, event_types: [EVENT], camera_scope: {type: 'selector', selector: {version: 1, expression: {op: 'camera_uuid', values: [server.cameras.A.camera_uuid]}}}}); + const source = (await ok(server, '/api/integrations/capabilities')).source.id; + const seeded = seedReady(server, 'A'); + const changes = () => sink.publications.map(p => JSON.parse(p.payload.toString())).filter(e => e.type === EVENT && e.data?.observation_id === seeded.observation); + + const created = await put(server, route(seeded, 'admin'), body(seeded)); + expect(created.response.status).toBe(201); + await expect.poll(() => changes().length, {timeout: 20000}).toBeGreaterThan(0); + const [event] = changes(); + expect(event.id).toBe(messageId(source, seeded.observation, 3)); + expect(event.source).toBe(source); + expect(event.data).toMatchObject({revision: 3, change_cause: 'classification', evidence_state: 'ready', asset_id: seeded.asset, label: 'bird'}); + valid('ObservationChangeEvent', event.data); + + // An identical retry stages nothing, so no new message appears. + expect((await put(server, route(seeded, 'admin'), body(seeded))).response.status).toBe(200); + await new Promise(resolve => setTimeout(resolve, 2500)); + expect(new Set(changes().map(e => e.id))).toEqual(new Set([event.id])); + } finally { await sink.stop(); } +}); diff --git a/tests/contracts/observationsHistory.api.spec.ts b/tests/contracts/observationsHistory.api.spec.ts new file mode 100644 index 000000000..f08274ef4 --- /dev/null +++ b/tests/contracts/observationsHistory.api.spec.ts @@ -0,0 +1,283 @@ +/** + * INT_01 L11 observation history and snapshot contracts against the real + * native server: A7 (camera-scoped isolation on list, get and snapshot, and + * rename / delete / name reuse never transferring access), A8 (pending, + * expired, unavailable, unknown and expired-cursor are distinct outcomes) and + * A5 cursor pagination including pending -> ready recovery. Contract: + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 7, 8 and 12. + */ +import {randomUUID} from 'node:crypto'; +import {test, expect, ok, admin, viewer, wire, valid, createUser, role, policy, token, cameraScope, database, type Server, type Identity, type Json} from './permissionCases'; +import {seedRecordings, seedHistoricalNameReuse} from './fixtures/recordings/seed'; +import {seedObservation, completeEvidence, transition, encodeCursor, decodeCursor, meta, sql} from './fixtures/observations/seed'; + +async function snapshot(server: Server, id: string, identity: Identity, headers: Record = {}) { + const response = await server.request(`/api/observations/${id}/snapshot`, {principal: 'anonymous', headers: {...identity.headers, ...headers}}); + const bytes = Buffer.from(await response.arrayBuffer()); + const json = (response.headers.get('content-type') || '').startsWith('application/json') ? JSON.parse(bytes.toString('utf8')) : undefined; + if (json) valid('ObservationError', json); + return {status: response.status, headers: response.headers, bytes, json}; +} +async function page(server: Server, query: string, identity: Identity = admin): Promise { + const body = await ok(server, `/api/observations${query}`, identity); + valid('ObservationPage', body); + return body; +} +const ids = (body: Json) => body.items.map((item: Json) => item.id); +async function error(server: Server, route: string, identity: Identity = admin) { + const result = await wire(server, route, identity); + valid('ObservationError', result.body); + return {status: result.response.status, body: result.body}; +} +async function viewerOf(server: Server, name: string, cameraUuid: string, actions = ['live.view', 'snapshot.create']): Promise { + const grants = await role(server, `Observation ${name}`, actions); + const user = await createUser(server, name); + await policy(server, user, [{role_uuid: grants, scope: cameraScope(cameraUuid)}]); + return user; +} + +test('A7: list, get and snapshot are isolated per camera for policies and scoped tokens', async ({server}) => { + const A = server.cameras.A.camera_uuid, B = server.cameras.B.camera_uuid; + const onA = seedObservation(server, {camera: A, stream: 'A'}), onB = seedObservation(server, {camera: B, stream: 'B'}); + + const all = await page(server, ''); + expect(ids(all)).toEqual([onA.observation, onB.observation]); + + // The fixture viewer holds the built-in Viewer role (live.view + snapshot.create) on A only. + const viewed = await page(server, '', viewer); + expect(ids(viewed)).toEqual([onA.observation]); + expect(JSON.stringify(viewed)).not.toContain(B); + for (const query of [`?camera_uuid=${B}`, `?camera_uuid=${A},${B}`, `?camera_uuid=${randomUUID()}`]) { + const denied = await error(server, `/api/observations${query}`, viewer); + expect(denied.status).toBe(403); + expect(denied.body.code).toBe('forbidden'); + } + const unknown = await error(server, `/api/observations/${randomUUID()}`, viewer); + expect(unknown.status).toBe(404); + expect(unknown.body.code).toBe('observation_not_found'); + const hidden = await error(server, `/api/observations/${onB.observation}`, viewer); + expect(hidden).toEqual(unknown); + const hiddenSnapshot = await snapshot(server, onB.observation, viewer); + const unknownSnapshot = await snapshot(server, randomUUID(), viewer); + expect(hiddenSnapshot.status).toBe(404); + expect(hiddenSnapshot.json).toEqual(unknownSnapshot.json); + expect(hiddenSnapshot.json).toEqual(unknown.body); + + const own = await ok(server, `/api/observations/${onA.observation}`, viewer); + valid('Observation', own); + expect(own).toMatchObject({id: onA.observation, camera_uuid: A, revision: 2, evidence: {asset_id: onA.asset, state: 'ready'}}); + expect(JSON.stringify(own)).not.toContain(onA.relativePath); // the internal path is never serialized + expect(own.evidence.snapshot_url).toBe(`/api/observations/${onA.observation}/snapshot`); + const image = await snapshot(server, onA.observation, viewer); + expect(image.status).toBe(200); + expect(image.headers.get('content-type')).toBe('image/jpeg'); + expect(image.headers.get('cache-control')).toBe('private, max-age=300'); + expect(image.headers.get('etag')).toBe(`"${onA.asset}"`); + expect(image.bytes.equals(onA.bytes!)).toBe(true); + expect((await snapshot(server, onA.observation, viewer, {'If-None-Match': `"${onA.asset}"`})).status).toBe(304); + + // A read-only scoped token (live.view on A): metadata yes, image no (D4 403), B invisible. + const readA = {headers: {Authorization: `Bearer ${server.tokens.readA}`}}; + expect(ids(await page(server, '', readA))).toEqual([onA.observation]); + expect((await ok(server, `/api/observations/${onA.observation}`, readA)).id).toBe(onA.observation); + const noImage = await snapshot(server, onA.observation, readA); + expect(noImage.status).toBe(403); + expect(noImage.json.code).toBe('forbidden'); + expect(noImage.bytes.includes(onA.bytes!)).toBe(false); + expect((await snapshot(server, onB.observation, readA)).json).toEqual(unknown.body); + + // A YA-WAMF-style token minted by the administrator but scoped to B only. + const tokenB = await token(server, admin, cameraScope(B), ['live.view', 'snapshot.create']); + expect(ids(await page(server, '', tokenB))).toEqual([onB.observation]); + expect((await error(server, `/api/observations?camera_uuid=${A}`, tokenB)).status).toBe(403); + expect((await error(server, `/api/observations/${onA.observation}`, tokenB))).toEqual(unknown); + expect((await snapshot(server, onA.observation, tokenB)).json).toEqual(unknown.body); + expect((await snapshot(server, onB.observation, tokenB)).bytes.equals(onB.bytes!)).toBe(true); + const capabilities = await ok(server, '/api/integrations/capabilities', tokenB); + valid('IntegrationCapabilities', capabilities); + expect(capabilities.cameras.map((camera: Json) => camera.camera_uuid)).toEqual([B]); + + // snapshot.create without live.view is not enough to read metadata or list. + const imageOnly = await viewerOf(server, 'image_only', A, ['snapshot.create']); + expect(ids(await page(server, '', imageOnly))).toEqual([]); + expect((await error(server, `/api/observations/${onA.observation}`, imageOnly)).status).toBe(404); +}); + +test('A7: rename, delete and name reuse never transfer observation access', async ({server}) => { + const A = server.cameras.A.camera_uuid, B = server.cameras.B.camera_uuid; + // Historical name reuse (recordings fixture): stream name "A" once belonged to camera B. + const recordings = seedRecordings(server), historical = seedHistoricalNameReuse(server, recordings); + const reusedName = seedObservation(server, {camera: B, stream: 'A', recordingId: historical.hidden}); + const visible = seedObservation(server, {camera: A, stream: 'B', recordingId: historical.visible}); + const viewed = await page(server, '', viewer); + expect(ids(viewed)).toEqual([visible.observation]); + expect(viewed.items[0]).toMatchObject({camera_uuid: A, stream_name_at_capture: 'B', recording_reference: {recording_id: historical.visible}}); + expect((await error(server, `/api/observations/${reusedName.observation}`, viewer)).status).toBe(404); + expect((await snapshot(server, reusedName.observation, viewer)).status).toBe(404); + expect(JSON.stringify(viewed)).not.toContain(String(historical.hidden)); + + // Rename: a camera keeps its UUID, so its grant follows it and its history keeps the capture name. + const created = await ok(server, '/api/streams', admin, 'POST', {name: 'C', url: 'rtsp://127.0.0.1:9/contract-c', enabled: false, streaming_enabled: false, record: false}); + const C = created.camera_uuid as string; + const onC = seedObservation(server, {camera: C, stream: 'C'}); + const holderC = await viewerOf(server, 'holder_c', C); + // lightNVR has no rename API; the rename is applied to the stream row and picked up by a restart. + await server.kill('SIGTERM'); + database(server, "UPDATE streams SET name = 'C-renamed' WHERE camera_uuid = ?", [C]); + await server.relaunch(); + expect((await ok(server, '/api/streams/C-renamed/full')).stream.camera_uuid).toBe(C); + expect(ids(await page(server, '', holderC))).toEqual([onC.observation]); + expect((await ok(server, `/api/observations/${onC.observation}`, holderC)).stream_name_at_capture).toBe('C'); + // ...and a new camera that takes the freed name gets nothing of the old one's history. + const reused = await ok(server, '/api/streams', admin, 'POST', {name: 'C', url: 'rtsp://127.0.0.1:9/contract-c2', enabled: false, streaming_enabled: false, record: false}); + expect(reused.camera_uuid).not.toBe(C); + const holderNewC = await viewerOf(server, 'holder_new_c', reused.camera_uuid); + expect(ids(await page(server, '', holderNewC))).toEqual([]); + expect((await error(server, `/api/observations/${onC.observation}`, holderNewC)).status).toBe(404); + expect((await snapshot(server, onC.observation, holderNewC)).status).toBe(404); + expect((await error(server, `/api/observations?camera_uuid=${C}`, holderNewC)).status).toBe(403); + + // Delete + recreate under the same name: the deleted camera's history is gone for everyone + // (deleted capture camera => 404) and never attaches to the replacement. + const onB = seedObservation(server, {camera: B, stream: 'B'}); + const holderB = await viewerOf(server, 'holder_b', B); + expect(ids(await page(server, '', holderB))).toEqual([reusedName.observation, onB.observation]); + expect((await wire(server, '/api/streams/B?permanent=true', admin, 'DELETE')).response.status).toBe(202); + await expect.poll(async () => (await server.request('/api/streams/B')).status).toBe(404); + const newB = await ok(server, '/api/streams', admin, 'POST', {name: 'B', url: 'rtsp://127.0.0.1:9/contract-b2', enabled: false, streaming_enabled: false, record: false}); + expect(newB.camera_uuid).not.toBe(B); + const holderNewB = await viewerOf(server, 'holder_new_b', newB.camera_uuid); + for (const identity of [admin, holderB, holderNewB]) { + const visibleIds = ids(await page(server, '', identity)); + expect(visibleIds).not.toContain(onB.observation); + expect(visibleIds).not.toContain(reusedName.observation); + expect((await error(server, `/api/observations/${onB.observation}`, identity)).body.code).toBe('observation_not_found'); + expect((await snapshot(server, onB.observation, identity)).status).toBe(404); + } + // The rows are retained (no implicit purge), just unreachable. + expect(sql(server, 'SELECT count(*) AS n FROM object_observations WHERE camera_uuid = ?', [B])[0].n).toBe(2); + const onNewB = seedObservation(server, {camera: newB.camera_uuid, stream: 'B'}); + expect(ids(await page(server, '', holderNewB))).toEqual([onNewB.observation]); +}); + +test('A8: pending, expired, unavailable, unknown and expired-cursor outcomes are distinct', async ({server}) => { + const A = server.cameras.A.camera_uuid; + const pending = seedObservation(server, {camera: A, state: 'pending'}); + const expired = seedObservation(server, {camera: A, state: 'expired', reason: 'retention_expired'}); + const unavailable = seedObservation(server, {camera: A, state: 'unavailable', reason: 'queue_full'}); + const ready = seedObservation(server, {camera: A}); + + const outcomes: Record = {}; + const p = await snapshot(server, pending.observation, admin); + expect(p.status).toBe(202); + expect(p.json.code).toBe('evidence_pending'); + const retryAfter = Number(p.headers.get('retry-after')); + expect(Number.isInteger(retryAfter) && retryAfter >= 1 && retryAfter <= 30).toBe(true); + expect(p.json.retry_after_seconds).toBe(retryAfter); + outcomes.pending = {status: p.status, code: p.json.code}; + + const e = await snapshot(server, expired.observation, admin); + expect(e.status).toBe(410); + expect(e.json).toMatchObject({code: 'evidence_expired', reason: 'retention_expired'}); + expect(Date.parse(e.json.expired_at)).toBeGreaterThan(0); + outcomes.expired = {status: e.status, code: e.json.code}; + + const u = await snapshot(server, unavailable.observation, admin); + expect(u.status).toBe(404); + expect(u.json).toMatchObject({code: 'evidence_unavailable', reason: 'queue_full'}); + outcomes.unavailable = {status: u.status, code: u.json.code}; + + const n = await snapshot(server, randomUUID(), admin); + expect(n.status).toBe(404); + expect(n.json.code).toBe('observation_not_found'); + outcomes.unknown = {status: n.status, code: n.json.code}; + + // Expired cursor: history before seq 3 is pruned (L4 advances oldest_seq with the prune). + const before = meta(server); + const stale = encodeCursor(1, before.epoch); + database(server, 'UPDATE observation_meta SET oldest_seq = 3 WHERE id = 1'); + const c = await error(server, `/api/observations?cursor=${stale}`); + expect(c.status).toBe(410); + expect(c.body.code).toBe('cursor_expired'); + expect(decodeCursor(c.body.oldest_cursor)).toEqual({v: 1, seq: 2, epoch: before.epoch}); + expect(Date.parse(c.body.oldest_observed_at)).toBeGreaterThan(0); + outcomes.expired_cursor = {status: c.status, code: c.body.code}; + // Resuming from oldest_cursor works and never re-serves the pruned gap. + const resumed = await page(server, `?cursor=${c.body.oldest_cursor}`); + expect(resumed.items.length).toBeGreaterThan(0); + + const invalid = await error(server, '/api/observations?cursor=not-a-cursor'); + expect(invalid.status).toBe(400); + expect(invalid.body.code).toBe('invalid_cursor'); + outcomes.invalid_cursor = {status: invalid.status, code: invalid.body.code}; + + // Every outcome is distinguishable by (status, code); nothing collapses into a generic 404. + const keys = Object.values(outcomes).map(o => `${o.status}:${o.code}`); + expect(new Set(keys).size).toBe(keys.length); + // The observation itself stays readable in every evidence state, carrying the state and reason. + for (const [seeded, state, reason] of [[pending, 'pending', null], [expired, 'expired', 'retention_expired'], [unavailable, 'unavailable', 'queue_full'], [ready, 'ready', null]] as const) { + const body = await ok(server, `/api/observations/${seeded.observation}`); + valid('Observation', body); + expect(body.evidence).toMatchObject({state, reason}); + } + // Ready evidence is served only for its own asset, never a substitute. + expect((await snapshot(server, ready.observation, admin)).bytes.equals(ready.bytes!)).toBe(true); +}); + +test('A5: cursor pages are stable, deduplicated and recover pending -> ready after the cursor passed', async ({server}) => { + const A = server.cameras.A.camera_uuid, B = server.cameras.B.camera_uuid; + const seeded = [0, 1, 2, 3, 4].map(i => seedObservation(server, {camera: i === 2 ? B : A, state: 'pending', observedAt: Date.now() - 60000 + i})); + const order = seeded.map(s => s.observation); + + // Unfiltered admin walk with limit 2: every observation exactly once, in persistence order. + const seen: string[] = []; + let cursor = '', pages = 0, last: Json = {}; + do { + last = await page(server, `?limit=2${cursor ? `&cursor=${cursor}` : ''}`); + expect(last.items.length).toBeLessThanOrEqual(2); + seen.push(...ids(last)); + cursor = last.next_cursor; + pages++; + } while (last.has_more && pages < 10); + expect(seen).toEqual(order); + expect(last.items.every((item: Json) => item.evidence.state === 'pending')).toBe(true); + const tail = cursor; + expect(decodeCursor(tail).seq).toBe(meta(server).head); + + // Filtered walk (camera A only): B's change is scanned and skipped, so cursors still advance. + const filtered = await page(server, `?camera_uuid=${A}&limit=10`); + expect(ids(filtered)).toEqual(order.filter((_, i) => i !== 2)); + expect(filtered.next_cursor).toBe(tail); + expect(filtered.has_more).toBe(false); + + // Nothing new: an empty page that keeps the cursor. `head` starts at the same place. + const idle = await page(server, `?cursor=${tail}`); + expect(idle).toEqual({items: [], next_cursor: tail, has_more: false}); + expect(await page(server, '?cursor=head')).toEqual({items: [], next_cursor: tail, has_more: false}); + + // Evidence finishes after the consumer already read the created changes (A5 recovery). + const bytes = completeEvidence(server, seeded[1]); + transition(server, seeded[3], 'unavailable', 'encode_failed'); + completeEvidence(server, seeded[4]); + transition(server, seeded[4], 'expired', 'retention_expired'); // two changes, one item + const recovered = await page(server, `?cursor=${tail}&limit=10`); + expect(ids(recovered)).toEqual([seeded[1], seeded[3], seeded[4]].map(s => s.observation)); + expect(recovered.items.map((item: Json) => [item.revision, item.evidence.state, item.evidence.reason])).toEqual([[2, 'ready', null], [2, 'unavailable', 'encode_failed'], [3, 'expired', 'retention_expired']]); + expect(recovered.has_more).toBe(false); + const ready = await snapshot(server, seeded[1].observation, admin); + expect(ready.status).toBe(200); + expect(ready.bytes.equals(bytes)).toBe(true); + + // limit counts observations after dedup: limit 1 returns one observation even though + // the page would otherwise have to include its later changes. + const one = await page(server, `?cursor=${tail}&limit=1`); + expect(ids(one)).toEqual([seeded[1].observation]); + expect(one.has_more).toBe(true); + const rest = await page(server, `?cursor=${one.next_cursor}&limit=10`); + expect(ids(rest)).toEqual([seeded[3], seeded[4]].map(s => s.observation)); + + // Re-reading from the beginning still yields every observation once (current state). + const replay = await page(server, '?limit=500'); + expect(ids(replay)).toEqual(order); + expect(replay.items.find((item: Json) => item.id === seeded[1].observation).evidence.state).toBe('ready'); +}); diff --git a/tests/contracts/observationsPipeline.api.spec.ts b/tests/contracts/observationsPipeline.api.spec.ts new file mode 100644 index 000000000..65a1db753 --- /dev/null +++ b/tests/contracts/observationsPipeline.api.spec.ts @@ -0,0 +1,112 @@ +/** + * INT_01 L11 end-to-end in the native binary, with no SQL seeding: an owned + * HTTP MPEG-TS source feeds a detection stream whose HTTP API detector is an + * owned loopback server. The real unified detection thread submits the exact + * JPEG it sent for inference (L7) to the real evidence worker (L3), which + * writes it, commits pending -> ready (L2) and wakes the relay (L5), which + * delivers to an owned MQTT sink. Covers A1 (both objects of one frame share + * one asset), A2 HTTP variant (the stored snapshot is byte-identical to the + * detector's input, and the detector was asked exactly once per frame) and + * the lightNVR side of A5 (cursor + one message ID per revision). + */ +import {execFileSync} from 'node:child_process'; +import {readFileSync} from 'node:fs'; +import {createServer, type IncomingMessage, type ServerResponse} from 'node:http'; +import path from 'node:path'; +import {startMqttSink} from './mqtt-sink'; +import {test, expect, ok, admin, valid, type Json} from './permissionCases'; +import {OBSERVATION_EVENT, messageId} from './fixtures/observations/seed'; + +test.skip(process.env.LIGHTNVR_CONTRACT_MQTT !== '1', 'Requires the explicitly MQTT-enabled contract build (enabled in CI).'); + +function jpegOf(body: Buffer): Buffer | null { + const start = body.indexOf(Buffer.from([0xff, 0xd8, 0xff])); + const end = body.lastIndexOf(Buffer.from([0xff, 0xd9])); + return start >= 0 && end > start ? Buffer.from(body.subarray(start, end + 2)) : null; +} +async function listen(handler: (req: IncomingMessage, res: ServerResponse) => void) { + const server = createServer(handler); + await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); + const address = server.address(); + if (!address || typeof address === 'string') throw new Error('owned fixture did not bind'); + return {server, port: address.port, async close() { server.closeAllConnections(); await new Promise(resolve => server.close(() => resolve())); }}; +} + +test('A1/A2/A5: a real detection frame becomes two observations sharing the exact inference JPEG, delivered once per revision', async ({server}) => { + test.setTimeout(150000); + const media = path.join(server.directory, 'feeder.ts'); + execFileSync('ffmpeg', ['-nostdin', '-hide_banner', '-loglevel', 'error', '-f', 'lavfi', '-i', 'testsrc=size=320x240:rate=10', '-t', '90', '-c:v', 'libx264', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', '-g', '10', '-f', 'mpegts', media], {timeout: 60000}); + const bytes = readFileSync(media); + const source = await listen((req, res) => { + res.writeHead(200, {'Content-Type': 'video/mp2t'}); + let offset = 0; + const timer = setInterval(() => { res.write(bytes.subarray(offset, offset + 188 * 40)); offset += 188 * 40; if (offset >= bytes.length) { clearInterval(timer); res.end(); } }, 20); + res.on('close', () => clearInterval(timer)); + }); + const inputs: Buffer[] = []; + const detector = await listen((req, res) => { + const chunks: Buffer[] = []; + req.on('data', chunk => chunks.push(chunk)); + req.on('end', () => { + const jpeg = jpegOf(Buffer.concat(chunks)); + if (jpeg) inputs.push(jpeg); + res.writeHead(200, {'Content-Type': 'application/json'}); + res.end(JSON.stringify({model: 'contract-birds', model_version: '1.0', detections: [ + {label: 'bird', confidence: 0.91, x_min: 0.1, y_min: 0.2, x_max: 0.3, y_max: 0.5}, + {label: 'bird', confidence: 0.82, x_min: 0.6, y_min: 0.2, x_max: 0.8, y_max: 0.4}, + ]})); + }); + }); + const sink = await startMqttSink(); + try { + const created = await ok(server, '/api/streams', admin, 'POST', {name: 'Feeder', url: `http://127.0.0.1:${source.port}/feeder.ts`, enabled: true, streaming_enabled: false, record: false, + detection_based_recording: true, detection_model: `http://127.0.0.1:${detector.port}/detect`, detection_threshold: 0.5, detection_interval: 1, + observation_evidence_enabled: true, observation_labels: 'bird'}); + const camera = created.camera_uuid as string; + const destination = await ok(server, '/api/event-destinations', admin, 'POST', {name: 'Pipeline sink', broker: {host: '127.0.0.1', port: sink.port, client_id: 'pipeline-sink', topic_template: 'fixture/{type}/{subject_id}', qos: 1, keepalive_seconds: 5}, tls: {mode: 'disabled'}}); + await ok(server, '/api/event-routes', admin, 'POST', {name: 'Pipeline route', destination: destination.key, event_types: [OBSERVATION_EVENT], camera_scope: {type: 'selector', selector: {version: 1, expression: {op: 'camera_uuid', values: [camera]}}}}); + const src = (await ok(server, '/api/integrations/capabilities')).source.id as string; + + let ready: Json[] = []; + await expect.poll(async () => { + const page = await ok(server, `/api/observations?camera_uuid=${camera}&limit=500`); + valid('ObservationPage', page); + ready = page.items.filter((item: Json) => item.evidence.state === 'ready'); + return ready.length; + }, {timeout: 90000, intervals: [1000]}).toBeGreaterThanOrEqual(2); + + // A1: one frame -> one batch -> two observations sharing one asset. + const first = ready[0]; + const siblings = ready.filter(item => item.batch_id === first.batch_id); + expect(siblings.map(item => item.object_index).sort()).toEqual([0, 1]); + expect(new Set(siblings.map(item => item.evidence.asset_id)).size).toBe(1); + for (const item of siblings) { + valid('Observation', item); + expect(item).toMatchObject({camera_uuid: camera, stream_name_at_capture: 'Feeder', label: 'bird', revision: 2, + detector: {engine_type: 'api', model: 'contract-birds', model_version: '1.0'}, evidence: {state: 'ready', origin: 'submitted_image', media_type: 'image/jpeg'}}); + } + // A2 (HTTP): the stored evidence is exactly an image the detector received; no later frame substituted. + const response = await server.request(`/api/observations/${first.id}/snapshot`); + expect(response.status).toBe(200); + const snapshot = Buffer.from(await response.arrayBuffer()); + expect(snapshot.length).toBe(first.evidence.bytes); + expect(inputs.filter(input => input.equals(snapshot)).length).toBe(1); + expect(first.evidence.width).toBe(320); + expect(first.evidence.height).toBe(240); + expect(first.box).toEqual({format: 'xywh_normalized', x: 0.1, y: 0.2, width: 0.2, height: 0.3}); + + // A5: each revision of each sibling reaches the broker under its deterministic ID. + const expected = siblings.flatMap(item => [messageId(src, item.id, 1), messageId(src, item.id, 2)]); + const received = () => sink.publications.map(p => JSON.parse(p.payload.toString())).filter(e => e.type === OBSERVATION_EVENT); + await expect.poll(() => expected.every(id => received().some(e => e.id === id)), {timeout: 30000}).toBe(true); + for (const item of siblings) { + const mine = received().filter(e => e.data.observation_id === item.id); + expect(new Set(mine.map(e => e.id))).toEqual(new Set([messageId(src, item.id, 1), messageId(src, item.id, 2)])); + expect(mine.find(e => e.data.revision === 1).data).toMatchObject({change_cause: 'created', evidence_state: 'pending', asset_id: item.evidence.asset_id}); + expect(mine.find(e => e.data.revision === 2).data).toMatchObject({change_cause: 'evidence', evidence_state: 'ready', asset_id: item.evidence.asset_id}); + } + } finally { + await ok(server, '/api/streams/Feeder?permanent=true', admin, 'DELETE').catch(() => undefined); + await sink.stop(); await detector.close(); await source.close(); + } +}); diff --git a/tests/contracts/observationsReliability.api.spec.ts b/tests/contracts/observationsReliability.api.spec.ts new file mode 100644 index 000000000..1fa1225f2 --- /dev/null +++ b/tests/contracts/observationsReliability.api.spec.ts @@ -0,0 +1,221 @@ +/** + * INT_01 L11 reliability against the real native binary (A5 lightNVR half, + * A6): process kills and restarts on the same database and storage tree, a + * DB restore, and a broker outage. Each case drives the shipped startup order + * (L4 reconcile -> L3 evidence -> L5 relay drain) and the real router, outbox + * and MQTT delivery worker against an owned MQTT sink. + * + * Kills inside the evidence worker and relay (after batch insert, after the + * tmp write, after rename before commit, after commit before relay, after the + * outbox step) need in-process stage hooks and are covered by + * tests/unit/test_observation_crash_boundaries.c. Here the binary is killed at + * the boundaries an external process can hit deterministically: committed but + * not yet relayed, and relayed but not yet delivered (no PUBACK). + * Contract: docs/integrations/OBSERVATIONS_CONTRACT.md sections 5.5, 7.5, 7.6, 14. + */ +import {copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync} from 'node:fs'; +import path from 'node:path'; +import {startMqttSink} from './mqtt-sink'; +import {reservePort} from './fixture-server.mjs'; +import {test, expect, ok, admin, wire, valid, type Server, type Json} from './permissionCases'; +import {OBSERVATION_EVENT, seedObservation, messageId, decodeCursor, encodeCursor, meta, sql} from './fixtures/observations/seed'; + +test.skip(process.env.LIGHTNVR_CONTRACT_MQTT !== '1', 'Requires the explicitly MQTT-enabled contract build (enabled in CI).'); + +async function route(server: Server, port: number, name: string) { + const destination = await ok(server, '/api/event-destinations', admin, 'POST', {name: `${name} sink`, broker: {host: '127.0.0.1', port, client_id: `${name}-sink`, topic_template: 'fixture/{type}/{subject_id}', qos: 1, keepalive_seconds: 5}, tls: {mode: 'disabled'}}); + valid('EventDestination', destination); + // Observation events need a route without a label predicate (label predicates only match detection.object.v1). + await ok(server, '/api/event-routes', admin, 'POST', {name: `${name} route`, destination: destination.key, event_types: [OBSERVATION_EVENT], camera_scope: {type: 'selector', selector: {version: 1, expression: {op: 'camera_uuid', values: [server.cameras.A.camera_uuid]}}}}); +} +const installation = async (server: Server) => (await ok(server, '/api/integrations/capabilities')).source.id as string; +const events = (sink: Awaited>) => sink.publications.map(p => JSON.parse(p.payload.toString())).filter(e => e.type === OBSERVATION_EVENT); +const outbox = (server: Server) => sql(server, 'SELECT event_id, state, attempt_count FROM event_outbox WHERE event_type = ? ORDER BY id', [OBSERVATION_EVENT]); +const publications = (server: Server, state: string) => Number(sql(server, 'SELECT count(*) AS n FROM observation_publications WHERE state = ?', [state])[0].n); +const serverLog = (server: Server) => readFileSync(path.join(server.directory, 'server.log'), 'utf8'); +async function walk(server: Server, cursor = ''): Promise<{items: Json[]; cursor: string}> { + const items: Json[] = []; + for (let pages = 0; pages < 100; pages++) { + const body = await ok(server, `/api/observations?limit=30${cursor ? `&cursor=${cursor}` : ''}`); + valid('ObservationPage', body); + items.push(...body.items); + cursor = body.next_cursor; + if (!body.has_more) break; + } + return {items, cursor}; +} + +test('A6: after SIGKILL the next start reconciles, then starts evidence, then drains committed changes to MQTT', async ({server}) => { + const sink = await startMqttSink(); + try { + await route(server, sink.port, 'restart-drain'); + const src = await installation(server), A = server.cameras.A.camera_uuid; + await server.kill('SIGKILL'); + + // Committed before the crash, never relayed (pending publications). + const ready = seedObservation(server, {camera: A, publication: 'pending', source: src}); + // Killed mid-write: a pending asset with only a .jpg.tmp, and one whose final file was renamed but never committed. + const midWrite = seedObservation(server, {camera: A, state: 'pending', publication: 'pending', source: src}); + const tmp = path.join(server.directory, 'recordings', `${midWrite.relativePath}.tmp`); + mkdirSync(path.dirname(tmp), {recursive: true}); + writeFileSync(tmp, 'partial jpeg'); + const renamed = seedObservation(server, {camera: A, state: 'pending', publication: 'pending', source: src}); + const uncommitted = path.join(server.directory, 'recordings', renamed.relativePath); + writeFileSync(uncommitted, Buffer.from([0xff, 0xd8, 0xff, 0xd9])); + + const offset = serverLog(server).length; + await server.relaunch(); + const log = serverLog(server).slice(offset); + const at = (needle: string) => { const i = log.indexOf(needle); expect(i, `startup log contains "${needle}"`).toBeGreaterThanOrEqual(0); return i; }; + const reconcile = at('Observation reconciliation: 1 temp and 1 stale files deleted, 2 pending interrupted'); + const evidence = at('Observation evidence initialized'); + const drained = at('Observation relay drained'); + const relay = at('Observation publication relay started'); + expect(reconcile).toBeLessThan(evidence); + expect(evidence).toBeLessThan(drained); + expect(drained).toBeLessThan(relay); + + // No ready asset points at an unfinished file; nothing unfinished is left on disk. + expect(existsSync(tmp)).toBe(false); + expect(existsSync(uncommitted)).toBe(false); + for (const seeded of [midWrite, renamed]) { + const body = await ok(server, `/api/observations/${seeded.observation}`); + expect(body).toMatchObject({revision: 2, evidence: {state: 'unavailable', reason: 'interrupted'}}); + const response = await server.request(`/api/observations/${seeded.observation}/snapshot`); + expect(response.status).toBe(404); + expect((await response.json()).reason).toBe('interrupted'); + } + const image = await server.request(`/api/observations/${ready.observation}/snapshot`); + expect(Buffer.from(await image.arrayBuffer()).equals(ready.bytes!)).toBe(true); + expect(sql(server, "SELECT count(*) AS n FROM observation_assets WHERE state = 'pending'")[0].n).toBe(0); + + // Every committed change (and the reconciliation's own) reaches the broker under its stable ID. + const expected = new Set([messageId(src, ready.observation, 1), messageId(src, ready.observation, 2), + messageId(src, midWrite.observation, 1), messageId(src, midWrite.observation, 2), + messageId(src, renamed.observation, 1), messageId(src, renamed.observation, 2)]); + await expect.poll(() => new Set(events(sink).map(e => e.id)).size, {timeout: 30000}).toBe(expected.size); + expect(new Set(events(sink).map(e => e.id))).toEqual(expected); + for (const event of events(sink)) valid('ObservationChangeEvent', event.data); + const interrupted = events(sink).find(e => e.id === messageId(src, midWrite.observation, 2)); + expect(interrupted.data).toMatchObject({revision: 2, change_cause: 'evidence', evidence_state: 'unavailable', evidence_reason: 'interrupted'}); + expect(publications(server, 'pending')).toBe(0); + } finally { await sink.stop(); } +}); + +test('A6: relayed but unacknowledged changes survive SIGKILL and are redelivered once acknowledged, with the same message IDs', async ({server}) => { + const sink = await startMqttSink(); + try { + await route(server, sink.port, 'redelivery'); + const src = await installation(server); + sink.acknowledge(false); + const seeded = seedObservation(server, {camera: server.cameras.A.camera_uuid, publication: 'pending', source: src}); + const ids = [messageId(src, seeded.observation, 1), messageId(src, seeded.observation, 2)]; + // The live relay (1 s poll) moves both revisions into the outbox; the broker sees them but never acknowledges. + await expect.poll(() => new Set(events(sink).map(e => e.id)).size, {timeout: 20000}).toBe(2); + expect(publications(server, 'relayed')).toBe(2); + expect(outbox(server).map(row => row.state)).not.toContain('delivered'); + + await server.kill('SIGKILL'); + const before = outbox(server); + expect(before.map(row => row.event_id)).toEqual(ids); + expect(before.every(row => row.state !== 'delivered')).toBe(true); + + sink.acknowledge(true); + sink.disconnect(); + await server.relaunch(); + await expect.poll(() => outbox(server).every(row => row.state === 'delivered'), {timeout: 45000}).toBe(true); + // At least once, never a new identity: each revision keeps one message ID across redelivery, + // and the restart's relay drain staged nothing new (no duplicate outbox rows). + const delivered = events(sink); + expect(new Set(delivered.map(e => e.id))).toEqual(new Set(ids)); + for (const id of ids) expect(delivered.filter(e => e.id === id).length).toBeGreaterThanOrEqual(2); + expect(outbox(server).map(row => row.event_id)).toEqual(ids); + expect(delivered.filter(e => e.id === ids[1]).every(e => e.data.revision === 2 && e.data.evidence_state === 'ready')).toBe(true); + expect(new Set(delivered.filter(e => e.id === ids[1]).map(e => JSON.stringify(e.data))).size).toBe(1); + } finally { await sink.stop(); } +}); + +test('D9: after a DB restore, cursors from before the restore return 410 and recovery resumes from oldest_cursor', async ({server}) => { + const A = server.cameras.A.camera_uuid; + const kept = [seedObservation(server, {camera: A}), seedObservation(server, {camera: A})]; + const src = await installation(server); + const atBackup = (await walk(server)).cursor; + + await server.kill('SIGTERM'); + const db = path.join(server.directory, 'lightnvr.db'), backup = path.join(server.directory, 'backup.db'); + // A file-level backup of the stopped server: the main file plus any un-checkpointed WAL. + for (const suffix of ['', '-wal']) if (existsSync(`${db}${suffix}`)) copyFileSync(`${db}${suffix}`, `${backup}${suffix}`); + await server.relaunch(); + + // Newer history the restore will lose. The relay records the head in the sidecar as it relays. + const lost = [0, 1, 2].map(() => seedObservation(server, {camera: A, publication: 'pending', source: src})); + await expect.poll(() => publications(server, 'pending'), {timeout: 20000}).toBe(0); + const sidecar = path.join(server.directory, 'recordings', 'observations', '.cursor_head.json'); + await expect.poll(() => existsSync(sidecar) && JSON.parse(readFileSync(sidecar, 'utf8')).head_seq, {timeout: 20000}).toBe(meta(server).head); + const latest = await walk(server, atBackup); + expect(latest.items.map(item => item.id)).toEqual(lost.map(s => s.observation)); + const beforeRestore = latest.cursor, oldEpoch = meta(server).epoch; + + await server.kill('SIGTERM'); + for (const suffix of ['', '-wal', '-shm']) rmSync(`${db}${suffix}`, {force: true}); + for (const suffix of ['', '-wal']) if (existsSync(`${backup}${suffix}`)) copyFileSync(`${backup}${suffix}`, `${db}${suffix}`); + const offset = serverLog(server).length; + await server.relaunch(); + expect(serverLog(server).slice(offset)).toContain('epoch regenerated'); + + const restored = meta(server); + expect(restored.epoch).not.toBe(oldEpoch); + for (const cursor of [beforeRestore, atBackup]) { + const result = await wire(server, `/api/observations?cursor=${cursor}`); + valid('ObservationError', result.body); + expect(result.response.status).toBe(410); + expect(result.body.code).toBe('cursor_expired'); + expect(decodeCursor(result.body.oldest_cursor)).toEqual({v: 1, seq: restored.oldest - 1, epoch: restored.epoch}); + } + // The consumer marks recovery and resumes: exactly the restored history, nothing from the lost generation. + const expired = await wire(server, `/api/observations?cursor=${beforeRestore}`); + const resumed = await walk(server, expired.body.oldest_cursor); + expect(resumed.items.map(item => item.id)).toEqual(kept.map(s => s.observation)); + for (const seeded of lost) expect((await wire(server, `/api/observations/${seeded.observation}`)).response.status).toBe(404); + + // Residual window (section 7.6): a same-epoch cursor beyond the head (seq rewound without an + // epoch change, e.g. a restore newer than the last sidecar write) is also 410. + const ahead = await wire(server, `/api/observations?cursor=${encodeCursor(restored.head + 5, restored.epoch)}`); + expect(ahead.response.status).toBe(410); + expect(ahead.body.code).toBe('cursor_expired'); +}); + +test('A5: with the broker down, 100 observations stay recoverable through the outbox and the cursor', async ({server}) => { + test.setTimeout(180000); + const down = await reservePort(); + await down.release(); // nothing listens: the broker is down + await route(server, down.port, 'outage'); + const src = await installation(server), A = server.cameras.A.camera_uuid; + const seeded = Array.from({length: 100}, () => seedObservation(server, {camera: A, publication: 'pending', source: src})); + const expected = new Set(seeded.flatMap(s => [messageId(src, s.observation, 1), messageId(src, s.observation, 2)])); + + // The relay accepts every change into the durable outbox even though nothing can be delivered. + await expect.poll(() => publications(server, 'pending'), {timeout: 60000}).toBe(0); + expect(publications(server, 'relayed')).toBe(200); + expect(new Set(outbox(server).map(row => row.event_id))).toEqual(expected); + expect(outbox(server).some(row => row.state === 'delivered')).toBe(false); + + // The cursor recovers all 100 while the broker is down. + const history = await walk(server); + expect(history.items.map(item => item.id)).toEqual(seeded.map(s => s.observation)); + expect(history.items.every(item => item.revision === 2 && item.evidence.state === 'ready')).toBe(true); + + // A crash during the outage loses nothing either. + await server.kill('SIGKILL'); + await server.relaunch(); + expect(new Set(outbox(server).map(row => row.event_id))).toEqual(expected); + + // The broker comes back on its address: every change is delivered under its stable ID. + const sink = await startMqttSink(down.port); + try { + await expect.poll(() => new Set(events(sink).map(e => e.id)).size, {timeout: 120000, intervals: [1000]}).toBe(expected.size); + expect(new Set(events(sink).map(e => e.id))).toEqual(expected); + await expect.poll(() => outbox(server).every(row => row.state === 'delivered'), {timeout: 30000}).toBe(true); + expect(outbox(server).length).toBe(expected.size); + } finally { await sink.stop(); } +}); diff --git a/tests/contracts/tooling/observation-fixtures.test.mjs b/tests/contracts/tooling/observation-fixtures.test.mjs new file mode 100644 index 000000000..21d22efb0 --- /dev/null +++ b/tests/contracts/tooling/observation-fixtures.test.mjs @@ -0,0 +1,416 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {createHash} from 'node:crypto'; +import {existsSync} from 'node:fs'; +import {readFile, readdir} from 'node:fs/promises'; +import path from 'node:path'; +import {pathToFileURL} from 'node:url'; +import Ajv2020 from 'ajv/dist/2020.js'; +import addFormats from 'ajv-formats'; +import {root, schemaDocument} from '../../../scripts/contracts/generate.mjs'; + +// INT_01 C3: canonical observation fixtures shared by lightNVR and YA-WAMF. +// Every fixture is validated against the C2 schemas in docs/api/openapi.json, +// and the cross-fixture invariants of docs/integrations/OBSERVATIONS_CONTRACT.md +// (message IDs, cursors, envelope shape, box math) are recomputed here +// independently of the generator. +const fixtureDir = path.join(root, 'tests/fixtures/integrations/observations'); +const fixturePath = file => path.join(fixtureDir, file); +const readJson = async file => JSON.parse(await readFile(fixturePath(file), 'utf8')); + +const doc = JSON.parse(await readFile(path.join(root, 'docs/api/openapi.json'), 'utf8')); +const ajv = new Ajv2020({strict: true, allErrors: true, allowUnionTypes: true, validateFormats: true}); +addFormats(ajv); +ajv.addSchema(schemaDocument(doc)); +const schema = name => { + const validate = ajv.getSchema(`urn:lightnvr:contracts#/$defs/${name}`); + assert.ok(validate, `OpenAPI schema ${name} exists`); + return validate; +}; +const generated = await import(pathToFileURL(path.join(root, 'web/generated/api/validators.js'))); + +// Validate with the unmodified-Ajv oracle and, where exported, the generated +// web validator; both must agree. +function check(name, value, label) { + const validate = schema(name); + const ok = validate(value); + assert.ok(ok, `${label}: ${name}: ${ajv.errorsText(validate.errors)}`); + const projected = generated[`validate${name}`]; + if (projected) assert.equal(projected(value), true, `${label}: generated validate${name}`); +} + +function uuidv5(namespace, name) { + const hash = createHash('sha1').update(Buffer.from(namespace.replaceAll('-', ''), 'hex')) + .update(Buffer.from(name, 'utf8')).digest(); + hash[6] = (hash[6] & 0x0f) | 0x50; + hash[8] = (hash[8] & 0x3f) | 0x80; + const hex = hash.subarray(0, 16).toString('hex'); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} +const NAMESPACE_URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'; +const decodeCursor = cursor => JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')); +const encodeCursor = (seq, epoch) => Buffer.from(JSON.stringify({v: 1, seq, epoch})).toString('base64url'); + +async function listFiles(dir, prefix = '') { + const out = []; + for (const entry of await readdir(dir, {withFileTypes: true})) { + const rel = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) out.push(...await listFiles(path.join(dir, entry.name), rel)); + else out.push(rel); + } + return out.sort(); +} + +const manifest = existsSync(fixturePath('manifest.json')) ? await readJson('manifest.json') : null; +const entries = kind => manifest.fixtures.filter(entry => entry.kind === kind); +const requireManifest = () => assert.ok(manifest, 'tests/fixtures/integrations/observations/manifest.json exists'); + +test('the manifest lists every fixture file and the required inventory', async () => { + requireManifest(); + const listed = manifest.fixtures.map(entry => entry.file).sort(); + const onDisk = (await listFiles(fixtureDir)).filter(file => !['manifest.json', 'README.md'].includes(file)); + assert.deepEqual(onDisk, listed, 'manifest matches the fixture directory'); + assert.equal(new Set(listed).size, listed.length, 'no duplicate manifest entries'); + + const observations = await Promise.all(entries('observation').map(e => readJson(e.file))); + for (const state of ['pending', 'ready', 'unavailable', 'expired']) { + assert.ok(observations.some(o => o.evidence.state === state && o.label === 'bird'), `single-bird ${state}`); + } + const byAsset = Object.groupBy(observations.filter(o => o.revision === 2 && o.evidence.state === 'ready'), + o => o.evidence.asset_id); + assert.ok(Object.values(byAsset).some(group => new Set(group.map(o => o.id)).size >= 2), 'two birds share one asset'); + assert.ok(observations.some(o => o.evidence.transform?.mode === 'letterbox' && o.detector.engine_type === 'api' + && o.evidence.origin === 'submitted_image'), 'letterboxed HTTP transform'); + + const events = await Promise.all(entries('event').map(e => readJson(e.file))); + for (const cause of ['created', 'evidence', 'classification']) { + assert.ok(events.some(e => e.data.change_cause === cause), `change event ${cause}`); + } + const pages = await Promise.all(entries('page').map(e => readJson(e.file))); + assert.ok(pages.some(p => p.has_more) && pages.some(p => !p.has_more && p.items.length), 'paginated pages'); + + const errors = await Promise.all(entries('error').map(async e => ({...e, body: await readJson(e.file)}))); + for (const [status, code] of [[410, 'cursor_expired'], [410, 'evidence_expired'], [202, 'evidence_pending']]) { + assert.ok(errors.some(e => e.status === status && e.body.code === code), `${status} ${code} body`); + } + assert.ok(entries('capabilities').length >= 1, 'capability document'); + + const exchanges = await Promise.all(entries('classification_exchange').map(e => readJson(e.file))); + const outcomes = exchanges.map(x => `${x.response.status}:${x.response.body.code ?? (x.response.body.changed ? 'changed' : 'noop')}`); + for (const outcome of ['201:changed', '200:noop', '200:changed', '409:revision_conflict', + '409:owner_decision_locked', '422:evidence_mismatch']) { + assert.ok(outcomes.includes(outcome), `classification exchange ${outcome}`); + } + assert.ok(entries('jpeg').length >= 1 && entries('message_id_vectors').length === 1, 'JPEG and message-ID vectors'); +}); + +test('every JSON fixture validates against the OpenAPI schemas', async () => { + requireManifest(); + let validated = 0; + for (const entry of manifest.fixtures) { + if (entry.schema) { + check(entry.schema, await readJson(entry.file), entry.file); + validated++; + } + if (entry.kind === 'event') { + check('ObservationChangeEvent', (await readJson(entry.file)).data, entry.file); + validated++; + } + if (entry.kind === 'classification_exchange') { + const exchange = await readJson(entry.file); + assert.equal(exchange.request.method, 'PUT', entry.file); + check('ObservationClassificationWrite', exchange.request.body, `${entry.file} request`); + const status = exchange.response.status; + check([200, 201].includes(status) ? 'ObservationClassificationResult' : 'ObservationError', + exchange.response.body, `${entry.file} response`); + if (status >= 400) check('ApiError', exchange.response.body, `${entry.file} response`); + validated += 2; + } + if (entry.kind === 'error') check('ApiError', await readJson(entry.file), entry.file); + } + assert.ok(validated >= 30, `validated ${validated} fixtures`); +}); + +test('the validator rejects malformed fixtures', async () => { + requireManifest(); + const ready = await readJson(entries('observation').find(e => e.file.endsWith('single-ready.json')).file); + const pending = await readJson(entries('observation').find(e => e.file.endsWith('single-pending.json')).file); + const observation = schema('Observation'); + const rejects = (validate, value, why) => assert.equal(validate(value), false, why); + rejects(observation, {...ready, evidence: {...ready.evidence, reason: 'queue_full'}}, 'reason on a ready state'); + rejects(observation, {...pending, evidence: {...pending.evidence, snapshot_url: ready.evidence.snapshot_url}}, + 'snapshot_url on a pending state'); + const {snapshot_url: _, ...noUrl} = ready.evidence; + rejects(observation, {...ready, evidence: noUrl}, 'ready without snapshot_url'); + rejects(observation, {...ready, evidence: {...ready.evidence, state: 'expired', reason: null}}, 'expired without reason'); + rejects(observation, {...ready, label: 'x'.repeat(32)}, 'label over 31 bytes'); + rejects(observation, {...ready, object_index: 20}, 'object_index over MAX_DETECTIONS'); + assert.equal(generated.validateObservation({...ready, evidence: {...ready.evidence, reason: 'queue_full'}}), false, + 'generated validator rejects reason on a ready state'); + const exchange = await readJson(entries('classification_exchange')[0].file); + rejects(schema('ObservationClassificationWrite'), {...exchange.request.body, unexpected: true}, 'closed PUT body'); + const event = await readJson(entries('event').find(e => e.change_cause === 'evidence' && e.evidence_state === 'ready').file); + rejects(schema('ObservationChangeEvent'), {...event.data, evidence_reason: 'disabled'}, 'event reason on ready'); +}); + +test('change events are full MQTT envelopes with contract §5.4 message IDs', async () => { + requireManifest(); + const vectors = await readJson('message-ids.json'); + const derived = uuidv5(NAMESPACE_URL, vectors.namespace.name); + assert.equal(derived, '0bc01005-a431-522b-9124-a114f67d187a', 'frozen namespace'); + assert.equal(vectors.namespace.uuid, derived); + const contractVector = uuidv5(derived, 'urn:lightnvr:11111111-1111-4111-8111-111111111111|55555555-5555-4555-8555-555555555555|2'); + assert.equal(contractVector, '43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9', 'contract test vector'); + assert.ok(vectors.vectors.some(v => v.message_id === contractVector), 'vectors include the contract test vector'); + for (const v of vectors.vectors) { + assert.equal(v.name, `${v.source}|${v.observation_id}|${v.revision}`); + assert.equal(uuidv5(derived, v.name), v.message_id, `vector ${v.name}`); + } + + const {constants} = manifest; + const keys = ['specversion', 'id', 'type', 'source', 'subject', 'time', 'datacontenttype', 'severity', 'sensitivity', 'data']; + const seen = new Set(); + for (const entry of entries('event')) { + const envelope = await readJson(entry.file); + assert.deepEqual(Object.keys(envelope), keys, `${entry.file}: envelope keys in event_envelope.c order`); + assert.equal(envelope.specversion, '1.0'); + assert.equal(envelope.type, 'io.lightnvr.detection.observation_changed.v1'); + assert.equal(envelope.source, constants.source); + assert.equal(envelope.datacontenttype, 'application/json'); + assert.equal(envelope.severity, 'info'); + assert.equal(envelope.sensitivity, 'operational'); + assert.equal(envelope.subject, `camera/${envelope.data.camera_uuid}`, `${entry.file}: subject`); + assert.match(envelope.time, /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$/, `${entry.file}: second-precision time`); + assert.equal(envelope.id, uuidv5(derived, `${envelope.source}|${envelope.data.observation_id}|${envelope.data.revision}`), + `${entry.file}: UUIDv5 message id`); + assert.ok(vectors.vectors.some(v => v.message_id === envelope.id), `${entry.file}: listed in message-ids.json`); + assert.equal(entry.topic, `${constants.topic_prefix}/v1/events/${envelope.type}/${envelope.data.camera_uuid}`); + assert.equal(entry.retain, false); + assert.equal(entry.change_cause, envelope.data.change_cause); + assert.equal(entry.evidence_state, envelope.data.evidence_state); + if (envelope.data.evidence_state === 'ready') { + assert.equal(envelope.data.snapshot_url, `/api/observations/${envelope.data.observation_id}/snapshot`); + } + // Privacy rules of event_envelope.c: no path-like or token keys. + for (const key of Object.keys(envelope.data)) assert.doesNotMatch(key, /(^path$|_path|token)/, key); + assert.ok(Buffer.byteLength(JSON.stringify(envelope)) < 96 * 1024); + const identity = `${envelope.source}|${envelope.id}`; + assert.ok(!seen.has(identity), `${entry.file}: unique source + id`); + seen.add(identity); + // The change event agrees with the observation fixture at that revision. + if (entry.observation) { + const observation = await readJson(entry.observation); + assert.equal(observation.id, envelope.data.observation_id); + assert.equal(observation.revision, envelope.data.revision); + assert.equal(observation.evidence.state, envelope.data.evidence_state); + assert.equal(observation.evidence.reason, envelope.data.evidence_reason); + assert.equal(observation.evidence.asset_id, envelope.data.asset_id); + assert.equal(observation.observed_at, envelope.data.observed_at); + assert.equal(observation.label, envelope.data.label); + } + } +}); + +test('cursors, pages and recovery bodies follow the §7 algorithm', async () => { + requireManifest(); + const {epoch_uuid: epoch, oldest_seq: oldestSeq} = manifest.constants; + const decoded = cursor => { + assert.ok(cursor.length <= 256 && !cursor.includes('='), 'cursor is unpadded base64url <= 256 chars'); + const value = decodeCursor(cursor); + assert.deepEqual(Object.keys(value), ['v', 'seq', 'epoch']); + assert.equal(encodeCursor(value.seq, value.epoch), cursor, 'compact JSON round trip'); + return value; + }; + // Contract §7.4 example cursor. + assert.equal(encodeCursor(1042, '66666666-6666-4666-8666-666666666666'), + 'eyJ2IjoxLCJzZXEiOjEwNDIsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0'); + + const changes = manifest.changes; + for (const entry of entries('page')) { + const page = await readJson(entry.file); + const next = decoded(page.next_cursor); + assert.equal(next.epoch, epoch); + assert.equal(next.seq, entry.next_seq, `${entry.file}: next_cursor seq`); + const start = entry.request.query.cursor === undefined ? oldestSeq - 1 + : entry.request.query.cursor === 'head' ? entry.watermark : decoded(entry.request.query.cursor).seq; + // Recompute the page from the change log (§7.3 steps 3-6). + const limit = Number(entry.request.query.limit ?? 100); + const ids = []; + let last = start; + for (const change of changes.filter(c => c.seq > start && c.seq <= entry.watermark)) { + if (!ids.includes(change.observation_id)) { + if (ids.length === limit) break; + ids.push(change.observation_id); + } + last = change.seq; + } + if (entry.request.query.cursor === 'head') ids.length = 0; + assert.deepEqual(page.items.map(item => item.id), ids, `${entry.file}: items`); + assert.equal(next.seq, last, `${entry.file}: next_cursor is the last scanned change`); + assert.equal(page.has_more, changes.some(c => c.seq > last && c.seq <= entry.watermark), `${entry.file}: has_more`); + // Items hold current state at read time (the latest revision <= watermark). + for (const item of page.items) { + const latest = changes.filter(c => c.observation_id === item.id && c.seq <= entry.watermark).at(-1); + assert.equal(item.revision, latest.revision, `${entry.file}: ${item.id} current revision`); + } + } + // Page chaining: each page's request cursor is the previous page's next_cursor. + const chain = entries('page').filter(e => e.chain).sort((a, b) => a.chain - b.chain); + for (let i = 1; i < chain.length; i++) { + assert.equal(chain[i].request.query.cursor, (await readJson(chain[i - 1].file)).next_cursor, `chain ${i}`); + } + + const expired = await readJson(entries('error').find(e => e.file.endsWith('cursor-expired.json')).file); + assert.equal(decoded(expired.oldest_cursor).seq, oldestSeq - 1, 'oldest_cursor encodes oldest_seq - 1'); + assert.equal(decoded(expired.oldest_cursor).epoch, epoch, 'oldest_cursor is in the current epoch'); + const capabilities = await Promise.all(entries('capabilities').map(e => readJson(e.file))); + for (const doc of capabilities) assert.equal(doc.history.oldest_cursor, expired.oldest_cursor); + + for (const entry of entries('error')) { + const body = await readJson(entry.file); + assert.equal(body.code, entry.code, entry.file); + if (entry.status === 202) { + const retry = Number(entry.headers['Retry-After']); + assert.ok(Number.isInteger(retry) && retry >= 1 && retry <= 30); + assert.equal(body.retry_after_seconds, retry, 'Retry-After header matches the body'); + } + } +}); + +test('the evidence JPEG has the boxes its manifest and observations describe', async () => { + requireManifest(); + for (const entry of entries('jpeg')) { + const bytes = await readFile(fixturePath(entry.file)); + const media = await readJson(entry.manifest); + assert.equal(media.sha256, createHash('sha256').update(bytes).digest('hex'), `${entry.file}: sha256`); + assert.equal(media.bytes, bytes.length); + // Parse the SOF0 marker for dimensions without a decoder. + assert.equal(bytes.readUInt16BE(0), 0xffd8, 'SOI'); + let offset = 2, size = null; + while (offset < bytes.length) { + const marker = bytes.readUInt16BE(offset); + if (marker === 0xffc0) { size = {height: bytes.readUInt16BE(offset + 5), width: bytes.readUInt16BE(offset + 7)}; break; } + offset += 2 + bytes.readUInt16BE(offset + 2); + } + assert.deepEqual(size, {width: media.width, height: media.height}, 'baseline JPEG dimensions'); + + const {default: sharp} = await import('sharp'); + const {data, info} = await sharp(bytes).raw().toBuffer({resolveWithObject: true}); + const pixel = (x, y) => Array.from(data.subarray((y * info.width + x) * info.channels, (y * info.width + x) * info.channels + 3)); + const near = (actual, expected, label) => assert.ok(actual.every((c, i) => Math.abs(c - expected[i]) <= 12), + `${label}: ${actual} vs ${expected}`); + near(pixel(2, 2), media.background_rgb, 'background'); + for (const box of media.boxes) { + const {x, y, width, height} = box.pixels; + assert.equal(box.normalized.x, Number((x / media.width).toFixed(6))); + assert.equal(box.normalized.y, Number((y / media.height).toFixed(6))); + assert.equal(box.normalized.width, Number((width / media.width).toFixed(6))); + assert.equal(box.normalized.height, Number((height / media.height).toFixed(6))); + for (const [px, py] of [[x + 1, y + 1], [x + width - 2, y + height - 2], [x + (width >> 1), y + (height >> 1)]]) { + near(pixel(px, py), box.rgb, `${box.observation_file} inside`); + } + near(pixel(x - 2, y + (height >> 1)), media.background_rgb, `${box.observation_file} left of box`); + near(pixel(x + width + 1, y + (height >> 1)), media.background_rgb, `${box.observation_file} right of box`); + const observation = await readJson(box.observation_file); + assert.deepEqual({...observation.box}, {format: 'xywh_normalized', ...box.normalized}); + assert.equal(observation.evidence.asset_id, media.asset_id); + assert.equal(observation.evidence.width, media.width); + assert.equal(observation.evidence.height, media.height); + assert.equal(observation.evidence.bytes, bytes.length); + // Consumer crop math (§3.2): px = x * evidence.width, never re-applying transform. + assert.equal(Math.round(observation.box.x * observation.evidence.width), x); + assert.equal(Math.round(observation.box.height * observation.evidence.height), height); + } + } +}); + +test('the letterbox transform maps the inference box onto the stored box', async () => { + requireManifest(); + const entry = entries('observation').find(e => e.inference_box); + assert.ok(entry, 'letterbox fixture declares its inference-space box'); + const observation = await readJson(entry.file); + const t = observation.evidence.transform; + assert.equal(t.mode, 'letterbox'); + assert.equal(t.scale_x, t.scale_y, 'letterbox scale is uniform'); + assert.equal(t.inference_width, Math.round(observation.evidence.width * t.scale_x + 2 * t.pad_x)); + assert.equal(t.inference_height, Math.round(observation.evidence.height * t.scale_y + 2 * t.pad_y)); + const {u, v, width, height} = entry.inference_box; + const round6 = value => Number(value.toFixed(6)); + assert.deepEqual({...observation.box}, { + format: 'xywh_normalized', + x: round6((u - t.pad_x) / t.scale_x / observation.evidence.width), + y: round6((v - t.pad_y) / t.scale_y / observation.evidence.height), + width: round6(width / t.scale_x / observation.evidence.width), + height: round6(height / t.scale_y / observation.evidence.height), + }); +}); + +test('classification exchanges follow the §10.4 rules', async () => { + requireManifest(); + const capabilities = await Promise.all(entries('capabilities').map(e => readJson(e.file))); + for (const entry of entries('classification_exchange')) { + const {request, response, observation_file: observationFile} = await readJson(entry.file); + const match = request.path.match(/^\/api\/observations\/([0-9a-f-]{36})\/classifications\/([^/]+)\/([^/]+)$/); + assert.ok(match, `${entry.file}: two-segment PUT path`); + const [, id, namespace, name] = match; + const observation = await readJson(observationFile); + assert.equal(observation.id, id, `${entry.file}: observation`); + // The writer namespace is user-derived (D3) and advertised in capabilities. + assert.ok(capabilities.some(c => c.principal.writer_namespace === request.principal.writer_namespace)); + const body = response.body; + if (response.status === 200 || response.status === 201) { + assert.equal(namespace, request.principal.writer_namespace); + assert.equal(body.annotation.key, `${namespace}/${name}`); + assert.equal(body.annotation.writer_namespace, namespace); + for (const field of ['classifier', 'label', 'confidence', 'classified_at', 'input_asset_id', 'input_revision', 'kind']) { + assert.deepEqual(body.annotation[field], request.body[field], `${entry.file}: ${field}`); + } + assert.equal(body.annotation.input_asset_id, observation.evidence.asset_id); + if (response.status === 201) assert.equal(body.annotation.annotation_revision, 1); + assert.equal(body.observation_revision, observation.revision + (body.changed ? 1 : 0), + `${entry.file}: observation revision bump`); + } else if (body.code === 'revision_conflict' || body.code === 'owner_decision_locked') { + assert.equal(response.status, 409); + assert.ok(body.current, `${entry.file}: 409 carries current`); + if (body.code === 'owner_decision_locked') { + assert.equal(body.current.kind, 'owner_confirmed'); + assert.equal(request.body.kind, 'automated'); + } else { + assert.notEqual(request.body.expected_revision ?? 0, body.current.annotation_revision); + } + } else if (body.code === 'evidence_mismatch') { + assert.equal(response.status, 422); + assert.equal(body.observation_asset_id, observation.evidence.asset_id); + assert.equal(body.evidence_state, observation.evidence.state); + assert.ok(request.body.input_asset_id !== observation.evidence.asset_id + || !['ready', 'expired'].includes(observation.evidence.state)); + } else if (body.code === 'namespace_forbidden') { + assert.equal(response.status, 403); + assert.notEqual(namespace, request.principal.writer_namespace); + } + } +}); + +// Vendored copy in YA-WAMF (backend/tests/fixtures/lightnvr/). Sync with +// `node scripts/contracts/observation-fixtures.mjs --vendor `. +const yawamf = process.env.LIGHTNVR_YAWAMF_DIR ?? path.resolve(root, '../YetAnother-WhosAtMyFeeder'); +const vendored = path.join(yawamf, 'backend/tests/fixtures/lightnvr'); +test('the YA-WAMF vendored copy matches', {skip: !existsSync(vendored) && `no vendored copy at ${vendored}`}, async () => { + requireManifest(); + const files = [...manifest.fixtures.map(e => e.file), 'manifest.json', 'README.md'].sort(); + assert.deepEqual((await listFiles(vendored)).filter(f => f !== 'SOURCE.md'), files, 'vendored file list'); + for (const file of files) { + assert.ok((await readFile(path.join(vendored, file))).equals(await readFile(fixturePath(file))), `vendored ${file}`); + } + const source = await readFile(path.join(vendored, 'SOURCE.md'), 'utf8'); + assert.match(source, /lightNVR commit `[0-9a-f]{40}`/, 'SOURCE.md names the lightNVR commit'); +}); + +test('the generator reproduces the committed fixtures', async () => { + requireManifest(); + const {buildFixtures} = await import(pathToFileURL(path.join(root, 'scripts/contracts/observation-fixtures.mjs'))); + const built = await buildFixtures(); + for (const [file, content] of built) { + assert.equal(content, await readFile(fixturePath(file), 'utf8'), `${file} is up to date`); + } +}); diff --git a/tests/contracts/tooling/observation-perf-metrics.test.mjs b/tests/contracts/tooling/observation-perf-metrics.test.mjs new file mode 100644 index 000000000..e0f46917b --- /dev/null +++ b/tests/contracts/tooling/observation-perf-metrics.test.mjs @@ -0,0 +1,278 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { + percentile, summarize, parsePrometheus, sumMetric, metricByLabel, segmentIntervals, + countIntervalGaps, catalogGaps, resourceSummary, relativeDifferencePct, repeatability, + releaseGate, workloadFingerprint, histogram, histogramDelta, histogramUpperQuantile, + observationFeatureSummary, assetReasonSummary, evidenceGate, pooledComparison, +} from '../../performance/observations/metrics.mjs'; + +test('percentile interpolates linearly (type 7)', () => { + assert.equal(percentile([1, 2, 3, 4, 5], 50), 3); + assert.equal(percentile([1, 2, 3, 4], 50), 2.5); + assert.equal(percentile([10, 20], 95), 19.5); + assert.equal(percentile([7], 95), 7); + assert.equal(percentile([], 95), null); + assert.equal(percentile([3, 1, 2], 0), 1); + assert.equal(percentile([3, 1, 2], 100), 3); + assert.throws(() => percentile([1], 101)); +}); + +test('summarize ignores non-finite values and reports null for empty input', () => { + const s = summarize([1, 2, NaN, 3, Infinity, 4]); + assert.equal(s.count, 4); + assert.equal(s.min, 1); + assert.equal(s.max, 4); + assert.equal(s.mean, 2.5); + assert.equal(summarize([]).p95, null); +}); + +test('parsePrometheus reads labels, values and skips comments', () => { + const text = [ + '# HELP lightnvr_recording_gaps_total Recording gaps detected', + '# TYPE lightnvr_recording_gaps_total counter', + 'lightnvr_recording_gaps_total{stream="Load0"} 0', + 'lightnvr_recording_gaps_total{stream="Load1"} 2', + 'lightnvr_instance_memory_rss_bytes 123456', + 'lightnvr_stream_error_total{stream="a b",type="decode"} 3', + ].join('\n'); + const parsed = parsePrometheus(text); + assert.equal(sumMetric(parsed, 'lightnvr_recording_gaps_total'), 2); + assert.deepEqual(metricByLabel(parsed, 'lightnvr_recording_gaps_total'), {Load0: 0, Load1: 2}); + assert.equal(sumMetric(parsed, 'lightnvr_instance_memory_rss_bytes'), 123456); + assert.equal(sumMetric(parsed, 'lightnvr_stream_error_total', l => l.type === 'decode'), 3); + assert.equal(parsed.get('lightnvr_stream_error_total')[0].labels.stream, 'a b'); + assert.equal(sumMetric(parsed, 'missing'), null); +}); + +test('segmentIntervals orders creation times per stream', () => { + const {perStream, all} = segmentIntervals([ + {stream: 'A', tMs: 20000}, {stream: 'A', tMs: 0}, {stream: 'A', tMs: 10000}, + {stream: 'B', tMs: 5000}, {stream: 'B', tMs: 15500}, + ]); + assert.deepEqual(perStream, {A: [10000, 10000], B: [10500]}); + assert.equal(all.length, 3); + assert.equal(countIntervalGaps(all, 10000, 400), 1); + assert.equal(countIntervalGaps(all, 10000, 5000), 0); +}); + +test('catalogGaps finds per-stream discontinuities beyond the threshold', () => { + const rows = [ + {id: 1, stream: 'A', start_time: 100, end_time: 110}, + {id: 2, stream: 'A', start_time: 110, end_time: 120}, + {id: 3, stream: 'A', start_time: 130, end_time: 140}, + {id: 4, stream: 'B', start_time: 100, end_time: 110}, + {id: 5, stream: 'B', start_time: 114, end_time: 124}, + ]; + const result = catalogGaps(rows, 5); + assert.equal(result.total, 1); + assert.deepEqual(result.perStream, {A: 1, B: 0}); + assert.deepEqual(result.gaps[0], {stream: 'A', after_id: 2, gap_seconds: 10}); +}); + +test('resourceSummary computes CPU percent and write rate from deltas', () => { + const samples = [ + {tMs: 0, cpuTicks: 0, rssKiB: 100, peakRssKiB: 100, writeBytes: 0, threads: 10, fds: 20}, + {tMs: 1000, cpuTicks: 50, rssKiB: 200, peakRssKiB: 210, writeBytes: 1000, threads: 12, fds: 21}, + {tMs: 2000, cpuTicks: 150, rssKiB: 150, peakRssKiB: 210, writeBytes: 3000, threads: 11, fds: 25}, + ]; + const r = resourceSummary(samples, 100); + assert.equal(r.window_seconds, 2); + assert.equal(r.cpu_percent_one_core_avg, 75); + assert.equal(r.cpu_percent_one_core.max, 100); + assert.equal(r.rss_high_water_kib, 210); + assert.equal(r.disk_write_bytes, 3000); + assert.equal(r.disk_write_bytes_per_sec, 1500); + assert.equal(r.threads_max, 12); + assert.equal(r.fds_max, 25); + assert.equal(resourceSummary([samples[0]]), null); +}); + +test('relativeDifferencePct is symmetric', () => { + assert.equal(relativeDifferencePct(98, 102), 4); + assert.equal(relativeDifferencePct(102, 98), 4); + assert.equal(relativeDifferencePct(0, 0), 0); + assert.equal(relativeDifferencePct(null, 1), null); +}); + +const run = (seg, db, gaps = 0) => ({ + latency: {segment_interval_ms: {p95: seg}, db_probe_ms: {p95: db}}, + gaps: {metrics_recording_gaps_total: gaps, catalog_gaps_total: gaps, segment_interval_gaps: gaps}, +}); + +test('repeatability passes only when every headline p95 differs by less than the limit', () => { + assert.equal(repeatability(run(10000, 2.0), run(10050, 2.02)).pass, true); + const fail = repeatability(run(10000, 2.0), run(10050, 2.2)); + assert.equal(fail.pass, false); + assert.equal(fail.checks.find(c => c.key === 'latency.db_probe_ms.p95').pass, false); + assert.equal(repeatability(run(10000, null), run(10000, null)).pass, false); +}); + +test('releaseGate enforces +5% p95 and no new gaps', () => { + assert.equal(releaseGate(run(10000, 2.0), run(10400, 2.1)).pass, true); + assert.equal(releaseGate(run(10000, 2.0), run(10600, 2.0)).pass, false); + const gapped = releaseGate(run(10000, 2.0), run(10000, 2.0, 1)); + assert.equal(gapped.pass, false); + assert.ok(gapped.checks.filter(c => !c.pass).every(c => c.key.startsWith('gaps.'))); +}); + +test('workloadFingerprint ignores timing but not workload shape', () => { + const base = {workload: {streams: 4, width: 1280, height: 720, fps: 15, gop: 15, segment_seconds: 10, + duration_seconds: 300, detector: {model_sha256: 'x', interval_seconds: 1}, source_sha256: 's'}, + host: {cpu_model: 'cpu', logical_cores: 32, storage: {device: '/dev/x'}}, latency: {a: 1}}; + const other = structuredClone(base); other.latency = {a: 2}; + assert.equal(workloadFingerprint(base), workloadFingerprint(other)); + other.workload.streams = 8; + assert.notEqual(workloadFingerprint(base), workloadFingerprint(other)); +}); + +// ---------- X2: evidence-enabled saturation gate ---------- + +const featureText = ({accepted = 0, queueFull = 0, untracked = 0, ready = 0, writeFailed = 0, hwItems = 0, hwBytes = 0, + camHw = 0, backlog = 0, buckets = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], sum = 0, max = 0, allocated = 0} = {}) => { + const les = ['0.000001', '0.000005', '0.00001', '0.00005', '0.0001', '0.00025', '0.0005', '0.001', '0.01', '+Inf']; + let cumulative = 0; + return [ + `lightnvr_observation_accepted_total ${accepted}`, + `lightnvr_observation_dropped_total{reason="queue_full"} ${queueFull}`, + 'lightnvr_observation_dropped_total{reason="budget_exhausted"} 0', + 'lightnvr_observation_dropped_total{reason="disabled"} 0', + `lightnvr_observation_dropped_total{reason="untracked"} ${untracked}`, + `lightnvr_observation_evidence_ready_total ${ready}`, + 'lightnvr_observation_evidence_failures_total{reason="encode_failed"} 0', + `lightnvr_observation_evidence_failures_total{reason="write_failed"} ${writeFailed}`, + 'lightnvr_observation_evidence_failures_total{reason="db_error"} 0', + 'lightnvr_observation_evidence_failures_total{reason="interrupted"} 0', + 'lightnvr_observation_evidence_bytes_written_total 1000', + `lightnvr_observation_evidence_backlog_items ${backlog}`, + `lightnvr_observation_evidence_backlog_bytes ${backlog * 100}`, + 'lightnvr_observation_evidence_drop_ring_depth 0', + `lightnvr_observation_evidence_high_water_items ${hwItems}`, + `lightnvr_observation_evidence_high_water_bytes ${hwBytes}`, + `lightnvr_observation_evidence_camera_high_water_items ${camHw}`, + 'lightnvr_observation_evidence_limit_items{scope="global"} 64', + 'lightnvr_observation_evidence_limit_items{scope="camera"} 8', + 'lightnvr_observation_evidence_limit_bytes{scope="global"} 4194304', + 'lightnvr_observation_evidence_limit_bytes{scope="camera"} 4194304', + `lightnvr_observation_evidence_discards_total{mode="deferred"} ${queueFull}`, + 'lightnvr_observation_evidence_discards_total{mode="inline"} 0', + 'lightnvr_observation_evidence_discard_pending_bytes 0', + 'lightnvr_observation_evidence_limit_bytes{scope="discard"} 33554432', + `lightnvr_observation_evidence_queue_allocated ${allocated}`, + `lightnvr_observation_evidence_worker_running ${allocated}`, + ...les.map((le, i) => `lightnvr_observation_submit_latency_seconds_bucket{le="${le}"} ${(cumulative += buckets[i])}`), + `lightnvr_observation_submit_latency_seconds_sum ${sum}`, + `lightnvr_observation_submit_latency_seconds_count ${cumulative}`, + `lightnvr_observation_submit_latency_max_seconds ${max}`, + ].join('\n'); +}; + +test('histogram helpers read cumulative buckets, subtract windows and bound quantiles', () => { + const start = histogram(parsePrometheus(featureText({buckets: [1, 1, 0, 0, 0, 0, 0, 0, 0, 0], sum: 0.000004})), + 'lightnvr_observation_submit_latency_seconds'); + const end = histogram(parsePrometheus(featureText({buckets: [5, 90, 4, 1, 0, 0, 0, 0, 0, 0], sum: 0.0004})), + 'lightnvr_observation_submit_latency_seconds'); + assert.equal(end.count, 100); + assert.equal(end.buckets.at(-1).le, Infinity); + const delta = histogramDelta(start, end); + assert.equal(delta.count, 98); + assert.equal(delta.buckets[0].count, 4); + assert.ok(Math.abs(delta.sum - 0.000396) < 1e-12); + assert.equal(histogramUpperQuantile(end, 0.5), 0.000005); + assert.equal(histogramUpperQuantile(end, 0.99), 0.00001); + assert.equal(histogramUpperQuantile(end, 1), 0.00005); + assert.equal(histogramUpperQuantile(null, 0.5), null); + assert.equal(histogram(parsePrometheus('x 1'), 'lightnvr_observation_submit_latency_seconds'), null); +}); + +test('observationFeatureSummary reports window deltas, run high-water marks and sample maxima', () => { + const start = parsePrometheus(featureText({accepted: 2, queueFull: 1, ready: 2, allocated: 1})); + const mid = parsePrometheus(featureText({accepted: 5, queueFull: 3, ready: 4, backlog: 3, allocated: 1})); + const end = parsePrometheus(featureText({accepted: 12, queueFull: 9, ready: 12, hwItems: 3, hwBytes: 300, camHw: 2, + backlog: 1, buckets: [0, 30, 0, 0, 0, 0, 0, 0, 0, 0], sum: 0.0001, max: 0.00004, allocated: 1})); + const s = observationFeatureSummary(start, end, [mid]); + assert.equal(s.accepted, 10); + assert.equal(s.ready, 10); + assert.deepEqual(s.dropped, {queue_full: 8, budget_exhausted: 0, disabled: 0, untracked: 0}); + assert.equal(s.high_water_items, 3); + assert.equal(s.high_water_bytes, 300); + assert.equal(s.camera_high_water_items, 2); + assert.equal(s.backlog_items_max, 3); + assert.deepEqual(s.limits, {items: 64, camera_items: 8, bytes: 4194304, camera_bytes: 4194304, discard_bytes: 33554432}); + assert.deepEqual(s.discards, {deferred: 8, inline: 0}); + assert.equal(s.discard_pending_bytes_max, 0); + assert.equal(s.submit_latency.count, 30); + assert.equal(s.submit_latency.max_seconds, 0.00004); + assert.equal(s.submit_latency.p99_upper_seconds, 0.000005); + assert.equal(s.queue_allocated_end, 1); + assert.equal(observationFeatureSummary(parsePrometheus('x 1'), parsePrometheus('x 1'), []), null); +}); + +test('assetReasonSummary flags unavailable assets without a bounded reason', () => { + const ok = assetReasonSummary([{state: 'ready', reason: null, n: 5}, {state: 'unavailable', reason: 'queue_full', n: 3}, + {state: 'expired', reason: 'retention_expired', n: 2}]); + assert.deepEqual(ok.by_state_reason, {ready: 5, 'unavailable:queue_full': 3, 'expired:retention_expired': 2}); + assert.equal(ok.unavailable_or_expired_without_reason, 0); + assert.deepEqual(ok.unknown_reasons, []); + const bad = assetReasonSummary([{state: 'unavailable', reason: '', n: 1}, {state: 'unavailable', reason: 'mystery', n: 1}]); + assert.equal(bad.unavailable_or_expired_without_reason, 1); + assert.deepEqual(bad.unknown_reasons, ['mystery']); +}); + +const enabledRun = (over = {}) => ({ + mode: 'enabled', + detector: {detections_in_window: 40}, + feature_db: {observations: 50, assets: assetReasonSummary([{state: 'ready', reason: null, n: 30}, + {state: 'unavailable', reason: 'queue_full', n: 9}])}, + feature_metrics: observationFeatureSummary(parsePrometheus(featureText({allocated: 1})), parsePrometheus(featureText({ + accepted: 30, queueFull: 9, ready: 30, hwItems: 1, hwBytes: 2800000, camHw: 1, + buckets: [0, 35, 4, 0, 0, 0, 0, 0, 0, 0], sum: 0.0001, max: 0.00002, allocated: 1})), []), + ...over, +}); +const disabledRun = () => ({mode: 'disabled', feature_metrics: observationFeatureSummary(parsePrometheus(featureText()), parsePrometheus(featureText()), [])}); + +test('evidenceGate passes a saturated run whose limits held and whose drops were recorded', () => { + const result = evidenceGate(disabledRun(), enabledRun(), {requireSaturation: true}); + assert.equal(result.pass, true, JSON.stringify(result.checks.filter(c => !c.pass))); +}); + +test('evidenceGate fails on each violated A11 property', () => { + const failing = (run, opts, key) => { + const result = evidenceGate(disabledRun(), run, opts); + assert.equal(result.pass, false, key); + assert.ok(result.checks.some(c => c.key === key && !c.pass), `${key}: ${JSON.stringify(result.checks)}`); + }; + const fm = over => observationFeatureSummary(parsePrometheus(featureText({allocated: 1})), + parsePrometheus(featureText({accepted: 30, queueFull: 9, ready: 30, hwItems: 1, hwBytes: 2800000, camHw: 1, + buckets: [0, 39, 0, 0, 0, 0, 0, 0, 0, 0], max: 0.00002, allocated: 1, ...over})), []); + failing(enabledRun({detector: {detections_in_window: 0}}), {}, 'evidence.detections'); + failing(enabledRun({feature_metrics: fm({accepted: 0, ready: 0})}), {}, 'evidence.ready'); + failing(enabledRun({feature_db: {observations: 0, assets: assetReasonSummary([])}}), {}, 'evidence.db_observations'); + failing(enabledRun({feature_metrics: fm({hwItems: 65})}), {}, 'limits.items'); + failing(enabledRun({feature_metrics: fm({hwBytes: 4194305})}), {}, 'limits.bytes'); + failing(enabledRun({feature_metrics: fm({camHw: 9})}), {}, 'limits.camera_items'); + failing(enabledRun({feature_metrics: fm({untracked: 1})}), {}, 'drops.untracked'); + failing(enabledRun({feature_metrics: fm({writeFailed: 1})}), {}, 'evidence.failures'); + failing(enabledRun({feature_metrics: fm({max: 0.0011})}), {}, 'submit.max_seconds'); + failing(enabledRun({feature_metrics: fm({buckets: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]})}), {}, 'submit.count'); + failing(enabledRun({feature_db: {observations: 50, assets: assetReasonSummary([{state: 'unavailable', reason: 'queue_full', n: 2}])}}), + {}, 'drops.reasons_recorded'); + failing(enabledRun({feature_db: {observations: 50, assets: assetReasonSummary([{state: 'unavailable', reason: 'queue_full', n: 9}, + {state: 'unavailable', reason: null, n: 1}])}}), {}, 'drops.reasons_recorded'); + failing(enabledRun({feature_metrics: fm({queueFull: 0})}), {requireSaturation: true}, 'saturation.queue_full_drops'); + failing(enabledRun(), {maxSubmitSeconds: 0.00001}, 'submit.max_seconds'); + const active = disabledRun(); + active.feature_metrics.accepted = 3; + assert.equal(evidenceGate(active, enabledRun()).checks.find(c => c.key === 'baseline.feature_inactive').pass, false); +}); + +test('pooledComparison pools raw samples per mode before taking percentiles', () => { + const runs = values => values.map(v => ({samples: {db_probe_ms: v}})); + const result = pooledComparison(runs([[1, 2, 3, 4], [5, 6, 7, 8]]), runs([[2, 3, 4, 5], [6, 7, 8, 9]]), 'db_probe_ms', 50); + assert.equal(result.baseline.count, 8); + assert.equal(result.baseline.p50, 4.5); + assert.equal(result.candidate.p50, 5.5); + assert.equal(result.increase_pct, 22.222); + assert.equal(result.pass, false); + assert.equal(pooledComparison(runs([[10, 10]]), runs([[10.4, 10.4]]), 'db_probe_ms', 95).pass, true); +}); diff --git a/tests/contracts/tooling/observations-contract.test.mjs b/tests/contracts/tooling/observations-contract.test.mjs new file mode 100644 index 000000000..5b9932aaa --- /dev/null +++ b/tests/contracts/tooling/observations-contract.test.mjs @@ -0,0 +1,190 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import {readFile} from 'node:fs/promises'; + +// INT_01 C2: the observations contract (docs/integrations/OBSERVATIONS_CONTRACT.md) +// is present in canonical OpenAPI with the frozen authorization mapping (§12.1), +// and every operation maps to a registered native route. +const root = new URL('../../../', import.meta.url); +const read = file => readFile(new URL(file, root), 'utf8'); + +const camera = action => ({version: 1, kind: 'camera', requirements: [{action, resource: 'raw'}]}); +const expected = [ + {id: 'getIntegrationCapabilities', method: 'get', path: '/api/integrations/capabilities', + route: '/api/integrations/capabilities', handler: 'handle_get_integration_capabilities', + auth: {version: 1, kind: 'authenticated'}}, + {id: 'listObservations', method: 'get', path: '/api/observations', + route: '/api/observations', handler: 'handle_get_observations', auth: camera('live.view')}, + {id: 'getObservation', method: 'get', path: '/api/observations/{id}', + route: '/api/observations/#', handler: 'handle_get_observation', auth: camera('live.view')}, + {id: 'getObservationSnapshot', method: 'get', path: '/api/observations/{id}/snapshot', + route: '/api/observations/#/snapshot', handler: 'handle_get_observation_snapshot', + auth: camera('snapshot.create')}, + // P2 (L14): authorized on the capture camera with observations.annotate. + {id: 'putObservationClassification', method: 'put', + path: '/api/observations/{id}/classifications/{namespace}/{name}', + route: '/api/observations/#/classifications/#/#', handler: 'handle_put_observation_classification', + auth: camera('observations.annotate')}, +]; +const schemas = ['Observation', 'ObservationEvidence', 'ObservationTransform', 'ObservationPage', + 'ObservationChangeEvent', 'ObservationClassification', 'IntegrationCapabilities', 'ObservationError']; + +test('observation operations carry the frozen authorization mapping', async () => { + const doc = JSON.parse(await read('docs/api/openapi.json')); + for (const op of expected) { + const operation = doc.paths[op.path]?.[op.method]; + assert.ok(operation, `missing ${op.method.toUpperCase()} ${op.path}`); + assert.equal(operation.operationId, op.id); + assert.doesNotMatch(operation.operationId, /Evidence/); + assert.deepEqual(operation['x-lightnvr-authorization'], op.auth, `authorization: ${op.id}`); + // L8 implemented the GET operations and L14 the PUT: none answers 501. + assert.equal(operation.responses['501'], undefined, `${op.id} no longer returns 501`); + assert.doesNotMatch(operation.description ?? '', /not yet implemented|until (task )?L14/, + `${op.id} carries no stub note`); + assert.ok(doc['x-lightnvr-coverage'].covered_operations.includes(op.id), `coverage: ${op.id}`); + } + for (const name of schemas) assert.ok(doc.components.schemas[name], `missing schema ${name}`); +}); + +test('observation operations map to registered native routes', async () => { + const [coverageText, source, stubs] = await Promise.all([read('docs/api/contracts/coverage.json'), + read('src/web/libuv_api_handlers.c'), read('src/web/api_handlers_observations.c')]); + const coverage = JSON.parse(coverageText); + for (const op of expected) { + const entry = coverage.operations.find(e => e.operation_id === op.id); + assert.ok(entry, `coverage entry: ${op.id}`); + assert.equal(entry.method, op.method.toUpperCase()); + assert.equal(entry.route_pattern, op.route); + assert.equal(entry.canonical_path, op.path); + assert.equal(entry.handler, op.handler); + assert.ok(source.includes(`"${op.route}", "${op.method.toUpperCase()}"`), `registration: ${op.id}`); + assert.match(stubs, new RegExp(`void ${op.handler}\\(`), `handler definition: ${op.handler}`); + } +}); + +test('the observation schemas accept the contract examples', async () => { + const validators = await import(new URL('web/generated/api/validators.js', root)); + const observation = { + id: '55555555-5555-4555-8555-555555555555', + source: 'urn:lightnvr:11111111-1111-4111-8111-111111111111', + camera_uuid: '22222222-2222-4222-8222-222222222222', + stream_name_at_capture: 'feeder-east', + batch_id: '77777777-7777-4777-8777-777777777777', + object_index: 0, + observed_at: '2026-11-08T14:03:12.481Z', + created_at: '2026-11-08T14:03:12.530Z', + detector: {engine_key: 'legacy-primary', engine_type: 'object', model: 'ssd_mobilenet_v2.sod', model_version: null}, + label: 'bird', confidence: 0.87, + box: {format: 'xywh_normalized', x: 0.412, y: 0.288, width: 0.12, height: 0.165}, + evidence: { + asset_id: '88888888-8888-4888-8888-888888888888', state: 'ready', reason: null, + origin: 'inference_input', media_type: 'image/jpeg', width: 640, height: 360, bytes: 48211, + captured_at: '2026-11-08T14:03:12.481Z', expires_at: '2026-11-15T14:03:12.481Z', + transform: {mode: 'none', source_width: 640, source_height: 360, inference_width: 640, + inference_height: 360, scale_x: 1.0, scale_y: 1.0, pad_x: 0, pad_y: 0}, + snapshot_url: '/api/observations/55555555-5555-4555-8555-555555555555/snapshot', + }, + track: null, recording_reference: {recording_id: 81234}, revision: 2, + classifications: {automated: [], owner_confirmed: []}, + }; + assert.equal(validators.validateObservation(observation), true); + assert.equal(validators.validateObservationPage({items: [observation], next_cursor: 'eyJ2IjoxfQ', has_more: false}), true); + assert.equal(validators.validateObservation({...observation, evidence: {...observation.evidence, state: 'done'}}), false); + assert.equal(validators.validateObservationChangeEvent({ + observation_id: observation.id, revision: 2, camera_uuid: observation.camera_uuid, label: 'bird', + observed_at: observation.observed_at, change_cause: 'evidence', evidence_state: 'ready', + evidence_reason: null, asset_id: observation.evidence.asset_id, snapshot_url: observation.evidence.snapshot_url, + }), true); + assert.equal(validators.validateObservationError({error: 'Not implemented', code: 'not_implemented'}), true); + assert.equal(validators.validateObservationError({ + error: 'Cursor is older than retained observation history', code: 'cursor_expired', + oldest_cursor: 'eyJ2IjoxfQ', oldest_observed_at: null, + }), true); + assert.equal(validators.validateApiError({error: 'Not implemented', code: 'not_implemented'}), true); + // P2 (L14): the closed PUT body and the 409 body with current. + const write = {classifier: 'yawamf', model_version: null, label: 'Black-capped Chickadee', + taxonomy_id: null, confidence: 0.93, classified_at: '2026-11-08T14:03:15.002Z', + input_asset_id: observation.evidence.asset_id, input_revision: 2, kind: 'automated'}; + assert.equal(validators.validateObservationClassificationWrite(write), true); + assert.equal(validators.validateObservationClassificationWrite({...write, extra: 1}), false); +}); + +// P2 (L15): detection results gain an optional, closed observation summary. +test('detection results accept the additive observation summary', async () => { + const validators = await import(new URL('web/generated/api/validators.js', root)); + const detection = {label: 'bird', confidence: 0.82, x: 0.1, y: 0.2, width: 0.3, height: 0.4, + timestamp: 1794000195, end_timestamp: 1794000195}; + const classification = {key: 'yawamf/species', writer_namespace: 'yawamf', classifier: 'yawamf', + model_version: null, label: 'Carolina Chickadee', taxonomy_id: null, confidence: 1, + classified_at: '2026-11-08T14:03:15.002Z', input_asset_id: '55555555-5555-4555-8555-555555555555', + input_revision: 3, kind: 'owner_confirmed', annotation_revision: 2, + created_at: '2026-11-08T14:03:15.100Z', updated_at: '2026-11-08T18:20:41.200Z'}; + const id = '44444444-4444-4444-8444-444444444444'; + const page = detections => ({detections, timestamp: '2026-11-08 14:03:15', stream_status: 'Running'}); + assert.equal(validators.validateEvidenceDetections(page([detection])), true, 'unlinked is unchanged'); + assert.equal(validators.validateEvidenceDetections(page([{...detection, + observation: {id, owner_confirmed: false, classification: null}}])), true); + assert.equal(validators.validateEvidenceDetections(page([{...detection, + observation: {id, owner_confirmed: true, classification}}])), true); + assert.equal(validators.validateEvidenceDetection({...detection, + observation: {id, owner_confirmed: true, classification, extra: 1}}), false); + assert.equal(validators.validateEvidenceDetection({...detection, + observation: {id, classification: null}}), false); + assert.equal(validators.validateEvidenceDetection({...detection, observation: null}), false); +}); + +// L10 follow-up (L12): /api/system/info documents the optional observation_pipeline +// member, mirroring observation_diagnostics_to_json() (src/core/observation_diagnostics.c). +test('system information documents the optional observation pipeline diagnostics', async () => { + const [docText, source] = await Promise.all([read('docs/api/openapi.json'), + read('src/core/observation_diagnostics.c')]); + const schemas = JSON.parse(docText).components.schemas; + const info = schemas.SystemInformation; + assert.deepEqual(info.properties.observation_pipeline, + {$ref: '#/components/schemas/ObservationPipelineDiagnostics'}); + assert.ok(!info.required.includes('observation_pipeline'), 'optional: older payloads omit it'); + const pipeline = schemas.ObservationPipelineDiagnostics; + assert.ok(pipeline, 'missing schema ObservationPipelineDiagnostics'); + + // Every key the C serializer emits is documented, and the schema names no other key. + const body = source.slice(source.indexOf('cJSON *observation_diagnostics_to_json(')); + const emitted = new Set([...body.matchAll(/(?:cJSON_Add\w+ToObject|add_u64)\(\s*\w+,\s*"([a-z_]+)"/g)].map(m => m[1])); + const documented = new Set(); + const walk = node => { + if (!node || typeof node !== 'object') return; + for (const [key, value] of Object.entries(node.properties ?? {})) { documented.add(key); walk(value); } + walk(node.items); + }; + walk(pipeline); + assert.ok(emitted.size > 30, `parsed ${emitted.size} serializer keys`); + assert.deepEqual([...documented].sort(), [...emitted].sort()); + + const validators = await import(new URL('web/generated/api/validators.js', root)); + const inactive = { + active: false, accepted: 0, + dropped: {queue_full: 0, budget_exhausted: 0, disabled: 0, untracked: 0}, + evidence: {queue_allocated: false, worker_running: false, backlog_items: 0, backlog_bytes: 0, + high_water_items: 0, high_water_bytes: 0, drop_ring_depth: 0, ready: 0, + unavailable_submitted: 0, bytes_written: 0, + failures: {encode_failed: 0, write_failed: 0, db_error: 0, disabled: 0, interrupted: 0}}, + history: {available: true, observations: 0, oldest_seq: 1, head_seq: 0, oldest_observed_at: null, + epoch_regenerations: 0}, + publications: {pending: 0, failed: 0, expired: 0, relay_lag_seconds: 0, relay_running: false, + relayed: 0, relayed_no_route: 0}, + budget_exhausted_cameras: 0, cameras: [], + }; + const active = {...inactive, active: true, accepted: 12, + history: {...inactive.history, observations: 12, head_seq: 24, oldest_observed_at: '2026-11-08T14:03:12.481Z'}, + publications: {...inactive.publications, relay_lag_seconds: 1.25, relay_running: true, relayed: 24}, + cameras: [{camera_uuid: '22222222-2222-4222-8222-222222222222', retained_bytes: 48211, + max_bytes: 2147483648, max_age_days: 7, budget_exhausted: false}]}; + assert.equal(validators.validateObservationPipelineDiagnostics(inactive), true); + assert.equal(validators.validateObservationPipelineDiagnostics(active), true); + assert.equal(validators.validateObservationPipelineDiagnostics({...inactive, active: 'no'}), false); + assert.equal(validators.validateObservationPipelineDiagnostics({...inactive, accepted: -1}), false); + const {evidence, ...withoutEvidence} = inactive; + assert.ok(evidence); + assert.equal(validators.validateObservationPipelineDiagnostics(withoutEvidence), false); + assert.equal(validators.validateObservationPipelineDiagnostics({...active, + cameras: [{...active.cameras[0], camera_uuid: 'not-a-uuid'}]}), false); +}); diff --git a/tests/fixtures/integrations/observations/README.md b/tests/fixtures/integrations/observations/README.md new file mode 100644 index 000000000..cb22fe5a3 --- /dev/null +++ b/tests/fixtures/integrations/observations/README.md @@ -0,0 +1,72 @@ +# Observation contract fixtures (INT_01 C3) + +Canonical examples of the [observations contract](../../../../docs/integrations/OBSERVATIONS_CONTRACT.md) +shared by lightNVR (L5, L7, L8, L11, L14) and YA-WAMF (W4, W5, W6, W10). Every +file is listed in `manifest.json` with its kind, the OpenAPI schema it +validates against, and the HTTP request, status and headers or the MQTT +topic it belongs to. + +Do not edit these files by hand. They are generated from a single scenario +by `scripts/contracts/observation-fixtures.mjs` and checked by +`tests/contracts/tooling/observation-fixtures.test.mjs`. + +## Scenario + +There is one installation (`urn:lightnvr:11111111-…`), one camera +(`22222222-…`, `feeder-east`) and DB epoch `66666666-…`. `manifest.changes` is +the complete change log in persistence (`seq`) order: + +| seq | Observation | Rev | Cause | Evidence | +| --- | --- | --- | --- | --- | +| 1001 | A `5555…` single bird | 1 | created | pending | +| 1002 | B `bbbb…` single bird | 1 | created | unavailable (`queue_full`) | +| 1003 | A | 2 | evidence | ready (contract §3.6 example; §5.4 test vector) | +| 1004–1007 | C0/C1 `cccc…00/01`, two birds sharing asset `9999…` | 1→2 | created, evidence | pending → ready | +| 1008–1009 | D `dddd…`, HTTP detector, letterboxed | 1→2 | created, evidence | pending → ready | +| 1010 | A | 3 | classification | ready (automated `yawamf/species`) | +| 1011 | E `e0e0…`, ONVIF, no box | 1 | created | unavailable (`no_matching_media`) | +| 1012 | A | 4 | classification | ready (owner correction) | +| 1013 | A | 5 | evidence | expired (`retention_expired`) | + +- `observations/`: the Observation at a given revision. +- `events/`: complete MQTT payloads (envelopes) for + `io.lightnvr.detection.observation_changed.v1`. The topic is in the + manifest. +- `message-ids.json`: the UUIDv5 namespace derivation and every envelope ID. +- `pages/`: three chained history pages (`limit=2`, watermark seq 1010) and a + `cursor=head` page. Items hold the current state at the watermark, so A + appears at revision 3 on pages 1 and 3. +- `errors/`: coded error bodies, including `cursor_expired`, + `evidence_expired` and the `202 evidence_pending` body with `Retry-After`. +- `capabilities/`: P1 (annotate not available) and P2 documents. +- `classifications/`: PUT request and response pairs. `observation_file` is + the observation state before the request. The writer namespace is + user-derived (D3). The example user is `yawamf`, but consumers MUST read it + from `capabilities.principal.writer_namespace`. +- `media/two-birds-160x80.jpg`: the evidence image of C0/C1, with box pixel + coordinates and colors in `media/two-birds-160x80.json`. + +Two conventions are fixture choices that the contract does not state outright: + +- **Pending evidence** keeps `origin`, `media_type`, `captured_at` and + `transform`, and leaves `width`, `height`, `bytes` and `expires_at` null + until the asset is ready. +- **Evidence that never had media** (`queue_full`, `no_matching_media`) has + every media field null. + +## Commands + +```sh +node scripts/contracts/observation-fixtures.mjs # regenerate JSON +node scripts/contracts/observation-fixtures.mjs --check # fail if stale +node scripts/contracts/observation-fixtures.mjs --jpeg # also re-encode the JPEG (needs sharp) +node --test tests/contracts/tooling/observation-fixtures.test.mjs + +# Vendor into YA-WAMF (backend/tests/fixtures/lightnvr/) after committing here: +node scripts/contracts/observation-fixtures.mjs --vendor ../YetAnother-WhosAtMyFeeder +node scripts/contracts/observation-fixtures.mjs --check-vendor ../YetAnother-WhosAtMyFeeder +``` + +When the YA-WAMF checkout is a sibling directory, or `LIGHTNVR_YAWAMF_DIR` +points at it, the tooling test also checks that the vendored copy is +byte-identical. diff --git a/tests/fixtures/integrations/observations/capabilities/p1.json b/tests/fixtures/integrations/observations/capabilities/p1.json new file mode 100644 index 000000000..c784f1d8c --- /dev/null +++ b/tests/fixtures/integrations/observations/capabilities/p1.json @@ -0,0 +1,80 @@ +{ + "contract_versions": { + "observations": 1, + "classifications": 1, + "observation_event": "io.lightnvr.detection.observation_changed.v1" + }, + "source": { + "id": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "kind": "lightnvr", + "installation_uuid": "11111111-1111-4111-8111-111111111111", + "product_version": "0.48.0" + }, + "principal": { + "writer_namespace": "yawamf" + }, + "operations": { + "observations.history": { + "supported": true, + "authorized": true + }, + "observations.snapshot": { + "supported": true, + "authorized": true + }, + "observations.annotate": { + "supported": false, + "authorized": false, + "reason": "not_available_in_this_version" + }, + "track_lifecycle": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + }, + "recorded_frame": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + }, + "event_clip": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + }, + "range_clip": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + } + }, + "limits": { + "history_page_default": 100, + "history_page_max": 500, + "history_filter_cameras_max": 10, + "history_filter_labels_max": 8, + "max_objects_per_batch": 20, + "label_max_bytes": 31, + "snapshot_retry_after_max_seconds": 30, + "annotations_per_observation_max": 8 + }, + "history": { + "retention_days": 30, + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjEwMDAsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "oldest_observed_at": "2026-11-08T14:03:12.481Z" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras": [ + { + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "evidence_enabled": true, + "actions": [ + "live.view", + "snapshot.create" + ] + } + ] +} diff --git a/tests/fixtures/integrations/observations/capabilities/p2.json b/tests/fixtures/integrations/observations/capabilities/p2.json new file mode 100644 index 000000000..77de7d2f1 --- /dev/null +++ b/tests/fixtures/integrations/observations/capabilities/p2.json @@ -0,0 +1,80 @@ +{ + "contract_versions": { + "observations": 1, + "classifications": 1, + "observation_event": "io.lightnvr.detection.observation_changed.v1" + }, + "source": { + "id": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "kind": "lightnvr", + "installation_uuid": "11111111-1111-4111-8111-111111111111", + "product_version": "0.49.0" + }, + "principal": { + "writer_namespace": "yawamf" + }, + "operations": { + "observations.history": { + "supported": true, + "authorized": true + }, + "observations.snapshot": { + "supported": true, + "authorized": true + }, + "observations.annotate": { + "supported": true, + "authorized": true + }, + "track_lifecycle": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + }, + "recorded_frame": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + }, + "event_clip": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + }, + "range_clip": { + "supported": false, + "authorized": false, + "reason": "not_implemented" + } + }, + "limits": { + "history_page_default": 100, + "history_page_max": 500, + "history_filter_cameras_max": 10, + "history_filter_labels_max": 8, + "max_objects_per_batch": 20, + "label_max_bytes": 31, + "snapshot_retry_after_max_seconds": 30, + "annotations_per_observation_max": 8 + }, + "history": { + "retention_days": 30, + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjEwMDAsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "oldest_observed_at": "2026-11-08T14:03:12.481Z" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras": [ + { + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "evidence_enabled": true, + "actions": [ + "live.view", + "snapshot.create", + "observations.annotate" + ] + } + ] +} diff --git a/tests/fixtures/integrations/observations/classifications/01-create-automated.json b/tests/fixtures/integrations/observations/classifications/01-create-automated.json new file mode 100644 index 000000000..0d8b34b68 --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/01-create-automated.json @@ -0,0 +1,47 @@ +{ + "description": "Create an automated annotation: 201, observation revision 2 -> 3.", + "observation_file": "observations/single-ready.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "expected_revision": 0 + } + }, + "response": { + "status": 201, + "body": { + "changed": true, + "observation_revision": 3, + "annotation": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T14:03:15.120Z" + } + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/02-retry-noop.json b/tests/fixtures/integrations/observations/classifications/02-retry-noop.json new file mode 100644 index 000000000..70d962dc6 --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/02-retry-noop.json @@ -0,0 +1,47 @@ +{ + "description": "Identical retry after a lost response: 200 changed=false regardless of expected_revision; nothing written, revision not bumped.", + "observation_file": "observations/single-ready-classified.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "expected_revision": 0 + } + }, + "response": { + "status": 200, + "body": { + "changed": false, + "observation_revision": 3, + "annotation": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T14:03:15.120Z" + } + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/03-revision-conflict.json b/tests/fixtures/integrations/observations/classifications/03-revision-conflict.json new file mode 100644 index 000000000..cd15f6b7f --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/03-revision-conflict.json @@ -0,0 +1,47 @@ +{ + "description": "Different automated result sent with a stale expected_revision (0, key exists at 1): 409 revision_conflict with current. Refetch and reconcile.", + "observation_file": "observations/single-ready-classified.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.11", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.95, + "classified_at": "2026-11-08T14:04:02.550Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "expected_revision": 0 + } + }, + "response": { + "status": 409, + "body": { + "error": "Annotation revision does not match", + "code": "revision_conflict", + "current": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T14:03:15.120Z" + } + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/04-owner-confirm.json b/tests/fixtures/integrations/observations/classifications/04-owner-confirm.json new file mode 100644 index 000000000..837fce319 --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/04-owner-confirm.json @@ -0,0 +1,47 @@ +{ + "description": "Owner correction replaces the automated annotation: 200, annotation_revision 1 -> 2, observation revision 3 -> 4.", + "observation_file": "observations/single-ready-classified.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": null, + "label": "Carolina Chickadee", + "taxonomy_id": null, + "confidence": 1, + "classified_at": "2026-11-08T18:20:41.100Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 3, + "kind": "owner_confirmed", + "expected_revision": 1 + } + }, + "response": { + "status": 200, + "body": { + "changed": true, + "observation_revision": 4, + "annotation": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": null, + "label": "Carolina Chickadee", + "taxonomy_id": null, + "confidence": 1, + "classified_at": "2026-11-08T18:20:41.100Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 3, + "kind": "owner_confirmed", + "annotation_revision": 2, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T18:20:41.300Z" + } + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/05-owner-decision-locked.json b/tests/fixtures/integrations/observations/classifications/05-owner-decision-locked.json new file mode 100644 index 000000000..941bdda7a --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/05-owner-decision-locked.json @@ -0,0 +1,47 @@ +{ + "description": "Automated re-run after an owner decision: 409 owner_decision_locked with current. Drop the automated write.", + "observation_file": "observations/single-owner-confirmed.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.11", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.95, + "classified_at": "2026-11-09T02:00:00.000Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 4, + "kind": "automated", + "expected_revision": 2 + } + }, + "response": { + "status": 409, + "body": { + "error": "An owner-confirmed annotation cannot be replaced by an automated one", + "code": "owner_decision_locked", + "current": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": null, + "label": "Carolina Chickadee", + "taxonomy_id": null, + "confidence": 1, + "classified_at": "2026-11-08T18:20:41.100Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 3, + "kind": "owner_confirmed", + "annotation_revision": 2, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T18:20:41.300Z" + } + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/06-evidence-mismatch-asset.json b/tests/fixtures/integrations/observations/classifications/06-evidence-mismatch-asset.json new file mode 100644 index 000000000..bc019a84b --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/06-evidence-mismatch-asset.json @@ -0,0 +1,33 @@ +{ + "description": "input_asset_id names another observation's asset: 422.", + "observation_file": "observations/single-ready.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "99999999-9999-4999-8999-999999999999", + "input_revision": 2, + "kind": "automated", + "expected_revision": 0 + } + }, + "response": { + "status": 422, + "body": { + "error": "Annotation input does not match the observation evidence", + "code": "evidence_mismatch", + "observation_asset_id": "88888888-8888-4888-8888-888888888888", + "evidence_state": "ready" + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/07-evidence-mismatch-pending.json b/tests/fixtures/integrations/observations/classifications/07-evidence-mismatch-pending.json new file mode 100644 index 000000000..018a59f6f --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/07-evidence-mismatch-pending.json @@ -0,0 +1,33 @@ +{ + "description": "Evidence still pending (not ready or expired): 422.", + "observation_file": "observations/single-pending.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/yawamf/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 1, + "kind": "automated", + "expected_revision": 0 + } + }, + "response": { + "status": 422, + "body": { + "error": "Annotation input does not match the observation evidence", + "code": "evidence_mismatch", + "observation_asset_id": "88888888-8888-4888-8888-888888888888", + "evidence_state": "pending" + } + } +} diff --git a/tests/fixtures/integrations/observations/classifications/08-namespace-forbidden.json b/tests/fixtures/integrations/observations/classifications/08-namespace-forbidden.json new file mode 100644 index 000000000..49bef1835 --- /dev/null +++ b/tests/fixtures/integrations/observations/classifications/08-namespace-forbidden.json @@ -0,0 +1,31 @@ +{ + "description": "Key namespace is not the principal's derived namespace: 403.", + "observation_file": "observations/single-ready.json", + "request": { + "method": "PUT", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/classifications/frigate-bridge/species", + "principal": { + "username": "yawamf", + "writer_namespace": "yawamf" + }, + "body": { + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "expected_revision": 0 + } + }, + "response": { + "status": 403, + "body": { + "error": "Writer namespace not permitted", + "code": "namespace_forbidden" + } + } +} diff --git a/tests/fixtures/integrations/observations/errors/cursor-expired.json b/tests/fixtures/integrations/observations/errors/cursor-expired.json new file mode 100644 index 000000000..7e7bcec81 --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/cursor-expired.json @@ -0,0 +1,6 @@ +{ + "error": "Cursor is older than retained observation history", + "code": "cursor_expired", + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjEwMDAsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "oldest_observed_at": "2026-11-08T14:03:12.481Z" +} diff --git a/tests/fixtures/integrations/observations/errors/evidence-expired.json b/tests/fixtures/integrations/observations/errors/evidence-expired.json new file mode 100644 index 000000000..891ceca9e --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/evidence-expired.json @@ -0,0 +1,6 @@ +{ + "error": "Evidence media has expired", + "code": "evidence_expired", + "reason": "retention_expired", + "expired_at": "2026-11-15T14:03:13.004Z" +} diff --git a/tests/fixtures/integrations/observations/errors/evidence-pending.json b/tests/fixtures/integrations/observations/errors/evidence-pending.json new file mode 100644 index 000000000..62cb10c3f --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/evidence-pending.json @@ -0,0 +1,5 @@ +{ + "error": "Evidence is not ready yet", + "code": "evidence_pending", + "retry_after_seconds": 2 +} diff --git a/tests/fixtures/integrations/observations/errors/evidence-unavailable.json b/tests/fixtures/integrations/observations/errors/evidence-unavailable.json new file mode 100644 index 000000000..ccf8adb96 --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/evidence-unavailable.json @@ -0,0 +1,5 @@ +{ + "error": "Evidence is unavailable", + "code": "evidence_unavailable", + "reason": "queue_full" +} diff --git a/tests/fixtures/integrations/observations/errors/invalid-cursor.json b/tests/fixtures/integrations/observations/errors/invalid-cursor.json new file mode 100644 index 000000000..a6616f0d5 --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/invalid-cursor.json @@ -0,0 +1,4 @@ +{ + "error": "Malformed cursor", + "code": "invalid_cursor" +} diff --git a/tests/fixtures/integrations/observations/errors/not-implemented.json b/tests/fixtures/integrations/observations/errors/not-implemented.json new file mode 100644 index 000000000..86d314c9f --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/not-implemented.json @@ -0,0 +1,4 @@ +{ + "error": "Not implemented", + "code": "not_implemented" +} diff --git a/tests/fixtures/integrations/observations/errors/observation-not-found.json b/tests/fixtures/integrations/observations/errors/observation-not-found.json new file mode 100644 index 000000000..3fe1a5c59 --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/observation-not-found.json @@ -0,0 +1,4 @@ +{ + "error": "Observation not found", + "code": "observation_not_found" +} diff --git a/tests/fixtures/integrations/observations/errors/service-unavailable.json b/tests/fixtures/integrations/observations/errors/service-unavailable.json new file mode 100644 index 000000000..4fa1ed514 --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/service-unavailable.json @@ -0,0 +1,4 @@ +{ + "error": "Service temporarily unavailable", + "code": "service_unavailable" +} diff --git a/tests/fixtures/integrations/observations/errors/snapshot-forbidden.json b/tests/fixtures/integrations/observations/errors/snapshot-forbidden.json new file mode 100644 index 000000000..2ddfb139d --- /dev/null +++ b/tests/fixtures/integrations/observations/errors/snapshot-forbidden.json @@ -0,0 +1,4 @@ +{ + "error": "Forbidden", + "code": "forbidden" +} diff --git a/tests/fixtures/integrations/observations/events/a-r1-created-pending.json b/tests/fixtures/integrations/observations/events/a-r1-created-pending.json new file mode 100644 index 000000000..919c21477 --- /dev/null +++ b/tests/fixtures/integrations/observations/events/a-r1-created-pending.json @@ -0,0 +1,22 @@ +{ + "specversion": "1.0", + "id": "085e44dc-9a26-5604-9e04-8e1425a0905b", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T14:03:12Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 1, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.481Z", + "change_cause": "created", + "evidence_state": "pending", + "evidence_reason": null, + "asset_id": "88888888-8888-4888-8888-888888888888" + } +} diff --git a/tests/fixtures/integrations/observations/events/a-r2-evidence-ready.json b/tests/fixtures/integrations/observations/events/a-r2-evidence-ready.json new file mode 100644 index 000000000..19c5fd7c8 --- /dev/null +++ b/tests/fixtures/integrations/observations/events/a-r2-evidence-ready.json @@ -0,0 +1,23 @@ +{ + "specversion": "1.0", + "id": "43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T14:03:12Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 2, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.481Z", + "change_cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "asset_id": "88888888-8888-4888-8888-888888888888", + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + } +} diff --git a/tests/fixtures/integrations/observations/events/a-r3-classification.json b/tests/fixtures/integrations/observations/events/a-r3-classification.json new file mode 100644 index 000000000..ae23596f3 --- /dev/null +++ b/tests/fixtures/integrations/observations/events/a-r3-classification.json @@ -0,0 +1,23 @@ +{ + "specversion": "1.0", + "id": "de82ee69-00f4-5e04-be22-834a86651682", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T14:03:15Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 3, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.481Z", + "change_cause": "classification", + "evidence_state": "ready", + "evidence_reason": null, + "asset_id": "88888888-8888-4888-8888-888888888888", + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + } +} diff --git a/tests/fixtures/integrations/observations/events/a-r5-evidence-expired.json b/tests/fixtures/integrations/observations/events/a-r5-evidence-expired.json new file mode 100644 index 000000000..2b6956766 --- /dev/null +++ b/tests/fixtures/integrations/observations/events/a-r5-evidence-expired.json @@ -0,0 +1,22 @@ +{ + "specversion": "1.0", + "id": "c9b83216-1196-546c-852f-fcfb272051eb", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-15T14:03:13Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 5, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.481Z", + "change_cause": "evidence", + "evidence_state": "expired", + "evidence_reason": "retention_expired", + "asset_id": "88888888-8888-4888-8888-888888888888" + } +} diff --git a/tests/fixtures/integrations/observations/events/b-r1-created-unavailable.json b/tests/fixtures/integrations/observations/events/b-r1-created-unavailable.json new file mode 100644 index 000000000..5d48de63c --- /dev/null +++ b/tests/fixtures/integrations/observations/events/b-r1-created-unavailable.json @@ -0,0 +1,22 @@ +{ + "specversion": "1.0", + "id": "d99668f3-4b89-5414-aeee-e4e65ee4c8a5", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T14:03:12Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "revision": 1, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:12.598Z", + "change_cause": "created", + "evidence_state": "unavailable", + "evidence_reason": "queue_full", + "asset_id": "b1b1b1b1-b1b1-4b1b-8b1b-b1b1b1b1b1b1" + } +} diff --git a/tests/fixtures/integrations/observations/events/c0-r2-evidence-ready.json b/tests/fixtures/integrations/observations/events/c0-r2-evidence-ready.json new file mode 100644 index 000000000..37a77dafd --- /dev/null +++ b/tests/fixtures/integrations/observations/events/c0-r2-evidence-ready.json @@ -0,0 +1,23 @@ +{ + "specversion": "1.0", + "id": "16ad08e3-9eb4-5226-82af-e7185fdcdabe", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T14:03:13Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "revision": 2, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:13.250Z", + "change_cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "asset_id": "99999999-9999-4999-8999-999999999999", + "snapshot_url": "/api/observations/cccccccc-cccc-4ccc-8ccc-cccccccccc00/snapshot" + } +} diff --git a/tests/fixtures/integrations/observations/events/c1-r2-evidence-ready.json b/tests/fixtures/integrations/observations/events/c1-r2-evidence-ready.json new file mode 100644 index 000000000..cdfb44a1c --- /dev/null +++ b/tests/fixtures/integrations/observations/events/c1-r2-evidence-ready.json @@ -0,0 +1,23 @@ +{ + "specversion": "1.0", + "id": "a6ebc805-5df5-5a19-9c14-43c9fd05ad26", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T14:03:13Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "revision": 2, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "bird", + "observed_at": "2026-11-08T14:03:13.250Z", + "change_cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "asset_id": "99999999-9999-4999-8999-999999999999", + "snapshot_url": "/api/observations/cccccccc-cccc-4ccc-8ccc-cccccccccc01/snapshot" + } +} diff --git a/tests/fixtures/integrations/observations/events/e-r1-created-no-matching-media.json b/tests/fixtures/integrations/observations/events/e-r1-created-no-matching-media.json new file mode 100644 index 000000000..d93788d2f --- /dev/null +++ b/tests/fixtures/integrations/observations/events/e-r1-created-no-matching-media.json @@ -0,0 +1,22 @@ +{ + "specversion": "1.0", + "id": "a977c786-3b49-5f9b-a6e4-dc078b44086a", + "type": "io.lightnvr.detection.observation_changed.v1", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "subject": "camera/22222222-2222-4222-8222-222222222222", + "time": "2026-11-08T15:30:02Z", + "datacontenttype": "application/json", + "severity": "info", + "sensitivity": "operational", + "data": { + "observation_id": "e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0", + "revision": 1, + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "label": "animal", + "observed_at": "2026-11-08T15:30:01.900Z", + "change_cause": "created", + "evidence_state": "unavailable", + "evidence_reason": "no_matching_media", + "asset_id": "e2e2e2e2-e2e2-4e2e-8e2e-e2e2e2e2e2e2" + } +} diff --git a/tests/fixtures/integrations/observations/manifest.json b/tests/fixtures/integrations/observations/manifest.json new file mode 100644 index 000000000..c240a7106 --- /dev/null +++ b/tests/fixtures/integrations/observations/manifest.json @@ -0,0 +1,604 @@ +{ + "description": "INT_01 canonical observation contract fixtures (task C3). Generated by scripts/contracts/observation-fixtures.mjs; validated by tests/contracts/tooling/observation-fixtures.test.mjs.", + "contract": "docs/integrations/OBSERVATIONS_CONTRACT.md", + "constants": { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "installation_uuid": "11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name": "feeder-east", + "epoch_uuid": "66666666-6666-4666-8666-666666666666", + "oldest_seq": 1001, + "topic_prefix": "lightnvr", + "event_type": "io.lightnvr.detection.observation_changed.v1", + "message_id_namespace": "0bc01005-a431-522b-9124-a114f67d187a", + "writer": { + "username": "yawamf", + "writer_namespace": "yawamf" + } + }, + "changes": [ + { + "seq": 1001, + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 1, + "cause": "created", + "evidence_state": "pending", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:12.530Z" + }, + { + "seq": 1002, + "observation_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "revision": 1, + "cause": "created", + "evidence_state": "unavailable", + "evidence_reason": "queue_full", + "created_at": "2026-11-08T14:03:12.611Z" + }, + { + "seq": 1003, + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 2, + "cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:12.712Z" + }, + { + "seq": 1004, + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "revision": 1, + "cause": "created", + "evidence_state": "pending", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:13.291Z" + }, + { + "seq": 1005, + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "revision": 1, + "cause": "created", + "evidence_state": "pending", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:13.291Z" + }, + { + "seq": 1006, + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "revision": 2, + "cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:13.402Z" + }, + { + "seq": 1007, + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "revision": 2, + "cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:13.402Z" + }, + { + "seq": 1008, + "observation_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "revision": 1, + "cause": "created", + "evidence_state": "pending", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:14.085Z" + }, + { + "seq": 1009, + "observation_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "revision": 2, + "cause": "evidence", + "evidence_state": "ready", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:14.140Z" + }, + { + "seq": 1010, + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 3, + "cause": "classification", + "evidence_state": "ready", + "evidence_reason": null, + "created_at": "2026-11-08T14:03:15.120Z" + }, + { + "seq": 1011, + "observation_id": "e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0", + "revision": 1, + "cause": "created", + "evidence_state": "unavailable", + "evidence_reason": "no_matching_media", + "created_at": "2026-11-08T15:30:02.018Z" + }, + { + "seq": 1012, + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 4, + "cause": "classification", + "evidence_state": "ready", + "evidence_reason": null, + "created_at": "2026-11-08T18:20:41.300Z" + }, + { + "seq": 1013, + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 5, + "cause": "evidence", + "evidence_state": "expired", + "evidence_reason": "retention_expired", + "created_at": "2026-11-15T14:03:13.004Z" + } + ], + "fixtures": [ + { + "file": "capabilities/p1.json", + "kind": "capabilities", + "schema": "IntegrationCapabilities", + "status": 200, + "request": { + "method": "GET", + "path": "/api/integrations/capabilities" + }, + "description": "P1 installation: annotate not available. writer_namespace is derived from the user (D3), here \"yawamf\"." + }, + { + "file": "capabilities/p2.json", + "kind": "capabilities", + "schema": "IntegrationCapabilities", + "status": 200, + "request": { + "method": "GET", + "path": "/api/integrations/capabilities" + }, + "description": "P2 installation: annotate supported and authorized on the camera." + }, + { + "file": "classifications/01-create-automated.json", + "kind": "classification_exchange", + "status": 201, + "code": null, + "description": "Create an automated annotation: 201, observation revision 2 -> 3." + }, + { + "file": "classifications/02-retry-noop.json", + "kind": "classification_exchange", + "status": 200, + "code": null, + "description": "Identical retry after a lost response: 200 changed=false regardless of expected_revision; nothing written, revision not bumped." + }, + { + "file": "classifications/03-revision-conflict.json", + "kind": "classification_exchange", + "status": 409, + "code": "revision_conflict", + "description": "Different automated result sent with a stale expected_revision (0, key exists at 1): 409 revision_conflict with current. Refetch and reconcile." + }, + { + "file": "classifications/04-owner-confirm.json", + "kind": "classification_exchange", + "status": 200, + "code": null, + "description": "Owner correction replaces the automated annotation: 200, annotation_revision 1 -> 2, observation revision 3 -> 4." + }, + { + "file": "classifications/05-owner-decision-locked.json", + "kind": "classification_exchange", + "status": 409, + "code": "owner_decision_locked", + "description": "Automated re-run after an owner decision: 409 owner_decision_locked with current. Drop the automated write." + }, + { + "file": "classifications/06-evidence-mismatch-asset.json", + "kind": "classification_exchange", + "status": 422, + "code": "evidence_mismatch", + "description": "input_asset_id names another observation's asset: 422." + }, + { + "file": "classifications/07-evidence-mismatch-pending.json", + "kind": "classification_exchange", + "status": 422, + "code": "evidence_mismatch", + "description": "Evidence still pending (not ready or expired): 422." + }, + { + "file": "classifications/08-namespace-forbidden.json", + "kind": "classification_exchange", + "status": 403, + "code": "namespace_forbidden", + "description": "Key namespace is not the principal's derived namespace: 403." + }, + { + "file": "errors/cursor-expired.json", + "kind": "error", + "schema": "ObservationError", + "status": 410, + "code": "cursor_expired", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations", + "query": { + "cursor": "eyJ2IjoxLCJzZXEiOjQxMiwiZXBvY2giOiI2NjY2NjY2Ni02NjY2LTQ2NjYtODY2Ni02NjY2NjY2NjY2NjYifQ" + } + }, + "description": "A cursor before the prune boundary. An epoch mismatch (DB restore) or a seq ahead of head returns the same body, e.g. cursor eyJ2IjoxLCJzZXEiOjEwMTAsImVwb2NoIjoiMGYwZjBmMGYtMGYwZi00ZjBmLThmMGYtMGYwZjBmMGYwZjBmIn0." + }, + { + "file": "errors/evidence-expired.json", + "kind": "error", + "schema": "ObservationError", + "status": 410, + "code": "evidence_expired", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "description": "Snapshot of single-expired.json; metadata stays readable." + }, + { + "file": "errors/evidence-pending.json", + "kind": "error", + "schema": "ObservationError", + "status": 202, + "code": "evidence_pending", + "headers": { + "Retry-After": "2" + }, + "request": { + "method": "GET", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "description": "Snapshot of single-pending.json. Retry-After header equals retry_after_seconds." + }, + { + "file": "errors/evidence-unavailable.json", + "kind": "error", + "schema": "ObservationError", + "status": 404, + "code": "evidence_unavailable", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations/bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb/snapshot" + }, + "description": "Snapshot of single-unavailable.json." + }, + { + "file": "errors/invalid-cursor.json", + "kind": "error", + "schema": "ObservationError", + "status": 400, + "code": "invalid_cursor", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations", + "query": { + "cursor": "not-a-cursor!" + } + }, + "description": "Malformed cursor; restart without one." + }, + { + "file": "errors/not-implemented.json", + "kind": "error", + "schema": "ObservationError", + "status": 501, + "code": "not_implemented", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations" + }, + "description": "C2 stub response, until the operation lands (L8/L14)." + }, + { + "file": "errors/observation-not-found.json", + "kind": "error", + "schema": "ObservationError", + "status": 404, + "code": "observation_not_found", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations/00000000-0000-4000-8000-000000000000" + }, + "description": "Unknown, unauthorized and deleted-camera observations are indistinguishable." + }, + { + "file": "errors/service-unavailable.json", + "kind": "error", + "schema": "ObservationError", + "status": 503, + "code": "service_unavailable", + "headers": { + "Retry-After": "5" + }, + "request": { + "method": "GET", + "path": "/api/observations" + }, + "description": "TemporarilyUnreachable outcome; honour Retry-After." + }, + { + "file": "errors/snapshot-forbidden.json", + "kind": "error", + "schema": "ObservationError", + "status": 403, + "code": "forbidden", + "headers": {}, + "request": { + "method": "GET", + "path": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "description": "live.view held without snapshot.create (D4)." + }, + { + "file": "events/a-r1-created-pending.json", + "kind": "event", + "seq": 1001, + "change_cause": "created", + "evidence_state": "pending", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/single-pending.json", + "description": "MQTT payload for change seq 1001 (created, evidence pending)." + }, + { + "file": "events/a-r2-evidence-ready.json", + "kind": "event", + "seq": 1003, + "change_cause": "evidence", + "evidence_state": "ready", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/single-ready.json", + "description": "MQTT payload for change seq 1003 (evidence, evidence ready)." + }, + { + "file": "events/a-r3-classification.json", + "kind": "event", + "seq": 1010, + "change_cause": "classification", + "evidence_state": "ready", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/single-ready-classified.json", + "description": "MQTT payload for change seq 1010 (classification, evidence ready)." + }, + { + "file": "events/a-r5-evidence-expired.json", + "kind": "event", + "seq": 1013, + "change_cause": "evidence", + "evidence_state": "expired", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/single-expired.json", + "description": "MQTT payload for change seq 1013 (evidence, evidence expired)." + }, + { + "file": "events/b-r1-created-unavailable.json", + "kind": "event", + "seq": 1002, + "change_cause": "created", + "evidence_state": "unavailable", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/single-unavailable.json", + "description": "MQTT payload for change seq 1002 (created, evidence unavailable)." + }, + { + "file": "events/c0-r2-evidence-ready.json", + "kind": "event", + "seq": 1006, + "change_cause": "evidence", + "evidence_state": "ready", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/two-birds-object-0.json", + "description": "MQTT payload for change seq 1006 (evidence, evidence ready)." + }, + { + "file": "events/c1-r2-evidence-ready.json", + "kind": "event", + "seq": 1007, + "change_cause": "evidence", + "evidence_state": "ready", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/two-birds-object-1.json", + "description": "MQTT payload for change seq 1007 (evidence, evidence ready)." + }, + { + "file": "events/e-r1-created-no-matching-media.json", + "kind": "event", + "seq": 1011, + "change_cause": "created", + "evidence_state": "unavailable", + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/22222222-2222-4222-8222-222222222222", + "retain": false, + "observation": "observations/onvif-no-matching-media.json", + "description": "MQTT payload for change seq 1011 (created, evidence unavailable)." + }, + { + "file": "media/two-birds-160x80.jpg", + "kind": "jpeg", + "manifest": "media/two-birds-160x80.json", + "description": "Unannotated evidence image shared by two-birds-object-0/1. Solid-color boxes on 8-pixel block boundaries; pixel coordinates are exact, colors are within JPEG tolerance (±12 per channel)." + }, + { + "file": "media/two-birds-160x80.json", + "kind": "jpeg_manifest", + "description": "Box pixel coordinates for the JPEG." + }, + { + "file": "message-ids.json", + "kind": "message_id_vectors", + "description": "Every change-event message ID, including the contract §5.4 test vector." + }, + { + "file": "observations/http-letterbox.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "revision": 2, + "description": "HTTP API detector, submitted 1280x720 JPEG letterboxed server-side to 640x640; box already mapped to the stored image.", + "inference_box": { + "u": 256, + "v": 185, + "width": 128, + "height": 72 + } + }, + { + "file": "observations/onvif-no-matching-media.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0", + "revision": 1, + "description": "ONVIF object-class event: no box, evidence unavailable (no_matching_media)." + }, + { + "file": "observations/single-expired.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 5, + "description": "Single bird, revision 5 (evidence): media expired by retention; metadata, dimensions and transform kept." + }, + { + "file": "observations/single-owner-confirmed.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 4, + "description": "Single bird, revision 4 (classification): owner_confirmed correction replaced the automated annotation." + }, + { + "file": "observations/single-pending.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 1, + "description": "Single bird, revision 1 (created): evidence pending; dimensions and bytes are set once ready." + }, + { + "file": "observations/single-ready-classified.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 3, + "description": "Single bird, revision 3 (classification): automated yawamf/species annotation stored." + }, + { + "file": "observations/single-ready.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 2, + "description": "Single bird, revision 2 (evidence): evidence ready. Identical to contract §3.6." + }, + { + "file": "observations/single-unavailable.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "revision": 1, + "description": "Single bird created directly unavailable (queue_full): no media fields, no transform." + }, + { + "file": "observations/two-birds-object-0.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "revision": 2, + "description": "Two birds in one frame, object 0: shares asset and batch with object 1. Box matches media/two-birds-160x80.json." + }, + { + "file": "observations/two-birds-object-1.json", + "kind": "observation", + "schema": "Observation", + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "revision": 2, + "description": "Two birds in one frame, object 1: shares asset and batch with object 0. Box matches media/two-birds-160x80.json." + }, + { + "file": "pages/head.json", + "kind": "page", + "schema": "ObservationPage", + "status": 200, + "request": { + "method": "GET", + "path": "/api/observations", + "query": { + "cursor": "head" + } + }, + "watermark": 1010, + "next_seq": 1010, + "description": "cursor=head (D10): no items, cursor at the watermark." + }, + { + "file": "pages/page-1.json", + "kind": "page", + "schema": "ObservationPage", + "chain": 1, + "status": 200, + "request": { + "method": "GET", + "path": "/api/observations", + "query": { + "limit": "2" + } + }, + "watermark": 1010, + "next_seq": 1003, + "description": "History page 1 of 3 (limit 2, watermark seq 1010). Items hold current state; next_cursor is the last change scanned." + }, + { + "file": "pages/page-2.json", + "kind": "page", + "schema": "ObservationPage", + "chain": 2, + "status": 200, + "request": { + "method": "GET", + "path": "/api/observations", + "query": { + "cursor": "eyJ2IjoxLCJzZXEiOjEwMDMsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "limit": "2" + } + }, + "watermark": 1010, + "next_seq": 1007, + "description": "History page 2 of 3 (limit 2, watermark seq 1010). Items hold current state; next_cursor is the last change scanned." + }, + { + "file": "pages/page-3.json", + "kind": "page", + "schema": "ObservationPage", + "chain": 3, + "status": 200, + "request": { + "method": "GET", + "path": "/api/observations", + "query": { + "cursor": "eyJ2IjoxLCJzZXEiOjEwMDcsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "limit": "2" + } + }, + "watermark": 1010, + "next_seq": 1010, + "description": "History page 3 of 3 (limit 2, watermark seq 1010). Items hold current state; next_cursor is the last change scanned." + } + ] +} diff --git a/tests/fixtures/integrations/observations/media/two-birds-160x80.jpg b/tests/fixtures/integrations/observations/media/two-birds-160x80.jpg new file mode 100644 index 000000000..5b375e99e Binary files /dev/null and b/tests/fixtures/integrations/observations/media/two-birds-160x80.jpg differ diff --git a/tests/fixtures/integrations/observations/media/two-birds-160x80.json b/tests/fixtures/integrations/observations/media/two-birds-160x80.json new file mode 100644 index 000000000..fcf49aaf2 --- /dev/null +++ b/tests/fixtures/integrations/observations/media/two-birds-160x80.json @@ -0,0 +1,59 @@ +{ + "description": "Unannotated evidence image shared by two-birds-object-0/1. Solid-color boxes on 8-pixel block boundaries; pixel coordinates are exact, colors are within JPEG tolerance (±12 per channel).", + "asset_id": "99999999-9999-4999-8999-999999999999", + "file": "media/two-birds-160x80.jpg", + "media_type": "image/jpeg", + "width": 160, + "height": 80, + "bytes": 1062, + "sha256": "f61a66623142b62ae0fc518130669dcca555d2ba5b7a83a2cd1b8c28d0d390ce", + "background_rgb": [ + 198, + 204, + 188 + ], + "boxes": [ + { + "observation_file": "observations/two-birds-object-0.json", + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "rgb": [ + 196, + 52, + 40 + ], + "pixels": { + "x": 16, + "y": 16, + "width": 32, + "height": 32 + }, + "normalized": { + "x": 0.1, + "y": 0.2, + "width": 0.2, + "height": 0.4 + } + }, + { + "observation_file": "observations/two-birds-object-1.json", + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "rgb": [ + 40, + 84, + 196 + ], + "pixels": { + "x": 96, + "y": 24, + "width": 48, + "height": 40 + }, + "normalized": { + "x": 0.6, + "y": 0.3, + "width": 0.3, + "height": 0.5 + } + } + ] +} diff --git a/tests/fixtures/integrations/observations/message-ids.json b/tests/fixtures/integrations/observations/message-ids.json new file mode 100644 index 000000000..30e9e13ee --- /dev/null +++ b/tests/fixtures/integrations/observations/message-ids.json @@ -0,0 +1,67 @@ +{ + "rule": "message_id = UUIDv5(namespace.uuid, source + \"|\" + observation_id + \"|\" + revision), revision as a decimal string", + "namespace": { + "name": "https://lightnvr.io/ns/observation-change/v1", + "parent": "NAMESPACE_URL 6ba7b811-9dad-11d1-80b4-00c04fd430c8", + "uuid": "0bc01005-a431-522b-9124-a114f67d187a" + }, + "contract_test_vector": "43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9", + "vectors": [ + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 1, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|55555555-5555-4555-8555-555555555555|1", + "message_id": "085e44dc-9a26-5604-9e04-8e1425a0905b" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "revision": 1, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb|1", + "message_id": "d99668f3-4b89-5414-aeee-e4e65ee4c8a5" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 2, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|55555555-5555-4555-8555-555555555555|2", + "message_id": "43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "revision": 2, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|cccccccc-cccc-4ccc-8ccc-cccccccccc00|2", + "message_id": "16ad08e3-9eb4-5226-82af-e7185fdcdabe" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "revision": 2, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|cccccccc-cccc-4ccc-8ccc-cccccccccc01|2", + "message_id": "a6ebc805-5df5-5a19-9c14-43c9fd05ad26" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 3, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|55555555-5555-4555-8555-555555555555|3", + "message_id": "de82ee69-00f4-5e04-be22-834a86651682" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0", + "revision": 1, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0|1", + "message_id": "a977c786-3b49-5f9b-a6e4-dc078b44086a" + }, + { + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "observation_id": "55555555-5555-4555-8555-555555555555", + "revision": 5, + "name": "urn:lightnvr:11111111-1111-4111-8111-111111111111|55555555-5555-4555-8555-555555555555|5", + "message_id": "c9b83216-1196-546c-852f-fcfb272051eb" + } + ] +} diff --git a/tests/fixtures/integrations/observations/observations/http-letterbox.json b/tests/fixtures/integrations/observations/observations/http-letterbox.json new file mode 100644 index 000000000..ba8f36bd9 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/http-letterbox.json @@ -0,0 +1,62 @@ +{ + "id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "object_index": 0, + "observed_at": "2026-11-08T14:03:14.000Z", + "created_at": "2026-11-08T14:03:14.085Z", + "detector": { + "engine_key": "http-yolo", + "engine_type": "api", + "model": "yolov8n", + "model_version": "8.3.0" + }, + "label": "bird", + "confidence": 0.84, + "box": { + "format": "xywh_normalized", + "x": 0.4, + "y": 0.125, + "width": 0.2, + "height": 0.2 + }, + "evidence": { + "asset_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", + "state": "ready", + "reason": null, + "origin": "submitted_image", + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "bytes": 182340, + "captured_at": "2026-11-08T14:03:14.000Z", + "expires_at": "2026-11-15T14:03:14.000Z", + "transform": { + "mode": "letterbox", + "source_width": 1280, + "source_height": 720, + "inference_width": 640, + "inference_height": 640, + "scale_x": 0.5, + "scale_y": 0.5, + "pad_x": 0, + "pad_y": 140 + }, + "snapshot_url": "/api/observations/dddddddd-dddd-4ddd-8ddd-dddddddddddd/snapshot" + }, + "track": { + "id": "17", + "namespace": "http-yolo", + "session": "9b0c7d1e-5f43-4b8e-9d0e-0b6c1f2a3d4e" + }, + "recording_reference": { + "recording_id": 81236 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/onvif-no-matching-media.json b/tests/fixtures/integrations/observations/observations/onvif-no-matching-media.json new file mode 100644 index 000000000..5568ba415 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/onvif-no-matching-media.json @@ -0,0 +1,39 @@ +{ + "id": "e0e0e0e0-e0e0-4e0e-8e0e-e0e0e0e0e0e0", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "e1e1e1e1-e1e1-4e1e-8e1e-e1e1e1e1e1e1", + "object_index": 0, + "observed_at": "2026-11-08T15:30:01.900Z", + "created_at": "2026-11-08T15:30:02.018Z", + "detector": { + "engine_key": "onvif-events", + "engine_type": "onvif", + "model": null, + "model_version": null + }, + "label": "animal", + "confidence": 1, + "box": null, + "evidence": { + "asset_id": "e2e2e2e2-e2e2-4e2e-8e2e-e2e2e2e2e2e2", + "state": "unavailable", + "reason": "no_matching_media", + "origin": null, + "media_type": null, + "width": null, + "height": null, + "bytes": null, + "captured_at": null, + "expires_at": null, + "transform": null + }, + "track": null, + "recording_reference": null, + "revision": 1, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/single-expired.json b/tests/fixtures/integrations/observations/observations/single-expired.json new file mode 100644 index 000000000..0dfff28a7 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/single-expired.json @@ -0,0 +1,74 @@ +{ + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "expired", + "reason": "retention_expired", + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 5, + "classifications": { + "automated": [], + "owner_confirmed": [ + { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": null, + "label": "Carolina Chickadee", + "taxonomy_id": null, + "confidence": 1, + "classified_at": "2026-11-08T18:20:41.100Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 3, + "kind": "owner_confirmed", + "annotation_revision": 2, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T18:20:41.300Z" + } + ] + } +} diff --git a/tests/fixtures/integrations/observations/observations/single-owner-confirmed.json b/tests/fixtures/integrations/observations/observations/single-owner-confirmed.json new file mode 100644 index 000000000..7793e8316 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/single-owner-confirmed.json @@ -0,0 +1,75 @@ +{ + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 4, + "classifications": { + "automated": [], + "owner_confirmed": [ + { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": null, + "label": "Carolina Chickadee", + "taxonomy_id": null, + "confidence": 1, + "classified_at": "2026-11-08T18:20:41.100Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 3, + "kind": "owner_confirmed", + "annotation_revision": 2, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T18:20:41.300Z" + } + ] + } +} diff --git a/tests/fixtures/integrations/observations/observations/single-pending.json b/tests/fixtures/integrations/observations/observations/single-pending.json new file mode 100644 index 000000000..3c067bd09 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/single-pending.json @@ -0,0 +1,57 @@ +{ + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "pending", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": null, + "height": null, + "bytes": null, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": null, + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 1, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/single-ready-classified.json b/tests/fixtures/integrations/observations/observations/single-ready-classified.json new file mode 100644 index 000000000..d9c85b0fb --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/single-ready-classified.json @@ -0,0 +1,75 @@ +{ + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 3, + "classifications": { + "automated": [ + { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T14:03:15.120Z" + } + ], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/single-ready.json b/tests/fixtures/integrations/observations/observations/single-ready.json new file mode 100644 index 000000000..7b7f77573 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/single-ready.json @@ -0,0 +1,58 @@ +{ + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/single-unavailable.json b/tests/fixtures/integrations/observations/observations/single-unavailable.json new file mode 100644 index 000000000..308f17dd2 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/single-unavailable.json @@ -0,0 +1,47 @@ +{ + "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "b0b0b0b0-b0b0-4b0b-8b0b-b0b0b0b0b0b0", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.598Z", + "created_at": "2026-11-08T14:03:12.611Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.71, + "box": { + "format": "xywh_normalized", + "x": 0.05, + "y": 0.4, + "width": 0.1, + "height": 0.14 + }, + "evidence": { + "asset_id": "b1b1b1b1-b1b1-4b1b-8b1b-b1b1b1b1b1b1", + "state": "unavailable", + "reason": "queue_full", + "origin": null, + "media_type": null, + "width": null, + "height": null, + "bytes": null, + "captured_at": null, + "expires_at": null, + "transform": null + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 1, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/two-birds-object-0.json b/tests/fixtures/integrations/observations/observations/two-birds-object-0.json new file mode 100644 index 000000000..8a5994315 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/two-birds-object-0.json @@ -0,0 +1,58 @@ +{ + "id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "object_index": 0, + "observed_at": "2026-11-08T14:03:13.250Z", + "created_at": "2026-11-08T14:03:13.291Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.91, + "box": { + "format": "xywh_normalized", + "x": 0.1, + "y": 0.2, + "width": 0.2, + "height": 0.4 + }, + "evidence": { + "asset_id": "99999999-9999-4999-8999-999999999999", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 160, + "height": 80, + "bytes": 1062, + "captured_at": "2026-11-08T14:03:13.250Z", + "expires_at": "2026-11-15T14:03:13.250Z", + "transform": { + "mode": "resize", + "source_width": 160, + "source_height": 80, + "inference_width": 320, + "inference_height": 320, + "scale_x": 2, + "scale_y": 4, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/cccccccc-cccc-4ccc-8ccc-cccccccccc00/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81235 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/observations/two-birds-object-1.json b/tests/fixtures/integrations/observations/observations/two-birds-object-1.json new file mode 100644 index 000000000..4c416b342 --- /dev/null +++ b/tests/fixtures/integrations/observations/observations/two-birds-object-1.json @@ -0,0 +1,58 @@ +{ + "id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "object_index": 1, + "observed_at": "2026-11-08T14:03:13.250Z", + "created_at": "2026-11-08T14:03:13.291Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.78, + "box": { + "format": "xywh_normalized", + "x": 0.6, + "y": 0.3, + "width": 0.3, + "height": 0.5 + }, + "evidence": { + "asset_id": "99999999-9999-4999-8999-999999999999", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 160, + "height": 80, + "bytes": 1062, + "captured_at": "2026-11-08T14:03:13.250Z", + "expires_at": "2026-11-15T14:03:13.250Z", + "transform": { + "mode": "resize", + "source_width": 160, + "source_height": 80, + "inference_width": 320, + "inference_height": 320, + "scale_x": 2, + "scale_y": 4, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/cccccccc-cccc-4ccc-8ccc-cccccccccc01/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81235 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } +} diff --git a/tests/fixtures/integrations/observations/pages/head.json b/tests/fixtures/integrations/observations/pages/head.json new file mode 100644 index 000000000..ec872b47c --- /dev/null +++ b/tests/fixtures/integrations/observations/pages/head.json @@ -0,0 +1,5 @@ +{ + "items": [], + "next_cursor": "eyJ2IjoxLCJzZXEiOjEwMTAsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "has_more": false +} diff --git a/tests/fixtures/integrations/observations/pages/page-1.json b/tests/fixtures/integrations/observations/pages/page-1.json new file mode 100644 index 000000000..7c51b2a46 --- /dev/null +++ b/tests/fixtures/integrations/observations/pages/page-1.json @@ -0,0 +1,128 @@ +{ + "items": [ + { + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 3, + "classifications": { + "automated": [ + { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T14:03:15.120Z" + } + ], + "owner_confirmed": [] + } + }, + { + "id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "b0b0b0b0-b0b0-4b0b-8b0b-b0b0b0b0b0b0", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.598Z", + "created_at": "2026-11-08T14:03:12.611Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.71, + "box": { + "format": "xywh_normalized", + "x": 0.05, + "y": 0.4, + "width": 0.1, + "height": 0.14 + }, + "evidence": { + "asset_id": "b1b1b1b1-b1b1-4b1b-8b1b-b1b1b1b1b1b1", + "state": "unavailable", + "reason": "queue_full", + "origin": null, + "media_type": null, + "width": null, + "height": null, + "bytes": null, + "captured_at": null, + "expires_at": null, + "transform": null + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 1, + "classifications": { + "automated": [], + "owner_confirmed": [] + } + } + ], + "next_cursor": "eyJ2IjoxLCJzZXEiOjEwMDMsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "has_more": true +} diff --git a/tests/fixtures/integrations/observations/pages/page-2.json b/tests/fixtures/integrations/observations/pages/page-2.json new file mode 100644 index 000000000..3a848adfe --- /dev/null +++ b/tests/fixtures/integrations/observations/pages/page-2.json @@ -0,0 +1,122 @@ +{ + "items": [ + { + "id": "cccccccc-cccc-4ccc-8ccc-cccccccccc00", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "object_index": 0, + "observed_at": "2026-11-08T14:03:13.250Z", + "created_at": "2026-11-08T14:03:13.291Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.91, + "box": { + "format": "xywh_normalized", + "x": 0.1, + "y": 0.2, + "width": 0.2, + "height": 0.4 + }, + "evidence": { + "asset_id": "99999999-9999-4999-8999-999999999999", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 160, + "height": 80, + "bytes": 1062, + "captured_at": "2026-11-08T14:03:13.250Z", + "expires_at": "2026-11-15T14:03:13.250Z", + "transform": { + "mode": "resize", + "source_width": 160, + "source_height": 80, + "inference_width": 320, + "inference_height": 320, + "scale_x": 2, + "scale_y": 4, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/cccccccc-cccc-4ccc-8ccc-cccccccccc00/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81235 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } + }, + { + "id": "cccccccc-cccc-4ccc-8ccc-cccccccccc01", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "object_index": 1, + "observed_at": "2026-11-08T14:03:13.250Z", + "created_at": "2026-11-08T14:03:13.291Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.78, + "box": { + "format": "xywh_normalized", + "x": 0.6, + "y": 0.3, + "width": 0.3, + "height": 0.5 + }, + "evidence": { + "asset_id": "99999999-9999-4999-8999-999999999999", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 160, + "height": 80, + "bytes": 1062, + "captured_at": "2026-11-08T14:03:13.250Z", + "expires_at": "2026-11-15T14:03:13.250Z", + "transform": { + "mode": "resize", + "source_width": 160, + "source_height": 80, + "inference_width": 320, + "inference_height": 320, + "scale_x": 2, + "scale_y": 4, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/cccccccc-cccc-4ccc-8ccc-cccccccccc01/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81235 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } + } + ], + "next_cursor": "eyJ2IjoxLCJzZXEiOjEwMDcsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "has_more": true +} diff --git a/tests/fixtures/integrations/observations/pages/page-3.json b/tests/fixtures/integrations/observations/pages/page-3.json new file mode 100644 index 000000000..ff4d2e726 --- /dev/null +++ b/tests/fixtures/integrations/observations/pages/page-3.json @@ -0,0 +1,143 @@ +{ + "items": [ + { + "id": "dddddddd-dddd-4ddd-8ddd-dddddddddddd", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "ffffffff-ffff-4fff-8fff-ffffffffffff", + "object_index": 0, + "observed_at": "2026-11-08T14:03:14.000Z", + "created_at": "2026-11-08T14:03:14.085Z", + "detector": { + "engine_key": "http-yolo", + "engine_type": "api", + "model": "yolov8n", + "model_version": "8.3.0" + }, + "label": "bird", + "confidence": 0.84, + "box": { + "format": "xywh_normalized", + "x": 0.4, + "y": 0.125, + "width": 0.2, + "height": 0.2 + }, + "evidence": { + "asset_id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", + "state": "ready", + "reason": null, + "origin": "submitted_image", + "media_type": "image/jpeg", + "width": 1280, + "height": 720, + "bytes": 182340, + "captured_at": "2026-11-08T14:03:14.000Z", + "expires_at": "2026-11-15T14:03:14.000Z", + "transform": { + "mode": "letterbox", + "source_width": 1280, + "source_height": 720, + "inference_width": 640, + "inference_height": 640, + "scale_x": 0.5, + "scale_y": 0.5, + "pad_x": 0, + "pad_y": 140 + }, + "snapshot_url": "/api/observations/dddddddd-dddd-4ddd-8ddd-dddddddddddd/snapshot" + }, + "track": { + "id": "17", + "namespace": "http-yolo", + "session": "9b0c7d1e-5f43-4b8e-9d0e-0b6c1f2a3d4e" + }, + "recording_reference": { + "recording_id": 81236 + }, + "revision": 2, + "classifications": { + "automated": [], + "owner_confirmed": [] + } + }, + { + "id": "55555555-5555-4555-8555-555555555555", + "source": "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "camera_uuid": "22222222-2222-4222-8222-222222222222", + "stream_name_at_capture": "feeder-east", + "batch_id": "77777777-7777-4777-8777-777777777777", + "object_index": 0, + "observed_at": "2026-11-08T14:03:12.481Z", + "created_at": "2026-11-08T14:03:12.530Z", + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "ssd_mobilenet_v2.sod", + "model_version": null + }, + "label": "bird", + "confidence": 0.87, + "box": { + "format": "xywh_normalized", + "x": 0.412, + "y": 0.288, + "width": 0.12, + "height": 0.165 + }, + "evidence": { + "asset_id": "88888888-8888-4888-8888-888888888888", + "state": "ready", + "reason": null, + "origin": "inference_input", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 48211, + "captured_at": "2026-11-08T14:03:12.481Z", + "expires_at": "2026-11-15T14:03:12.481Z", + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/55555555-5555-4555-8555-555555555555/snapshot" + }, + "track": null, + "recording_reference": { + "recording_id": 81234 + }, + "revision": 3, + "classifications": { + "automated": [ + { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "convnext-v2-2026.10", + "label": "Black-capped Chickadee", + "taxonomy_id": "inat:144815", + "confidence": 0.93, + "classified_at": "2026-11-08T14:03:15.002Z", + "input_asset_id": "88888888-8888-4888-8888-888888888888", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T14:03:15.120Z", + "updated_at": "2026-11-08T14:03:15.120Z" + } + ], + "owner_confirmed": [] + } + } + ], + "next_cursor": "eyJ2IjoxLCJzZXEiOjEwMTAsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0", + "has_more": false +} diff --git a/tests/integration/specs/api-tokens.ui.spec.ts b/tests/integration/specs/api-tokens.ui.spec.ts index 550d6917f..b3b0f2937 100644 --- a/tests/integration/specs/api-tokens.ui.spec.ts +++ b/tests/integration/specs/api-tokens.ui.spec.ts @@ -15,6 +15,8 @@ const enforcedKeys: Action[] = [ 'ptz.control', 'evidence.protect', 'recording.delete', 'camera.configure', 'storage.configure', 'events.configure', 'users.manage', 'system.admin', 'lpr.read', 'lpr.search', 'lpr.export', 'lpr.delete', + // INT_01 P2: enforced by the annotation PUT (L14). + 'observations.annotate', ]; const unenforcedKeys: Action[] = ['audio.listen', 'audio.talk', 'fleet.execute_job']; @@ -71,7 +73,7 @@ test.describe('API token permission catalog @ui @users', () => { await page.getByTitle('Manage API access').click(); const dialog = page.getByRole('dialog', { name: 'API Access — admin' }); await expect(dialog).toBeVisible(); - await expect(dialog.locator('fieldset input[type="checkbox"]')).toHaveCount(16); + await expect(dialog.locator('fieldset input[type="checkbox"]')).toHaveCount(17); for (const key of enforcedKeys) { const checkbox = dialog.getByRole('checkbox', { name: new RegExp(`^${key.replace('.', '\\.')}`) }); await checkbox.scrollIntoViewIfNeeded(); diff --git a/tests/integration/specs/detection-classification.ui.spec.ts b/tests/integration/specs/detection-classification.ui.spec.ts new file mode 100644 index 000000000..2aac7fdc3 --- /dev/null +++ b/tests/integration/specs/detection-classification.ui.spec.ts @@ -0,0 +1,100 @@ +import { test, expect, Page } from '@playwright/test'; +import { login, USERS } from '../fixtures/test-fixtures'; +import { TimelinePage } from '../pages/TimelinePage'; + +// INT_01 L15: detections linked to an observation show the classification +// beside the detector's own label, with an owner badge and a link back. +type Segment = { id: number; stream: string; start_timestamp: number; end_timestamp: number }; + +const OWNER_ID = '44444444-4444-4444-8444-444444444444'; +const AUTO_ID = '66666666-6666-4666-8666-666666666666'; + +function localTimestamp(date: string, time: string): number { + const [y, m, d] = date.split('-').map(Number); + const [hh, mm, ss] = time.split(':').map(Number); + return Math.floor(new Date(y, m - 1, d, hh, mm, ss).getTime() / 1000); +} + +function annotation(kind: 'automated' | 'owner_confirmed', label: string, confidence: number) { + return { + key: 'yawamf/species', writer_namespace: 'yawamf', classifier: 'yawamf', model_version: null, + label, taxonomy_id: null, confidence, classified_at: '2026-03-08T12:01:00.000Z', + input_asset_id: '55555555-5555-4555-8555-555555555555', input_revision: 2, kind, + annotation_revision: 1, created_at: '2026-03-08T12:01:00.000Z', updated_at: '2026-03-08T12:01:00.000Z', + }; +} + +async function mockTimeline(page: Page, stream: string, segment: Segment) { + await page.route(/\/api\/streams(?:\?.*)?$/, route => route.fulfill({ + json: { streams: [{ name: stream }], total_pages: 1 }, + })); + await page.route('**/api/timeline/segments?**', route => route.fulfill({ json: { segments: [segment] } })); + await page.route('**/api/timeline/segments-by-ids?**', route => route.fulfill({ json: { + segments: [segment], multi_stream: false, + start_time: new Date(segment.start_timestamp * 1000).toISOString(), + end_time: new Date(segment.end_timestamp * 1000).toISOString(), + } })); + const at = segment.start_timestamp + 30; + const box = { x: 0.1, y: 0.2, width: 0.3, height: 0.4 }; + await page.route('**/api/detection/results/**', route => route.fulfill({ json: { + timestamp: '2026-03-08 12:00:30', stream_status: 'Running', + detections: [ + { label: 'bird', confidence: 0.82, ...box, timestamp: at, end_timestamp: at, + observation: { id: OWNER_ID, owner_confirmed: true, + classification: annotation('owner_confirmed', 'Carolina Chickadee', 1) } }, + { label: 'bird', confidence: 0.71, ...box, timestamp: at + 5, end_timestamp: at + 5, + observation: { id: AUTO_ID, owner_confirmed: false, + classification: annotation('automated', 'House Finch', 0.88) } }, + { label: 'person', confidence: 0.9, ...box, timestamp: at + 9, end_timestamp: at + 9 }, + ], + } })); + await page.route('**/api/recordings/**', async route => { + const url = new URL(route.request().url()); + if (/\/api\/recordings\/play\/\d+$/.test(url.pathname)) { + return route.fulfill(url.searchParams.get('prepare') === '1' + ? { json: { status: 'ready' } } : { status: 204, body: '' }); + } + if (/\/api\/recordings\/\d+\/tags$/.test(url.pathname)) return route.fulfill({ json: { tags: [] } }); + if (/\/api\/recordings\/\d+$/.test(url.pathname)) { + return route.fulfill({ json: { + id: segment.id, stream, protected: false, + start_time: new Date(segment.start_timestamp * 1000).toISOString(), + end_time: new Date(segment.end_timestamp * 1000).toISOString(), + } }); + } + return route.continue(); + }); +} + +test.describe('Detection classifications @ui @timeline', () => { + test.beforeEach(async ({ page }) => { + await login(page, USERS.admin); + }); + + test('timeline shows the detector label next to the linked classification', async ({ page }) => { + const stream = 'feeder'; + const date = '2026-03-08'; + const segment: Segment = { id: 901, stream, start_timestamp: localTimestamp(date, '12:00:00'), + end_timestamp: localTimestamp(date, '12:05:00') }; + await mockTimeline(page, stream, segment); + await page.goto(`/timeline.html?stream=${stream}&date=${date}&time=12:00:30`, { waitUntil: 'domcontentloaded' }); + await expect(new TimelinePage(page).timelineContainer).toBeVisible(); + + const rows = page.getByTestId('detection-classification'); + await expect(rows).toHaveCount(2); // the unlinked "person" detection has no row + const owner = rows.nth(0); + await expect(owner).toContainText('Detector: bird (82%)'); + await expect(owner).toContainText('Carolina Chickadee (100%)'); + await expect(owner).toContainText('via yawamf'); + await expect(owner.getByTestId('detection-classification-owner')).toHaveText('Owner confirmed'); + await expect(owner.getByRole('link', { name: 'Observation' })) + .toHaveAttribute('href', `/api/observations/${OWNER_ID}`); + + const automated = rows.nth(1); + await expect(automated).toContainText('Detector: bird (71%)'); + await expect(automated).toContainText('House Finch (88%)'); + await expect(automated.getByTestId('detection-classification-owner')).toHaveCount(0); + await expect(automated.getByRole('link', { name: 'Observation' })) + .toHaveAttribute('href', `/api/observations/${AUTO_ID}`); + }); +}); diff --git a/tests/integration/yawamf_e2e/A14_PILOT_CHECKLIST.md b/tests/integration/yawamf_e2e/A14_PILOT_CHECKLIST.md new file mode 100644 index 000000000..ce47f52b7 --- /dev/null +++ b/tests/integration/yawamf_e2e/A14_PILOT_CHECKLIST.md @@ -0,0 +1,83 @@ +# A14 operator pilot: lightNVR + YA-WAMF on a real feeder camera + +PRD INT_01 A14: "Operator installs from the documented recipe using scoped +credentials, sees a real feeder visit, exercises reconnect recovery, and +records the tested source/model versions." + +This is a manual checklist. Follow +[`docs/integrations/YA_WAMF.md`](../../../docs/integrations/YA_WAMF.md) +step by step, without workarounds. Wherever a step is wrong or missing, write +what you did instead under **Notes**, and fix the recipe in the same change. +When everything is done, tick the boxes, fill in the tables and commit this +file. Then move the matrix row "Real-camera operator pilot (A14)" from +**pending** to **tested**. + +**Status: not started.** X1 ran the recipe on synthetic fixtures only (see +`README.md` and `results/`). Nobody has run this pilot yet. + +## 0. Record what you are testing + +| Item | Value | +| --- | --- | +| Date, operator | | +| lightNVR commit / version string (`GET /api/system/info` → `version`) | | +| lightNVR build options (MQTT, SOD, LiteRT) | | +| YA-WAMF commit (`/health` → `version`) and image flavour | | +| MQTT broker and version | | +| Camera make/model, main and detection stream resolution | | +| Detector engine (SOD / LiteRT / HTTP light-object-detect / DOODS2) | | +| Detector model file name, version and SHA-256 | | +| Detector labels that include `bird` | | +| YA-WAMF classifier model id and version (Settings → Detection) | | +| Host OS, CPU or accelerator | | + +## 1. Install from the recipe + +- [ ] Prerequisites: lightNVR built with MQTT, a broker both apps reach, YA-WAMF from `feature/lightnvr-provider`, an administrator login. +- [ ] Back up lightNVR (`POST /api/system/backup`) and YA-WAMF `/data` before the first start of the new builds. +- [ ] Step 1: the feeder stream uses a bird-capable detector (`detection_based_recording: true`, `detection_model`). The model's labels include `bird`. +- [ ] Step 2: evidence is on for the feeder camera only (`observation_evidence_enabled: true`, `observation_labels: "bird"`). Recorded `camera_uuid`: ____________ +- [ ] Step 3: the `[mqtt]` block is set and lightNVR restarted. If any event route exists, the observation route was added with **no** predicate. +- [ ] Step 4: user `yawamf` → role (`live.view`, `snapshot.create`, optional `observations.annotate`) → feeder-only policy → scoped token. The token expiry is recorded: ____________ +- [ ] Step 5: YA-WAMF environment set (`LIGHTNVR__*`, `FRIGATE__MQTT_*` pointing at the same broker), then restarted. +- [ ] Step 6.1: **Test lightNVR connection** shows API access, event connectivity and evidence access as ok, and features as unsupported-video only. +- [ ] Step 6.2: the token probe returns `200`; a camera outside the token scope returns `403`. +- [ ] Step 6.3: `mosquitto_sub` shows `created`/`pending`, then `evidence`/`ready`, for a visit. + +## 2. A real feeder visit + +- [ ] A bird visits. lightNVR shows the detection, and `GET /api/observations?camera_uuid=…` lists it with evidence `ready`. +- [ ] YA-WAMF shows the detection with the right camera, a species, and a photo cropped around the bird. +- [ ] The photo in YA-WAMF is the low-resolution detection image (not a later recording frame), and the crop matches where the bird was. +- [ ] With two birds in frame at once (if it happens): two YA-WAMF detections, one per bird. + +| Observation id | Detector label / confidence | YA-WAMF species / score | Crop correct? | +| --- | --- | --- | --- | +| | | | | + +## 3. Reconnect recovery + +- [ ] Stop the broker for at least one visit, then start it again. YA-WAMF catches up (from the cursor, the outbox, or both) and shows the missed visit once. +- [ ] Restart YA-WAMF while lightNVR keeps detecting. Visits made while it was down appear once after it starts. +- [ ] Restart lightNVR during a visit. YA-WAMF converges, and lightNVR's `observation_pipeline` (System page) shows no stuck pending items. +- [ ] YA-WAMF Health shows the source and recovery state, with no unexpected gap. + +| Interruption | Visits during the outage | Visits in YA-WAMF afterwards | Time to converge | +| --- | --- | --- | --- | +| broker stopped | | | | +| YA-WAMF restarted | | | | +| lightNVR restarted | | | | + +## 4. Outcomes and diagnostics + +- [ ] lightNVR `GET /api/metrics` (as admin): `lightnvr_observation_*` present, with no unexplained `dropped` reasons. +- [ ] YA-WAMF `/health` → `source_recovery` and `notification_intents` look sane. +- [ ] Optional P2: write-back on; lightNVR shows the YA-WAMF species next to the detection. + +## 5. Sign-off + +- [ ] Versions recorded in section 0 and copied to the supported-version matrix in `docs/integrations/YA_WAMF.md`. +- [ ] Recipe corrections (if any) committed. + +**Notes** (anything that differed from the recipe): + diff --git a/tests/integration/yawamf_e2e/README.md b/tests/integration/yawamf_e2e/README.md new file mode 100644 index 000000000..10b86d005 --- /dev/null +++ b/tests/integration/yawamf_e2e/README.md @@ -0,0 +1,168 @@ +# INT_01 X1/X3: lightNVR + YA-WAMF end-to-end suite + +This suite runs the deployment recipe +[`docs/integrations/YA_WAMF.md`](../../../docs/integrations/YA_WAMF.md) +against real processes, then checks PRD INT_01 acceptance A1–A6 and A8 (X1, +P1) and A12, A13 and A7-annotate (X3, P2 classification write-back) across +both applications. A14, the operator pilot on a real camera, is a manual +checklist: [`A14_PILOT_CHECKLIST.md`](A14_PILOT_CHECKLIST.md). + +## What runs + +| Process | What it is | +| --- | --- | +| Mosquitto 2.x | A real broker on a loopback port. It is stopped and restarted for A5. | +| lightNVR | The binary under test (`--lightnvr-bin`), with the recipe's `[mqtt]` block. | +| YA-WAMF backend | The branch under test, unmodified, started by `harness/yawamf_launcher.py` with a pinned classifier (below). | +| Feeder source | Three 640×360, 10 fps H.264 MPEG-TS scenes rendered by ffmpeg and served over HTTP: one static red bird, one red bird crossing the frame at 240 px/s, and a red and a blue bird together. | +| Detector | A loopback HTTP detector in the light-object-detect wire format. It finds the red and blue fixture birds in the submitted JPEG by colour. Each camera reports birds only for the number of frames the scenario arms, so observation counts are exact. | + +The orchestrator (`run_e2e.py`) runs recipe steps 1–6 verbatim through the +public APIs: streams, evidence settings, the `mqtt:default` route, user → role → +policy → scoped token, the YA-WAMF environment, the connection test and the +token probes. It records each step's outcome in the results JSON (`recipe`). + +### Pinned models + +- **Detector:** `x1-colour-detector 1.0`, the loopback detector described + above. No bird-capable neural model runs in this suite. +- **YA-WAMF classifier:** `x1-colour-rule-v1`. The launcher replaces + `ClassifierService.classify_async_live` only. A crop in which at least 10% + of pixels are saturated red is *Prunella modularis* 0.93; otherwise, at least + 10% saturated blue is *Erithacus rubecula* 0.93. Anything else (a crop that + missed the bird) gets no result. Every + call is logged with the crop's size and mean colour, so the suite can count + calls and confirm each crop really contains its bird. + +### A2 with a local detector (optional `A2-local`) + +`--sod-model` and `--bird-image` add a seventh camera. It runs a real local +SOD model on a real bird photo that crosses a grey frame at 120 px/s. The +photo is not committed, because its licence (CC BY-SA 4.0) does not fit this +repository. X1 used: + +- model: VOC `tiny20.sod` (SHA-256 prefix recorded in the results), whose + classes include `bird`; +- photo: Wikimedia Commons + `File:Northern_cardinal_male_(84894).jpg`, as the 330 px thumbnail + (`sha256 85bf66b1c51ad1aeb08d3d90f1a39a59f6cb6161d91f7e7b51d4d187847f3cdf`). + +There is no submitted JPEG to compare against, because the stored image is +the decoded inference frame. Instead, the suite finds the red bird in each +stored image and requires the observation box to contain the bird's centre +with IoU ≥ 0.2. The bird must also have moved at least 10% of the frame width +between the stored frames, and the evidence origin must be +`inference_input`. + +### Not covered here + +- YA-WAMF's real classifier model. The colour rule above stands in for it. +- LiteRT/TFLite (this build has `ENABLE_LITERT=OFF`), DOODS2, and ONVIF + evidence end to end. They are covered by unit tests + (`test_detection_evidence`, `test_detection_observation_evidence`, + `test_api_detection_observation_evidence`). + +Both remain pending in the +[supported-version matrix](../../../docs/integrations/YA_WAMF.md#supported-version-matrix). + +## Scenarios + +| ID | What the suite does | What must hold | +| --- | --- | --- | +| A1 | One frame with one bird | One observation (`created`/`pending`, then `evidence`/`ready` on the real broker with deterministic message IDs). One YA-WAMF detection keyed `lnvr::`, with the camera UUID, label and species. One classifier call. | +| A2 / A2-local | Three frames of the moving bird (HTTP detector); optionally a real SOD model on a real photo (above) | Each stored snapshot is byte-equal to one image the detector received, and its box is that detector box. The box covers the bird in its own image and misses it in the other frames (the bird moved at least 64 px). YA-WAMF identifies every crop as the red bird. | +| A3 | One frame with two birds | Two sibling observations share one batch and one asset. Each box covers its own bird. YA-WAMF creates two detections with the right species each, from two classifier calls. | +| A4 | Republish every captured A1/A3 message 10× to the broker while `POST /api/backfill` runs recovery | No new detection, no new classifier call, and the notification intent is unchanged. The backfill reports `new_detections: 0`. | +| A5 | Broker stopped; 100 observations (50 two-bird frames); both apps stopped; broker, lightNVR and YA-WAMF started again | All 100 observations appear exactly once in YA-WAMF. The outbox also delivers all 200 revisions once the broker returns. | +| A6 | SIGKILL lightNVR mid-burst, then restart. Separately, SIGKILL YA-WAMF mid-burst, then restart | lightNVR: startup order (reconcile → evidence → relay drain → relay), no pending asset, no `.jpg.tmp`, every ready row has its file. YA-WAMF converges to exactly the ready observations, with no duplicates and nothing admitted for evidence that is not ready. | +| A8 | Temporary: lightNVR frozen (SIGSTOP) while YA-WAMF starts with missed history, then resumed. Expired: observations backdated 8 days while lightNVR is stopped, so the first retention pass expires them. Unavailable: ready files deleted while stopped, so startup reconciliation marks them `unavailable(write_failed)`. Expired cursor: file-level DB restore, so lightNVR regenerates the epoch | Nothing is admitted while lightNVR is unreachable, and everything is admitted after it resumes. Snapshots answer `410 evidence_expired` and `404 evidence_unavailable`. YA-WAMF admits none of those and reports the reasons (`evidence_expired`, `evidence_unavailable`) in recovery and backfill. The old cursor answers `410 cursor_expired`, and YA-WAMF shows `recovery_required` until it is acknowledged. | +| A12 | One bird on `feeder-wb`; YA-WAMF runs with `LIGHTNVR__WRITEBACK_ENABLED=true` (the recipe's optional P2 step). Then: every captured message for it republished 10×, 5 identical annotation PUTs, a stale (`expected_revision: 0`) and a future revision; an owner correction (`PATCH /api/events/{id}`); an automated PUT over the owner decision; YA-WAMF's detection reverted to an automated result with its write-back job re-queued | The species is on YA-WAMF's row, in lightNVR's `observation_classifications` (`yawamf/species`, `automated`), in `GET /api/observations/{id}` and on `GET /api/detection/results/{stream}` (`observation.classification`), while the detector's `bird` label and confidence are unchanged. The classification change is published once (revision 3, deterministic ID) and causes no classifier call, no new row, no intent change and no new write-back job. Replays and retries are `200 changed:false`, stale and future revisions `409 revision_conflict`; one row per key stays at annotation revision 1. The correction arrives as `owner_confirmed` (revision 2, confidence 1). A later automated result is refused (`409 owner_decision_locked`) and YA-WAMF's job ends terminal with `dropped:owner_decision_locked`. | +| A13 | One bird on `feeder-wb-outage`, written back; lightNVR stopped; owner correction in YA-WAMF while it is down; lightNVR started again | The correction is saved and served by YA-WAMF while lightNVR is down, and the write-back job is `retryable` with `retry:source_unreachable` ("sync pending"). After lightNVR returns, the job succeeds and lightNVR shows `owner_confirmed` with the corrected species. No classifier call, duplicate row or notification-intent change. | +| A7-annotate | Two more tokens for the same user: one without `observations.annotate`, one with it but scoped to another camera | Without the action: `403 forbidden` on an existing and on a new key. Another camera's scope: `404 observation_not_found`. Neither writes a row or bumps the revision. The recipe token creates `yawamf/x3probe` (`201`) and its retry is `200 changed:false`; `yawamf/species` is untouched. | + +## Running it + +### Host-process mode (what X1 executed) + +Requirements: a lightNVR build with MQTT, `mosquitto` and `mosquitto_pub`/`mosquitto_sub` 2.x, +ffmpeg with libx264, and a YA-WAMF checkout whose `backend/.venv` was +created from its requirements (it provides Pillow and numpy). + +```bash +../YetAnother-WhosAtMyFeeder/backend/.venv/bin/python tests/integration/yawamf_e2e/run_e2e.py \ + --lightnvr-bin build/bin/lightnvr --yawamf ../YetAnother-WhosAtMyFeeder \ + [--scenarios A1,A3] [--workdir /path/to/keep] [--run-name NAME] [--no-writeback] \ + [--sod-model /path/to/tiny20.sod --bird-image /path/to/cardinal.jpg] +``` + +- A full run takes about 5 minutes. The exit status is 0 only when every + selected scenario passed. +- A4 replays the messages A1 and A3 captured, so run A1 and A3 with it. +- YA-WAMF starts with `LIGHTNVR__WRITEBACK_ENABLED=true`, so the P1 + scenarios also run with write-back on. `--no-writeback` starts it without + (the P1 recipe only); A12, A13 and A7-annotate then fail, which is how X3 + recorded its RED run. +- A12's automated-after-owner step waits for YA-WAMF's write-back worker, + which polls every 60 s. A13 waits for the first retry, 30 s after the + failed attempt. +- `results/.json` holds versions, recipe-step outcomes and per-scenario + evidence. Next to it are the MQTT capture, the detector log and the + classifier log. +- Mosquitto runs without a config file (`mosquitto -p `, the 2.x + local-only mode). On Ubuntu, AppArmor lets `/usr/sbin/mosquitto` read + configs only under `/etc/mosquitto`. + +### Container mode (documented CI/nightly path; not executed in X1) + +```bash +YAWAMF_SRC=/path/to/YetAnother-WhosAtMyFeeder \ + docker compose -f tests/integration/yawamf_e2e/compose/docker-compose.yml run --rm e2e +``` + +This builds one image with every process the suite owns: lightNVR from this +checkout, YA-WAMF's backend with its pinned requirements, Mosquitto and ffmpeg. +It then runs the same `run_e2e.py`. Everything is in one container because the +suite SIGKILLs, SIGSTOPs and restarts processes and reads both SQLite +databases. X1 committed host-mode results only; record the first container +run in the matrix. + +## Gotchas found while running the recipe + +- Stream names may contain only letters, digits, `.`, `-` and `_`. The + recipe's earlier `Feeder East` example was rejected. +- `PUT /api/streams/{name}` answers `202`, and the change applies + asynchronously. +- `POST /api/authorization/roles` and `PUT /api/authorization/users/{id}` + need `expected_policy_version` from a prior GET. +- YA-WAMF's connection test (`POST /api/setup/lightnvr/test`) needs `api_url` + in the body. A missing `token` keeps the saved token. +- In the stream API, `detection_threshold` is a percentage (`50`, not + `0.5`). With `0.5`, every low-confidence SOD box became an observation. +- YA-WAMF did not start when `LIGHTNVR__CAMERA_UUIDS` held a UUID + (pydantic-settings JSON-decoded the list). Fixed in YA-WAMF `b89846d0`. +- After recovery skipped an observation for an expired or unavailable photo, + Scan History counted it as "already existed". Fixed in YA-WAMF `fcf449f2`. +- A live message that arrived while lightNVR was down (SIGKILL in A6) was + retried only by the next 5-minute recovery interval. It took 297 s in + `results/x1-host-20261108-prefix-a6.json`. Since YA-WAMF `37c0877f`, a live + retry requests recovery straight away. Since `1cbbae74`, a burst of live + retries no longer multiplies the backoff: one run took 121 s instead of + 12 s before that fix. +- On a first start, YA-WAMF begins at `cursor=head`. Start it before creating + observations, or seed `source_cursors`. +- YA-WAMF ignores live messages until its capabilities fetch succeeds. When + it starts with lightNVR unreachable, recovery catches up later. +- MQTT reconnect backoff reaches about 35 s on both sides after a broker + outage or a SIGKILL. Keep timeouts generous. +- (X3) An owner correction is `PATCH /api/events/{event}` with + `{"display_name": ...}`. YA-WAMF has no `GET /api/events/{event}`; use + `GET /api/events?event_id=`. +- (X3) A token's camera selector must equal one of its user's grants, not a + subset of it (`403 API token scope exceeds live.view authority`). The + other-camera A7 probe is therefore a second user, `yawamf-east`, holding the + same role on one camera. +- (X3) YA-WAMF writes back only after a new detection and after an owner + correction. **Reclassify** and video classification change the species + without queuing a write-back. +- Wait for `recordings/observations/.cursor_head.json` to reach the head + before taking a file-level backup (it is written at most once a second). diff --git a/tests/integration/yawamf_e2e/compose/Dockerfile b/tests/integration/yawamf_e2e/compose/Dockerfile new file mode 100644 index 000000000..993ac08d7 --- /dev/null +++ b/tests/integration/yawamf_e2e/compose/Dockerfile @@ -0,0 +1,52 @@ +# syntax=docker/dockerfile:1.7 +# +# INT_01 X1: one image that holds every process the E2E suite owns (Mosquitto, +# lightNVR built from this checkout, the YA-WAMF backend from its branch, ffmpeg +# for the feeder fixture). The suite starts, stops, SIGKILLs and SIGSTOPs those +# processes and reads both SQLite databases, so they share one container. +# +# NOT EXECUTED IN X1: the committed results come from the host-process mode +# (see ../README.md). This is the documented CI/nightly path. +# +# Build contexts (see docker-compose.yml): +# lightnvr this repository +# yawamf a YA-WAMF checkout on feature/lightnvr-provider + +ARG DEBIAN_SUITE=sid + +FROM debian:${DEBIAN_SUITE}-slim AS lightnvr-build +# Same package set as .github/workflows/sanitizer.yml, plus MQTT on. +RUN apt-get update && apt-get install -y --no-install-recommends \ + git ca-certificates build-essential cmake pkg-config python3 \ + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ + libsqlite3-dev libuv1-dev libllhttp-dev libcurl4-openssl-dev \ + libcjson-dev libmbedtls-dev libmosquitto-dev \ + && rm -rf /var/lib/apt/lists/* +COPY --from=lightnvr . /src +RUN git config --global --add safe.directory /src \ + && (cd /src && git submodule update --init --depth 1 third_party/faac || true) \ + && cmake -S /src -B /build -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF \ + -DENABLE_MQTT=ON -DENABLE_SOD=OFF -DENABLE_GO2RTC=OFF -DENABLE_LITERT=OFF -DENABLE_SSL=ON \ + && cmake --build /build -j"$(nproc)" --target lightnvr + +# The build stage already carries every runtime library lightNVR links against. +FROM lightnvr-build +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl sqlite3 ffmpeg mosquitto mosquitto-clients libgl1 libglib2.0-0t64 \ + && rm -rf /var/lib/apt/lists/* +COPY --from=ghcr.io/astral-sh/uv:0.11.21 /uv /usr/local/bin/uv +COPY --from=lightnvr-build /build/bin/lightnvr /opt/lightnvr/bin/lightnvr +# The suite only uses the API; an empty web root is enough. +RUN mkdir -p /opt/lightnvr/www +COPY --from=yawamf . /opt/yawamf +# YA-WAMF's own pins (python 3.12, base + CPU provider requirements), in the +# venv path the suite expects (/backend/.venv). +RUN uv python install 3.12 \ + && uv venv --python 3.12 /opt/yawamf/backend/.venv \ + && VIRTUAL_ENV=/opt/yawamf/backend/.venv uv pip install \ + -r /opt/yawamf/backend/requirements-base.txt -r /opt/yawamf/backend/requirements-provider-cpu.txt +COPY --from=lightnvr tests/integration/yawamf_e2e /opt/x1 +WORKDIR /opt/x1 +ENTRYPOINT ["/opt/yawamf/backend/.venv/bin/python", "/opt/x1/run_e2e.py", \ + "--lightnvr-bin", "/opt/lightnvr/bin/lightnvr", "--web-root", "/opt/lightnvr/www", \ + "--yawamf", "/opt/yawamf", "--results-dir", "/results"] diff --git a/tests/integration/yawamf_e2e/compose/docker-compose.yml b/tests/integration/yawamf_e2e/compose/docker-compose.yml new file mode 100644 index 000000000..b5bf503cd --- /dev/null +++ b/tests/integration/yawamf_e2e/compose/docker-compose.yml @@ -0,0 +1,25 @@ +# INT_01 X1 lightNVR <-> YA-WAMF E2E suite, container mode (CI/nightly path). +# +# YAWAMF_SRC=../../../../YetAnother-WhosAtMyFeeder \ +# docker compose -f tests/integration/yawamf_e2e/compose/docker-compose.yml run --rm e2e +# +# Results land in tests/integration/yawamf_e2e/results/. Pass scenario subsets +# after the service name, e.g. `run --rm e2e --scenarios A1,A3`. +# +# NOT EXECUTED IN X1 (see ../README.md): the committed results are from the +# host-process mode, which runs the same run_e2e.py against the same kinds of +# processes on the host. +services: + e2e: + build: + context: . + dockerfile: Dockerfile + additional_contexts: + lightnvr: ../../../.. + yawamf: ${YAWAMF_SRC:?set YAWAMF_SRC to a YA-WAMF checkout on feature/lightnvr-provider} + image: lightnvr-x1-e2e:local + volumes: + - ../results:/results + # Everything binds to 127.0.0.1 inside the one container. + network_mode: none + init: true diff --git a/tests/integration/yawamf_e2e/harness/components.py b/tests/integration/yawamf_e2e/harness/components.py new file mode 100644 index 000000000..ca995e363 --- /dev/null +++ b/tests/integration/yawamf_e2e/harness/components.py @@ -0,0 +1,526 @@ +"""Host-process components for the INT_01 X1 lightNVR <-> YA-WAMF E2E suite. + +Every component is owned by the suite: a real Mosquitto broker, the lightNVR +binary under test, the YA-WAMF backend (branch under test, pinned classifier, +see ``yawamf_launcher.py``), an HTTP MPEG-TS feeder source and a loopback HTTP +object detector. Nothing here talks to a network service outside 127.0.0.1. + +Only the standard library plus Pillow/numpy is used, so the suite runs from +YA-WAMF's own venv (``backend/.venv/bin/python``). +""" +from __future__ import annotations + +import base64 +import hashlib +import io +import json +import os +import signal +import socket +import sqlite3 +import subprocess +import threading +import time +import urllib.error +import urllib.request +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +from pathlib import Path +from typing import Any, Callable, Optional + +import numpy as np +from PIL import Image + + +def free_port() -> int: + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + return sock.getsockname()[1] + + +def wait_for(predicate: Callable[[], Any], timeout: float, interval: float = 0.5, what: str = "condition") -> Any: + deadline = time.monotonic() + timeout + last_error: Optional[BaseException] = None + while time.monotonic() < deadline: + try: + value = predicate() + if value: + return value + except Exception as error: # noqa: BLE001 - polled until the deadline + last_error = error + time.sleep(interval) + raise TimeoutError(f"timed out after {timeout:.0f}s waiting for {what}" + (f" (last error: {last_error})" if last_error else "")) + + +class HttpError(Exception): + def __init__(self, status: int, body: bytes, headers: dict[str, str]): + super().__init__(f"HTTP {status}: {body[:300]!r}") + self.status, self.body, self.headers = status, body, headers + + def json(self) -> Any: + return json.loads(self.body) + + +def http(method: str, url: str, body: Any = None, headers: Optional[dict[str, str]] = None, timeout: float = 15.0) -> tuple[int, dict[str, str], bytes]: + data = None + request_headers = dict(headers or {}) + if body is not None: + data = json.dumps(body).encode() + request_headers["Content-Type"] = "application/json" + request = urllib.request.Request(url, data=data, method=method, headers=request_headers) + try: + with urllib.request.urlopen(request, timeout=timeout) as response: + return response.status, dict(response.headers), response.read() + except urllib.error.HTTPError as error: + return error.code, dict(error.headers), error.read() + + +def http_json(method: str, url: str, body: Any = None, headers: Optional[dict[str, str]] = None, expect: tuple[int, ...] = (200, 201), timeout: float = 15.0) -> Any: + status, response_headers, payload = http(method, url, body, headers, timeout) + if status not in expect: + raise HttpError(status, payload, response_headers) + return json.loads(payload) if payload else None + + +def basic(username: str, password: str) -> dict[str, str]: + return {"Authorization": "Basic " + base64.b64encode(f"{username}:{password}".encode()).decode()} + + +# --------------------------------------------------------------------------- broker + + +class Mosquitto: + """A real Mosquitto 2.x broker on a fixed loopback port; stop/start keeps the port.""" + + def __init__(self, workdir: Path, port: int, binary: str = "mosquitto"): + self.workdir, self.port, self.binary = workdir, port, binary + self.proc: Optional[subprocess.Popen] = None + # No config file: Ubuntu's AppArmor profile for /usr/sbin/mosquitto only lets it + # read configs under /etc/mosquitto. "-p" without "-c" is Mosquitto 2.x local-only + # mode: loopback listener, anonymous clients, no persistence. + self.log = workdir / "mosquitto.log" + + def version(self) -> str: + out = subprocess.run([self.binary, "-h"], capture_output=True, text=True).stdout + return out.splitlines()[0].strip() if out else "unknown" + + def start(self) -> None: + assert self.proc is None or self.proc.poll() is not None + # The child keeps its own copy of the descriptor; ours closes right away. + with open(self.log, "ab") as log: + self.proc = subprocess.Popen([self.binary, "-p", str(self.port), "-v"], stdout=log, stderr=subprocess.STDOUT) + wait_for(self._accepts, 10, 0.1, "mosquitto to listen") + + def _accepts(self) -> bool: + with socket.socket() as sock: + sock.settimeout(0.5) + return sock.connect_ex(("127.0.0.1", self.port)) == 0 + + def stop(self) -> None: + if self.proc and self.proc.poll() is None: + self.proc.terminate() + self.proc.wait(10) + self.proc = None + + def publish(self, topic: str, payload: bytes, qos: int = 1) -> None: + subprocess.run(["mosquitto_pub", "-h", "127.0.0.1", "-p", str(self.port), "-q", str(qos), "-t", topic, "-s"], input=payload, check=True, timeout=10) + + +class MqttCapture: + """mosquitto_sub on the observation topic; records (topic, payload) as published by lightNVR.""" + + def __init__(self, workdir: Path, port: int, topic: str): + self.path = workdir / "mqtt-capture.jsonl" + self.port, self.topic = port, topic + self.proc: Optional[subprocess.Popen] = None + self._lock = threading.Lock() + self.messages: list[tuple[str, bytes]] = [] + self._reader: Optional[threading.Thread] = None + + def start(self) -> None: + """(Re)subscribe; the messages captured so far are kept.""" + self.stop() + # -F '%t %x': topic, then the payload as hex (binary-safe, one line per message). + self.proc = subprocess.Popen(["mosquitto_sub", "-h", "127.0.0.1", "-p", str(self.port), "-q", "1", "-i", "x1-capture", "-t", self.topic, "-F", "%t %x"], + stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + self._reader = threading.Thread(target=self._read, daemon=True) + self._reader.start() + + def _read(self) -> None: + assert self.proc and self.proc.stdout + with open(self.path, "a", encoding="utf-8") as sink: + for raw in self.proc.stdout: + line = raw.decode().strip() + if " " not in line: + continue + topic, hexed = line.split(" ", 1) + payload = bytes.fromhex(hexed) + with self._lock: + self.messages.append((topic, payload)) + sink.write(json.dumps({"t": time.time(), "topic": topic, "payload": payload.decode("utf-8", "replace")}) + "\n") + sink.flush() + + def envelopes(self) -> list[dict]: + with self._lock: + return [json.loads(payload) for _topic, payload in self.messages] + + def raw(self) -> list[tuple[str, bytes]]: + with self._lock: + return list(self.messages) + + def stop(self) -> None: + if self.proc and self.proc.poll() is None: + self.proc.terminate() + self.proc.wait(5) + + +# --------------------------------------------------------------------------- feeder source + + +SCENES: dict[str, str] = { + # 640x360 "garden" with one or two saturated squares as birds. The detector + # finds them by colour; the pinned classifier names them by colour. + "red_static": "color=c=0x3c6e3c:s=640x360:r=10[bg];color=c=0xdc1414:s=64x64:r=10[red];[bg][red]overlay=x=200:y=150:shortest=1", + # The red bird crosses the frame at 240 px/s: one detection interval apart, + # it is ~240 px away, so a frame other than the inference frame misses it. + "red_moving": "color=c=0x3c6e3c:s=640x360:r=10[bg];color=c=0xdc1414:s=64x64:r=10[red];[bg][red]overlay=x='16+mod(t*240\\,544)':y=150:shortest=1", + "two_birds": "color=c=0x3c6e3c:s=640x360:r=10[bg];color=c=0xdc1414:s=64x64:r=10[red];color=c=0x1414dc:s=64x64:r=10[blue];[bg][red]overlay=x=120:y=140[a];[a][blue]overlay=x=430:y=170:shortest=1", +} + + +def render_scene(workdir: Path, name: str, seconds: int = 300) -> Path: + out = workdir / f"{name}.ts" + if not out.exists(): + graph = SCENES[name] + subprocess.run(["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y", "-filter_complex", f"{graph}[v]", "-map", "[v]", + "-t", str(seconds), "-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p", "-g", "10", "-f", "mpegts", str(out)], check=True, timeout=300) + return out + + +def render_image_scene(workdir: Path, name: str, image: Path, seconds: int = 300) -> Path: + """A real bird photo (scaled to 200 px high) crossing a grey 640x360 frame at 120 px/s.""" + out = workdir / f"{name}.ts" + if not out.exists(): + graph = ("color=c=0x8a8f94:s=640x360:r=10[bg];[0:v]scale=-2:200,format=yuv420p[bird];" + "[bg][bird]overlay=x='mod(t*120\\,500)-20':y=80:shortest=1[v]") + subprocess.run(["ffmpeg", "-nostdin", "-hide_banner", "-loglevel", "error", "-y", "-loop", "1", "-framerate", "10", "-i", str(image), + "-filter_complex", graph, "-map", "[v]", "-t", str(seconds), "-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p", + "-g", "10", "-f", "mpegts", str(out)], check=True, timeout=300) + return out + + +class FeederServer: + """Serves each scene as an endless HTTP MPEG-TS stream at roughly its real bitrate.""" + + def __init__(self, scenes: dict[str, Path], seconds: int): + self.scenes = {name: path.read_bytes() for name, path in scenes.items()} + self.seconds = seconds + outer = self + + class Handler(BaseHTTPRequestHandler): + def log_message(self, *_args): + pass + + def do_GET(self): # noqa: N802 + name = self.path.strip("/").removesuffix(".ts") + data = outer.scenes.get(name) + if data is None: + self.send_error(404) + return + self.send_response(200) + self.send_header("Content-Type", "video/mp2t") + self.end_headers() + rate = max(len(data) / outer.seconds, 188 * 50) # bytes per second + chunk = 188 * 64 + try: + while True: + start = time.monotonic() + for offset in range(0, len(data), chunk): + self.wfile.write(data[offset:offset + chunk]) + target = start + (offset + chunk) / rate + delay = target - time.monotonic() + if delay > 0: + time.sleep(delay) + # End of clip: close, so the client reconnects at a clean start. + return + except (BrokenPipeError, ConnectionResetError): + return + + self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + self.server.daemon_threads = True + self.port = self.server.server_address[1] + threading.Thread(target=self.server.serve_forever, daemon=True).start() + + def url(self, name: str) -> str: + return f"http://127.0.0.1:{self.port}/{name}.ts" + + def close(self) -> None: + self.server.shutdown() + + +# --------------------------------------------------------------------------- detector + + +def find_birds(jpeg: bytes) -> list[dict]: + """Boxes of the saturated red and blue squares, normalized to the submitted image.""" + image = np.asarray(Image.open(io.BytesIO(jpeg)).convert("RGB")).astype(np.int16) + height, width = image.shape[:2] + r, g, b = image[..., 0], image[..., 1], image[..., 2] + boxes = [] + for colour, mask in (("red", (r > 150) & (g < 90) & (b < 90)), ("blue", (b > 150) & (r < 90) & (g < 90))): + ys, xs = np.nonzero(mask) + if len(xs) < 200: + continue + x0, x1, y0, y1 = xs.min(), xs.max() + 1, ys.min(), ys.max() + 1 + boxes.append({"colour": colour, "x_min": x0 / width, "y_min": y0 / height, "x_max": x1 / width, "y_max": y1 / height, + "px": [int(x0), int(y0), int(x1), int(y1)]}) + return boxes + + +class Detector: + """Loopback light-object-detect style detector, armed per camera key. + + ``POST /detect/`` always locates the birds in the submitted JPEG, but + reports them only while ``arm(key, frames)`` has frames left, so each + scenario creates an exact number of observation batches. + """ + + def __init__(self, workdir: Path): + self.log_path = workdir / "detector.jsonl" + self._lock = threading.Lock() + self.quota: dict[str, int] = {} + self.reported: list[dict] = [] # every request that returned birds + self.requests = 0 + outer = self + + class Handler(BaseHTTPRequestHandler): + def log_message(self, *_args): + pass + + def do_POST(self): # noqa: N802 + length = int(self.headers.get("Content-Length") or 0) + body = self.rfile.read(length) + key = self.path.split("?", 1)[0].strip("/").split("/")[-1] + start, end = body.find(b"\xff\xd8\xff"), body.rfind(b"\xff\xd9") + detections: list[dict] = [] + record = None + if start >= 0 and end > start: + jpeg = body[start:end + 2] + with outer._lock: + outer.requests += 1 + armed = outer.quota.get(key, 0) > 0 + birds = find_birds(jpeg) if armed else [] + if armed and birds: + outer.quota[key] -= 1 + record = {"t": time.time(), "key": key, "sha256": hashlib.sha256(jpeg).hexdigest(), "bytes": len(jpeg), "birds": birds} + outer.reported.append(record) + if record: + with open(outer.log_path, "a", encoding="utf-8") as sink: + sink.write(json.dumps(record) + "\n") + detections = [{"label": "bird", "confidence": 0.9 if bird["colour"] == "red" else 0.85, + "x_min": bird["x_min"], "y_min": bird["y_min"], "x_max": bird["x_max"], "y_max": bird["y_max"]} for bird in birds] + payload = json.dumps({"model": "x1-colour-detector", "model_version": "1.0", "detections": detections}).encode() + self.send_response(200) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.wfile.write(payload) + + self.server = ThreadingHTTPServer(("127.0.0.1", 0), Handler) + self.server.daemon_threads = True + self.port = self.server.server_address[1] + threading.Thread(target=self.server.serve_forever, daemon=True).start() + + def url(self, key: str) -> str: + return f"http://127.0.0.1:{self.port}/detect/{key}" + + def arm(self, key: str, frames: int) -> None: + with self._lock: + self.quota[key] = self.quota.get(key, 0) + frames + + def remaining(self, key: str) -> int: + with self._lock: + return self.quota.get(key, 0) + + def disarm(self, key: str) -> None: + with self._lock: + self.quota[key] = 0 + + def reports(self, key: Optional[str] = None) -> list[dict]: + with self._lock: + return [r for r in self.reported if key is None or r["key"] == key] + + def close(self) -> None: + self.server.shutdown() + + +# --------------------------------------------------------------------------- lightNVR + + +class LightNvr: + ADMIN_PASSWORD = "X1-E2E-Admin-123!" + + def __init__(self, workdir: Path, binary: Path, web_root: Path, port: int, mqtt_port: int, topic_prefix: str = "lightnvr"): + self.dir, self.binary, self.port = workdir, binary, port + for sub in ("recordings", "models"): + (workdir / sub).mkdir(parents=True, exist_ok=True) + self.base = f"http://127.0.0.1:{port}" + self.db_path = workdir / "lightnvr.db" + self.log_path = workdir / "server.log" + self.proc: Optional[subprocess.Popen] = None + self.generation = 0 + # The [mqtt] block is the recipe's step 3.1, with the suite's broker address. + (workdir / "lightnvr.ini").write_text( + f"[general]\npid_file = {workdir}/lightnvr.pid\nlog_file = {workdir}/server.log\nlog_level = 2\nsyslog_enabled = false\n" + f"[storage]\npath = {workdir}/recordings\nmax_size = 0\nrecord_mp4_directly = false\n" + f"[database]\npath = {self.db_path}\n" + f"[web]\nport = {port}\nbind_ip = 127.0.0.1\nroot = {web_root}\nauth_enabled = true\nusername = admin\npassword = admin\nweb_thread_pool_size = 8\n" + f"[streams]\nmax_streams = 16\n[models]\npath = {workdir}/models\n[go2rtc]\nenabled = false\nauto_start = false\n" + f"[mqtt]\nenabled = true\nbroker_host = 127.0.0.1\nbroker_port = {mqtt_port}\ntopic_prefix = {topic_prefix}\nqos = 1\n") + self.admin = basic("admin", "admin") + + def _log_offset(self) -> int: + return self.log_path.stat().st_size if self.log_path.exists() else 0 + + def start(self) -> None: + assert self.proc is None or self.proc.poll() is not None + offset = self._log_offset() + env = {k: v for k, v in os.environ.items() if not k.startswith("LIGHTNVR_")} + with open(self.dir / "process.log", "ab") as log: + self.proc = subprocess.Popen([str(self.binary), "-c", str(self.dir / "lightnvr.ini")], cwd=str(self.dir), env=env, + stdout=log, stderr=subprocess.STDOUT) + self.generation += 1 + + def ready() -> bool: + if self.proc.poll() is not None: + raise RuntimeError(f"lightNVR exited during startup ({self.proc.returncode})") + log = self.log_path.read_bytes()[offset:].decode("utf-8", "replace") if self.log_path.exists() else "" + if f"Listening on 127.0.0.1:{self.port}" not in log: + return False + status, _h, _b = http("GET", f"{self.base}/api/streams", timeout=2) + return status == 401 + wait_for(ready, 60, 0.2, "lightNVR to listen") + if self.generation == 1: + # First start: replace the bootstrap password, as an operator would. + http_json("PUT", f"{self.base}/api/auth/users/1/password", {"old_password": "admin", "new_password": self.ADMIN_PASSWORD}, self.admin) + self.admin = basic("admin", self.ADMIN_PASSWORD) + http_json("POST", f"{self.base}/api/setup/status", {"complete": True}, self.admin) + + def log_since(self, offset: int) -> str: + return self.log_path.read_bytes()[offset:].decode("utf-8", "replace") + + def stop(self) -> None: + if self.proc and self.proc.poll() is None: + self.proc.send_signal(signal.SIGTERM) + try: + self.proc.wait(30) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + self.proc = None + + def kill(self) -> None: + assert self.proc and self.proc.poll() is None + self.proc.send_signal(signal.SIGKILL) + self.proc.wait(15) + self.proc = None + + def running(self) -> bool: + return bool(self.proc and self.proc.poll() is None) + + def api(self, method: str, route: str, body: Any = None, expect: tuple[int, ...] = (200, 201), headers: Optional[dict[str, str]] = None) -> Any: + return http_json(method, f"{self.base}{route}", body, headers or self.admin, expect) + + def sql(self, query: str, params: tuple = ()) -> list[tuple]: + with sqlite3.connect(self.db_path, timeout=10) as conn: + return conn.execute(query, params).fetchall() + + def version(self) -> str: + try: + return self.api("GET", "/api/system/info").get("version", "unknown") + except Exception: # noqa: BLE001 + return "unknown" + + +# --------------------------------------------------------------------------- YA-WAMF + + +class YaWamf: + def __init__(self, workdir: Path, repo: Path, port: int, env: dict[str, str], launcher: Path): + self.dir, self.repo, self.port, self.launcher = workdir, repo, port, launcher + self.backend = repo / "backend" + self.python = self.backend / ".venv" / "bin" / "python" + for sub in ("data", "config"): + (workdir / sub).mkdir(parents=True, exist_ok=True) + self.db_path = workdir / "data" / "speciesid.db" + self.classifier_log = workdir / "classifier.jsonl" + self.log_path = workdir / "yawamf.log" + self.base = f"http://127.0.0.1:{port}" + self.env = { + "CONFIG_FILE": str(workdir / "config" / "config.json"), + "DATA_DIR": str(workdir / "data"), + "DB_PATH": str(self.db_path), + "SPECIES_CATALOG_PATH": str(workdir / "data" / "species_catalog.db"), + "MEDIA_CACHE_DIR": str(workdir / "data" / "media_cache"), + "MODEL_DIR": str(workdir / "data" / "models"), + "YAWAMF_E2E_CLASSIFIER_LOG": str(self.classifier_log), + "PATH": f"{self.backend / '.venv' / 'bin'}:{os.environ.get('PATH', '')}", + "HOME": os.environ.get("HOME", str(workdir)), + **env, + } + self.proc: Optional[subprocess.Popen] = None + + def start(self, wait_mqtt: bool = True) -> None: + assert self.proc is None or self.proc.poll() is not None + offset = self.log_path.stat().st_size if self.log_path.exists() else 0 + with open(self.log_path, "ab") as log: + self.proc = subprocess.Popen([str(self.python), str(self.launcher), "--backend", str(self.backend), "--port", str(self.port)], + cwd=str(self.dir), env=self.env, stdout=log, stderr=subprocess.STDOUT) + + def ready() -> bool: + if self.proc.poll() is not None: + raise RuntimeError(f"YA-WAMF exited during startup ({self.proc.returncode})") + status, _h, _b = http("GET", f"{self.base}/health", timeout=3) + if status != 200: + return False + return not wait_mqtt or "Connected to MQTT" in self.log_since(offset) + wait_for(ready, 120, 0.5, "YA-WAMF to start and subscribe") + + def log_since(self, offset: int) -> str: + return self.log_path.read_bytes()[offset:].decode("utf-8", "replace") if self.log_path.exists() else "" + + def log_size(self) -> int: + return self.log_path.stat().st_size if self.log_path.exists() else 0 + + def stop(self) -> None: + if self.proc and self.proc.poll() is None: + self.proc.send_signal(signal.SIGTERM) + try: + self.proc.wait(60) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait() + self.proc = None + + def kill(self) -> None: + assert self.proc and self.proc.poll() is None + self.proc.send_signal(signal.SIGKILL) + self.proc.wait(15) + self.proc = None + + def api(self, method: str, route: str, body: Any = None, expect: tuple[int, ...] = (200, 201), timeout: float = 60) -> Any: + return http_json(method, f"{self.base}{route}", body, None, expect, timeout) + + def sql(self, query: str, params: tuple = ()) -> list[tuple]: + with sqlite3.connect(self.db_path, timeout=10) as conn: + return conn.execute(query, params).fetchall() + + def detections(self) -> list[dict]: + rows = self.sql("SELECT frigate_event, external_observation_id, source_kind, source_instance, source_camera_id, camera_name, " + "scientific_name, display_name, score, notification_intent, notification_outcome FROM detections WHERE source_kind = 'lightnvr'") + keys = ["event", "observation_id", "source_kind", "source_instance", "camera_uuid", "camera_name", "scientific_name", "display_name", "score", "intent", "outcome"] + return [dict(zip(keys, row)) for row in rows] + + def classifier_calls(self) -> list[dict]: + if not self.classifier_log.exists(): + return [] + return [json.loads(line) for line in self.classifier_log.read_text().splitlines() if line.strip()] diff --git a/tests/integration/yawamf_e2e/harness/yawamf_launcher.py b/tests/integration/yawamf_e2e/harness/yawamf_launcher.py new file mode 100644 index 000000000..f8abfd727 --- /dev/null +++ b/tests/integration/yawamf_e2e/harness/yawamf_launcher.py @@ -0,0 +1,82 @@ +"""Start the YA-WAMF backend with a pinned, deterministic bird classifier. + +Test-only. The YA-WAMF code is the branch under test, unmodified; the only +substitution is ``ClassifierService.classify_async_live``, which this launcher +replaces with a colour rule so the E2E suite needs no downloaded model: + +- at least 10% of the crop's pixels are saturated red (R > 140 and R exceeds + G and B by 60) -> ``Prunella modularis`` (0.93) +- otherwise at least 10% saturated blue (the same rule on B) -> + ``Erithacus rubecula`` (0.93) +- anything else (a crop that missed the bird) -> ``[]`` (nothing to admit) + +So a correctly aligned crop of a red or blue fixture bird is identified, and a +misaligned crop (background) yields no species. Every call is appended to +``$YAWAMF_E2E_CLASSIFIER_LOG`` (JSON lines) so the suite can count classifier +calls and see the colour of each crop. + +Run with YA-WAMF's own venv, from any directory: + /backend/.venv/bin/python yawamf_launcher.py --backend /backend --port 8099 +""" +from __future__ import annotations + +import argparse +import json +import os +import sys +import time + +PINNED_CLASSIFIER = "x1-colour-rule-v1" +RED_SPECIES = "Prunella modularis" +BLUE_SPECIES = "Erithacus rubecula" + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--backend", required=True) + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, required=True) + args = parser.parse_args() + + sys.path.insert(0, args.backend) + os.chdir(args.backend) + + import numpy as np # noqa: E402 + from PIL import ImageStat # noqa: E402 + + from app.services import classifier_service as cs # noqa: E402 + + log_path = os.environ.get("YAWAMF_E2E_CLASSIFIER_LOG") + + async def classify_async_live(self, image, camera_name=None, model_id=None, input_context=None, queue_timeout_seconds=None): + rgb = image.convert("RGB") + red, green, blue = ImageStat.Stat(rgb).mean + pixels = np.asarray(rgb).astype(int) + r, g, b = pixels[..., 0], pixels[..., 1], pixels[..., 2] + red_fraction = float(((r > 140) & (r > g + 60) & (r > b + 60)).mean()) + blue_fraction = float(((b > 140) & (b > r + 60) & (b > g + 60)).mean()) + if red_fraction >= 0.10: + results = [{"label": RED_SPECIES, "score": 0.93, "index": 1}] + elif blue_fraction >= 0.10: + results = [{"label": BLUE_SPECIES, "score": 0.93, "index": 2}] + else: + results = [] + if log_path: + with open(log_path, "a", encoding="utf-8") as handle: + handle.write(json.dumps({ + "t": time.time(), "camera": camera_name, "size": list(rgb.size), + "mean_rgb": [round(red, 1), round(green, 1), round(blue, 1)], + "red_fraction": round(red_fraction, 3), "blue_fraction": round(blue_fraction, 3), + "result": results[0]["label"] if results else None, + }) + "\n") + return results + + cs.ClassifierService.classify_async_live = classify_async_live + + import uvicorn # noqa: E402 + + uvicorn.run("app.main:app", host=args.host, port=args.port, log_level="info") + + +if __name__ == "__main__": + main() diff --git a/tests/integration/yawamf_e2e/p2_scenarios.py b/tests/integration/yawamf_e2e/p2_scenarios.py new file mode 100644 index 000000000..951f530a3 --- /dev/null +++ b/tests/integration/yawamf_e2e/p2_scenarios.py @@ -0,0 +1,364 @@ +"""INT_01 X3: P2 classification feedback end to end (A12, A13, A7-annotate). + +Mixed into ``run_e2e.Suite``. YA-WAMF runs with ``LIGHTNVR__WRITEBACK_ENABLED=true`` +(the recipe's optional P2 step), so each detection it classifies is written +back to lightNVR as ``/species`` through +``PUT /api/observations/{id}/classifications/{namespace}/{name}``. + +Everything is read from where each product keeps it: YA-WAMF's ``detections`` +and ``processing_job_state`` rows, lightNVR's ``observation_classifications`` +rows, ``GET /api/observations/{id}``, ``GET /api/detection/results/{stream}`` +(the L15 ``observation`` enrichment) and the MQTT capture. +""" +from __future__ import annotations + +import json +import time +import urllib.parse +import uuid +from typing import Any + +from components import http, wait_for + +ANNOTATION_KEY = "yawamf/species" +MESSAGE_NS = uuid.UUID("0bc01005-a431-522b-9124-a114f67d187a") +WRITEBACK_PIPELINE = "lightnvr_writeback" +INTENT_COLUMNS = ("notification_intent", "notification_intent_at", "notification_outcome", "notification_outcome_at") +REQUEST_FIELDS = ("classifier", "model_version", "label", "taxonomy_id", "confidence", "classified_at", "input_asset_id", "input_revision", "kind") + + +def message_id(source: str, observation: str, revision: int) -> str: + return str(uuid.uuid5(MESSAGE_NS, f"{source}|{observation}|{revision}")) + + +class P2Scenarios: + """A12 / A13 / A7-annotate. Expects the attributes ``run_e2e.Suite`` sets up in ``deploy()``.""" + + # The owner's correction: the fixture bird is red (Prunella), the owner says it is a robin. + CORRECTION = "Erithacus rubecula" + # A13 keeps lightNVR down past the first retry (30 s after the failed attempt), so two attempts fail. + OUTAGE_SECONDS = 45 + + # ------------------------------------------------------------------ helpers + + def lnvr_annotations(self, observation_id: str) -> list[dict]: + rows = self.lnvr.sql("SELECT key, kind, label, confidence, annotation_revision, writer_namespace, input_revision, classifier " + "FROM observation_classifications WHERE observation_uuid = ? ORDER BY key", (observation_id,)) + keys = ("key", "kind", "label", "confidence", "annotation_revision", "writer_namespace", "input_revision", "classifier") + return [dict(zip(keys, row)) for row in rows] + + def lnvr_species(self, observation_id: str) -> dict | None: + rows = [r for r in self.lnvr_annotations(observation_id) if r["key"] == ANNOTATION_KEY] + return rows[0] if rows else None + + def ya_detection(self, event: str) -> dict: + cols = ("frigate_event", "display_name", "scientific_name", "common_name", "category_name", "score", "manual_tagged", *INTENT_COLUMNS) + rows = self.ya.sql(f"SELECT {', '.join(cols)} FROM detections WHERE frigate_event = ?", (event,)) + return dict(zip(cols, rows[0])) if rows else {} + + def writeback_job(self, event: str) -> dict: + rows = self.ya.sql("SELECT status, attempt_count, retry_after, last_error, revision FROM processing_job_state WHERE pipeline = ? AND event_id = ?", + (WRITEBACK_PIPELINE, event)) + return dict(zip(("status", "attempt_count", "retry_after", "last_error", "revision"), rows[0])) if rows else {} + + @staticmethod + def expected_label(row: dict) -> str: + """The label W10 writes: the first usable of common, scientific, category, then display name.""" + for name in ("common_name", "scientific_name", "category_name", "display_name"): + if isinstance(row.get(name), str) and row[name].strip(): + return row[name] + raise AssertionError(f"detection has no species name: {row}") + + def observation_full(self, observation_id: str, headers: dict | None = None) -> dict: + return self.lnvr.api("GET", f"/api/observations/{observation_id}", headers=headers or self.token_headers) + + def stored_annotation(self, observation_id: str) -> dict: + obs = self.observation_full(observation_id) + for kind in ("owner_confirmed", "automated"): + for entry in obs.get("classifications", {}).get(kind, []): + if entry["key"] == ANNOTATION_KEY: + return entry + raise AssertionError(f"{ANNOTATION_KEY} missing from GET /api/observations/{observation_id}: {obs.get('classifications')}") + + def put_annotation(self, observation_id: str, body: dict, token: str | None = None, name: str = "species", namespace: str = "yawamf") -> tuple[int, dict]: + status, _h, payload = http("PUT", f"{self.lnvr.base}/api/observations/{observation_id}/classifications/{namespace}/{name}", body, + {"Authorization": f"Bearer {token or self.token}"}) + return status, (json.loads(payload) if payload else {}) + + def detection_link(self, camera: str, observation_id: str) -> dict: + """The L15 enrichment on lightNVR's detection results for this observation's detection row.""" + observed_ms = self.lnvr.sql("SELECT observed_at_ms FROM object_observations WHERE uuid = ?", (observation_id,))[0][0] + second = observed_ms // 1000 + query = urllib.parse.urlencode({"start": second - 5, "end": second + 5}) + body = self.lnvr.api("GET", f"/api/detection/results/{urllib.parse.quote(camera)}?{query}") + linked = [d for d in body.get("detections", []) if (d.get("observation") or {}).get("id") == observation_id] + if not linked: + raise AssertionError(f"no detection result linked to {observation_id}: {json.dumps(body)[:600]}") + return linked[0] + + def classification_envelopes(self, observation_id: str) -> list[dict]: + return [e for e in self.capture.envelopes() if e["data"]["observation_id"] == observation_id and e["data"]["change_cause"] == "classification"] + + def wait_species(self, observation_id: str, predicate, timeout: float, what: str) -> dict: + return wait_for(lambda: (lambda row: row if row and predicate(row) else None)(self.lnvr_species(observation_id)), timeout, 1.0, what) + + def one_bird(self, camera: str, key: str) -> tuple[dict, dict]: + """Arm one frame, wait for the ready observation and its YA-WAMF row.""" + before = {o["id"] for o in self.observations(camera)} + self.detector.arm(key, 1) + items = [o for o in self.settled(camera, len(before) + 1, 90) if o["id"] not in before] + if len(items) != 1 or items[0]["evidence"]["state"] != "ready": + raise AssertionError(f"expected one new ready observation on {camera}: {items}") + obs = items[0] + row = self.wait_rows(camera, {obs["id"]}, 90) + return obs, next(r for r in row if r["observation_id"] == obs["id"]) + + # ------------------------------------------------------------------ A12 + + def a12(self) -> dict: + """Species in both products; replays and stale revisions keep one annotation; owner precedence; no loop.""" + camera, key = "feeder-wb", "a12" + out: dict[str, Any] = {} + obs, row = self.one_bird(camera, key) + event = row["event"] + calls_before = len(self.classifier_calls(camera)) + + # 1. Species in both products: YA-WAMF's row, then lightNVR's annotation, observation and detection results. + ya = self.ya_detection(event) + label = self.expected_label(ya) + stored = self.wait_species(obs["id"], lambda r: r["kind"] == "automated", 120, "the automated write-back on lightNVR") + expect_(stored["label"] == label and abs(stored["confidence"] - ya["score"]) < 1e-6 and stored["writer_namespace"] == "yawamf", + f"lightNVR stored {stored}, YA-WAMF has {ya}") + expect_(stored["input_revision"] == 2 and stored["annotation_revision"] == 1, f"annotation provenance {stored}") + full = self.observation_full(obs["id"]) + expect_(full["label"] == "bird" and abs(full["confidence"] - obs["confidence"]) < 1e-9, f"detector fields changed: {full['label']} {full['confidence']}") + expect_([a["key"] for a in full["classifications"]["automated"]] == [ANNOTATION_KEY] and not full["classifications"]["owner_confirmed"], + f"GET classifications {full['classifications']}") + expect_("writer_user_id" not in json.dumps(full), "writer_user_id leaked") + link = self.detection_link(camera, obs["id"]) + expect_(link["label"] == "bird" and link["observation"]["classification"]["label"] == label and link["observation"]["classification"]["kind"] == "automated" + and link["observation"]["owner_confirmed"] is False, f"detection results enrichment {link}") + job = self.writeback_job(event) + expect_(job.get("status") == "succeeded", f"write-back job {job}") + out["species_in_both"] = {"observation_id": obs["id"], "yawamf_event": event, "yawamf": {k: ya[k] for k in ("display_name", "scientific_name", "common_name", "score", "manual_tagged")}, + "lightnvr_row": stored, "lightnvr_get_classifications": full["classifications"], + "lightnvr_detection_result": {k: link.get(k) for k in ("label", "confidence", "observation")}, "writeback_job": job} + + # 2. The classification change reaches the broker (revision 3, deterministic ID) and starts no second cycle in YA-WAMF. + wait_for(lambda: self.classification_envelopes(obs["id"]), 60, 1.0, "the classification change on the broker") + env = self.classification_envelopes(obs["id"])[0] + expect_(env["id"] == message_id(self.source_id, obs["id"], 3) and env["data"]["revision"] == 3 and env["data"]["label"] == "bird", + f"classification envelope {env}") + time.sleep(10) # YA-WAMF has received and handled it by now + after = self.ya_detection(event) + rows = [r for r in self.yawamf_rows(camera) if r["observation_id"] == obs["id"]] + expect_(len(rows) == 1, f"{len(rows)} YA-WAMF rows after the classification change") + expect_(len(self.classifier_calls(camera)) == calls_before, f"classifier re-ran on the classification change: {len(self.classifier_calls(camera)) - calls_before}") + expect_(all(after[c] == ya[c] for c in INTENT_COLUMNS), f"notification intent changed: {ya} -> {after}") + expect_(self.writeback_job(event).get("revision") == job.get("revision"), "the classification change queued another write-back") + out["no_feedback_loop"] = {"classification_message_id": env["id"], "observation_revision": env["data"]["revision"], + "classifier_calls_for_observation": calls_before, "intent": {c: after[c] for c in INTENT_COLUMNS}, + "writeback_job_revision": job.get("revision")} + + # 3. Replays and stale revisions: one annotation per (observation, key), conflicts explicit, detector output intact. + replay = [(t, p) for t, p in self.capture.raw() if json.loads(p)["data"]["observation_id"] == obs["id"]] + for _ in range(10): + for topic, payload in replay: + self.broker.publish(topic, payload) + current = self.stored_annotation(obs["id"]) + same = {k: current[k] for k in REQUEST_FIELDS} | {"expected_revision": current["annotation_revision"]} + retry_statuses = [self.put_annotation(obs["id"], same) for _ in range(5)] + expect_(all(s == 200 and b.get("changed") is False for s, b in retry_statuses), f"identical retries {retry_statuses[:2]}") + stale = dict(same, label="Turdus merula", confidence=0.51, expected_revision=0) + stale_status, stale_body = self.put_annotation(obs["id"], stale) + expect_(stale_status == 409 and stale_body.get("code") == "revision_conflict" and stale_body.get("current", {}).get("label") == label, + f"stale revision {stale_status} {stale_body}") + ahead_status, ahead_body = self.put_annotation(obs["id"], dict(stale, expected_revision=current["annotation_revision"] + 5)) + expect_(ahead_status == 409 and ahead_body.get("code") == "revision_conflict", f"future revision {ahead_status} {ahead_body}") + time.sleep(10) + anns = [a for a in self.lnvr_annotations(obs["id"]) if a["key"] == ANNOTATION_KEY] + expect_(len(anns) == 1 and anns[0]["annotation_revision"] == 1 and anns[0]["label"] == label, f"annotations after replay {anns}") + expect_(self.lnvr.sql("SELECT revision FROM object_observations WHERE uuid = ?", (obs["id"],))[0][0] == 3, "observation revision moved on a no-op or a conflict") + expect_(len(self.classification_envelopes(obs["id"])) >= 1 and {e["id"] for e in self.classification_envelopes(obs["id"])} == {env["id"]}, + "a no-op or a conflict published a new revision") + expect_(len([r for r in self.yawamf_rows(camera) if r["observation_id"] == obs["id"]]) == 1 and len(self.classifier_calls(camera)) == calls_before, + "replay re-created or re-classified the detection") + out["replays_and_stale_revisions"] = {"mqtt_messages_replayed": len(replay) * 10, "identical_put_retries": [s for s, _b in retry_statuses], + "stale_revision": {"status": stale_status, "code": stale_body.get("code"), "current_label": stale_body.get("current", {}).get("label")}, + "future_revision": {"status": ahead_status, "code": ahead_body.get("code")}, + "annotations_after": anns, "observation_revision_after": 3} + + # 4. Owner correction in YA-WAMF -> owner_confirmed on lightNVR. + corrected = self.ya.api("PATCH", f"/api/events/{urllib.parse.quote(event, safe='')}", {"display_name": self.CORRECTION}, timeout=120) + ya_owner = self.ya_detection(event) + owner_label = self.expected_label(ya_owner) + expect_(ya_owner["manual_tagged"] in (1, True) and owner_label != label, f"YA-WAMF correction {corrected} -> {ya_owner}") + owner = self.wait_species(obs["id"], lambda r: r["kind"] == "owner_confirmed", 120, "the owner correction on lightNVR") + expect_(owner["label"] == owner_label and owner["confidence"] == 1.0 and owner["annotation_revision"] == 2, f"owner row {owner}") + link = self.detection_link(camera, obs["id"]) + expect_(link["observation"]["owner_confirmed"] is True and link["observation"]["classification"]["kind"] == "owner_confirmed" + and link["observation"]["classification"]["label"] == owner_label and link["label"] == "bird", f"enrichment after the correction {link}") + full = self.observation_full(obs["id"]) + expect_([a["key"] for a in full["classifications"]["owner_confirmed"]] == [ANNOTATION_KEY] and not full["classifications"]["automated"], + f"classifications after the correction {full['classifications']}") + wait_for(lambda: any(e["data"]["revision"] == 4 for e in self.classification_envelopes(obs["id"])), 60, 1.0, "the owner change on the broker") + out["owner_correction"] = {"patch_response": {k: corrected.get(k) for k in ("status", "old_species", "new_species", "manual_tagged")}, + "yawamf": {k: ya_owner[k] for k in ("display_name", "scientific_name", "common_name", "manual_tagged")}, + "lightnvr_row": owner, "lightnvr_detection_result": link["observation"]} + + # 5. A later automated result does not override it. + # a) on lightNVR: an automated PUT from the same writer is refused; + current = self.stored_annotation(obs["id"]) + auto = {k: current[k] for k in REQUEST_FIELDS} | {"kind": "automated", "label": label, "confidence": 0.97, "model_version": "x1-colour-rule-v1", + "classified_at": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()), + "expected_revision": current["annotation_revision"]} + locked_status, locked_body = self.put_annotation(obs["id"], auto) + expect_(locked_status == 409 and locked_body.get("code") == "owner_decision_locked", f"automated over owner: {locked_status} {locked_body}") + # b) in YA-WAMF: the detection reverts to an automated result (as a later automated reclassification would + # leave it) and its write-back job runs again. The worker reads lightNVR first and drops the write. + self.ya.sql("UPDATE detections SET manual_tagged = 0, display_name = ?, scientific_name = ?, common_name = ?, score = 0.97 WHERE frigate_event = ?", + (label, ya["scientific_name"], ya["common_name"], event)) + self.ya.sql("UPDATE processing_job_state SET status = 'queued', retry_after = NULL, last_error = NULL, attempt_count = 0, " + "revision = revision + 1, updated_at = CURRENT_TIMESTAMP WHERE pipeline = ? AND event_id = ?", (WRITEBACK_PIPELINE, event)) + job = wait_for(lambda: (lambda j: j if j.get("status") not in ("queued", "retryable") else None)(self.writeback_job(event)), 90, 1.0, + "the automated write-back attempt (worker poll is 60 s)") + expect_(job["status"] == "terminal" and "owner_decision_locked" in (job.get("last_error") or ""), f"automated attempt over owner decision: {job}") + held = self.lnvr_species(obs["id"]) + expect_(held["kind"] == "owner_confirmed" and held["label"] == owner_label and held["annotation_revision"] == 2, f"owner decision overridden: {held}") + # Put YA-WAMF's owner decision back, so later scenarios see the real state. + self.ya.sql("UPDATE detections SET manual_tagged = 1, display_name = ?, scientific_name = ?, common_name = ?, score = ? WHERE frigate_event = ?", + (ya_owner["display_name"], ya_owner["scientific_name"], ya_owner["common_name"], ya_owner["score"], event)) + expect_(len(self.classifier_calls(camera)) == calls_before, "the owner path ran the classifier") + final = self.ya_detection(event) + expect_(all(final[c] == ya[c] for c in INTENT_COLUMNS), f"notification intent changed by the correction or write-back: {ya} -> {final}") + out["automated_after_owner"] = {"lightnvr_put": {"status": locked_status, "code": locked_body.get("code"), "current_kind": locked_body.get("current", {}).get("kind")}, + "yawamf_writeback_job": job, "lightnvr_row_after": held} + out["totals"] = {"classifier_calls_for_observation": calls_before, "yawamf_rows": 1, "intent": {c: final[c] for c in INTENT_COLUMNS}, + "classification_revisions_published": sorted({e["data"]["revision"] for e in self.classification_envelopes(obs["id"])})} + self.a12_obs = obs + return out + + # ------------------------------------------------------------------ A13 + + def a13(self) -> dict: + """A lightNVR outage during write-back leaves the YA-WAMF detection intact; write-back completes after it returns.""" + camera, key = "feeder-wb-outage", "a13" + out: dict[str, Any] = {} + obs, row = self.one_bird(camera, key) + event = row["event"] + auto = self.wait_species(obs["id"], lambda r: r["kind"] == "automated", 120, "the automated write-back before the outage") + calls_before = len(self.classifier_calls(camera)) + before = self.ya_detection(event) + + self.lnvr.stop() + down = time.time() + try: + corrected = self.ya.api("PATCH", f"/api/events/{urllib.parse.quote(event, safe='')}", {"display_name": self.CORRECTION}, timeout=120) + during = self.ya_detection(event) + owner_label = self.expected_label(during) + expect_(during["manual_tagged"] in (1, True) and owner_label != auto["label"], f"the correction was not saved while lightNVR was down: {during}") + job = wait_for(lambda: (lambda j: j if j.get("status") == "retryable" else None)(self.writeback_job(event)), 60, 1.0, + "a failed write-back attempt while lightNVR is down") + expect_((job.get("last_error") or "").startswith("retry:source_unreachable"), f"outage job {job}") + # The YA-WAMF result stays usable: the event API serves it, and health counts it as waiting. + listed = self.ya.api("GET", "/api/events?" + urllib.parse.urlencode({"event_id": event})) + listed = listed if isinstance(listed, list) else listed.get("events", listed.get("items", [])) + expect_(len(listed) == 1, f"GET /api/events?event_id= during the outage: {listed}") + api_row = listed[0] + health = self.ya.api("GET", "/health").get("classification_writeback", {}) + rows = [r for r in self.yawamf_rows(camera) if r["observation_id"] == obs["id"]] + expect_(len(rows) == 1, f"{len(rows)} rows during the outage") + # Keep lightNVR down a while longer: the result stays put and the job keeps waiting. + time.sleep(self.OUTAGE_SECONDS) + still = self.ya_detection(event) + expect_(still == during, f"the YA-WAMF detection changed during the outage: {during} -> {still}") + job_late = self.writeback_job(event) + expect_(job_late.get("status") == "retryable", f"job gave up during the outage: {job_late}") + outage_seconds = round(time.time() - down, 1) + finally: + self.lnvr.start() + up = time.time() + owner = self.wait_species(obs["id"], lambda r: r["kind"] == "owner_confirmed", 240, "the write-back after lightNVR returned") + converged = round(time.time() - up, 1) + expect_(owner["label"] == owner_label and owner["annotation_revision"] == 2, f"after the outage {owner}") + final_job = self.writeback_job(event) + expect_(final_job.get("status") == "succeeded", f"job after the outage {final_job}") + link = self.detection_link(camera, obs["id"]) + expect_(link["observation"]["owner_confirmed"] is True and link["observation"]["classification"]["label"] == owner_label, f"enrichment {link}") + time.sleep(10) + after = self.ya_detection(event) + expect_(len([r for r in self.yawamf_rows(camera) if r["observation_id"] == obs["id"]]) == 1, "duplicate rows after the outage") + expect_(len(self.classifier_calls(camera)) == calls_before, "the outage or the retry ran the classifier") + expect_(all(after[c] == before[c] for c in INTENT_COLUMNS), f"notification intent changed: {before} -> {after}") + expect_(after["manual_tagged"] in (1, True) and self.expected_label(after) == owner_label, f"YA-WAMF detection changed: {after}") + out.update({"observation_id": obs["id"], "yawamf_event": event, "automated_before_outage": auto, + "during_outage": {"patch_response": {k: corrected.get(k) for k in ("status", "new_species", "manual_tagged")}, + "yawamf_detection": {k: during[k] for k in ("display_name", "scientific_name", "manual_tagged")}, + "event_api_species": {k: api_row.get(k) for k in ("display_name", "scientific_name", "manual_tagged")}, + "writeback_job": job, "writeback_job_end_of_outage": job_late, "health_classification_writeback": health, + "seconds_down": outage_seconds}, + "after_restart": {"lightnvr_row": owner, "writeback_job": final_job, "converged_seconds_after_restart": converged, + "lightnvr_detection_result": link["observation"]}, + "classifier_calls_for_observation": calls_before, "intent": {c: after[c] for c in INTENT_COLUMNS}}) + return out + + # ------------------------------------------------------------------ A7-annotate + + def a7_annotate(self) -> dict: + """A token without observations.annotate gets 403 on PUT and writes nothing; with it, the PUT succeeds.""" + obs = getattr(self, "a12_obs", None) or next(iter(self.ready("feeder-wb")), None) + expect_(obs is not None, "A7-annotate needs a ready observation on feeder-wb (run A12 first)") + scope = {"type": "selector", "selector": {"version": 1, "expression": {"op": "camera_uuid", "values": list(self.cameras.values())}}} + no_annotate = self.lnvr.api("POST", f"/api/authorization/users/{self.user_id}/tokens", { + "description": "X3 read-only probe", "expires_at": int(time.time()) + 86400, "actions": ["live.view", "snapshot.create"], "scope": scope})["secret"] + # A token's selector must equal one of its user's grants, so the other-camera case is a second + # integration user holding the same role on feeder-east only. + other_scope = {"type": "selector", "selector": {"version": 1, "expression": {"op": "camera_uuid", "values": [self.cameras["feeder-east"]]}}} + other = self.lnvr.api("POST", "/api/auth/users", {"username": "yawamf-east", "password": "X3-Yawamf-East-Integration-1!", "role": 2, "is_active": True}) + other_id = other.get("id") or other.get("user", {}).get("id") + policy = self.lnvr.api("GET", f"/api/authorization/users/{other_id}") + self.lnvr.api("PUT", f"/api/authorization/users/{other_id}", {"mode": "policy", "expected_policy_version": policy["policy_version"], + "grants": [{"role_uuid": self.role_uuid, "scope": other_scope}]}) + other_camera = self.lnvr.api("POST", f"/api/authorization/users/{other_id}/tokens", { + "description": "X3 other-camera probe", "expires_at": int(time.time()) + 86400, + "actions": ["live.view", "snapshot.create", "observations.annotate"], "scope": other_scope})["secret"] + other_caps = self.lnvr.api("GET", "/api/integrations/capabilities", headers={"Authorization": f"Bearer {other_camera}"}) + other_ns = other_caps["principal"]["writer_namespace"] + before_rows = self.lnvr_annotations(obs["id"]) + expect_(self.lnvr_species(obs["id"]) is not None, f"no {ANNOTATION_KEY} written back for {obs['id']}: {before_rows}") + current = self.stored_annotation(obs["id"]) + before_rev = self.lnvr.sql("SELECT revision FROM object_observations WHERE uuid = ?", (obs["id"],))[0][0] + body = {k: current[k] for k in REQUEST_FIELDS} | {"label": "Parus major", "expected_revision": current["annotation_revision"]} + probe = {"classifier": "x3-probe", "model_version": None, "label": "Parus major", "taxonomy_id": None, "confidence": 0.5, + "classified_at": time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime()), "input_asset_id": obs["evidence"]["asset_id"], + "input_revision": 2, "kind": "automated", "expected_revision": 0} + denied = self.put_annotation(obs["id"], body, token=no_annotate) + denied_new = self.put_annotation(obs["id"], probe, token=no_annotate, name="x3probe") + hidden = self.put_annotation(obs["id"], probe, token=other_camera, name="x3probe", namespace=other_ns) + expect_(denied[0] == 403 and denied[1].get("code") == "forbidden", f"no-annotate token on an existing key: {denied}") + expect_(denied_new[0] == 403 and denied_new[1].get("code") == "forbidden", f"no-annotate token on a new key: {denied_new}") + expect_(hidden[0] == 404 and hidden[1].get("code") == "observation_not_found", f"token scoped to another camera: {hidden}") + expect_(self.lnvr_annotations(obs["id"]) == before_rows, "a refused PUT wrote a row") + expect_(self.lnvr.sql("SELECT revision FROM object_observations WHERE uuid = ?", (obs["id"],))[0][0] == before_rev, "a refused PUT bumped the revision") + allowed = self.put_annotation(obs["id"], probe, name="x3probe") + expect_(allowed[0] == 201 and allowed[1].get("changed") is True and allowed[1]["annotation"]["key"] == "yawamf/x3probe", f"annotate token: {allowed}") + again = self.put_annotation(obs["id"], probe, name="x3probe") + expect_(again[0] == 200 and again[1].get("changed") is False, f"annotate token retry: {again}") + audit = self.lnvr.sql("SELECT outcome, details_json FROM audit_events WHERE action = 'observations.annotate' ORDER BY id DESC LIMIT 6") + results = [] + for outcome, details in audit: + try: + results.append({"outcome": outcome, "result": json.loads(details or "{}").get("context", {}).get("result")}) + except ValueError: + results.append({"outcome": outcome}) + species = self.lnvr_species(obs["id"]) + expect_(species["kind"] == "owner_confirmed", f"the probe touched {ANNOTATION_KEY}: {species}") + return {"observation_id": obs["id"], "without_annotate": {"existing_key": denied, "new_key": denied_new}, + "annotate_other_camera_scope": {"writer_namespace": other_ns, "response": hidden}, "with_annotate": {"create": {"status": allowed[0], "changed": allowed[1].get("changed")}, + "retry": {"status": again[0], "changed": again[1].get("changed")}}, + "rows_unchanged_by_refusals": True, "audit_latest": results} + + +def expect_(condition: bool, message: str) -> None: + if not condition: + raise AssertionError(message) diff --git a/tests/integration/yawamf_e2e/results/x1-host-20261108-prefix-a6.json b/tests/integration/yawamf_e2e/results/x1-host-20261108-prefix-a6.json new file mode 100644 index 000000000..5347e5c23 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x1-host-20261108-prefix-a6.json @@ -0,0 +1,991 @@ +{ + "suite": "INT_01 X1 lightNVR <-> YA-WAMF E2E", + "mode": "host-process", + "started_at": 1794159426.9800665, + "finished_at": 1794159932.01698, + "seconds": 505.0, + "versions": { + "lightnvr": { + "commit": "e294f956", + "branch": "feat/int01-event-evidence", + "dirty_tracked": true, + "binary": "/home/matteius/opensensor/lightNVR/build-int01-X1/bin/lightnvr" + }, + "yawamf": { + "commit": "fcf449f2", + "branch": "feature/lightnvr-provider" + }, + "mosquitto": "mosquitto version 2.0.22", + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "detector": "loopback HTTP x1-colour-detector 1.0 (light-object-detect wire format; finds the red/blue fixture birds by colour)", + "classifier": "pinned x1-colour-rule-v1 (harness/yawamf_launcher.py replaces ClassifierService.classify_async_live)", + "python": "3.12.12" + }, + "recipe": [ + { + "step": "prereq: MQTT broker", + "outcome": "ok", + "detail": { + "broker": "mosquitto version 2.0.22", + "port": 46415 + } + }, + { + "step": "prereq: lightNVR with [mqtt] block (step 3.1)", + "outcome": "ok", + "detail": { + "version": "0.47.3" + } + }, + { + "step": "step 1-2: detector + evidence per camera", + "outcome": "ok", + "detail": { + "cameras": { + "feeder-bulk": "b97a3d77-844c-4193-90c4-3386ed64bf52", + "feeder-east": "736f7034-0ecf-4d0d-8810-32557fe12ccf", + "feeder-kill": "9d045ad6-2831-42b4-9941-ff04f3753194", + "feeder-local": "9b0541e9-4f2a-4080-a77e-e6e0a4c48e8c", + "feeder-moving": "db0aa4d9-3a9d-4584-b329-28b6071da724", + "feeder-outcomes": "b20fbb9f-f594-425a-887f-4f577747006c", + "feeder-pair": "7fa73bb3-0f4f-4c32-a8e5-6d946f1b3566" + }, + "observation_evidence_enabled": true, + "observation_labels": "bird" + } + }, + { + "step": "step 3.2: event route", + "outcome": "ok", + "detail": { + "route": "64aab6b6-3d87-4341-8916-2aa5537e1418" + } + }, + { + "step": "step 4.2: create role (recipe body)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.2: create role with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.3: grant policy (no expected_policy_version)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.3: grant policy with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.4: scoped token", + "outcome": "ok", + "detail": { + "principal": { + "writer_namespace": "yawamf" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras_evidence_enabled": { + "736f7034-0ecf-4d0d-8810-32557fe12ccf": true, + "7fa73bb3-0f4f-4c32-a8e5-6d946f1b3566": true, + "9b0541e9-4f2a-4080-a77e-e6e0a4c48e8c": false, + "9d045ad6-2831-42b4-9941-ff04f3753194": true, + "b20fbb9f-f594-425a-887f-4f577747006c": true, + "b97a3d77-844c-4193-90c4-3386ed64bf52": true, + "db0aa4d9-3a9d-4584-b329-28b6071da724": true + } + } + }, + { + "step": "step 5: YA-WAMF started (MQTT subscribed)", + "outcome": "ok", + "detail": { + "health_version": "2.23.1-feature/lightnvr-provider+b89846d0" + } + }, + { + "step": "step 6.1: Test lightNVR connection", + "outcome": "ok", + "detail": { + "ok": true, + "results": [ + { + "id": "api_access", + "status": "ok", + "code": "connected", + "message": "Connected to lightNVR 0.47.3.", + "values": { + "product_version": "0.47.3", + "camera_count": 7 + } + }, + { + "id": "event_connectivity", + "status": "ok", + "code": "connected", + "message": "The MQTT broker answered and the lightNVR topic matches.", + "values": { + "topic": "lightnvr" + } + }, + { + "id": "evidence_access", + "status": "warning", + "code": "cameras_without_photos", + "message": "Some chosen cameras do not keep detection photos for this token. Turn on observation photos for them in lightNVR, or allow snapshots.", + "values": { + "cameras": "9b0541e9-4f2a-4080-a77e-e6e0a4c48e8c" + } + }, + { + "id": "features", + "status": "ok", + "code": "listed", + "message": "Available: history, snapshots, annotate. Not available with this source: track_lifecycle, recorded_frame, event_clip, range_clip.", + "values": { + "supported": "history,snapshots,annotate", + "unsupported": "track_lifecycle,recorded_frame,event_clip,range_clip" + } + } + ], + "features": [ + { + "name": "history", + "supported": true, + "reason": null + }, + { + "name": "snapshots", + "supported": true, + "reason": null + }, + { + "name": "annotate", + "supported": true, + "reason": null + }, + { + "name": "track_lifecycle", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "recorded_frame", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "event_clip", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "range_clip", + "supported": false, + "reason": "not_implemented" + } + ], + "latest_observation": { + "state": "none_yet", + "code": null, + "observation_id": null, + "camera_uuid": null, + "camera_name": null, + "label": null, + "observed_at": null, + "photo_state": null, + "photo_reason": null, + "searched_labels": [ + "bird" + ] + } + } + }, + { + "step": "step 6.2: setup probe", + "outcome": "ok", + "detail": { + "status": 200, + "body": { + "items": [], + "next_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiNjQ1NjMwNDUtMjRkOS00NzViLWJjODYtYzdjNWI1NGY0MzkzIn0", + "has_more": false + } + } + }, + { + "step": "step 6.2: setup probe, camera outside the token scope", + "outcome": "ok", + "detail": { + "status": 403 + } + }, + { + "step": "diagnostics: token cannot read /api/system/info", + "outcome": "ok", + "detail": { + "status": 403 + } + } + ], + "deploy_error": null, + "scenarios": [ + { + "id": "A1", + "title": "identity and labels", + "started_at": 1794159432.6980371, + "evidence": { + "observation": { + "id": "b07a4b12-5134-45ef-b035-f603c76876da", + "camera_uuid": "736f7034-0ecf-4d0d-8810-32557fe12ccf", + "stream_name_at_capture": "feeder-east", + "label": "bird", + "confidence": 0.899999976158142, + "box": { + "format": "xywh_normalized", + "x": 0.3125, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "revision": 2 + }, + "evidence": { + "asset_id": "319c20ff-73ee-4062-88a5-6f265421e1ba", + "state": "ready", + "reason": null, + "origin": "submitted_image", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 2125, + "captured_at": "2026-11-08T17:37:13.300Z", + "expires_at": null, + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/b07a4b12-5134-45ef-b035-f603c76876da/snapshot" + }, + "yawamf_row": { + "event": "lnvr:c9f304cb-3ad8-4565-a5b3-c0eb9f38b035:b07a4b12-5134-45ef-b035-f603c76876da", + "observation_id": "b07a4b12-5134-45ef-b035-f603c76876da", + "source_kind": "lightnvr", + "source_instance": "c9f304cb-3ad8-4565-a5b3-c0eb9f38b035", + "camera_uuid": "736f7034-0ecf-4d0d-8810-32557fe12ccf", + "camera_name": "feeder-east", + "scientific_name": "Prunella modularis", + "display_name": "Dunnock", + "score": 0.93, + "intent": "dispatch", + "outcome": "skipped" + }, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/736f7034-0ecf-4d0d-8810-32557fe12ccf", + "mqtt_revisions": { + "1": { + "id": "bf49262e-9f36-5a46-a9bc-7b13d89ad812", + "cause": "created", + "evidence_state": "pending" + }, + "2": { + "id": "f62e4e9b-a2b4-512a-bf42-dc450baed43c", + "cause": "evidence", + "evidence_state": "ready" + } + }, + "classifier_calls": [ + { + "t": 1794159433.3833816, + "camera": "feeder-east", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.9, + 38.3, + 25.7 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ] + }, + "result": "pass", + "seconds": 3.0 + }, + { + "id": "A2", + "title": "moving-bird alignment (HTTP detector)", + "started_at": 1794159435.7207372, + "evidence": { + "detector": "http (loopback x1-colour-detector)", + "observations": [ + { + "id": "297f830a-6367-4a2c-9548-1d9d48b1f295", + "sha256": "d84a6d7aac33817ba45b29fe227186378f221f840f33e34f22feee08e1b466c8", + "box": { + "format": "xywh_normalized", + "x": 0.475, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 304, + 150, + 368, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + { + "id": "46da8b7a-344d-47d6-a9ed-f857dd7c2194", + "sha256": "4338a66ac1a2a64a105d98b41aca3b165df6c10bb84c0c66bf68ca5eafbf7baf", + "box": { + "format": "xywh_normalized", + "x": 0.85, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 544, + 150, + 608, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + { + "id": "d3a49208-ad3b-408e-8c67-b1843977f04e", + "sha256": "731fadb3aaa07602b894dc532fad34c6522c71347e287107835a190c669c18e9", + "box": { + "format": "xywh_normalized", + "x": 0.75, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 480, + 150, + 544, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + } + ], + "bird_x_px_per_frame": [ + 304, + 544, + 480 + ], + "cross_frame_crop_colours": [ + "background", + "background", + "background", + "background", + "background", + "background" + ], + "yawamf_species": [ + "Prunella modularis", + "Prunella modularis", + "Prunella modularis" + ], + "classifier_calls": [ + { + "t": 1794159435.9662864, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794159437.2421205, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794159438.5189548, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ], + "local_detector": "pending: no bird-capable local model drawn on this fixture (see README)" + }, + "result": "pass", + "seconds": 3.1 + }, + { + "id": "A2-local", + "title": "moving-bird alignment (local SOD detector, real bird photo)", + "started_at": 1794159438.78332, + "evidence": { + "detector": "local SOD tiny20.sod (sha256 2f2f0af3c8915093)", + "bird_image_sha256": "85bf66b1c51ad1aeb08d3d90f1a39a59f6cb6161d91f7e7b51d4d187847f3cdf", + "observations": [ + { + "id": "38ed1c36-0049-4b4a-8d94-432047b95367", + "label": "bird", + "confidence": 0.8647809624671936, + "box": { + "format": "xywh_normalized", + "x": 0.13125, + "y": 0.302778, + "width": 0.1625, + "height": 0.427778 + }, + "bird_in_stored_image": [ + 0.1484, + 0.3278, + 0.2625, + 0.6528 + ], + "iou": 0.533, + "origin": "inference_input", + "transform": { + "mode": "resize", + "source_width": 640, + "source_height": 360, + "inference_width": 416, + "inference_height": 416, + "scale_x": 0.65, + "scale_y": 1.15556, + "pad_x": 0, + "pad_y": 0 + }, + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "tiny20.sod", + "model_version": null + } + }, + { + "id": "4c75375b-d1c1-4b6f-9630-e65df4507426", + "label": "bird", + "confidence": 0.9369244575500488, + "box": { + "format": "xywh_normalized", + "x": 0.496875, + "y": 0.294444, + "width": 0.16875, + "height": 0.436111 + }, + "bird_in_stored_image": [ + 0.5234, + 0.3278, + 0.6359, + 0.6528 + ], + "iou": 0.497, + "origin": "inference_input", + "transform": { + "mode": "resize", + "source_width": 640, + "source_height": 360, + "inference_width": 416, + "inference_height": 416, + "scale_x": 0.65, + "scale_y": 1.15556, + "pad_x": 0, + "pad_y": 0 + }, + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "tiny20.sod", + "model_version": null + } + }, + { + "id": "99f27225-2ea4-46c5-baf7-52695984fdd6", + "label": "bird", + "confidence": 0.9514039754867554, + "box": { + "format": "xywh_normalized", + "x": 0.084375, + "y": 0.311111, + "width": 0.18125, + "height": 0.402778 + }, + "bird_in_stored_image": [ + 0.1172, + 0.3278, + 0.2312, + 0.6944 + ], + "iou": 0.573, + "origin": "inference_input", + "transform": { + "mode": "resize", + "source_width": 640, + "source_height": 360, + "inference_width": 416, + "inference_height": 416, + "scale_x": 0.65, + "scale_y": 1.15556, + "pad_x": 0, + "pad_y": 0 + }, + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "tiny20.sod", + "model_version": null + } + } + ], + "yawamf_rows": [ + { + "observation_id": "38ed1c36-0049-4b4a-8d94-432047b95367", + "scientific_name": "Prunella modularis", + "score": 0.93 + }, + { + "observation_id": "4c75375b-d1c1-4b6f-9630-e65df4507426", + "scientific_name": "Prunella modularis", + "score": 0.93 + }, + { + "observation_id": "99f27225-2ea4-46c5-baf7-52695984fdd6", + "scientific_name": "Prunella modularis", + "score": 0.93 + } + ], + "classifier_calls": [ + { + "t": 1794159440.7926214, + "camera": "feeder-local", + "size": [ + 171, + 171 + ], + "mean_rgb": [ + 151.2, + 135.7, + 133.3 + ], + "red_fraction": 0.127, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794159442.2773635, + "camera": "feeder-local", + "size": [ + 174, + 174 + ], + "mean_rgb": [ + 151.4, + 136.7, + 134.4 + ], + "red_fraction": 0.123, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794159444.801629, + "camera": "feeder-local", + "size": [ + 162, + 162 + ], + "mean_rgb": [ + 153.0, + 134.6, + 131.8 + ], + "red_fraction": 0.143, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ] + }, + "result": "pass", + "seconds": 6.2 + }, + { + "id": "A3", + "title": "two birds in one frame", + "started_at": 1794159444.9393938, + "evidence": { + "asset_id": "d9712f94-4c73-43f5-8586-f57600639f06", + "batch_id": "642115d5-5539-4957-bc28-9c5a6c7367d4", + "box_colours": { + "8188615c-2491-446d-b054-d8bdca179bd0": "red", + "9fce4c12-1e69-415a-96aa-86bdf3289545": "blue" + }, + "yawamf_species": { + "8188615c-2491-446d-b054-d8bdca179bd0": "Prunella modularis", + "9fce4c12-1e69-415a-96aa-86bdf3289545": "Erithacus rubecula" + }, + "classifier_calls": [ + { + "t": 1794159446.257124, + "camera": "feeder-pair", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.8 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794159446.2585094, + "camera": "feeder-pair", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 28.2, + 38.0, + 185.0 + ], + "red_fraction": 0.0, + "blue_fraction": 0.789, + "result": "Erithacus rubecula" + } + ], + "yawamf_detection_rows": 2 + }, + "result": "pass", + "seconds": 2.0 + }, + { + "id": "A4", + "title": "replay x10 with overlapping API recovery", + "started_at": 1794159446.9801197, + "evidence": { + "messages_replayed": 60, + "distinct_messages": 6, + "observations": 3, + "rows_after": 3, + "extra_classifier_calls": 0, + "intents": { + "8188615c-2491-446d-b054-d8bdca179bd0": [ + "dispatch", + "skipped" + ], + "9fce4c12-1e69-415a-96aa-86bdf3289545": [ + "dispatch", + "skipped" + ], + "b07a4b12-5134-45ef-b035-f603c76876da": [ + "dispatch", + "skipped" + ] + }, + "overlapping_backfill": { + "status": "completed", + "processed": 9, + "new_detections": 0, + "updated": 0, + "skipped": 9, + "errors": 0, + "skipped_reasons": { + "already_exists": 9 + }, + "error_reasons": {}, + "message": "No new detections found, 9 already existed" + } + }, + "result": "pass", + "seconds": 15.2 + }, + { + "id": "A5", + "title": "broker down for 100 observations, both apps restarted", + "started_at": 1794159462.1623738, + "evidence": { + "observations_created_while_broker_down": 100, + "outbox_states_while_broker_down": { + "delivered": 18, + "pending": 200 + }, + "outbox_states_after": { + "delivered": 218 + }, + "yawamf_rows_while_down": 0, + "yawamf_rows_after_restart": 100, + "converged_seconds_after_restart": 6.6, + "classifier_calls_for_camera": 100, + "outbox": "all 200 revisions delivered after the broker returned", + "intents": { + "suppressed_recovery": 100 + }, + "recovery_status": { + "state": "caught_up", + "source_kind": "lightnvr", + "source_instance": "c9f304cb-3ad8-4565-a5b3-c0eb9f38b035", + "recovery_required": false, + "gap_reason": null, + "cursor_saved": true, + "last_error": null, + "updated_at": "2026-11-08 17:38:54", + "last_run": { + "trigger": "start", + "started_at": 1794159531.149692, + "finished_at": 1794159534.4863207, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 2, + "items": 110, + "outcomes": { + "already_exists": 10, + "admitted": 100 + } + } + } + }, + "result": "pass", + "seconds": 73.1 + }, + { + "id": "A6", + "title": "kill points end to end", + "started_at": 1794159535.308564, + "result": "fail", + "error": "Check: YA-WAMF is missing 2 of 24 observations on feeder-kill after 240s", + "traceback": "Traceback (most recent call last):\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 143, in wait_rows\n return wait_for(done, timeout, 1.0, f\"YA-WAMF detections for {len(ids)} observations on {camera}\")\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/harness/components.py\", line 51, in wait_for\n raise TimeoutError(f\"timed out after {timeout:.0f}s waiting for {what}\" + (f\" (last error: {last_error})\" if last_error else \"\"))\nTimeoutError: timed out after 240s waiting for YA-WAMF detections for 24 observations on feeder-kill\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 176, in scenario\n entry[\"evidence\"] = fn()\n ^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 565, in a6\n rows = self.wait_rows(camera, ready_ids, 240)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 146, in wait_rows\n raise Check(f\"YA-WAMF is missing {len(ids - have)} of {len(ids)} observations on {camera} after {timeout:.0f}s\")\nCheck: YA-WAMF is missing 2 of 24 observations on feeder-kill after 240s\n", + "seconds": 257.3 + }, + { + "id": "A8", + "title": "expired, unavailable, temporary and expired-cursor outcomes", + "started_at": 1794159792.6471102, + "evidence": { + "temporary_lightnvr_unreachable": { + "rows_while_unreachable": 0, + "recovery_status_while_unreachable": { + "state": "unreachable", + "source_kind": null, + "source_instance": null, + "recovery_required": false, + "gap_reason": null, + "cursor_saved": false, + "last_error": null, + "updated_at": null, + "last_run": { + "trigger": "start", + "started_at": 1794159802.375418, + "finished_at": 1794159807.3774307, + "result": "unreachable", + "detail": "lightNVR request to /api/integrations/capabilities timed out", + "start": null, + "pages": 0, + "items": 0, + "outcomes": {} + } + }, + "rows_after_resume": 2, + "converged_seconds_after_resume": 16.0 + }, + "expired_and_unavailable": { + "lightnvr": { + "5a2c4d3a-f516-4c29-9316-b415497c5e40": { + "case": "expired", + "state": "expired", + "reason": "retention_expired", + "snapshot_status": 410, + "snapshot_code": "evidence_expired" + }, + "7a28b9ea-18b0-4387-b04f-13ced3f75fe2": { + "case": "expired", + "state": "expired", + "reason": "retention_expired", + "snapshot_status": 410, + "snapshot_code": "evidence_expired" + }, + "6f15df83-09da-4c4f-9188-c6f0759bdaab": { + "case": "unavailable", + "state": "unavailable", + "reason": "write_failed", + "snapshot_status": 404, + "snapshot_code": "evidence_unavailable" + }, + "3e375a2b-54e2-4275-ae88-0ead30ee693b": { + "case": "unavailable", + "state": "unavailable", + "reason": "write_failed", + "snapshot_status": 404, + "snapshot_code": "evidence_unavailable" + } + }, + "recovery_run": { + "trigger": "start", + "started_at": 1794159910.3652592, + "finished_at": 1794159910.3734753, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 1, + "items": 4, + "outcomes": { + "evidence_expired": 2, + "evidence_unavailable": 2 + } + }, + "yawamf_rows": 0, + "backfill": { + "status": "completed", + "processed": 140, + "new_detections": 0, + "updated": 0, + "skipped": 140, + "errors": 0, + "skipped_reasons": { + "already_exists": 136, + "evidence_expired": 2, + "evidence_unavailable": 2 + }, + "error_reasons": {}, + "message": "No new detections found, 136 already existed, 2 with evidence expired, 2 without an evidence image" + } + }, + "expired_cursor_after_restore": { + "old_cursor_response": { + "status": 410, + "body": { + "error": "Cursor is older than retained observation history", + "code": "cursor_expired", + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiNjlkOTE4MWItZDBkZC00YjVkLTk0MzktMjgxMGIxNjRhMTgwIn0", + "oldest_observed_at": "2026-10-31T17:43:59.237Z" + } + }, + "recovery_after_restore": { + "state": "caught_up", + "source_kind": "lightnvr", + "source_instance": "c9f304cb-3ad8-4565-a5b3-c0eb9f38b035", + "recovery_required": true, + "gap_reason": "cursor_expired", + "cursor_saved": true, + "last_error": null, + "updated_at": "2026-11-08 17:45:31", + "last_run": { + "trigger": "start", + "started_at": 1794159931.750674, + "finished_at": 1794159931.8089347, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 2, + "items": 140, + "outcomes": { + "already_exists": 136, + "evidence_expired": 2, + "evidence_unavailable": 2 + } + } + }, + "diagnostics_entries": null, + "after_acknowledge": { + "state": "caught_up", + "recovery_required": false, + "gap_reason": null + }, + "observations_lost_by_restore_still_in_yawamf": 2 + } + }, + "result": "pass", + "seconds": 139.4 + } + ], + "workdir": "/tmp/claude-1000/-home-matteius-opensensor-lightNVR/b308eba0-16af-4c23-91be-f0b323521d94/scratchpad/final", + "counts": { + "detector_requests": 2047, + "mqtt_messages_captured": 346 + } +} diff --git a/tests/integration/yawamf_e2e/results/x1-host-20261108.classifier.jsonl b/tests/integration/yawamf_e2e/results/x1-host-20261108.classifier.jsonl new file mode 100644 index 000000000..c13420a26 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x1-host-20261108.classifier.jsonl @@ -0,0 +1,157 @@ +{"t": 1794161321.7186322, "camera": "feeder-east", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161324.298721, "camera": "feeder-moving", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.4], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161325.5808592, "camera": "feeder-moving", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.4], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161326.8577363, "camera": "feeder-moving", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.4], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161329.1054893, "camera": "feeder-local", "size": [171, 171], "mean_rgb": [151.2, 135.7, 133.3], "red_fraction": 0.127, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161331.598742, "camera": "feeder-local", "size": [148, 148], "mean_rgb": [155.0, 131.9, 128.3], "red_fraction": 0.171, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161333.1178896, "camera": "feeder-local", "size": [162, 162], "mean_rgb": [153.0, 134.6, 131.8], "red_fraction": 0.143, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161334.5868456, "camera": "feeder-pair", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161334.5878732, "camera": "feeder-pair", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161421.9710772, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.5575168, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.568049, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.5718572, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.6231596, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.6268601, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.6776402, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.6815438, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.7327435, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.736409, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.7874856, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.7909572, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.8406436, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.8444417, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.894659, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.8981237, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161422.9488606, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161422.9522452, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.0027075, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.0061634, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.0580208, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.0621278, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.1136832, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.1188033, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.1696565, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.1735623, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.224598, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.2280822, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.2775946, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.2808871, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.3325288, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.3363159, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.3867218, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.3903866, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.4423523, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.4457638, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.4956827, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.499466, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.5497081, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.553633, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.6043835, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.6077175, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.658728, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.6621258, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.7116165, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.7149599, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.7667546, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.770136, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.8196182, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.8229485, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.8726456, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.876521, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.9267995, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.9302993, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161423.9795527, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161423.9826827, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.0315738, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.0349905, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.0856645, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.0888224, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.1383913, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.1415925, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.1904287, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.1938295, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.2436771, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.2469811, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.2984295, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.3016815, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.3506672, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.3537364, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.4051497, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.4083233, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.4594452, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.4627848, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.5126376, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.515894, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.5674486, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.5706227, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.6207755, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.6242034, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.6734262, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.6768017, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.7275784, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.730873, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.7805076, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.7839675, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.8344839, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.8377209, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.8884425, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.8914974, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161424.9404647, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161424.9531913, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161425.0040777, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161425.007984, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161425.058446, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161425.0617077, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161425.1144867, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161425.1182482, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161425.1687846, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161425.1722517, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161427.1417933, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161427.1427832, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161428.4207027, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161428.4216874, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161429.6999369, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161429.700914, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161430.9619899, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161430.9629467, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161433.7006218, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161433.702538, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161434.9970362, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161434.9980118, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161436.2830136, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161436.2840028, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161437.5467424, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161437.548969, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161438.829399, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161438.8309593, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161440.0978048, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161440.098819, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161441.3715456, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161441.37594, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161442.6623511, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161442.6646626, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161443.9498832, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161443.9508457, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161445.2249868, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161445.2260013, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.04178, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.568994, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.5785975, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.5829806, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.6338136, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.6386464, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.6908512, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.6954217, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.7453787, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.7498693, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.8014984, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.805585, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.8565922, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.8602378, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161458.910707, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161458.9146326, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794161504.3207612, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161504.8957493, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161588.1233702, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794161589.39786, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} diff --git a/tests/integration/yawamf_e2e/results/x1-host-20261108.detector.jsonl b/tests/integration/yawamf_e2e/results/x1-host-20261108.detector.jsonl new file mode 100644 index 000000000..fb86a8ade --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x1-host-20261108.detector.jsonl @@ -0,0 +1,85 @@ +{"t": 1794161321.6505115, "key": "a1", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161324.2366648, "key": "a2", "sha256": "d84a6d7aac33817ba45b29fe227186378f221f840f33e34f22feee08e1b466c8", "bytes": 1867, "birds": [{"colour": "red", "x_min": 0.475, "y_min": 0.4166666666666667, "x_max": 0.575, "y_max": 0.5944444444444444, "px": [304, 150, 368, 214]}]} +{"t": 1794161325.516237, "key": "a2", "sha256": "4338a66ac1a2a64a105d98b41aca3b165df6c10bb84c0c66bf68ca5eafbf7baf", "bytes": 1867, "birds": [{"colour": "red", "x_min": 0.85, "y_min": 0.4166666666666667, "x_max": 0.95, "y_max": 0.5944444444444444, "px": [544, 150, 608, 214]}]} +{"t": 1794161326.7964032, "key": "a2", "sha256": "731fadb3aaa07602b894dc532fad34c6522c71347e287107835a190c669c18e9", "bytes": 1867, "birds": [{"colour": "red", "x_min": 0.75, "y_min": 0.4166666666666667, "x_max": 0.85, "y_max": 0.5944444444444444, "px": [480, 150, 544, 214]}]} +{"t": 1794161334.5092654, "key": "a3", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161351.183718, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161352.4633746, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161353.7429576, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161355.023503, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161356.3034863, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161357.583092, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161358.8626652, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161360.143298, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161361.4232824, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161362.702742, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161363.9832907, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161365.2630188, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161366.5431244, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161367.8239033, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161369.1031702, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161370.3831568, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161371.662898, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161372.9429767, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161374.2231646, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161375.5031064, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161376.783115, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161378.0628898, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161379.3428502, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161380.6230264, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161381.903113, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161383.182769, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161384.463003, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161385.7431848, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161387.0230577, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161388.303429, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161389.5830126, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161390.8631947, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161392.1429155, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161393.4233534, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161394.7031484, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161395.982817, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161397.2626057, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161398.5428796, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161399.823289, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161401.1033869, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161402.3829973, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161403.6627984, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161404.9429781, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161406.2230654, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161407.5032423, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161408.7830472, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161410.063211, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161411.3436935, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161412.6227543, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161413.902906, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161427.0580218, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161428.3390608, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161429.617133, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161430.8976643, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161433.6171064, "key": "a6", "sha256": "666ac51dac4763b68e6fd1043fbd181360cd3a9829aaf970c6f39364dba8f610", "bytes": 2827, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161434.8974147, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161436.1774378, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161437.4568267, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161438.7369177, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161440.0161614, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161441.2964683, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161442.5764372, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161443.8573868, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161445.1381483, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161446.4172454, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161447.6964338, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161448.9764225, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161450.2567093, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161451.536924, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161452.8171084, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161454.097653, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161455.3775568, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794161460.4986136, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161461.7784894, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161506.1683266, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161507.4468184, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161508.7264996, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161510.0100348, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161588.0526662, "key": "a8", "sha256": "7aef40f8db2e08620cf354a13870961c0e18e857c1f07f6c2823738980b4d031", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794161589.3323946, "key": "a8", "sha256": "7aef40f8db2e08620cf354a13870961c0e18e857c1f07f6c2823738980b4d031", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} diff --git a/tests/integration/yawamf_e2e/results/x1-host-20261108.json b/tests/integration/yawamf_e2e/results/x1-host-20261108.json new file mode 100644 index 000000000..750c883a1 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x1-host-20261108.json @@ -0,0 +1,1032 @@ +{ + "suite": "INT_01 X1 lightNVR <-> YA-WAMF E2E", + "mode": "host-process", + "started_at": 1794161315.3228471, + "finished_at": 1794161600.9277005, + "seconds": 285.6, + "versions": { + "lightnvr": { + "commit": "e294f956", + "branch": "feat/int01-event-evidence", + "dirty_tracked": true, + "binary": "/home/matteius/opensensor/lightNVR/build-int01-X1/bin/lightnvr" + }, + "yawamf": { + "commit": "1cbbae74", + "branch": "feature/lightnvr-provider" + }, + "mosquitto": "mosquitto version 2.0.22", + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "detector": "loopback HTTP x1-colour-detector 1.0 (light-object-detect wire format; finds the red/blue fixture birds by colour)", + "classifier": "pinned x1-colour-rule-v1 (harness/yawamf_launcher.py replaces ClassifierService.classify_async_live)", + "python": "3.12.12" + }, + "recipe": [ + { + "step": "prereq: MQTT broker", + "outcome": "ok", + "detail": { + "broker": "mosquitto version 2.0.22", + "port": 46469 + } + }, + { + "step": "prereq: lightNVR with [mqtt] block (step 3.1)", + "outcome": "ok", + "detail": { + "version": "0.47.3" + } + }, + { + "step": "step 1-2: detector + evidence per camera", + "outcome": "ok", + "detail": { + "cameras": { + "feeder-bulk": "a292d981-f8b6-4fd5-b194-fb827336840d", + "feeder-east": "1ec86da7-9373-4b52-8d09-b0c598f37918", + "feeder-kill": "8db6637e-dc86-44fd-b0ff-cf95ef48ea52", + "feeder-local": "08d1344b-a324-4bd3-b567-38ecab646135", + "feeder-moving": "e427dfe3-b009-4f3d-955d-ced6062da73b", + "feeder-outcomes": "1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", + "feeder-pair": "5cc522ad-22f6-4bb8-9200-a93220acbfab" + }, + "observation_evidence_enabled": true, + "observation_labels": "bird" + } + }, + { + "step": "step 3.2: event route", + "outcome": "ok", + "detail": { + "route": "ead81d93-77c3-49ad-ae34-31cecb064bec" + } + }, + { + "step": "step 4.2: create role (recipe body)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.2: create role with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.3: grant policy (no expected_policy_version)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.3: grant policy with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.4: scoped token", + "outcome": "ok", + "detail": { + "principal": { + "writer_namespace": "yawamf" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras_evidence_enabled": { + "08d1344b-a324-4bd3-b567-38ecab646135": false, + "1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa": true, + "1ec86da7-9373-4b52-8d09-b0c598f37918": true, + "5cc522ad-22f6-4bb8-9200-a93220acbfab": true, + "8db6637e-dc86-44fd-b0ff-cf95ef48ea52": true, + "a292d981-f8b6-4fd5-b194-fb827336840d": true, + "e427dfe3-b009-4f3d-955d-ced6062da73b": true + } + } + }, + { + "step": "step 5: YA-WAMF started (MQTT subscribed)", + "outcome": "ok", + "detail": { + "health_version": "2.23.1-feature/lightnvr-provider+1cbbae74" + } + }, + { + "step": "step 6.1: Test lightNVR connection", + "outcome": "ok", + "detail": { + "ok": true, + "results": [ + { + "id": "api_access", + "status": "ok", + "code": "connected", + "message": "Connected to lightNVR 0.47.3.", + "values": { + "product_version": "0.47.3", + "camera_count": 7 + } + }, + { + "id": "event_connectivity", + "status": "ok", + "code": "connected", + "message": "The MQTT broker answered and the lightNVR topic matches.", + "values": { + "topic": "lightnvr" + } + }, + { + "id": "evidence_access", + "status": "warning", + "code": "cameras_without_photos", + "message": "Some chosen cameras do not keep detection photos for this token. Turn on observation photos for them in lightNVR, or allow snapshots.", + "values": { + "cameras": "08d1344b-a324-4bd3-b567-38ecab646135" + } + }, + { + "id": "features", + "status": "ok", + "code": "listed", + "message": "Available: history, snapshots, annotate. Not available with this source: track_lifecycle, recorded_frame, event_clip, range_clip.", + "values": { + "supported": "history,snapshots,annotate", + "unsupported": "track_lifecycle,recorded_frame,event_clip,range_clip" + } + } + ], + "features": [ + { + "name": "history", + "supported": true, + "reason": null + }, + { + "name": "snapshots", + "supported": true, + "reason": null + }, + { + "name": "annotate", + "supported": true, + "reason": null + }, + { + "name": "track_lifecycle", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "recorded_frame", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "event_clip", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "range_clip", + "supported": false, + "reason": "not_implemented" + } + ], + "latest_observation": { + "state": "none_yet", + "code": null, + "observation_id": null, + "camera_uuid": null, + "camera_name": null, + "label": null, + "observed_at": null, + "photo_state": null, + "photo_reason": null, + "searched_labels": [ + "bird" + ] + } + } + }, + { + "step": "step 6.2: setup probe", + "outcome": "ok", + "detail": { + "status": 200, + "body": { + "items": [], + "next_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiZDViZmYyNDUtYzUzMS00ZmM4LThhY2EtMGE3Zjk1NDE1YWE0In0", + "has_more": false + } + } + }, + { + "step": "step 6.2: setup probe, camera outside the token scope", + "outcome": "ok", + "detail": { + "status": 403 + } + }, + { + "step": "diagnostics: token cannot read /api/system/info", + "outcome": "ok", + "detail": { + "status": 403 + } + } + ], + "deploy_error": null, + "scenarios": [ + { + "id": "A1", + "title": "identity and labels", + "started_at": 1794161321.5314584, + "evidence": { + "observation": { + "id": "d07d075d-55d7-495d-a69e-be06fb5a1346", + "camera_uuid": "1ec86da7-9373-4b52-8d09-b0c598f37918", + "stream_name_at_capture": "feeder-east", + "label": "bird", + "confidence": 0.899999976158142, + "box": { + "format": "xywh_normalized", + "x": 0.3125, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "revision": 2 + }, + "evidence": { + "asset_id": "bed89e80-7b93-4e5a-8dcb-babfbfc8d709", + "state": "ready", + "reason": null, + "origin": "submitted_image", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 2125, + "captured_at": "2026-11-08T18:08:41.640Z", + "expires_at": null, + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot" + }, + "yawamf_row": { + "event": "lnvr:12e0cfcb-359e-460f-af91-093a2c93724f:d07d075d-55d7-495d-a69e-be06fb5a1346", + "observation_id": "d07d075d-55d7-495d-a69e-be06fb5a1346", + "source_kind": "lightnvr", + "source_instance": "12e0cfcb-359e-460f-af91-093a2c93724f", + "camera_uuid": "1ec86da7-9373-4b52-8d09-b0c598f37918", + "camera_name": "feeder-east", + "scientific_name": "Prunella modularis", + "display_name": "Dunnock", + "score": 0.93, + "intent": "dispatch", + "outcome": "skipped" + }, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", + "mqtt_revisions": { + "1": { + "id": "4fa98daa-716e-597f-860e-59b6567bd9b2", + "cause": "created", + "evidence_state": "pending" + }, + "2": { + "id": "188f7cc4-0a23-566f-9186-a25a90efee26", + "cause": "evidence", + "evidence_state": "ready" + } + }, + "classifier_calls": [ + { + "t": 1794161321.7186322, + "camera": "feeder-east", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.9, + 38.3, + 25.7 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ] + }, + "result": "pass", + "seconds": 2.0 + }, + { + "id": "A2", + "title": "moving-bird alignment (HTTP detector)", + "started_at": 1794161323.552895, + "evidence": { + "detector": "http (loopback x1-colour-detector)", + "observations": [ + { + "id": "cb7110c5-4692-4c8e-a83e-cc4c2707a7e9", + "sha256": "d84a6d7aac33817ba45b29fe227186378f221f840f33e34f22feee08e1b466c8", + "box": { + "format": "xywh_normalized", + "x": 0.475, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 304, + 150, + 368, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + { + "id": "101b69a9-337a-4ba2-91f0-36b378ec6d1d", + "sha256": "4338a66ac1a2a64a105d98b41aca3b165df6c10bb84c0c66bf68ca5eafbf7baf", + "box": { + "format": "xywh_normalized", + "x": 0.85, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 544, + 150, + 608, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + { + "id": "17e8ecd6-0231-4221-8e67-1e21fc934cac", + "sha256": "731fadb3aaa07602b894dc532fad34c6522c71347e287107835a190c669c18e9", + "box": { + "format": "xywh_normalized", + "x": 0.75, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 480, + 150, + 544, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + } + ], + "bird_x_px_per_frame": [ + 304, + 544, + 480 + ], + "cross_frame_crop_colours": [ + "background", + "background", + "background", + "background", + "background", + "background" + ], + "yawamf_species": [ + "Prunella modularis", + "Prunella modularis", + "Prunella modularis" + ], + "classifier_calls": [ + { + "t": 1794161324.298721, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794161325.5808592, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794161326.8577363, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ], + "local_detector": "pending: no bird-capable local model drawn on this fixture (see README)" + }, + "result": "pass", + "seconds": 4.1 + }, + { + "id": "A2-local", + "title": "moving-bird alignment (local SOD detector, real bird photo)", + "started_at": 1794161327.626307, + "evidence": { + "detector": "local SOD tiny20.sod (sha256 2f2f0af3c8915093)", + "bird_image_sha256": "85bf66b1c51ad1aeb08d3d90f1a39a59f6cb6161d91f7e7b51d4d187847f3cdf", + "observations": [ + { + "id": "4beb8152-ba61-44ad-bb4c-a27dce9895c2", + "label": "bird", + "confidence": 0.8647809624671936, + "box": { + "format": "xywh_normalized", + "x": 0.13125, + "y": 0.302778, + "width": 0.1625, + "height": 0.427778 + }, + "bird_in_stored_image": [ + 0.1484, + 0.3278, + 0.2625, + 0.6528 + ], + "iou": 0.533, + "origin": "inference_input", + "transform": { + "mode": "resize", + "source_width": 640, + "source_height": 360, + "inference_width": 416, + "inference_height": 416, + "scale_x": 0.65, + "scale_y": 1.15556, + "pad_x": 0, + "pad_y": 0 + }, + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "tiny20.sod", + "model_version": null + } + }, + { + "id": "9a28105c-1941-4d9b-a98c-ee51dcb228e4", + "label": "bird", + "confidence": 0.936571717262268, + "box": { + "format": "xywh_normalized", + "x": 0.69375, + "y": 0.322222, + "width": 0.16875, + "height": 0.369444 + }, + "bird_in_stored_image": [ + 0.7109, + 0.3278, + 0.825, + 0.6889 + ], + "iou": 0.661, + "origin": "inference_input", + "transform": { + "mode": "resize", + "source_width": 640, + "source_height": 360, + "inference_width": 416, + "inference_height": 416, + "scale_x": 0.65, + "scale_y": 1.15556, + "pad_x": 0, + "pad_y": 0 + }, + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "tiny20.sod", + "model_version": null + } + }, + { + "id": "22dd9e4f-9422-4857-88ff-5818c10b1836", + "label": "bird", + "confidence": 0.9514039754867554, + "box": { + "format": "xywh_normalized", + "x": 0.084375, + "y": 0.311111, + "width": 0.18125, + "height": 0.402778 + }, + "bird_in_stored_image": [ + 0.1172, + 0.3278, + 0.2312, + 0.6944 + ], + "iou": 0.573, + "origin": "inference_input", + "transform": { + "mode": "resize", + "source_width": 640, + "source_height": 360, + "inference_width": 416, + "inference_height": 416, + "scale_x": 0.65, + "scale_y": 1.15556, + "pad_x": 0, + "pad_y": 0 + }, + "detector": { + "engine_key": "legacy-primary", + "engine_type": "object", + "model": "tiny20.sod", + "model_version": null + } + } + ], + "yawamf_rows": [ + { + "observation_id": "22dd9e4f-9422-4857-88ff-5818c10b1836", + "scientific_name": "Prunella modularis", + "score": 0.93 + }, + { + "observation_id": "4beb8152-ba61-44ad-bb4c-a27dce9895c2", + "scientific_name": "Prunella modularis", + "score": 0.93 + }, + { + "observation_id": "9a28105c-1941-4d9b-a98c-ee51dcb228e4", + "scientific_name": "Prunella modularis", + "score": 0.93 + } + ], + "classifier_calls": [ + { + "t": 1794161329.1054893, + "camera": "feeder-local", + "size": [ + 171, + 171 + ], + "mean_rgb": [ + 151.2, + 135.7, + 133.3 + ], + "red_fraction": 0.127, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794161331.598742, + "camera": "feeder-local", + "size": [ + 148, + 148 + ], + "mean_rgb": [ + 155.0, + 131.9, + 128.3 + ], + "red_fraction": 0.171, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794161333.1178896, + "camera": "feeder-local", + "size": [ + 162, + 162 + ], + "mean_rgb": [ + 153.0, + 134.6, + 131.8 + ], + "red_fraction": 0.143, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ] + }, + "result": "pass", + "seconds": 6.2 + }, + { + "id": "A3", + "title": "two birds in one frame", + "started_at": 1794161333.781131, + "evidence": { + "asset_id": "91027bf9-8fce-4618-baf2-4c27d5e8ac08", + "batch_id": "5fe7413d-438d-4872-98d1-9bd796de8d8e", + "box_colours": { + "0818984d-9f18-4816-a629-8a0dcd5310f6": "red", + "746f7429-8c09-4924-809a-52f8969cda2d": "blue" + }, + "yawamf_species": { + "0818984d-9f18-4816-a629-8a0dcd5310f6": "Prunella modularis", + "746f7429-8c09-4924-809a-52f8969cda2d": "Erithacus rubecula" + }, + "classifier_calls": [ + { + "t": 1794161334.5868456, + "camera": "feeder-pair", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 28.2, + 38.0, + 185.0 + ], + "red_fraction": 0.0, + "blue_fraction": 0.789, + "result": "Erithacus rubecula" + }, + { + "t": 1794161334.5878732, + "camera": "feeder-pair", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.8 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ], + "yawamf_detection_rows": 2 + }, + "result": "pass", + "seconds": 2.0 + }, + { + "id": "A4", + "title": "replay x10 with overlapping API recovery", + "started_at": 1794161335.8095086, + "evidence": { + "messages_replayed": 60, + "distinct_messages": 6, + "observations": 3, + "rows_after": 3, + "extra_classifier_calls": 0, + "intents": { + "0818984d-9f18-4816-a629-8a0dcd5310f6": [ + "dispatch", + "skipped" + ], + "746f7429-8c09-4924-809a-52f8969cda2d": [ + "dispatch", + "skipped" + ], + "d07d075d-55d7-495d-a69e-be06fb5a1346": [ + "dispatch", + "skipped" + ] + }, + "overlapping_backfill": { + "status": "completed", + "processed": 9, + "new_detections": 0, + "updated": 0, + "skipped": 9, + "errors": 0, + "skipped_reasons": { + "already_exists": 9 + }, + "error_reasons": {}, + "message": "No new detections found, 9 already existed" + } + }, + "result": "pass", + "seconds": 15.2 + }, + { + "id": "A5", + "title": "broker down for 100 observations, both apps restarted", + "started_at": 1794161350.9806743, + "evidence": { + "observations_created_while_broker_down": 100, + "outbox_states_while_broker_down": { + "delivered": 18, + "pending": 200 + }, + "outbox_states_after": { + "delivered": 218 + }, + "yawamf_rows_while_down": 0, + "yawamf_rows_after_restart": 100, + "converged_seconds_after_restart": 6.6, + "classifier_calls_for_camera": 100, + "outbox": "all 200 revisions delivered after the broker returned", + "intents": { + "suppressed_recovery": 100 + }, + "recovery_status": { + "state": "caught_up", + "source_kind": "lightnvr", + "source_instance": "12e0cfcb-359e-460f-af91-093a2c93724f", + "recovery_required": false, + "gap_reason": null, + "cursor_saved": true, + "last_error": null, + "updated_at": "2026-11-08 18:10:25", + "last_run": { + "trigger": "start", + "started_at": 1794161421.9046676, + "finished_at": 1794161425.174027, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 2, + "items": 110, + "outcomes": { + "already_exists": 10, + "admitted": 100 + } + } + } + }, + "result": "pass", + "seconds": 75.2 + }, + { + "id": "A6", + "title": "kill points end to end", + "started_at": 1794161426.1383388, + "evidence": { + "phase1_lightnvr_sigkill": { + "frames_reported_before_kill": 4, + "observations": 24, + "integrity": { + "asset_states": { + "ready": 12 + }, + "final_files": 12, + "tmp_files": 0 + }, + "startup_log": [ + "[2026-11-08 13:10:31.029] [INFO] Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "[2026-11-08 13:10:31.044] [INFO] Observation publication relay started" + ], + "converged_seconds_after_restart": 12.0, + "ready": 24, + "not_ready": {}, + "yawamf_rows": 24 + }, + "phase2_yawamf_sigkill": { + "observations_total": 44, + "ready": 44, + "yawamf_rows": 44, + "classifier_calls_in_phase": 20, + "converged_seconds_after_restart": 4.1, + "integrity": { + "asset_states": { + "ready": 22 + }, + "final_files": 22, + "tmp_files": 0 + } + } + }, + "result": "pass", + "seconds": 33.4 + }, + { + "id": "A8", + "title": "expired, unavailable, temporary and expired-cursor outcomes", + "started_at": 1794161459.5679119, + "evidence": { + "temporary_lightnvr_unreachable": { + "rows_while_unreachable": 0, + "recovery_status_while_unreachable": { + "state": "unreachable", + "source_kind": null, + "source_instance": null, + "recovery_required": false, + "gap_reason": null, + "cursor_saved": false, + "last_error": null, + "updated_at": null, + "last_run": { + "trigger": "start", + "started_at": 1794161469.2491968, + "finished_at": 1794161474.2509947, + "result": "unreachable", + "detail": "lightNVR request to /api/integrations/capabilities timed out", + "start": null, + "pages": 0, + "items": 0, + "outcomes": {} + } + }, + "rows_after_resume": 2, + "converged_seconds_after_resume": 16.0 + }, + "expired_and_unavailable": { + "lightnvr": { + "8a4cd465-38d6-4270-a2ed-9674a245dfb4": { + "case": "expired", + "state": "expired", + "reason": "retention_expired", + "snapshot_status": 410, + "snapshot_code": "evidence_expired" + }, + "addd231b-f70d-4239-a4d5-8d5eb25035f9": { + "case": "expired", + "state": "expired", + "reason": "retention_expired", + "snapshot_status": 410, + "snapshot_code": "evidence_expired" + }, + "8895eee4-b0f0-41c7-8bd5-876bdca8cd53": { + "case": "unavailable", + "state": "unavailable", + "reason": "write_failed", + "snapshot_status": 404, + "snapshot_code": "evidence_unavailable" + }, + "7e928156-af15-4b87-b3f3-86ecde0e93c4": { + "case": "unavailable", + "state": "unavailable", + "reason": "write_failed", + "snapshot_status": 404, + "snapshot_code": "evidence_unavailable" + } + }, + "recovery_run": { + "trigger": "start", + "started_at": 1794161577.4695296, + "finished_at": 1794161577.4808931, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 1, + "items": 4, + "outcomes": { + "evidence_expired": 2, + "evidence_unavailable": 2 + } + }, + "yawamf_rows": 0, + "backfill": { + "status": "completed", + "processed": 160, + "new_detections": 0, + "updated": 0, + "skipped": 160, + "errors": 0, + "skipped_reasons": { + "already_exists": 156, + "evidence_expired": 2, + "evidence_unavailable": 2 + }, + "error_reasons": {}, + "message": "No new detections found, 156 already existed, 2 with evidence expired, 2 without an evidence image" + } + }, + "expired_cursor_after_restore": { + "old_cursor_response": { + "status": 410, + "body": { + "error": "Cursor is older than retained observation history", + "code": "cursor_expired", + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiYTQ5OWM1ZDEtYWI0My00YjJkLTk5ZjctNGE2YzA2NWM2N2Y0In0", + "oldest_observed_at": "2026-10-31T18:11:46.162Z" + } + }, + "recovery_after_restore": { + "state": "caught_up", + "source_kind": "lightnvr", + "source_instance": "12e0cfcb-359e-460f-af91-093a2c93724f", + "recovery_required": true, + "gap_reason": "cursor_expired", + "cursor_saved": true, + "last_error": null, + "updated_at": "2026-11-08 18:13:19", + "last_run": { + "trigger": "start", + "started_at": 1794161599.7979414, + "finished_at": 1794161599.9763477, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 2, + "items": 160, + "outcomes": { + "already_exists": 156, + "evidence_expired": 2, + "evidence_unavailable": 2 + } + } + }, + "diagnostics_entries": [ + { + "source": "source_recovery", + "component": "lightnvr", + "severity": "warning", + "reason_code": "cursor_expired", + "message": "Source history expired before it was recovered; sightings in the gap cannot be recovered" + } + ], + "after_acknowledge": { + "state": "caught_up", + "recovery_required": false, + "gap_reason": null + }, + "observations_lost_by_restore_still_in_yawamf": 2 + } + }, + "result": "pass", + "seconds": 141.4 + } + ], + "workdir": "/tmp/claude-1000/-home-matteius-opensensor-lightNVR/b308eba0-16af-4c23-91be-f0b323521d94/scratchpad/final4", + "counts": { + "detector_requests": 1077, + "mqtt_messages_captured": 386 + } +} diff --git a/tests/integration/yawamf_e2e/results/x1-host-20261108.mqtt-capture.jsonl b/tests/integration/yawamf_e2e/results/x1-host-20261108.mqtt-capture.jsonl new file mode 100644 index 000000000..48f7147c4 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x1-host-20261108.mqtt-capture.jsonl @@ -0,0 +1,386 @@ +{"t": 1794161321.6553159, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161321.6557424, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161324.2404675, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/e427dfe3-b009-4f3d-955d-ced6062da73b", "payload": "{\"specversion\":\"1.0\",\"id\":\"db242b07-f2aa-52c9-add1-dafb69be20b8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/e427dfe3-b009-4f3d-955d-ced6062da73b\",\"time\":\"2026-11-08T18:08:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cb7110c5-4692-4c8e-a83e-cc4c2707a7e9\",\"revision\":1,\"camera_uuid\":\"e427dfe3-b009-4f3d-955d-ced6062da73b\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:44.233Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d3a4a743-558a-4d34-8f30-0701d2057393\"}}"} +{"t": 1794161324.2407007, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/e427dfe3-b009-4f3d-955d-ced6062da73b", "payload": "{\"specversion\":\"1.0\",\"id\":\"1f4ed2be-5232-5a3b-a894-73d33e55e40d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/e427dfe3-b009-4f3d-955d-ced6062da73b\",\"time\":\"2026-11-08T18:08:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cb7110c5-4692-4c8e-a83e-cc4c2707a7e9\",\"revision\":2,\"camera_uuid\":\"e427dfe3-b009-4f3d-955d-ced6062da73b\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:44.233Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d3a4a743-558a-4d34-8f30-0701d2057393\",\"snapshot_url\":\"/api/observations/cb7110c5-4692-4c8e-a83e-cc4c2707a7e9/snapshot\"}}"} +{"t": 1794161325.5195937, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/e427dfe3-b009-4f3d-955d-ced6062da73b", "payload": "{\"specversion\":\"1.0\",\"id\":\"387fc54b-9b9a-5d16-a602-a39d2fbf2608\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/e427dfe3-b009-4f3d-955d-ced6062da73b\",\"time\":\"2026-11-08T18:08:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"101b69a9-337a-4ba2-91f0-36b378ec6d1d\",\"revision\":1,\"camera_uuid\":\"e427dfe3-b009-4f3d-955d-ced6062da73b\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:45.513Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f469ab50-89b7-48af-8c28-62f9c00a31fa\"}}"} +{"t": 1794161325.519851, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/e427dfe3-b009-4f3d-955d-ced6062da73b", "payload": "{\"specversion\":\"1.0\",\"id\":\"1a4922ed-80d1-5824-9c95-9e31d1356f20\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/e427dfe3-b009-4f3d-955d-ced6062da73b\",\"time\":\"2026-11-08T18:08:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"101b69a9-337a-4ba2-91f0-36b378ec6d1d\",\"revision\":2,\"camera_uuid\":\"e427dfe3-b009-4f3d-955d-ced6062da73b\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:45.513Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f469ab50-89b7-48af-8c28-62f9c00a31fa\",\"snapshot_url\":\"/api/observations/101b69a9-337a-4ba2-91f0-36b378ec6d1d/snapshot\"}}"} +{"t": 1794161326.7995071, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/e427dfe3-b009-4f3d-955d-ced6062da73b", "payload": "{\"specversion\":\"1.0\",\"id\":\"a27c6e10-f8c5-5f4a-a1e3-23dd54d68c18\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/e427dfe3-b009-4f3d-955d-ced6062da73b\",\"time\":\"2026-11-08T18:08:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"17e8ecd6-0231-4221-8e67-1e21fc934cac\",\"revision\":1,\"camera_uuid\":\"e427dfe3-b009-4f3d-955d-ced6062da73b\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:46.793Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d3960539-f6a8-4a4e-8d05-c1ffbaee307c\"}}"} +{"t": 1794161326.7998524, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/e427dfe3-b009-4f3d-955d-ced6062da73b", "payload": "{\"specversion\":\"1.0\",\"id\":\"55c34813-fbc9-5e71-8960-aaf4ce5b4b03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/e427dfe3-b009-4f3d-955d-ced6062da73b\",\"time\":\"2026-11-08T18:08:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"17e8ecd6-0231-4221-8e67-1e21fc934cac\",\"revision\":2,\"camera_uuid\":\"e427dfe3-b009-4f3d-955d-ced6062da73b\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:46.793Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d3960539-f6a8-4a4e-8d05-c1ffbaee307c\",\"snapshot_url\":\"/api/observations/17e8ecd6-0231-4221-8e67-1e21fc934cac/snapshot\"}}"} +{"t": 1794161329.0465646, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/08d1344b-a324-4bd3-b567-38ecab646135", "payload": "{\"specversion\":\"1.0\",\"id\":\"2fee436f-ad36-589d-88f0-f825fa8e48d6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/08d1344b-a324-4bd3-b567-38ecab646135\",\"time\":\"2026-11-08T18:08:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4beb8152-ba61-44ad-bb4c-a27dce9895c2\",\"revision\":1,\"camera_uuid\":\"08d1344b-a324-4bd3-b567-38ecab646135\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:48.000Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ab914759-18e3-4d39-ba52-ea93c97c4484\"}}"} +{"t": 1794161329.0520813, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/08d1344b-a324-4bd3-b567-38ecab646135", "payload": "{\"specversion\":\"1.0\",\"id\":\"d903d35f-b7ee-5789-93a6-7fc166060695\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/08d1344b-a324-4bd3-b567-38ecab646135\",\"time\":\"2026-11-08T18:08:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4beb8152-ba61-44ad-bb4c-a27dce9895c2\",\"revision\":2,\"camera_uuid\":\"08d1344b-a324-4bd3-b567-38ecab646135\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:48.000Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ab914759-18e3-4d39-ba52-ea93c97c4484\",\"snapshot_url\":\"/api/observations/4beb8152-ba61-44ad-bb4c-a27dce9895c2/snapshot\"}}"} +{"t": 1794161331.5414324, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/08d1344b-a324-4bd3-b567-38ecab646135", "payload": "{\"specversion\":\"1.0\",\"id\":\"1feff8c3-5bd8-58fa-8f4d-31e51c8d0302\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/08d1344b-a324-4bd3-b567-38ecab646135\",\"time\":\"2026-11-08T18:08:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9a28105c-1941-4d9b-a98c-ee51dcb228e4\",\"revision\":1,\"camera_uuid\":\"08d1344b-a324-4bd3-b567-38ecab646135\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:50.000Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ad3bbb31-6647-44fd-a636-b1a7f3ea0d2f\"}}"} +{"t": 1794161331.541669, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/08d1344b-a324-4bd3-b567-38ecab646135", "payload": "{\"specversion\":\"1.0\",\"id\":\"8cc94fcd-bb5d-5e91-baf3-790543c6fc72\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/08d1344b-a324-4bd3-b567-38ecab646135\",\"time\":\"2026-11-08T18:08:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9a28105c-1941-4d9b-a98c-ee51dcb228e4\",\"revision\":2,\"camera_uuid\":\"08d1344b-a324-4bd3-b567-38ecab646135\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:50.000Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ad3bbb31-6647-44fd-a636-b1a7f3ea0d2f\",\"snapshot_url\":\"/api/observations/9a28105c-1941-4d9b-a98c-ee51dcb228e4/snapshot\"}}"} +{"t": 1794161333.0594769, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/08d1344b-a324-4bd3-b567-38ecab646135", "payload": "{\"specversion\":\"1.0\",\"id\":\"60a6c48c-7b0c-5c48-bf09-d08ba230b5e2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/08d1344b-a324-4bd3-b567-38ecab646135\",\"time\":\"2026-11-08T18:08:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"22dd9e4f-9422-4857-88ff-5818c10b1836\",\"revision\":1,\"camera_uuid\":\"08d1344b-a324-4bd3-b567-38ecab646135\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:52.000Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"09d0d5a2-856c-49ee-a85e-e137abc52c67\"}}"} +{"t": 1794161333.064445, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/08d1344b-a324-4bd3-b567-38ecab646135", "payload": "{\"specversion\":\"1.0\",\"id\":\"207972cc-88de-5400-9360-9e5798a1923d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/08d1344b-a324-4bd3-b567-38ecab646135\",\"time\":\"2026-11-08T18:08:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"22dd9e4f-9422-4857-88ff-5818c10b1836\",\"revision\":2,\"camera_uuid\":\"08d1344b-a324-4bd3-b567-38ecab646135\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:52.000Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"09d0d5a2-856c-49ee-a85e-e137abc52c67\",\"snapshot_url\":\"/api/observations/22dd9e4f-9422-4857-88ff-5818c10b1836/snapshot\"}}"} +{"t": 1794161334.5128672, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161334.5131028, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161334.5133133, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161334.5135033, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.8129997, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.815537, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.8194284, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.822745, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.8262894, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.8302848, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.833195, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.8365877, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.8398745, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.843351, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.8467035, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.850338, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.8536859, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.857122, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.8604107, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.861739, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.8637676, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.8667724, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.8702674, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.8736062, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.8748975, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.8762217, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.8775642, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.8788393, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.8801498, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.8815541, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.8849187, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.8883474, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.891708, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.8953054, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.898628, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.9019983, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.9053752, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9087923, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9101439, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.9139376, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.9170177, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.9185245, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.9220939, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9252753, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.926587, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.9279003, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.929193, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.9305074, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.9318933, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9334486, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9377446, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.9405017, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.9443126, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.947374, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.950831, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9540982, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.9582808, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.9611967, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161335.964728, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"4fa98daa-716e-597f-860e-59b6567bd9b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":1,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\"}}"} +{"t": 1794161335.9683409, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1ec86da7-9373-4b52-8d09-b0c598f37918", "payload": "{\"specversion\":\"1.0\",\"id\":\"188f7cc4-0a23-566f-9186-a25a90efee26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1ec86da7-9373-4b52-8d09-b0c598f37918\",\"time\":\"2026-11-08T18:08:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d07d075d-55d7-495d-a69e-be06fb5a1346\",\"revision\":2,\"camera_uuid\":\"1ec86da7-9373-4b52-8d09-b0c598f37918\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:41.640Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bed89e80-7b93-4e5a-8dcb-babfbfc8d709\",\"snapshot_url\":\"/api/observations/d07d075d-55d7-495d-a69e-be06fb5a1346/snapshot\"}}"} +{"t": 1794161335.9715977, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a32507c-a07a-5038-b090-719c3514b654\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.975058, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"b00306f3-74b3-57ac-9d34-ffab9146f37b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":1,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\"}}"} +{"t": 1794161335.978251, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2256d30-ab2e-54fe-88a9-26beec60e743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0818984d-9f18-4816-a629-8a0dcd5310f6\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/0818984d-9f18-4816-a629-8a0dcd5310f6/snapshot\"}}"} +{"t": 1794161335.979531, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/5cc522ad-22f6-4bb8-9200-a93220acbfab", "payload": "{\"specversion\":\"1.0\",\"id\":\"e8bd383f-ae87-5e7c-96b1-74e0aab0db7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"time\":\"2026-11-08T18:08:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"746f7429-8c09-4924-809a-52f8969cda2d\",\"revision\":2,\"camera_uuid\":\"5cc522ad-22f6-4bb8-9200-a93220acbfab\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:08:54.506Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91027bf9-8fce-4618-baf2-4c27d5e8ac08\",\"snapshot_url\":\"/api/observations/746f7429-8c09-4924-809a-52f8969cda2d/snapshot\"}}"} +{"t": 1794161419.3691812, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d9124994-bab1-57aa-993f-5a14efac679a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"55f6bc7c-92ab-4bfd-88fc-3248e22804b9\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:11.179Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"851dcf79-24cd-4113-91c9-2ee4168101ad\"}}"} +{"t": 1794161419.3698533, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"eff9acee-06b6-5b07-a55a-a193859cf492\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b9c6c4d5-14ff-417c-8e87-e2ffa526cbd8\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:11.179Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"851dcf79-24cd-4113-91c9-2ee4168101ad\"}}"} +{"t": 1794161419.370553, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b72b6fbd-9e1f-56eb-a40e-77a224c5d329\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"55f6bc7c-92ab-4bfd-88fc-3248e22804b9\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:11.179Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"851dcf79-24cd-4113-91c9-2ee4168101ad\",\"snapshot_url\":\"/api/observations/55f6bc7c-92ab-4bfd-88fc-3248e22804b9/snapshot\"}}"} +{"t": 1794161419.371052, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b9593c22-cb15-5a83-8529-73bfb1a09be8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b9c6c4d5-14ff-417c-8e87-e2ffa526cbd8\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:11.179Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"851dcf79-24cd-4113-91c9-2ee4168101ad\",\"snapshot_url\":\"/api/observations/b9c6c4d5-14ff-417c-8e87-e2ffa526cbd8/snapshot\"}}"} +{"t": 1794161419.371467, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0ae19b66-40ed-5885-950c-2b4efadecde8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a9fea0aa-4f9e-400e-a449-690cc8f799f7\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:12.459Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"904f981b-3302-44a5-933c-1ae5812e6343\"}}"} +{"t": 1794161419.3719752, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f336bf42-6759-539a-b7eb-27cfc567bbc8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c583dd7f-ef0c-41cb-8248-a53561a7b509\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:12.459Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"904f981b-3302-44a5-933c-1ae5812e6343\"}}"} +{"t": 1794161419.3723722, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e9542509-487a-5aa1-b9d4-7ceedcf279c7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a9fea0aa-4f9e-400e-a449-690cc8f799f7\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:12.459Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"904f981b-3302-44a5-933c-1ae5812e6343\",\"snapshot_url\":\"/api/observations/a9fea0aa-4f9e-400e-a449-690cc8f799f7/snapshot\"}}"} +{"t": 1794161419.3729637, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f1bbafb7-f519-543a-9f23-34a6a18beaf5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c583dd7f-ef0c-41cb-8248-a53561a7b509\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:12.459Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"904f981b-3302-44a5-933c-1ae5812e6343\",\"snapshot_url\":\"/api/observations/c583dd7f-ef0c-41cb-8248-a53561a7b509/snapshot\"}}"} +{"t": 1794161419.3734217, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"43954c4b-94c7-58e6-b25b-a2a9ba35c3d2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf100a19-7199-4556-8583-401568edda78\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:13.739Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b059008f-a6e7-4256-9caf-4a58c798e91e\"}}"} +{"t": 1794161419.3736491, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d66f1ce3-fbbc-59fa-87ff-e159b77094c6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"24e39e9d-a2a0-46f7-a7b1-34349aafadcf\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:13.739Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b059008f-a6e7-4256-9caf-4a58c798e91e\"}}"} +{"t": 1794161419.3741283, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"6b1c7a39-6fdf-55c4-b974-7bff3f265751\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf100a19-7199-4556-8583-401568edda78\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:13.739Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b059008f-a6e7-4256-9caf-4a58c798e91e\",\"snapshot_url\":\"/api/observations/bf100a19-7199-4556-8583-401568edda78/snapshot\"}}"} +{"t": 1794161419.3747344, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e64c03f1-3e94-55a1-bf22-9073c91186e8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"24e39e9d-a2a0-46f7-a7b1-34349aafadcf\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:13.739Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b059008f-a6e7-4256-9caf-4a58c798e91e\",\"snapshot_url\":\"/api/observations/24e39e9d-a2a0-46f7-a7b1-34349aafadcf/snapshot\"}}"} +{"t": 1794161419.3751974, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a14728ec-ee64-52d5-b30b-bf713fef339e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3b5b6f65-2bc8-4843-b4e2-43b3fc592936\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:15.019Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5909fc8e-33ab-4317-a7de-b9838926619d\"}}"} +{"t": 1794161419.3755019, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f17818d3-0e0b-5564-a334-e2f7552e05d2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ae865071-94f3-43bb-b2ab-e76878ebb7e7\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:15.019Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5909fc8e-33ab-4317-a7de-b9838926619d\"}}"} +{"t": 1794161419.3758419, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"77a234b3-2576-5d0e-a537-bcce67ac9199\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3b5b6f65-2bc8-4843-b4e2-43b3fc592936\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:15.019Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5909fc8e-33ab-4317-a7de-b9838926619d\",\"snapshot_url\":\"/api/observations/3b5b6f65-2bc8-4843-b4e2-43b3fc592936/snapshot\"}}"} +{"t": 1794161419.3763554, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"8f9ee638-9d44-53fe-8a59-35b5667d22e4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ae865071-94f3-43bb-b2ab-e76878ebb7e7\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:15.019Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5909fc8e-33ab-4317-a7de-b9838926619d\",\"snapshot_url\":\"/api/observations/ae865071-94f3-43bb-b2ab-e76878ebb7e7/snapshot\"}}"} +{"t": 1794161419.376545, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3a0bc261-426e-5552-82df-6efc608f3b21\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4a8ede46-ead7-4016-87ba-8a404490a782\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:16.299Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0a6992f8-a8b9-4433-9e5c-88d43cddf8c9\"}}"} +{"t": 1794161419.376747, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"35723a90-217c-58fa-b772-9151b326250e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"571050a6-50ef-4f47-a0a9-8fb182948d28\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:16.299Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0a6992f8-a8b9-4433-9e5c-88d43cddf8c9\"}}"} +{"t": 1794161419.3770337, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"de14b7af-f8fa-5b3b-81ac-a3fdc76be9a2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4a8ede46-ead7-4016-87ba-8a404490a782\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:16.299Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0a6992f8-a8b9-4433-9e5c-88d43cddf8c9\",\"snapshot_url\":\"/api/observations/4a8ede46-ead7-4016-87ba-8a404490a782/snapshot\"}}"} +{"t": 1794161419.3775406, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"00fb5808-6497-5391-8652-39754ceffc55\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"571050a6-50ef-4f47-a0a9-8fb182948d28\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:16.299Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0a6992f8-a8b9-4433-9e5c-88d43cddf8c9\",\"snapshot_url\":\"/api/observations/571050a6-50ef-4f47-a0a9-8fb182948d28/snapshot\"}}"} +{"t": 1794161419.3778539, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f19d25b1-4c59-58db-87aa-bd67176c565b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:17Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2510acf9-6eee-4f2b-b4eb-b8345cb0de9b\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:17.579Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"aee8b310-96cf-4e4a-b72e-ff5a7e11753d\"}}"} +{"t": 1794161419.3781784, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f784e009-913f-5d82-9531-fbc63a242fd7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:17Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1a55d883-c027-436d-a2ca-e2492359496a\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:17.579Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"aee8b310-96cf-4e4a-b72e-ff5a7e11753d\"}}"} +{"t": 1794161419.3784473, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"2ebc1ff2-cd4d-5e87-91bf-8cc99039edd5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:17Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2510acf9-6eee-4f2b-b4eb-b8345cb0de9b\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:17.579Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"aee8b310-96cf-4e4a-b72e-ff5a7e11753d\",\"snapshot_url\":\"/api/observations/2510acf9-6eee-4f2b-b4eb-b8345cb0de9b/snapshot\"}}"} +{"t": 1794161419.3789508, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"458384fb-2ca1-5c3a-9349-24c87d8e5035\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:17Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1a55d883-c027-436d-a2ca-e2492359496a\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:17.579Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"aee8b310-96cf-4e4a-b72e-ff5a7e11753d\",\"snapshot_url\":\"/api/observations/1a55d883-c027-436d-a2ca-e2492359496a/snapshot\"}}"} +{"t": 1794161419.379255, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5d55eec6-70dc-54f3-800d-5c5db2277da7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5c122067-1d94-4e1a-8c00-3cd5f8e71c86\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:18.859Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7f6b23ba-6026-47d9-a6ed-2f3fee56355d\"}}"} +{"t": 1794161419.3794363, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4d054b43-1073-5264-a384-7d3083f8b9f9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d2de2adc-83c9-4d92-89eb-494f807de076\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:18.859Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7f6b23ba-6026-47d9-a6ed-2f3fee56355d\"}}"} +{"t": 1794161419.379727, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"20645cdb-c554-506e-8425-71af959c6743\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5c122067-1d94-4e1a-8c00-3cd5f8e71c86\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:18.859Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7f6b23ba-6026-47d9-a6ed-2f3fee56355d\",\"snapshot_url\":\"/api/observations/5c122067-1d94-4e1a-8c00-3cd5f8e71c86/snapshot\"}}"} +{"t": 1794161419.379984, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"bb5b7a15-4dd6-5d76-b5b1-6b69e913f79d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d2de2adc-83c9-4d92-89eb-494f807de076\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:18.859Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7f6b23ba-6026-47d9-a6ed-2f3fee56355d\",\"snapshot_url\":\"/api/observations/d2de2adc-83c9-4d92-89eb-494f807de076/snapshot\"}}"} +{"t": 1794161419.3801706, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"83e2676e-f06e-5d34-9f9c-7424e03a8821\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7af175a6-f6dd-4d58-951f-c19092fddc98\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:20.139Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"62cdaf69-00aa-4c08-8c67-377a8595a9fc\"}}"} +{"t": 1794161419.3803504, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"adfbcbef-ab32-5743-9313-876546610809\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"24cdf50d-e8f9-43de-ad6e-f9878f21b3c9\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:20.139Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"62cdaf69-00aa-4c08-8c67-377a8595a9fc\"}}"} +{"t": 1794161419.380603, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"8f6fdc72-d464-5b78-81e9-85367fbc94bd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7af175a6-f6dd-4d58-951f-c19092fddc98\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:20.139Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"62cdaf69-00aa-4c08-8c67-377a8595a9fc\",\"snapshot_url\":\"/api/observations/7af175a6-f6dd-4d58-951f-c19092fddc98/snapshot\"}}"} +{"t": 1794161419.3810549, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d2940b1f-2abb-505c-9de7-fcb9e5e82df8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"24cdf50d-e8f9-43de-ad6e-f9878f21b3c9\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:20.139Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"62cdaf69-00aa-4c08-8c67-377a8595a9fc\",\"snapshot_url\":\"/api/observations/24cdf50d-e8f9-43de-ad6e-f9878f21b3c9/snapshot\"}}"} +{"t": 1794161419.3812282, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d19806eb-88c3-5742-890d-30a335e78f63\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:21Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bcb866ff-b6ea-456b-bf6a-31beaa62f975\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:21.419Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"37bd9e01-5b40-4d5c-b580-f3a8bcc929d8\"}}"} +{"t": 1794161419.3813653, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"14ce023c-06ef-56cc-abeb-a2ad33a41141\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:21Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8f56c0b0-d0df-49c7-bb56-4354cf77cf33\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:21.419Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"37bd9e01-5b40-4d5c-b580-f3a8bcc929d8\"}}"} +{"t": 1794161419.3815148, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"531dd211-f666-518e-8d64-e12d3f2e498c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:21Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bcb866ff-b6ea-456b-bf6a-31beaa62f975\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:21.419Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"37bd9e01-5b40-4d5c-b580-f3a8bcc929d8\",\"snapshot_url\":\"/api/observations/bcb866ff-b6ea-456b-bf6a-31beaa62f975/snapshot\"}}"} +{"t": 1794161419.381661, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9eb78bfd-1487-58a5-b88a-e7efbaa4ff7f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:21Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8f56c0b0-d0df-49c7-bb56-4354cf77cf33\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:21.419Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"37bd9e01-5b40-4d5c-b580-f3a8bcc929d8\",\"snapshot_url\":\"/api/observations/8f56c0b0-d0df-49c7-bb56-4354cf77cf33/snapshot\"}}"} +{"t": 1794161419.381794, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"31b2c240-ba99-5e7b-accc-49dd7133efb2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aa4bba68-e3e4-426d-8dae-24dfb1fded7a\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:22.699Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c3c0c802-db4f-47ae-937e-02647cbf37ff\"}}"} +{"t": 1794161419.381935, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b2ba4c5d-dda0-50b4-81c0-5f08809f558d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aa9d640c-b301-4662-b038-0feeadd80cf6\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:22.699Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c3c0c802-db4f-47ae-937e-02647cbf37ff\"}}"} +{"t": 1794161419.3820772, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"714783a9-67e2-56eb-a059-be41b438687b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aa4bba68-e3e4-426d-8dae-24dfb1fded7a\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:22.699Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c3c0c802-db4f-47ae-937e-02647cbf37ff\",\"snapshot_url\":\"/api/observations/aa4bba68-e3e4-426d-8dae-24dfb1fded7a/snapshot\"}}"} +{"t": 1794161419.3822389, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4be91281-40df-5c30-b4a4-8bb2f4443f07\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aa9d640c-b301-4662-b038-0feeadd80cf6\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:22.699Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c3c0c802-db4f-47ae-937e-02647cbf37ff\",\"snapshot_url\":\"/api/observations/aa9d640c-b301-4662-b038-0feeadd80cf6/snapshot\"}}"} +{"t": 1794161419.3823738, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5794f303-3734-5c69-936d-093248ea663f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"547073df-7884-419d-93bc-53afc639f13e\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:23.979Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e3ce255e-7f94-4ad8-8dfe-2ce30487b6d2\"}}"} +{"t": 1794161419.382508, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"80c2d8c6-793e-5593-a482-ce2f4eeec0a5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4e684128-c108-4471-af61-21d2f678cb59\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:23.979Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e3ce255e-7f94-4ad8-8dfe-2ce30487b6d2\"}}"} +{"t": 1794161419.382655, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d523e74d-2346-5f9c-8351-1ec33940533e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"547073df-7884-419d-93bc-53afc639f13e\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:23.979Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e3ce255e-7f94-4ad8-8dfe-2ce30487b6d2\",\"snapshot_url\":\"/api/observations/547073df-7884-419d-93bc-53afc639f13e/snapshot\"}}"} +{"t": 1794161419.3828027, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f22aa4a8-4b31-5a5a-b8b7-81a5f7ee2d9f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4e684128-c108-4471-af61-21d2f678cb59\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:23.979Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e3ce255e-7f94-4ad8-8dfe-2ce30487b6d2\",\"snapshot_url\":\"/api/observations/4e684128-c108-4471-af61-21d2f678cb59/snapshot\"}}"} +{"t": 1794161419.3829339, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9ebded41-e692-504c-9bcf-722a6fd7ba91\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"eb6f457f-ba3c-4044-8b63-1d3d6434ee53\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:25.259Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"74a4be3c-87c2-4081-ae7d-b1b88db985c7\"}}"} +{"t": 1794161419.383067, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0f8b8f67-a4cc-5986-a268-1dd2748494c0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"82b465a7-7307-41d6-bc23-d73f1d53773c\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:25.259Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"74a4be3c-87c2-4081-ae7d-b1b88db985c7\"}}"} +{"t": 1794161419.383232, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4df75ed0-36dc-5513-8ab1-1ba94b10bf26\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"eb6f457f-ba3c-4044-8b63-1d3d6434ee53\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:25.259Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"74a4be3c-87c2-4081-ae7d-b1b88db985c7\",\"snapshot_url\":\"/api/observations/eb6f457f-ba3c-4044-8b63-1d3d6434ee53/snapshot\"}}"} +{"t": 1794161419.383373, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"72b7fac9-70a8-5844-a161-aec0219a0406\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"82b465a7-7307-41d6-bc23-d73f1d53773c\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:25.259Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"74a4be3c-87c2-4081-ae7d-b1b88db985c7\",\"snapshot_url\":\"/api/observations/82b465a7-7307-41d6-bc23-d73f1d53773c/snapshot\"}}"} +{"t": 1794161419.3835042, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b24ee7a1-2cc3-5ca0-8c1c-883f3ffab537\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cfe315c8-2f31-41f7-9be7-a1559c76d36d\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:26.539Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d4ff12bb-dca9-4173-92c4-5df9e6f580ff\"}}"} +{"t": 1794161419.3836696, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a43e2bcd-cec8-53d2-9c86-f8a8163321fe\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d9d83cd4-afb9-4281-b59d-8e3eab8baa81\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:26.539Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d4ff12bb-dca9-4173-92c4-5df9e6f580ff\"}}"} +{"t": 1794161419.3838108, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"395ddc8c-e112-5bd9-b07c-513c8e57d7d6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cfe315c8-2f31-41f7-9be7-a1559c76d36d\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:26.539Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d4ff12bb-dca9-4173-92c4-5df9e6f580ff\",\"snapshot_url\":\"/api/observations/cfe315c8-2f31-41f7-9be7-a1559c76d36d/snapshot\"}}"} +{"t": 1794161419.3839648, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"6bb68897-92b2-5ff5-aaa1-c41abb7a6702\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d9d83cd4-afb9-4281-b59d-8e3eab8baa81\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:26.539Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d4ff12bb-dca9-4173-92c4-5df9e6f580ff\",\"snapshot_url\":\"/api/observations/d9d83cd4-afb9-4281-b59d-8e3eab8baa81/snapshot\"}}"} +{"t": 1794161419.3841078, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"fa6a5260-d932-5087-b8e3-4c8861dfb5f9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5e589bd-d50e-4586-8a66-98be40458e8f\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:27.819Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"362b1ed0-3337-4288-ba0b-b771c77c9a6e\"}}"} +{"t": 1794161419.3842945, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"dfe3d8b7-2697-5b28-8caf-55f8534cde1a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"27bd36e6-c693-4676-9279-387f147d0134\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:27.819Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"362b1ed0-3337-4288-ba0b-b771c77c9a6e\"}}"} +{"t": 1794161419.3844528, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b8e1b54c-75a9-5bab-9d8d-7e5a5634def2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5e589bd-d50e-4586-8a66-98be40458e8f\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:27.819Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"362b1ed0-3337-4288-ba0b-b771c77c9a6e\",\"snapshot_url\":\"/api/observations/c5e589bd-d50e-4586-8a66-98be40458e8f/snapshot\"}}"} +{"t": 1794161419.3846319, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"69d67e42-5a58-5563-9104-5db1933d9cfc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"27bd36e6-c693-4676-9279-387f147d0134\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:27.819Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"362b1ed0-3337-4288-ba0b-b771c77c9a6e\",\"snapshot_url\":\"/api/observations/27bd36e6-c693-4676-9279-387f147d0134/snapshot\"}}"} +{"t": 1794161419.3847816, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"c3c360fb-e6a0-5df5-b235-f175bf8e10b9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2cf03215-38ed-476b-91b5-8be39fb4103c\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:29.099Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5cd21742-c06b-4423-82eb-ed754012695a\"}}"} +{"t": 1794161419.384947, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3a56a747-345a-51cc-bd90-e4386abcfaef\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34545d05-ad60-4e38-815e-0acea41d85d1\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:29.099Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5cd21742-c06b-4423-82eb-ed754012695a\"}}"} +{"t": 1794161419.3851244, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"ebdbc2aa-10b0-5c92-9d03-b8507dc114bf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2cf03215-38ed-476b-91b5-8be39fb4103c\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:29.099Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5cd21742-c06b-4423-82eb-ed754012695a\",\"snapshot_url\":\"/api/observations/2cf03215-38ed-476b-91b5-8be39fb4103c/snapshot\"}}"} +{"t": 1794161419.3852935, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"97e7a75c-f4e8-5d2e-ba01-1175999f5a84\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34545d05-ad60-4e38-815e-0acea41d85d1\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:29.099Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5cd21742-c06b-4423-82eb-ed754012695a\",\"snapshot_url\":\"/api/observations/34545d05-ad60-4e38-815e-0acea41d85d1/snapshot\"}}"} +{"t": 1794161419.3854403, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"82e47791-d729-5b89-a0b9-a1617a2c86fa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4d667f98-eac7-4474-80e1-210059caf484\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:30.379Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c55b5e53-9cbe-429f-a212-e6fc14154186\"}}"} +{"t": 1794161419.3856013, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f3e776cc-ec6c-5d71-a6e2-f207f974e5bd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6652664e-abad-4bc6-817e-fa098a8a823f\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:30.379Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c55b5e53-9cbe-429f-a212-e6fc14154186\"}}"} +{"t": 1794161419.385782, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"7b19308a-80f3-5283-8a07-5d363d9d8318\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4d667f98-eac7-4474-80e1-210059caf484\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:30.379Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c55b5e53-9cbe-429f-a212-e6fc14154186\",\"snapshot_url\":\"/api/observations/4d667f98-eac7-4474-80e1-210059caf484/snapshot\"}}"} +{"t": 1794161419.3859699, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f14fde84-c473-5083-abb0-5dc170d2545b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6652664e-abad-4bc6-817e-fa098a8a823f\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:30.379Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c55b5e53-9cbe-429f-a212-e6fc14154186\",\"snapshot_url\":\"/api/observations/6652664e-abad-4bc6-817e-fa098a8a823f/snapshot\"}}"} +{"t": 1794161419.3861287, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"7f0ac7f6-3139-588a-a45f-ecf8906f70a5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"58af05d7-c6f4-4cd6-ad74-5ed0f0a57d20\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:31.659Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"309b6f92-29d5-4f35-ad70-4fa55a0f1fcf\"}}"} +{"t": 1794161419.3862872, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3759beb0-ea20-5c38-8e3c-b72674a3bde3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2bf4b24e-03d6-4e43-b827-32693b7129ba\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:31.659Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"309b6f92-29d5-4f35-ad70-4fa55a0f1fcf\"}}"} +{"t": 1794161419.3864415, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"c8f41e62-2523-5a4b-81a3-2c56ad6efb23\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"58af05d7-c6f4-4cd6-ad74-5ed0f0a57d20\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:31.659Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"309b6f92-29d5-4f35-ad70-4fa55a0f1fcf\",\"snapshot_url\":\"/api/observations/58af05d7-c6f4-4cd6-ad74-5ed0f0a57d20/snapshot\"}}"} +{"t": 1794161419.3865967, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"46b8bded-4ccf-5a56-9b7e-c0879f7089e2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2bf4b24e-03d6-4e43-b827-32693b7129ba\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:31.659Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"309b6f92-29d5-4f35-ad70-4fa55a0f1fcf\",\"snapshot_url\":\"/api/observations/2bf4b24e-03d6-4e43-b827-32693b7129ba/snapshot\"}}"} +{"t": 1794161419.3867326, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"ffbe6ddd-e4f9-5e87-a157-5a351fd80f98\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0657b4e2-3589-4a6a-af5c-ccd4d26110d6\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:32.939Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"303cb62d-7034-4576-8401-dcd9af9c6b63\"}}"} +{"t": 1794161419.3868778, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"fddf58b5-7fb7-56ee-bb79-d9f55c9513ee\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f30914f7-dd40-48f9-8cf5-fef70291ae17\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:32.939Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"303cb62d-7034-4576-8401-dcd9af9c6b63\"}}"} +{"t": 1794161419.3870199, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"44f16855-76e2-57fa-84e2-cc3933ec00f2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0657b4e2-3589-4a6a-af5c-ccd4d26110d6\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:32.939Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"303cb62d-7034-4576-8401-dcd9af9c6b63\",\"snapshot_url\":\"/api/observations/0657b4e2-3589-4a6a-af5c-ccd4d26110d6/snapshot\"}}"} +{"t": 1794161419.3871844, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"14cbd042-6c5d-554b-bb52-c00f0ab5cfe9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f30914f7-dd40-48f9-8cf5-fef70291ae17\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:32.939Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"303cb62d-7034-4576-8401-dcd9af9c6b63\",\"snapshot_url\":\"/api/observations/f30914f7-dd40-48f9-8cf5-fef70291ae17/snapshot\"}}"} +{"t": 1794161419.3873253, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"43cf7020-fb3c-5b7e-8c6b-72b7ae1d66f5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"663b70e8-3fe7-41ef-865a-70dc6a789694\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:34.219Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"85d4ee40-6838-4a6b-bcb4-8bb3ec006002\"}}"} +{"t": 1794161419.387466, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"255f72ef-56ce-5792-bf6b-84043df292f0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c04a1956-5fbb-4ff7-99f7-d7cf2bf87864\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:34.219Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"85d4ee40-6838-4a6b-bcb4-8bb3ec006002\"}}"} +{"t": 1794161419.387616, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"96fd4c6e-7590-5dbb-899e-3e08d8cf25f4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"663b70e8-3fe7-41ef-865a-70dc6a789694\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:34.219Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"85d4ee40-6838-4a6b-bcb4-8bb3ec006002\",\"snapshot_url\":\"/api/observations/663b70e8-3fe7-41ef-865a-70dc6a789694/snapshot\"}}"} +{"t": 1794161419.3877609, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"20270f2f-5b87-5ce4-b881-1e0b1f5c68b5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c04a1956-5fbb-4ff7-99f7-d7cf2bf87864\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:34.219Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"85d4ee40-6838-4a6b-bcb4-8bb3ec006002\",\"snapshot_url\":\"/api/observations/c04a1956-5fbb-4ff7-99f7-d7cf2bf87864/snapshot\"}}"} +{"t": 1794161419.387895, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a3ffbf10-4550-52ca-ad2c-c52c6fd69919\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"157f4776-d8f5-48ad-bdce-74fd5c8031ce\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:35.499Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2508baa5-3d4b-4a58-a6fe-20da1e1cbb8a\"}}"} +{"t": 1794161419.3880339, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"80b33dd0-ca1f-5ed7-97fe-90eddb7049fc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"70b3d18d-50e3-4a88-a3b3-c65b4f73a9ce\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:35.499Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2508baa5-3d4b-4a58-a6fe-20da1e1cbb8a\"}}"} +{"t": 1794161419.388198, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"305b9bdb-82c1-565a-b4e0-fa61919317c3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"157f4776-d8f5-48ad-bdce-74fd5c8031ce\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:35.499Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2508baa5-3d4b-4a58-a6fe-20da1e1cbb8a\",\"snapshot_url\":\"/api/observations/157f4776-d8f5-48ad-bdce-74fd5c8031ce/snapshot\"}}"} +{"t": 1794161419.388345, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"486421f6-5aad-54a7-b289-6e437ff85414\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"70b3d18d-50e3-4a88-a3b3-c65b4f73a9ce\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:35.499Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2508baa5-3d4b-4a58-a6fe-20da1e1cbb8a\",\"snapshot_url\":\"/api/observations/70b3d18d-50e3-4a88-a3b3-c65b4f73a9ce/snapshot\"}}"} +{"t": 1794161419.3884907, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"6ce78924-ce52-5b9e-a597-098eef08ab83\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b68bba8-fa8b-4e52-b6f9-6a7f63debb42\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:36.779Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"60348bb3-56e3-4b31-89b9-fb9e41e6dd22\"}}"} +{"t": 1794161419.3886578, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"47151cc4-3093-593c-b972-b291f1768914\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ef8a5914-e863-4dab-a5cd-c1bd342e20f5\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:36.779Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"60348bb3-56e3-4b31-89b9-fb9e41e6dd22\"}}"} +{"t": 1794161419.3888793, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5dd5ca19-b878-5a95-b13d-8d2baec8fd65\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b68bba8-fa8b-4e52-b6f9-6a7f63debb42\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:36.779Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"60348bb3-56e3-4b31-89b9-fb9e41e6dd22\",\"snapshot_url\":\"/api/observations/4b68bba8-fa8b-4e52-b6f9-6a7f63debb42/snapshot\"}}"} +{"t": 1794161419.3890932, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"c052b0de-1dca-5897-bd9f-9c7fee9bb102\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ef8a5914-e863-4dab-a5cd-c1bd342e20f5\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:36.779Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"60348bb3-56e3-4b31-89b9-fb9e41e6dd22\",\"snapshot_url\":\"/api/observations/ef8a5914-e863-4dab-a5cd-c1bd342e20f5/snapshot\"}}"} +{"t": 1794161419.389293, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"89191ccc-3e42-5ca7-823c-9cc5c2ba5eef\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"19d0b122-db87-4e00-b880-f332b7216b55\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:38.059Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8e391043-79e2-49da-a586-46db21cb2195\"}}"} +{"t": 1794161419.389479, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0abafd61-e7e0-5e35-b26c-33af0affcdc3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"97bc3dff-49bc-4a17-906c-d3d4b3a22b7f\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:38.059Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8e391043-79e2-49da-a586-46db21cb2195\"}}"} +{"t": 1794161419.3896894, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f6355e13-63b4-5409-9ce4-f99743c2c806\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"19d0b122-db87-4e00-b880-f332b7216b55\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:38.059Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8e391043-79e2-49da-a586-46db21cb2195\",\"snapshot_url\":\"/api/observations/19d0b122-db87-4e00-b880-f332b7216b55/snapshot\"}}"} +{"t": 1794161419.3898878, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"30be40fc-62c2-527b-82c4-14741f863e18\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"97bc3dff-49bc-4a17-906c-d3d4b3a22b7f\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:38.059Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8e391043-79e2-49da-a586-46db21cb2195\",\"snapshot_url\":\"/api/observations/97bc3dff-49bc-4a17-906c-d3d4b3a22b7f/snapshot\"}}"} +{"t": 1794161419.3900661, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3e92db54-04f7-51e7-a755-c0d35f7f4e11\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34dd20bb-7fa1-4d97-85f2-f60dfaeda943\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:39.339Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"07094eeb-426a-416d-8aa3-0c861faebefa\"}}"} +{"t": 1794161419.3902824, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"35689edc-b518-5570-a27f-9995933dc8cf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3e0f7027-c6d7-47c6-b43a-e61d955514cf\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:39.339Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"07094eeb-426a-416d-8aa3-0c861faebefa\"}}"} +{"t": 1794161419.3904328, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3da4021a-a8f1-5792-b737-55c3ad930e20\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34dd20bb-7fa1-4d97-85f2-f60dfaeda943\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:39.339Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"07094eeb-426a-416d-8aa3-0c861faebefa\",\"snapshot_url\":\"/api/observations/34dd20bb-7fa1-4d97-85f2-f60dfaeda943/snapshot\"}}"} +{"t": 1794161419.390572, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"865b87e5-9dba-5cf6-b76b-d43c39e415c4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3e0f7027-c6d7-47c6-b43a-e61d955514cf\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:39.339Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"07094eeb-426a-416d-8aa3-0c861faebefa\",\"snapshot_url\":\"/api/observations/3e0f7027-c6d7-47c6-b43a-e61d955514cf/snapshot\"}}"} +{"t": 1794161419.3907154, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0610ba2e-fe43-54ea-9f38-363c8f12bb3e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8ee5ffd8-48d4-4d0c-b64f-66ca30b441a2\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:40.619Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"47d6b47b-93ca-4cbd-b853-6b13eb30f71e\"}}"} +{"t": 1794161419.390854, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4747bded-fe90-5bdf-b8b1-6240fea26972\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e85233dd-b641-4e2c-a7b1-96d21b52a96b\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:40.619Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"47d6b47b-93ca-4cbd-b853-6b13eb30f71e\"}}"} +{"t": 1794161419.3909957, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f48a8019-78e2-58b2-8007-76204d787746\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8ee5ffd8-48d4-4d0c-b64f-66ca30b441a2\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:40.619Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"47d6b47b-93ca-4cbd-b853-6b13eb30f71e\",\"snapshot_url\":\"/api/observations/8ee5ffd8-48d4-4d0c-b64f-66ca30b441a2/snapshot\"}}"} +{"t": 1794161419.3911426, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"82556a6f-2def-5af3-8cde-a06fa88b86a5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e85233dd-b641-4e2c-a7b1-96d21b52a96b\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:40.619Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"47d6b47b-93ca-4cbd-b853-6b13eb30f71e\",\"snapshot_url\":\"/api/observations/e85233dd-b641-4e2c-a7b1-96d21b52a96b/snapshot\"}}"} +{"t": 1794161419.3912702, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e0fa41fa-c3a3-5417-8486-cc5be5f03d47\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c6b96737-db92-4aca-b9d1-6abab02c6325\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:41.899Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4c2caa6b-e1e3-415e-a5ab-b420eaa4af1b\"}}"} +{"t": 1794161419.3913996, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b5b6b8b0-8c20-5ed4-b81f-961b7610ad03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9cd321e3-dec1-45c0-b7da-49700a292d84\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:41.899Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4c2caa6b-e1e3-415e-a5ab-b420eaa4af1b\"}}"} +{"t": 1794161419.3915396, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"c77ea023-7305-53af-b8c4-31f912adc13a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c6b96737-db92-4aca-b9d1-6abab02c6325\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:41.899Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4c2caa6b-e1e3-415e-a5ab-b420eaa4af1b\",\"snapshot_url\":\"/api/observations/c6b96737-db92-4aca-b9d1-6abab02c6325/snapshot\"}}"} +{"t": 1794161419.3916779, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"aca56724-fcd4-56ca-90fc-4d09882df084\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9cd321e3-dec1-45c0-b7da-49700a292d84\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:41.899Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4c2caa6b-e1e3-415e-a5ab-b420eaa4af1b\",\"snapshot_url\":\"/api/observations/9cd321e3-dec1-45c0-b7da-49700a292d84/snapshot\"}}"} +{"t": 1794161419.3918087, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b122e4fd-82b9-5cd5-81bf-e88d7d1f59e7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7a38110e-05a1-4c12-aaaa-afb1e35bd449\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:43.179Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ebe94a27-3a27-41d4-8737-766d5f4966b1\"}}"} +{"t": 1794161419.3919363, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"1c6151a9-aef8-5774-9ff6-c50a2bb13feb\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8d6e26aa-0213-4467-8b4d-e066ca54a468\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:43.179Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ebe94a27-3a27-41d4-8737-766d5f4966b1\"}}"} +{"t": 1794161419.392073, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9b20884-2b3e-5776-92f5-21bff6a8296e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7a38110e-05a1-4c12-aaaa-afb1e35bd449\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:43.179Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ebe94a27-3a27-41d4-8737-766d5f4966b1\",\"snapshot_url\":\"/api/observations/7a38110e-05a1-4c12-aaaa-afb1e35bd449/snapshot\"}}"} +{"t": 1794161419.3922768, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"fe2ca600-e234-5378-9e56-9f3230288616\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8d6e26aa-0213-4467-8b4d-e066ca54a468\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:43.179Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ebe94a27-3a27-41d4-8737-766d5f4966b1\",\"snapshot_url\":\"/api/observations/8d6e26aa-0213-4467-8b4d-e066ca54a468/snapshot\"}}"} +{"t": 1794161419.3924277, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"56abdb35-42c1-5e27-ac10-37ba3dfb5d89\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"12a10629-aeaa-4427-b34a-e5869a07caa7\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:44.459Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"42c55e02-46fd-4110-bf1a-11898f7198e8\"}}"} +{"t": 1794161419.3925743, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9da697a3-74e5-53e9-ab9b-c7e6855c954b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ac4cb934-d361-44a9-9e9e-41222554e4bc\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:44.459Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"42c55e02-46fd-4110-bf1a-11898f7198e8\"}}"} +{"t": 1794161419.3927195, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"6cb6a2fc-c041-5793-847c-f5c8cdfb4109\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"12a10629-aeaa-4427-b34a-e5869a07caa7\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:44.459Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"42c55e02-46fd-4110-bf1a-11898f7198e8\",\"snapshot_url\":\"/api/observations/12a10629-aeaa-4427-b34a-e5869a07caa7/snapshot\"}}"} +{"t": 1794161419.3928654, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b356d479-5274-5bdf-8c0c-5c20f6735136\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ac4cb934-d361-44a9-9e9e-41222554e4bc\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:44.459Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"42c55e02-46fd-4110-bf1a-11898f7198e8\",\"snapshot_url\":\"/api/observations/ac4cb934-d361-44a9-9e9e-41222554e4bc/snapshot\"}}"} +{"t": 1794161419.392995, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a3c948c8-594c-5131-8704-8268af709af8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"64fa66fb-5888-403d-8d68-6d3c240bdb9f\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:45.739Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1184ae40-b050-420f-9182-a9c34f6ede4b\"}}"} +{"t": 1794161419.393132, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"c9676785-2faa-573e-9f13-c993443e327a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c6091056-9f26-4704-9126-830d14147d3b\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:45.739Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1184ae40-b050-420f-9182-a9c34f6ede4b\"}}"} +{"t": 1794161419.3932807, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"6a8dc32f-97d4-57b6-a321-de5da98321c0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"64fa66fb-5888-403d-8d68-6d3c240bdb9f\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:45.739Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1184ae40-b050-420f-9182-a9c34f6ede4b\",\"snapshot_url\":\"/api/observations/64fa66fb-5888-403d-8d68-6d3c240bdb9f/snapshot\"}}"} +{"t": 1794161419.3934135, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"1b4a8fd7-27bc-5be9-90b0-fb838a8fec92\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c6091056-9f26-4704-9126-830d14147d3b\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:45.739Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1184ae40-b050-420f-9182-a9c34f6ede4b\",\"snapshot_url\":\"/api/observations/c6091056-9f26-4704-9126-830d14147d3b/snapshot\"}}"} +{"t": 1794161419.3935406, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"8b26cb79-5442-5c39-9e77-4568f383e058\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"75610fa4-aefa-4794-9fca-3229a9a21bb8\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:47.019Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"dfd73e87-74a5-4425-a9c9-0cd2fb3427e3\"}}"} +{"t": 1794161419.39367, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4090bf4e-bf89-5d86-9561-d929dd62641e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"60a29ab6-5da4-4fc4-ae42-973a5842f6dc\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:47.019Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"dfd73e87-74a5-4425-a9c9-0cd2fb3427e3\"}}"} +{"t": 1794161419.3938096, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5621863b-68fd-5fde-b084-063651578ad9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"75610fa4-aefa-4794-9fca-3229a9a21bb8\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:47.019Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"dfd73e87-74a5-4425-a9c9-0cd2fb3427e3\",\"snapshot_url\":\"/api/observations/75610fa4-aefa-4794-9fca-3229a9a21bb8/snapshot\"}}"} +{"t": 1794161419.393954, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"cca4c7aa-356d-583c-8072-70c8f2b4fdfa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"60a29ab6-5da4-4fc4-ae42-973a5842f6dc\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:47.019Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"dfd73e87-74a5-4425-a9c9-0cd2fb3427e3\",\"snapshot_url\":\"/api/observations/60a29ab6-5da4-4fc4-ae42-973a5842f6dc/snapshot\"}}"} +{"t": 1794161419.3940735, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"2c373684-09cc-5632-8a07-c02fd86bf634\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"938a5c32-c9cd-4445-8809-43e00b92ef29\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:48.299Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"697add21-c68e-4816-a5c0-970d1538660c\"}}"} +{"t": 1794161419.3942122, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e28df128-8341-538d-ade3-ff6e849ada54\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9e56e989-64cd-4ff8-833d-14cc683c3889\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:48.299Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"697add21-c68e-4816-a5c0-970d1538660c\"}}"} +{"t": 1794161419.394354, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b662e49a-9b2d-50a8-b29f-11bed492d8f6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"938a5c32-c9cd-4445-8809-43e00b92ef29\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:48.299Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"697add21-c68e-4816-a5c0-970d1538660c\",\"snapshot_url\":\"/api/observations/938a5c32-c9cd-4445-8809-43e00b92ef29/snapshot\"}}"} +{"t": 1794161419.394485, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"78a01624-84e9-5faf-a7b8-0ec9536f889f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9e56e989-64cd-4ff8-833d-14cc683c3889\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:48.299Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"697add21-c68e-4816-a5c0-970d1538660c\",\"snapshot_url\":\"/api/observations/9e56e989-64cd-4ff8-833d-14cc683c3889/snapshot\"}}"} +{"t": 1794161419.3946087, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5e76bfb5-bd15-503d-b115-06c83b029e65\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"05831178-2872-4885-87f1-fdc387d08660\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:49.579Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8ae9ea2d-4354-44c2-8a26-b04ea3680e6b\"}}"} +{"t": 1794161419.3947396, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9b31db6b-1dfb-5425-9f55-593c4d850f6f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ce3d0008-fc4b-4eb2-953c-34cca60a4dc0\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:49.579Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8ae9ea2d-4354-44c2-8a26-b04ea3680e6b\"}}"} +{"t": 1794161419.395036, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"78f3645d-04e0-5d26-ae8c-8fe9ced0b6b8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"05831178-2872-4885-87f1-fdc387d08660\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:49.579Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8ae9ea2d-4354-44c2-8a26-b04ea3680e6b\",\"snapshot_url\":\"/api/observations/05831178-2872-4885-87f1-fdc387d08660/snapshot\"}}"} +{"t": 1794161419.395203, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e51627f3-5724-51a7-aac7-6b0dd0e3863b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ce3d0008-fc4b-4eb2-953c-34cca60a4dc0\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:49.579Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8ae9ea2d-4354-44c2-8a26-b04ea3680e6b\",\"snapshot_url\":\"/api/observations/ce3d0008-fc4b-4eb2-953c-34cca60a4dc0/snapshot\"}}"} +{"t": 1794161419.3953273, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0f70ea46-9b8b-5ce0-8c77-5b779f8f1e2b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"78d39d67-7949-4500-b329-775b6f646317\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:50.859Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a767502d-fa24-4497-90dd-f87ddd4471b1\"}}"} +{"t": 1794161419.3954525, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"bc52d871-4397-5a0f-843c-2ed00ecd8cf7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"79a779c0-c77d-4029-bdf2-bfa1decc76ee\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:50.859Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a767502d-fa24-4497-90dd-f87ddd4471b1\"}}"} +{"t": 1794161419.3955848, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"ec42f466-aca3-598e-a6fd-2307b22014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"78d39d67-7949-4500-b329-775b6f646317\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:50.859Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a767502d-fa24-4497-90dd-f87ddd4471b1\",\"snapshot_url\":\"/api/observations/78d39d67-7949-4500-b329-775b6f646317/snapshot\"}}"} +{"t": 1794161419.395717, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"cab299e3-5134-5451-a18d-74481772fe93\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"79a779c0-c77d-4029-bdf2-bfa1decc76ee\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:50.859Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a767502d-fa24-4497-90dd-f87ddd4471b1\",\"snapshot_url\":\"/api/observations/79a779c0-c77d-4029-bdf2-bfa1decc76ee/snapshot\"}}"} +{"t": 1794161419.395838, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"2299408a-2ae6-5aaf-ab93-5803254210ba\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"051d61f2-b3f6-42f6-aecc-60410b521930\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:52.139Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4f0d3100-0f0f-43eb-a371-7f14a29c0860\"}}"} +{"t": 1794161419.395965, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"129cda64-a1fd-588c-a66b-f42a92343d3b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2dc68f4-a4e5-4ac4-9907-735861567961\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:52.139Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4f0d3100-0f0f-43eb-a371-7f14a29c0860\"}}"} +{"t": 1794161419.3960955, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"8fd632d9-709b-544e-a77c-59466a7a9c20\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"051d61f2-b3f6-42f6-aecc-60410b521930\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:52.139Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4f0d3100-0f0f-43eb-a371-7f14a29c0860\",\"snapshot_url\":\"/api/observations/051d61f2-b3f6-42f6-aecc-60410b521930/snapshot\"}}"} +{"t": 1794161419.3962336, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"41626c13-44db-5192-bc7f-bc4a27cacdf2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2dc68f4-a4e5-4ac4-9907-735861567961\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:52.139Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4f0d3100-0f0f-43eb-a371-7f14a29c0860\",\"snapshot_url\":\"/api/observations/e2dc68f4-a4e5-4ac4-9907-735861567961/snapshot\"}}"} +{"t": 1794161419.396357, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"997772c8-ed3b-5f51-abe5-6307f9a2b82c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"807ca03c-46fe-4003-847a-682eca74fe31\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:53.419Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c54a77fa-1a6e-45c1-9d17-dac9be62bf7d\"}}"} +{"t": 1794161419.3964846, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"7de877f6-a08a-5e2b-a6f8-49cf4f70296d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"64dcaeb0-fe24-4063-a100-0b4071fe2f9f\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:53.419Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c54a77fa-1a6e-45c1-9d17-dac9be62bf7d\"}}"} +{"t": 1794161419.3966088, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4d00d60e-66cf-5dad-805c-3b2cae7e86d7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"807ca03c-46fe-4003-847a-682eca74fe31\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:53.419Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c54a77fa-1a6e-45c1-9d17-dac9be62bf7d\",\"snapshot_url\":\"/api/observations/807ca03c-46fe-4003-847a-682eca74fe31/snapshot\"}}"} +{"t": 1794161419.3967378, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"ece7e92c-50e4-5ee7-8fa9-18980c30ae0f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"64dcaeb0-fe24-4063-a100-0b4071fe2f9f\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:53.419Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c54a77fa-1a6e-45c1-9d17-dac9be62bf7d\",\"snapshot_url\":\"/api/observations/64dcaeb0-fe24-4063-a100-0b4071fe2f9f/snapshot\"}}"} +{"t": 1794161419.396859, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d5de7201-b076-59f0-a6fc-47e97b87c48c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf17c5ef-953a-4054-acd9-3aaefed7516f\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:54.699Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a49c04e4-d3d9-44ae-9135-582cf9dbb924\"}}"} +{"t": 1794161419.3969805, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"fc38ffd5-8d57-5dc6-b9c1-a6b2d2da6f12\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"391d0c5f-0299-4bf2-9acc-51947b5f7ddf\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:54.699Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a49c04e4-d3d9-44ae-9135-582cf9dbb924\"}}"} +{"t": 1794161419.3971236, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"68f704f9-9ef4-5af0-875e-2faf9f13fafa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf17c5ef-953a-4054-acd9-3aaefed7516f\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:54.699Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a49c04e4-d3d9-44ae-9135-582cf9dbb924\",\"snapshot_url\":\"/api/observations/bf17c5ef-953a-4054-acd9-3aaefed7516f/snapshot\"}}"} +{"t": 1794161419.397269, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5beda4a3-63ae-59ae-8cbf-915dd20f0b03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"391d0c5f-0299-4bf2-9acc-51947b5f7ddf\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:54.699Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a49c04e4-d3d9-44ae-9135-582cf9dbb924\",\"snapshot_url\":\"/api/observations/391d0c5f-0299-4bf2-9acc-51947b5f7ddf/snapshot\"}}"} +{"t": 1794161419.3973958, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"00f64c4e-4aaf-5f23-9b4b-fd6d61b7f852\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9cbf5737-66e9-41c8-ab79-8986af435417\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:55.979Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4404e2fa-6d35-40ab-ba26-7bea30d17627\"}}"} +{"t": 1794161419.3975189, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"859d967b-fe16-52b7-ad20-dad7d8b4bfb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"766472d8-2db2-4a0f-a413-ac746d617f88\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:55.979Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4404e2fa-6d35-40ab-ba26-7bea30d17627\"}}"} +{"t": 1794161419.397655, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f2e19d0f-27c2-5293-ba8e-040596e9bf8c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9cbf5737-66e9-41c8-ab79-8986af435417\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:55.979Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4404e2fa-6d35-40ab-ba26-7bea30d17627\",\"snapshot_url\":\"/api/observations/9cbf5737-66e9-41c8-ab79-8986af435417/snapshot\"}}"} +{"t": 1794161419.3977776, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b1a8098b-4fba-5b63-8b86-62222431fdbf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"766472d8-2db2-4a0f-a413-ac746d617f88\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:55.979Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4404e2fa-6d35-40ab-ba26-7bea30d17627\",\"snapshot_url\":\"/api/observations/766472d8-2db2-4a0f-a413-ac746d617f88/snapshot\"}}"} +{"t": 1794161419.3978972, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"74d857a4-38ad-5dc6-8244-55f149ce8aa5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1a7f057c-57f0-4c03-8dee-0ae8c96ca97e\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:57.259Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0f103228-b73f-4781-82e3-84bf2defa213\"}}"} +{"t": 1794161419.3980172, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b1f604ee-f666-54a6-8b80-e2c4e0998c37\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"81c97f26-6069-4036-bdd8-5b6fbf6a75e1\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:57.259Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0f103228-b73f-4781-82e3-84bf2defa213\"}}"} +{"t": 1794161419.3981643, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"cd0eda84-b1b1-56a7-84de-39f74a8d9ba8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1a7f057c-57f0-4c03-8dee-0ae8c96ca97e\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:57.259Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0f103228-b73f-4781-82e3-84bf2defa213\",\"snapshot_url\":\"/api/observations/1a7f057c-57f0-4c03-8dee-0ae8c96ca97e/snapshot\"}}"} +{"t": 1794161419.3983076, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"c74d0f29-9143-5107-aea7-9980fa025c60\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"81c97f26-6069-4036-bdd8-5b6fbf6a75e1\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:57.259Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0f103228-b73f-4781-82e3-84bf2defa213\",\"snapshot_url\":\"/api/observations/81c97f26-6069-4036-bdd8-5b6fbf6a75e1/snapshot\"}}"} +{"t": 1794161419.398432, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"403a1535-7e58-52bf-bdc7-bacc84b3dae1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d896c7df-f32d-4c07-99b8-857168a6bc15\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:58.539Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ad9adf38-0cee-4526-9949-94fd0cf1d576\"}}"} +{"t": 1794161419.3985581, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"558f97ca-33c1-5430-becb-9ec65d08482c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"79c27e83-ba53-40c0-ae22-5d86f4b56fb5\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:58.539Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ad9adf38-0cee-4526-9949-94fd0cf1d576\"}}"} +{"t": 1794161419.39869, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d9fbabde-8c72-5c81-82c9-4866d85c2a38\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d896c7df-f32d-4c07-99b8-857168a6bc15\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:58.539Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ad9adf38-0cee-4526-9949-94fd0cf1d576\",\"snapshot_url\":\"/api/observations/d896c7df-f32d-4c07-99b8-857168a6bc15/snapshot\"}}"} +{"t": 1794161419.3988194, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f0b8b7cc-8b45-5c05-b9a3-529b0d680483\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"79c27e83-ba53-40c0-ae22-5d86f4b56fb5\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:58.539Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ad9adf38-0cee-4526-9949-94fd0cf1d576\",\"snapshot_url\":\"/api/observations/79c27e83-ba53-40c0-ae22-5d86f4b56fb5/snapshot\"}}"} +{"t": 1794161419.398937, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e1b255fb-e52f-5aa8-9ed1-6605804761cc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"76445506-efb5-483f-a230-fec55abee98b\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:59.819Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ec0d138a-21b8-4027-b87e-92cb5cf77cfd\"}}"} +{"t": 1794161419.3990602, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"39411dfb-d771-5b34-9c20-20c9f3a24381\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3695062f-6379-4ae6-bc06-042f42df998c\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:59.819Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ec0d138a-21b8-4027-b87e-92cb5cf77cfd\"}}"} +{"t": 1794161419.39919, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a505485a-ef9d-5875-bcfb-2acf6b0472b6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"76445506-efb5-483f-a230-fec55abee98b\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:59.819Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ec0d138a-21b8-4027-b87e-92cb5cf77cfd\",\"snapshot_url\":\"/api/observations/76445506-efb5-483f-a230-fec55abee98b/snapshot\"}}"} +{"t": 1794161419.3993146, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9fc12220-60f1-5e91-b46e-12d0d494f010\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:09:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3695062f-6379-4ae6-bc06-042f42df998c\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:09:59.819Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ec0d138a-21b8-4027-b87e-92cb5cf77cfd\",\"snapshot_url\":\"/api/observations/3695062f-6379-4ae6-bc06-042f42df998c/snapshot\"}}"} +{"t": 1794161419.3994465, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a4ce723-c2f0-5b78-b077-76092240d12a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf4a0239-235b-4f29-8577-20b2b05189d2\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:01.099Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3d0fef76-bf34-49da-b4cd-ab6199dd376e\"}}"} +{"t": 1794161419.3995702, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"5ef124b0-bfdc-5d67-9fcb-54c8b607835d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6c4f0b67-d6c1-4287-a56a-2854bbb03e49\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:01.099Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3d0fef76-bf34-49da-b4cd-ab6199dd376e\"}}"} +{"t": 1794161419.3996933, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"ccd2ae75-eb86-56da-a9bc-65eb86fceba1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf4a0239-235b-4f29-8577-20b2b05189d2\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:01.099Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3d0fef76-bf34-49da-b4cd-ab6199dd376e\",\"snapshot_url\":\"/api/observations/bf4a0239-235b-4f29-8577-20b2b05189d2/snapshot\"}}"} +{"t": 1794161419.3998199, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3f8852ce-effb-567a-97bb-de95db16005f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6c4f0b67-d6c1-4287-a56a-2854bbb03e49\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:01.099Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3d0fef76-bf34-49da-b4cd-ab6199dd376e\",\"snapshot_url\":\"/api/observations/6c4f0b67-d6c1-4287-a56a-2854bbb03e49/snapshot\"}}"} +{"t": 1794161419.399938, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"1a72eb7c-1ed7-53c0-958e-faf267f50a18\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bae2eee8-cea6-4da1-a0dd-2403d42dd108\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:02.379Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8f11e757-617d-4e11-ba92-a5695987d300\"}}"} +{"t": 1794161419.400063, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f0e3b584-db1a-50e5-94b4-6c776d3777fe\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f354388b-af14-4198-b01a-7c727a17a357\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:02.379Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8f11e757-617d-4e11-ba92-a5695987d300\"}}"} +{"t": 1794161419.4001968, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a867213f-9535-50ac-b4b6-0817da2bc7a4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bae2eee8-cea6-4da1-a0dd-2403d42dd108\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:02.379Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f11e757-617d-4e11-ba92-a5695987d300\",\"snapshot_url\":\"/api/observations/bae2eee8-cea6-4da1-a0dd-2403d42dd108/snapshot\"}}"} +{"t": 1794161419.400334, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9706d705-4128-53d6-9773-8d4f7bc9a22a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f354388b-af14-4198-b01a-7c727a17a357\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:02.379Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f11e757-617d-4e11-ba92-a5695987d300\",\"snapshot_url\":\"/api/observations/f354388b-af14-4198-b01a-7c727a17a357/snapshot\"}}"} +{"t": 1794161419.4004512, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d62ea9ae-f760-5c4d-92b1-c6079ac2a9df\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ced3ad25-8a21-4733-8d58-f07c4db135ea\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:03.659Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91348d42-e919-4bec-b804-7f559d847b38\"}}"} +{"t": 1794161419.40057, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"be66a8d4-14a5-5e3a-9dbb-7a7a1f8bf710\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d8246f8c-743f-44a8-aa78-ebd28a6424af\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:03.659Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91348d42-e919-4bec-b804-7f559d847b38\"}}"} +{"t": 1794161419.400697, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a7590e94-1356-5922-90e8-92217e323fe9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ced3ad25-8a21-4733-8d58-f07c4db135ea\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:03.659Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91348d42-e919-4bec-b804-7f559d847b38\",\"snapshot_url\":\"/api/observations/ced3ad25-8a21-4733-8d58-f07c4db135ea/snapshot\"}}"} +{"t": 1794161419.4008167, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d4a36afd-657d-523b-bec6-9482a1580484\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d8246f8c-743f-44a8-aa78-ebd28a6424af\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:03.659Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91348d42-e919-4bec-b804-7f559d847b38\",\"snapshot_url\":\"/api/observations/d8246f8c-743f-44a8-aa78-ebd28a6424af/snapshot\"}}"} +{"t": 1794161419.4009333, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"acacc4dd-8587-5cec-b738-2b8b91d5f984\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d1bfd3d8-870c-4791-9d67-21403544fc60\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:04.939Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"604cb98b-8258-4701-aa8d-3442b0f555fa\"}}"} +{"t": 1794161419.4010606, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"15d54e1c-2ffd-57ed-abbe-944ec8a0c0fc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"97aaf755-b872-4fca-81fe-c841fb91d22e\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:04.939Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"604cb98b-8258-4701-aa8d-3442b0f555fa\"}}"} +{"t": 1794161419.401184, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0e028444-d0b7-5506-967b-b8474aba6696\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d1bfd3d8-870c-4791-9d67-21403544fc60\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:04.939Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"604cb98b-8258-4701-aa8d-3442b0f555fa\",\"snapshot_url\":\"/api/observations/d1bfd3d8-870c-4791-9d67-21403544fc60/snapshot\"}}"} +{"t": 1794161419.4013093, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"3cbcaf15-81e4-598a-a568-c25a733f9465\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"97aaf755-b872-4fca-81fe-c841fb91d22e\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:04.939Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"604cb98b-8258-4701-aa8d-3442b0f555fa\",\"snapshot_url\":\"/api/observations/97aaf755-b872-4fca-81fe-c841fb91d22e/snapshot\"}}"} +{"t": 1794161419.4014218, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"cd028d78-0ec0-5b6c-81f3-94eca5a4ced8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77e7fe76-9a61-488b-941f-d580e3851c15\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:06.219Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d71ed821-0f41-491d-aba3-d1079cfecf30\"}}"} +{"t": 1794161419.4015384, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"1bf5275b-1bf0-5503-8206-3649817d130e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"43041f0f-e18c-4690-ada0-1eae9e37b9df\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:06.219Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d71ed821-0f41-491d-aba3-d1079cfecf30\"}}"} +{"t": 1794161419.4016602, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a712064e-2a41-5fba-885e-5d28cc250d4d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77e7fe76-9a61-488b-941f-d580e3851c15\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:06.219Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d71ed821-0f41-491d-aba3-d1079cfecf30\",\"snapshot_url\":\"/api/observations/77e7fe76-9a61-488b-941f-d580e3851c15/snapshot\"}}"} +{"t": 1794161419.4017851, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"e28908ec-4f26-5d19-9506-f6a17c3018fc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"43041f0f-e18c-4690-ada0-1eae9e37b9df\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:06.219Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d71ed821-0f41-491d-aba3-d1079cfecf30\",\"snapshot_url\":\"/api/observations/43041f0f-e18c-4690-ada0-1eae9e37b9df/snapshot\"}}"} +{"t": 1794161419.4019015, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"63114242-5120-5c13-80e1-deaf911135a7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:07Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"abec62d9-ce6d-4fc3-aa8c-68aaaa6445d0\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:07.499Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1298ad03-c38a-439f-8c2a-cebb53b30e49\"}}"} +{"t": 1794161419.4020169, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"0de43b25-d8e3-5adc-8d8d-6e8eba3f0477\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:07Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5e285315-7fbc-4844-a2b2-440f110e7ec8\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:07.499Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1298ad03-c38a-439f-8c2a-cebb53b30e49\"}}"} +{"t": 1794161419.4021428, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"827105bf-32ed-57c9-b038-d7e929be6966\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:07Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"abec62d9-ce6d-4fc3-aa8c-68aaaa6445d0\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:07.499Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1298ad03-c38a-439f-8c2a-cebb53b30e49\",\"snapshot_url\":\"/api/observations/abec62d9-ce6d-4fc3-aa8c-68aaaa6445d0/snapshot\"}}"} +{"t": 1794161419.402269, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b53da1d7-5b58-5ab6-b38c-8510df19302e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:07Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5e285315-7fbc-4844-a2b2-440f110e7ec8\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:07.499Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1298ad03-c38a-439f-8c2a-cebb53b30e49\",\"snapshot_url\":\"/api/observations/5e285315-7fbc-4844-a2b2-440f110e7ec8/snapshot\"}}"} +{"t": 1794161419.40238, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"9dcc225f-4d3d-50bd-a35e-c884eb61bf8e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd7e5b21-597a-4fd1-8b4f-7d98d4e408e3\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:08.779Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5456e30b-b010-4741-b22b-250bc3f84e7f\"}}"} +{"t": 1794161419.402498, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"bceed122-e786-5e45-8e80-a9c3a2da8306\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8b370499-a58c-4d1c-b733-b2c95eda7ac8\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:08.779Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5456e30b-b010-4741-b22b-250bc3f84e7f\"}}"} +{"t": 1794161419.4026186, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"f823044b-0d33-59a5-b137-8f73c5750909\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd7e5b21-597a-4fd1-8b4f-7d98d4e408e3\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:08.779Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5456e30b-b010-4741-b22b-250bc3f84e7f\",\"snapshot_url\":\"/api/observations/dd7e5b21-597a-4fd1-8b4f-7d98d4e408e3/snapshot\"}}"} +{"t": 1794161419.4027398, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"019eb905-703f-5b78-abfe-f0701d20e76d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8b370499-a58c-4d1c-b733-b2c95eda7ac8\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:08.779Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5456e30b-b010-4741-b22b-250bc3f84e7f\",\"snapshot_url\":\"/api/observations/8b370499-a58c-4d1c-b733-b2c95eda7ac8/snapshot\"}}"} +{"t": 1794161419.4028578, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"ed8d11ca-6726-589d-a4aa-c71ff3b18d88\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ee0b1b01-791d-4d25-8908-e574b8a999d8\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:10.059Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4cfc62d3-8559-4d3d-af82-2cbe65adabb0\"}}"} +{"t": 1794161419.402972, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"205b8501-4126-54bc-84c1-cb4f24ee2194\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3addc662-ef61-462b-a804-6c513d1d40b2\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:10.059Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4cfc62d3-8559-4d3d-af82-2cbe65adabb0\"}}"} +{"t": 1794161419.4030926, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"4b2ee266-f449-57b6-bbf9-0baa1317b41c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ee0b1b01-791d-4d25-8908-e574b8a999d8\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:10.059Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4cfc62d3-8559-4d3d-af82-2cbe65adabb0\",\"snapshot_url\":\"/api/observations/ee0b1b01-791d-4d25-8908-e574b8a999d8/snapshot\"}}"} +{"t": 1794161419.4032204, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"cde6fb58-d3e7-5416-a406-9fdc494a826f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3addc662-ef61-462b-a804-6c513d1d40b2\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:10.059Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4cfc62d3-8559-4d3d-af82-2cbe65adabb0\",\"snapshot_url\":\"/api/observations/3addc662-ef61-462b-a804-6c513d1d40b2/snapshot\"}}"} +{"t": 1794161419.40333, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"b67a18d5-0fb1-5e27-bd78-7c241fdeee4c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0e81aeef-53d9-4228-aa64-78b225cafd6c\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:11.339Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c023273a-3d65-4b70-9374-1c84af22c272\"}}"} +{"t": 1794161419.4034445, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"fed36d54-6af3-5d49-8fe7-f9a660988cb3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"697516b9-f7c9-4eee-8e3e-615d63121902\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:11.339Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c023273a-3d65-4b70-9374-1c84af22c272\"}}"} +{"t": 1794161419.4035606, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"42468ec7-a780-5cc6-b69d-e22cf4e52f0a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0e81aeef-53d9-4228-aa64-78b225cafd6c\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:11.339Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c023273a-3d65-4b70-9374-1c84af22c272\",\"snapshot_url\":\"/api/observations/0e81aeef-53d9-4228-aa64-78b225cafd6c/snapshot\"}}"} +{"t": 1794161419.4036772, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"d2e7ee62-446f-5b78-b7bd-b3987573ae25\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"697516b9-f7c9-4eee-8e3e-615d63121902\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:11.339Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c023273a-3d65-4b70-9374-1c84af22c272\",\"snapshot_url\":\"/api/observations/697516b9-f7c9-4eee-8e3e-615d63121902/snapshot\"}}"} +{"t": 1794161419.4037848, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"72ee37ce-de5c-525d-b131-fc06ae806d15\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"576803c3-e15e-448a-bfe9-8e970e462eac\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:12.619Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"19cd706a-8b6e-410e-823b-c7949474985e\"}}"} +{"t": 1794161419.403902, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"fc3f89cc-75b8-50fa-98a5-6d75cf2e3f82\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b6fe99f-a98c-45f9-af66-29bd5c6df902\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:12.619Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"19cd706a-8b6e-410e-823b-c7949474985e\"}}"} +{"t": 1794161419.4040165, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"506b995d-4e7a-567e-a9a3-aab03f0e139d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"576803c3-e15e-448a-bfe9-8e970e462eac\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:12.619Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"19cd706a-8b6e-410e-823b-c7949474985e\",\"snapshot_url\":\"/api/observations/576803c3-e15e-448a-bfe9-8e970e462eac/snapshot\"}}"} +{"t": 1794161419.4041357, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"6f1661f7-aff5-53a4-a904-92253fd20aa9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:12Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b6fe99f-a98c-45f9-af66-29bd5c6df902\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:12.619Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"19cd706a-8b6e-410e-823b-c7949474985e\",\"snapshot_url\":\"/api/observations/4b6fe99f-a98c-45f9-af66-29bd5c6df902/snapshot\"}}"} +{"t": 1794161419.40425, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"2c195bf0-4665-5f70-8e33-d038872df36f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cd84838a-409c-43b7-aecf-f870bc2a707b\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:13.899Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"52008dee-d7ac-4960-87fe-7041e560c6d8\"}}"} +{"t": 1794161419.404361, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"a52b4c3e-4ad0-512b-ba92-abdbe4fbea21\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7193c6cc-757a-4e66-b049-a198baafcc0a\",\"revision\":1,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:13.899Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"52008dee-d7ac-4960-87fe-7041e560c6d8\"}}"} +{"t": 1794161419.404475, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"373fd9c5-8e78-5217-b235-6afd15729606\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cd84838a-409c-43b7-aecf-f870bc2a707b\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:13.899Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"52008dee-d7ac-4960-87fe-7041e560c6d8\",\"snapshot_url\":\"/api/observations/cd84838a-409c-43b7-aecf-f870bc2a707b/snapshot\"}}"} +{"t": 1794161419.404586, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/a292d981-f8b6-4fd5-b194-fb827336840d", "payload": "{\"specversion\":\"1.0\",\"id\":\"8485319a-e0b9-5f24-ae80-c83256675644\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/a292d981-f8b6-4fd5-b194-fb827336840d\",\"time\":\"2026-11-08T18:10:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7193c6cc-757a-4e66-b049-a198baafcc0a\",\"revision\":2,\"camera_uuid\":\"a292d981-f8b6-4fd5-b194-fb827336840d\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:13.899Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"52008dee-d7ac-4960-87fe-7041e560c6d8\",\"snapshot_url\":\"/api/observations/7193c6cc-757a-4e66-b049-a198baafcc0a/snapshot\"}}"} +{"t": 1794161427.0626929, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"4f6b1927-e7bb-588a-8687-abc1ac2a11c7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"11b8c24a-dce5-4a1d-96b3-c2600b41a143\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:27.054Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"16e9598e-3753-4441-aec6-2da647cc8a39\"}}"} +{"t": 1794161427.062856, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"5658f38f-3b80-5b7a-a1cb-912e40c54a9f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77bf3d15-893b-4087-96b0-de90f84650db\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:27.054Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"16e9598e-3753-4441-aec6-2da647cc8a39\"}}"} +{"t": 1794161427.0631888, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"4e02a5ff-0569-5efc-befc-a020b8a8e3e3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"11b8c24a-dce5-4a1d-96b3-c2600b41a143\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:27.054Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"16e9598e-3753-4441-aec6-2da647cc8a39\",\"snapshot_url\":\"/api/observations/11b8c24a-dce5-4a1d-96b3-c2600b41a143/snapshot\"}}"} +{"t": 1794161427.0635746, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"b436e5b3-0ec0-5fc0-8d5a-23b108934150\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77bf3d15-893b-4087-96b0-de90f84650db\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:27.054Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"16e9598e-3753-4441-aec6-2da647cc8a39\",\"snapshot_url\":\"/api/observations/77bf3d15-893b-4087-96b0-de90f84650db/snapshot\"}}"} +{"t": 1794161428.3418863, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"538fb6df-debe-54fd-82e7-e4e70f90ef31\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3dbfb24a-e3a7-410b-b79b-ad1c37ed21cc\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:28.334Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d6e6a800-fc91-4d61-8a61-66632d6d5e60\"}}"} +{"t": 1794161428.3420267, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"ca97ab9b-6049-5d07-837a-865ef5ada514\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6853411f-8d43-4410-b07e-0bcc5439cfe0\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:28.334Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d6e6a800-fc91-4d61-8a61-66632d6d5e60\"}}"} +{"t": 1794161428.3422506, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"19965ae7-66ff-5b13-8d00-74ad6ebb648d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3dbfb24a-e3a7-410b-b79b-ad1c37ed21cc\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:28.334Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d6e6a800-fc91-4d61-8a61-66632d6d5e60\",\"snapshot_url\":\"/api/observations/3dbfb24a-e3a7-410b-b79b-ad1c37ed21cc/snapshot\"}}"} +{"t": 1794161428.3424478, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"9ba7997a-7de7-5fad-8542-6d263f4a7aaa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6853411f-8d43-4410-b07e-0bcc5439cfe0\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:28.334Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d6e6a800-fc91-4d61-8a61-66632d6d5e60\",\"snapshot_url\":\"/api/observations/6853411f-8d43-4410-b07e-0bcc5439cfe0/snapshot\"}}"} +{"t": 1794161429.619937, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"463c3e89-c65d-5364-b19a-f63380f8808c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"12063009-495a-4857-8bef-5ad55caaee76\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:29.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9619abff-5f68-4af6-a1fb-6616e2562e63\"}}"} +{"t": 1794161429.6201644, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"7ea8fa76-69d8-56d0-95f6-e77481aa279e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"86c3e0d0-2c53-47aa-a889-16ad6397f878\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:29.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9619abff-5f68-4af6-a1fb-6616e2562e63\"}}"} +{"t": 1794161429.6204453, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"576725ce-73ff-59c3-93e1-c2a35ac6647f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"12063009-495a-4857-8bef-5ad55caaee76\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:29.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9619abff-5f68-4af6-a1fb-6616e2562e63\",\"snapshot_url\":\"/api/observations/12063009-495a-4857-8bef-5ad55caaee76/snapshot\"}}"} +{"t": 1794161429.6206815, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"06cf97fb-e6a5-579e-bf57-e427bdaeb448\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"86c3e0d0-2c53-47aa-a889-16ad6397f878\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:29.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9619abff-5f68-4af6-a1fb-6616e2562e63\",\"snapshot_url\":\"/api/observations/86c3e0d0-2c53-47aa-a889-16ad6397f878/snapshot\"}}"} +{"t": 1794161430.9005616, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"2b196237-2442-5c67-8e06-bd5e59b34114\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d0ff2cdb-0de9-4714-9da3-123cfaf692fe\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:30.894Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e9d7ac88-5b58-480c-9874-629793b26e88\"}}"} +{"t": 1794161430.9008176, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"a404fded-82ca-5105-be35-d675919493e9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"01e294b9-f62b-45b4-9b5d-94e6d02f38ee\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:30.894Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e9d7ac88-5b58-480c-9874-629793b26e88\"}}"} +{"t": 1794161430.9008937, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"f06542b2-43b1-554c-a742-8c434de5d336\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d0ff2cdb-0de9-4714-9da3-123cfaf692fe\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:30.894Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e9d7ac88-5b58-480c-9874-629793b26e88\",\"snapshot_url\":\"/api/observations/d0ff2cdb-0de9-4714-9da3-123cfaf692fe/snapshot\"}}"} +{"t": 1794161430.9011633, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"97de3ed9-a007-56a5-943f-64e1c39366e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"01e294b9-f62b-45b4-9b5d-94e6d02f38ee\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:30.894Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e9d7ac88-5b58-480c-9874-629793b26e88\",\"snapshot_url\":\"/api/observations/01e294b9-f62b-45b4-9b5d-94e6d02f38ee/snapshot\"}}"} +{"t": 1794161433.6212485, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"d98ae977-4ba9-5c5c-ae64-ee6c796a16cc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cd9bf8e0-a6f0-44f8-b941-87e1846e480f\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:33.613Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"36c282e1-1050-49b5-98c4-ceb24489e941\"}}"} +{"t": 1794161433.6214523, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"eea95b92-c1c2-52b7-827b-10347cd7ec88\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"39eff000-7f35-4958-96d9-7fd1c904ef99\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:33.613Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"36c282e1-1050-49b5-98c4-ceb24489e941\"}}"} +{"t": 1794161433.6216335, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"f1ccac43-dd97-5d24-be60-3cb2e5598029\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cd9bf8e0-a6f0-44f8-b941-87e1846e480f\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:33.613Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"36c282e1-1050-49b5-98c4-ceb24489e941\",\"snapshot_url\":\"/api/observations/cd9bf8e0-a6f0-44f8-b941-87e1846e480f/snapshot\"}}"} +{"t": 1794161433.6218581, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"53ca4523-beba-52e3-845b-84eb7602f145\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"39eff000-7f35-4958-96d9-7fd1c904ef99\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:33.613Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"36c282e1-1050-49b5-98c4-ceb24489e941\",\"snapshot_url\":\"/api/observations/39eff000-7f35-4958-96d9-7fd1c904ef99/snapshot\"}}"} +{"t": 1794161434.9006333, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"3ff76762-db25-5ca2-81c1-3de83e7991d4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d3644161-d77c-4802-8c4e-03ced3634e7a\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:34.893Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ac50d861-7d55-4018-8b13-268a151dd0c9\"}}"} +{"t": 1794161434.9008055, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"d47c7331-65df-5918-8e1e-2b6a60bd99a9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b7670ca5-a762-43bb-8b51-480382e51b90\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:34.893Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ac50d861-7d55-4018-8b13-268a151dd0c9\"}}"} +{"t": 1794161434.9010265, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ea6d576-b228-5c1b-a2da-905c85127f77\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d3644161-d77c-4802-8c4e-03ced3634e7a\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:34.893Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ac50d861-7d55-4018-8b13-268a151dd0c9\",\"snapshot_url\":\"/api/observations/d3644161-d77c-4802-8c4e-03ced3634e7a/snapshot\"}}"} +{"t": 1794161434.9012954, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"99a66b58-97fb-5f4f-ba1e-c7fbd55f498f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b7670ca5-a762-43bb-8b51-480382e51b90\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:34.893Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ac50d861-7d55-4018-8b13-268a151dd0c9\",\"snapshot_url\":\"/api/observations/b7670ca5-a762-43bb-8b51-480382e51b90/snapshot\"}}"} +{"t": 1794161436.1800945, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"44fdf962-10df-5490-8cb7-09453dfb959d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"40faa154-a785-46b8-878f-5e5cee5af7e1\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:36.173Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2c0f89f7-b7b2-4af2-a9d4-7309047b43d4\"}}"} +{"t": 1794161436.180288, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"6549adc1-6014-5ab5-b004-050971d35d0e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a523666a-dc48-48fc-9273-f0f2c3163df4\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:36.173Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2c0f89f7-b7b2-4af2-a9d4-7309047b43d4\"}}"} +{"t": 1794161436.1805098, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"821292a6-7c87-5129-90dd-86930a32d5c9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"40faa154-a785-46b8-878f-5e5cee5af7e1\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:36.173Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2c0f89f7-b7b2-4af2-a9d4-7309047b43d4\",\"snapshot_url\":\"/api/observations/40faa154-a785-46b8-878f-5e5cee5af7e1/snapshot\"}}"} +{"t": 1794161436.180723, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"2e7c8905-9977-5e2f-91b6-c60c9b0ad8bf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a523666a-dc48-48fc-9273-f0f2c3163df4\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:36.173Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2c0f89f7-b7b2-4af2-a9d4-7309047b43d4\",\"snapshot_url\":\"/api/observations/a523666a-dc48-48fc-9273-f0f2c3163df4/snapshot\"}}"} +{"t": 1794161437.4602933, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"283e458b-2750-5203-bfc1-6f86cc2e0817\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"26952b2a-6bc2-4757-9701-c187b7ac6f93\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:37.453Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3c58a2c1-091f-4854-8db9-bb848a8377a9\"}}"} +{"t": 1794161437.4605656, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"5cbe89c8-2011-5562-8ab8-d2cff3edb7e3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4fc71250-4cd9-4cd2-8216-408094710116\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:37.453Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3c58a2c1-091f-4854-8db9-bb848a8377a9\"}}"} +{"t": 1794161437.4609525, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"cc4b0f22-ce17-54b4-8d58-7406f49a9d66\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"26952b2a-6bc2-4757-9701-c187b7ac6f93\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:37.453Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c58a2c1-091f-4854-8db9-bb848a8377a9\",\"snapshot_url\":\"/api/observations/26952b2a-6bc2-4757-9701-c187b7ac6f93/snapshot\"}}"} +{"t": 1794161437.461444, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"cab9604e-f55a-5f53-8a3a-05e67868447e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4fc71250-4cd9-4cd2-8216-408094710116\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:37.453Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c58a2c1-091f-4854-8db9-bb848a8377a9\",\"snapshot_url\":\"/api/observations/4fc71250-4cd9-4cd2-8216-408094710116/snapshot\"}}"} +{"t": 1794161438.7398767, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"06b72809-7a4b-56ab-b8dd-9d052e41e0c4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"99c3d132-6206-4c84-8721-929b65d57bbd\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:38.733Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b268a742-3831-4ebb-baad-8f7e6e209a11\"}}"} +{"t": 1794161438.7400327, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"50ea7e76-446b-5e2a-9614-3febb219f78f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2af4973f-6a3c-4ece-9f2b-0e9991a9b9ea\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:38.733Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b268a742-3831-4ebb-baad-8f7e6e209a11\"}}"} +{"t": 1794161438.7402456, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"33b78825-9de5-56a3-afef-79bd9aa34692\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"99c3d132-6206-4c84-8721-929b65d57bbd\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:38.733Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b268a742-3831-4ebb-baad-8f7e6e209a11\",\"snapshot_url\":\"/api/observations/99c3d132-6206-4c84-8721-929b65d57bbd/snapshot\"}}"} +{"t": 1794161438.7404397, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"a22eba8c-38fc-55f9-bffa-b58b669287a3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2af4973f-6a3c-4ece-9f2b-0e9991a9b9ea\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:38.733Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b268a742-3831-4ebb-baad-8f7e6e209a11\",\"snapshot_url\":\"/api/observations/2af4973f-6a3c-4ece-9f2b-0e9991a9b9ea/snapshot\"}}"} +{"t": 1794161440.0189557, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"0f6174fc-311a-54b7-99d7-8423aa818638\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"910337bb-1d26-4ba8-b8c9-3c00db85d99c\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:40.013Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2ae3df21-6258-4a40-9c8a-6cf2a58b8f24\"}}"} +{"t": 1794161440.0192213, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"b9c9d5ee-fbdf-5f06-86e3-e5022ea80557\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"32466129-deb1-4e27-9b1b-f70d8eaca654\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:40.013Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2ae3df21-6258-4a40-9c8a-6cf2a58b8f24\"}}"} +{"t": 1794161440.0194645, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"6a1fcb99-28be-5583-b8bf-ec3828468491\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"910337bb-1d26-4ba8-b8c9-3c00db85d99c\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:40.013Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2ae3df21-6258-4a40-9c8a-6cf2a58b8f24\",\"snapshot_url\":\"/api/observations/910337bb-1d26-4ba8-b8c9-3c00db85d99c/snapshot\"}}"} +{"t": 1794161440.019673, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"4785424a-914f-59ff-bcaf-a20ad6690081\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"32466129-deb1-4e27-9b1b-f70d8eaca654\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:40.013Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2ae3df21-6258-4a40-9c8a-6cf2a58b8f24\",\"snapshot_url\":\"/api/observations/32466129-deb1-4e27-9b1b-f70d8eaca654/snapshot\"}}"} +{"t": 1794161441.2992554, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"8cc6171c-ac45-5397-9fd1-5d1399f791a8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"96f49124-26da-4bd8-9eab-71b9b81adb81\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:41.293Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2205aa10-73b4-4875-b4e8-7af0a6cb46b5\"}}"} +{"t": 1794161441.2994585, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"cfaa4ad2-9891-5b1c-8514-1d69b90b7e2d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"26e451fd-ee8f-4997-8915-c45c07f84e87\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:41.293Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2205aa10-73b4-4875-b4e8-7af0a6cb46b5\"}}"} +{"t": 1794161441.2997313, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"8cf40d4c-33cd-5eaa-be7a-cfcd65429630\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"96f49124-26da-4bd8-9eab-71b9b81adb81\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:41.293Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2205aa10-73b4-4875-b4e8-7af0a6cb46b5\",\"snapshot_url\":\"/api/observations/96f49124-26da-4bd8-9eab-71b9b81adb81/snapshot\"}}"} +{"t": 1794161441.3000226, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"e2514262-6683-51ac-bcdd-91fde5530505\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"26e451fd-ee8f-4997-8915-c45c07f84e87\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:41.293Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2205aa10-73b4-4875-b4e8-7af0a6cb46b5\",\"snapshot_url\":\"/api/observations/26e451fd-ee8f-4997-8915-c45c07f84e87/snapshot\"}}"} +{"t": 1794161442.5796647, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"24e564c5-8fa2-5a95-9c42-92df3a9642aa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e287d544-21ec-4af7-ba11-06abce62a82f\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:42.573Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ae112e1e-97eb-46f4-a721-16e2f85c65c5\"}}"} +{"t": 1794161442.5798588, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"fd54a7f4-c5af-5a49-8b35-eaebf5af2022\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"51d85c83-6065-4fe8-8dae-25ef1d4aa32d\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:42.573Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ae112e1e-97eb-46f4-a721-16e2f85c65c5\"}}"} +{"t": 1794161442.5800753, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"3c40092f-514a-5c7c-8fe8-feb6c5fb6cfc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e287d544-21ec-4af7-ba11-06abce62a82f\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:42.573Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ae112e1e-97eb-46f4-a721-16e2f85c65c5\",\"snapshot_url\":\"/api/observations/e287d544-21ec-4af7-ba11-06abce62a82f/snapshot\"}}"} +{"t": 1794161442.5802882, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"33704af2-1d43-5c1a-88d6-5887dd5b8419\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"51d85c83-6065-4fe8-8dae-25ef1d4aa32d\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:42.573Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ae112e1e-97eb-46f4-a721-16e2f85c65c5\",\"snapshot_url\":\"/api/observations/51d85c83-6065-4fe8-8dae-25ef1d4aa32d/snapshot\"}}"} +{"t": 1794161443.8607574, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"304eeae9-76b2-56ce-9352-b26b2c0a541f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f4916b4-1b24-4d3a-9e00-99e37047ec30\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:43.853Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a9f7d40c-82cd-43c2-8b9f-e947c554a393\"}}"} +{"t": 1794161443.8609118, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"48fe4cd3-7c6f-5f4b-8c65-6ba170e4cade\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ca5c5b54-c734-4d92-a45a-51a3fb600bb9\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:43.853Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a9f7d40c-82cd-43c2-8b9f-e947c554a393\"}}"} +{"t": 1794161443.8611014, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"98ed67f5-5811-5997-baf9-3829e58c79d9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f4916b4-1b24-4d3a-9e00-99e37047ec30\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:43.853Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a9f7d40c-82cd-43c2-8b9f-e947c554a393\",\"snapshot_url\":\"/api/observations/7f4916b4-1b24-4d3a-9e00-99e37047ec30/snapshot\"}}"} +{"t": 1794161443.8613005, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"b10530a5-e42e-5722-83de-7969926ae5f9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ca5c5b54-c734-4d92-a45a-51a3fb600bb9\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:43.853Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a9f7d40c-82cd-43c2-8b9f-e947c554a393\",\"snapshot_url\":\"/api/observations/ca5c5b54-c734-4d92-a45a-51a3fb600bb9/snapshot\"}}"} +{"t": 1794161445.1410055, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"bf2f38c6-0260-5aba-a050-3ef1d1d4a239\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"daecb46f-beb7-4300-bd58-608b70e27efd\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:45.133Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3c99dc7c-c85c-413b-b10f-94ecc8ceecb5\"}}"} +{"t": 1794161445.1411266, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"aa94c05b-7216-5963-9213-6c2be77881a0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f337b76f-5d29-4dca-b0cb-56e18bb6fac6\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:45.133Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3c99dc7c-c85c-413b-b10f-94ecc8ceecb5\"}}"} +{"t": 1794161445.1413262, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"674092eb-3641-56c2-811c-bc6886ced7fd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"daecb46f-beb7-4300-bd58-608b70e27efd\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:45.133Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c99dc7c-c85c-413b-b10f-94ecc8ceecb5\",\"snapshot_url\":\"/api/observations/daecb46f-beb7-4300-bd58-608b70e27efd/snapshot\"}}"} +{"t": 1794161445.141529, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"5d47f37f-7365-5d7d-8c7a-e1e36c9e7d30\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f337b76f-5d29-4dca-b0cb-56e18bb6fac6\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:45.133Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c99dc7c-c85c-413b-b10f-94ecc8ceecb5\",\"snapshot_url\":\"/api/observations/f337b76f-5d29-4dca-b0cb-56e18bb6fac6/snapshot\"}}"} +{"t": 1794161446.4201458, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"fb1daa20-f175-571c-b33c-2a252c94261d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"33ea5ccc-3820-4c20-ba47-0619ddfb3f5a\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:46.413Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e2fa01fa-b877-4830-b9ad-4cd33d8bfad6\"}}"} +{"t": 1794161446.4203007, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"a3218880-940b-548e-8ff1-7bec9e8c2d87\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"14378067-babd-4b89-9fbc-5f5140ecfa4e\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:46.413Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e2fa01fa-b877-4830-b9ad-4cd33d8bfad6\"}}"} +{"t": 1794161446.4205241, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"bb1ebd03-ae99-5dd2-911d-b48261ba9240\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"33ea5ccc-3820-4c20-ba47-0619ddfb3f5a\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:46.413Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e2fa01fa-b877-4830-b9ad-4cd33d8bfad6\",\"snapshot_url\":\"/api/observations/33ea5ccc-3820-4c20-ba47-0619ddfb3f5a/snapshot\"}}"} +{"t": 1794161446.4207022, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"96938899-7aaf-5499-bf4a-3a499164bd75\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"14378067-babd-4b89-9fbc-5f5140ecfa4e\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:46.413Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e2fa01fa-b877-4830-b9ad-4cd33d8bfad6\",\"snapshot_url\":\"/api/observations/14378067-babd-4b89-9fbc-5f5140ecfa4e/snapshot\"}}"} +{"t": 1794161447.6990223, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"22f1b3d7-95dd-59e3-a81d-3629e91c909a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2144f077-505b-4554-9a9a-4ea0b593f9b8\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:47.693Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"69783b9e-e7fb-41bd-a037-7885aee9cd0a\"}}"} +{"t": 1794161447.6991367, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"a55ddf7a-01db-515b-ab6b-86550548b590\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3019b4a3-5d65-4f5a-95b2-74c5d8f8476f\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:47.693Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"69783b9e-e7fb-41bd-a037-7885aee9cd0a\"}}"} +{"t": 1794161447.6994228, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"bd18ccbb-b67a-5960-bc1e-a98ebf23f04a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2144f077-505b-4554-9a9a-4ea0b593f9b8\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:47.693Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"69783b9e-e7fb-41bd-a037-7885aee9cd0a\",\"snapshot_url\":\"/api/observations/2144f077-505b-4554-9a9a-4ea0b593f9b8/snapshot\"}}"} +{"t": 1794161447.6996114, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"30f0ed0c-f504-56a0-9590-6922e3d857fe\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3019b4a3-5d65-4f5a-95b2-74c5d8f8476f\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:47.693Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"69783b9e-e7fb-41bd-a037-7885aee9cd0a\",\"snapshot_url\":\"/api/observations/3019b4a3-5d65-4f5a-95b2-74c5d8f8476f/snapshot\"}}"} +{"t": 1794161448.9795363, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"2179732c-8f46-5d1f-bdc4-052d81d07f63\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"342db797-c5be-4f4a-bcb5-6e1d8595c8fe\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:48.973Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"da3451bf-5dc5-44db-8b34-50aff5d82b2c\"}}"} +{"t": 1794161448.9796867, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"192c7ec9-bdd3-5fb4-b789-e63bcdf73cea\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f4fdbfe6-fe23-43cc-8d78-c6391ae3313e\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:48.973Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"da3451bf-5dc5-44db-8b34-50aff5d82b2c\"}}"} +{"t": 1794161448.9798496, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"8f5f1340-1d96-5404-89a6-08d48b0c9b2d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"342db797-c5be-4f4a-bcb5-6e1d8595c8fe\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:48.973Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"da3451bf-5dc5-44db-8b34-50aff5d82b2c\",\"snapshot_url\":\"/api/observations/342db797-c5be-4f4a-bcb5-6e1d8595c8fe/snapshot\"}}"} +{"t": 1794161448.9800677, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"8b4cf583-a0f0-5632-b860-3ead82a72601\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f4fdbfe6-fe23-43cc-8d78-c6391ae3313e\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:48.973Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"da3451bf-5dc5-44db-8b34-50aff5d82b2c\",\"snapshot_url\":\"/api/observations/f4fdbfe6-fe23-43cc-8d78-c6391ae3313e/snapshot\"}}"} +{"t": 1794161450.259702, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"7c528e8e-390d-5103-b46c-844e66ada829\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0d984f04-8082-4d31-ad25-0eb9d4f69e22\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:50.253Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5e8ff520-603b-4594-a200-92d7273dd2d5\"}}"} +{"t": 1794161450.2598405, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"7e0fd3b9-3b14-55e3-8738-86bccecc0716\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c7ea4d32-2223-42a0-bfbe-5b3ba7c946ca\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:50.253Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5e8ff520-603b-4594-a200-92d7273dd2d5\"}}"} +{"t": 1794161450.260103, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"d4a38232-0294-53de-af07-c21a3ed60bff\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0d984f04-8082-4d31-ad25-0eb9d4f69e22\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:50.253Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5e8ff520-603b-4594-a200-92d7273dd2d5\",\"snapshot_url\":\"/api/observations/0d984f04-8082-4d31-ad25-0eb9d4f69e22/snapshot\"}}"} +{"t": 1794161450.2602963, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"76e7c990-db6d-5585-8cb8-d793ff6b71a5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c7ea4d32-2223-42a0-bfbe-5b3ba7c946ca\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:50.253Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5e8ff520-603b-4594-a200-92d7273dd2d5\",\"snapshot_url\":\"/api/observations/c7ea4d32-2223-42a0-bfbe-5b3ba7c946ca/snapshot\"}}"} +{"t": 1794161451.5398643, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"edc053fd-495a-5621-9542-c39ccc35cdaf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f20575f6-bff1-4fef-93d8-7ec1d516b66b\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:51.533Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c63717e5-4323-4437-acea-36f933e73860\"}}"} +{"t": 1794161451.5400038, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"a0252a8b-ee24-5c48-b8c0-bdcd405209bb\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34a16e74-31ac-42d5-8c07-2865e0cef656\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:51.533Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c63717e5-4323-4437-acea-36f933e73860\"}}"} +{"t": 1794161451.540226, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"c80c383d-dfb0-5966-ad48-cd46a3c923e1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f20575f6-bff1-4fef-93d8-7ec1d516b66b\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:51.533Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c63717e5-4323-4437-acea-36f933e73860\",\"snapshot_url\":\"/api/observations/f20575f6-bff1-4fef-93d8-7ec1d516b66b/snapshot\"}}"} +{"t": 1794161451.5404277, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"87e7003e-0497-561d-922f-8507b0d6aea1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34a16e74-31ac-42d5-8c07-2865e0cef656\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:51.533Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c63717e5-4323-4437-acea-36f933e73860\",\"snapshot_url\":\"/api/observations/34a16e74-31ac-42d5-8c07-2865e0cef656/snapshot\"}}"} +{"t": 1794161452.8201313, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"606c866a-7a04-5c88-a19e-241212eb8c50\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"56a2c34d-fb7f-4ccd-829d-07257c3093ec\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:52.813Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f25d5d4e-ee30-432e-8450-a053ff8476fb\"}}"} +{"t": 1794161452.8202639, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"72ccea28-3b64-5b2f-9925-420034515645\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d8715d53-6b68-47ba-a279-3d5475253711\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:52.813Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f25d5d4e-ee30-432e-8450-a053ff8476fb\"}}"} +{"t": 1794161452.8204772, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"eaeead5e-a8d4-5292-98a8-136ef45d2899\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"56a2c34d-fb7f-4ccd-829d-07257c3093ec\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:52.813Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f25d5d4e-ee30-432e-8450-a053ff8476fb\",\"snapshot_url\":\"/api/observations/56a2c34d-fb7f-4ccd-829d-07257c3093ec/snapshot\"}}"} +{"t": 1794161452.82068, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"13372538-f802-57b0-a103-b10d42f3583f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d8715d53-6b68-47ba-a279-3d5475253711\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:52.813Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f25d5d4e-ee30-432e-8450-a053ff8476fb\",\"snapshot_url\":\"/api/observations/d8715d53-6b68-47ba-a279-3d5475253711/snapshot\"}}"} +{"t": 1794161454.1016505, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"dc5ebd4a-103c-5e8b-8b86-c9a75a1a0f3e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"020a4203-e700-44cf-954d-a0ba99adc9a9\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:54.093Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"94d082fc-fbc2-49d1-9a44-db211c3362b5\"}}"} +{"t": 1794161454.1017823, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"8dc318c4-b37b-54db-a4c0-9ece79545717\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2335371-4aba-48fc-931c-7075f83ccacb\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:54.093Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"94d082fc-fbc2-49d1-9a44-db211c3362b5\"}}"} +{"t": 1794161454.1020055, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"b7b06f6f-3e4d-5b29-9ab4-d3c4693f8d6c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"020a4203-e700-44cf-954d-a0ba99adc9a9\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:54.093Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"94d082fc-fbc2-49d1-9a44-db211c3362b5\",\"snapshot_url\":\"/api/observations/020a4203-e700-44cf-954d-a0ba99adc9a9/snapshot\"}}"} +{"t": 1794161454.1023023, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"36eb1c9e-2edd-5f10-acab-0f127a5746f9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2335371-4aba-48fc-931c-7075f83ccacb\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:54.093Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"94d082fc-fbc2-49d1-9a44-db211c3362b5\",\"snapshot_url\":\"/api/observations/e2335371-4aba-48fc-931c-7075f83ccacb/snapshot\"}}"} +{"t": 1794161455.3805442, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"b1a3f609-c1f6-59b1-9fdf-987681c3715b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9ac9042d-c29d-47cd-9257-d9f3114788da\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:55.373Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b5f6cc4e-bd6d-4c64-8a76-b870571dddf5\"}}"} +{"t": 1794161455.3807054, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"36d6fa55-e51b-51e2-b264-c64b753e2420\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"217b89f3-bc51-43a0-8c4b-f1b33551dbdd\",\"revision\":1,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:55.373Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b5f6cc4e-bd6d-4c64-8a76-b870571dddf5\"}}"} +{"t": 1794161455.3809078, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"9180e1d1-13e3-5dfd-b01a-d4f0b3088ac7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9ac9042d-c29d-47cd-9257-d9f3114788da\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:55.373Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b5f6cc4e-bd6d-4c64-8a76-b870571dddf5\",\"snapshot_url\":\"/api/observations/9ac9042d-c29d-47cd-9257-d9f3114788da/snapshot\"}}"} +{"t": 1794161455.38107, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/8db6637e-dc86-44fd-b0ff-cf95ef48ea52", "payload": "{\"specversion\":\"1.0\",\"id\":\"2af724fe-6693-5a1c-a99b-3e403e7d9191\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"time\":\"2026-11-08T18:10:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"217b89f3-bc51-43a0-8c4b-f1b33551dbdd\",\"revision\":2,\"camera_uuid\":\"8db6637e-dc86-44fd-b0ff-cf95ef48ea52\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:10:55.373Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b5f6cc4e-bd6d-4c64-8a76-b870571dddf5\",\"snapshot_url\":\"/api/observations/217b89f3-bc51-43a0-8c4b-f1b33551dbdd/snapshot\"}}"} +{"t": 1794161460.5020835, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"5fc85f06-367f-56e6-8e7e-5b5867261232\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4ac9eb69-af84-4973-89de-6f874849187d\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:00.495Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2d4d63a3-21a5-4189-b78c-80530c3bfdf5\"}}"} +{"t": 1794161460.502321, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"eb2a92d8-d8c7-5c93-a0ad-1a055cc6777e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4ac9eb69-af84-4973-89de-6f874849187d\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:00.495Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2d4d63a3-21a5-4189-b78c-80530c3bfdf5\",\"snapshot_url\":\"/api/observations/4ac9eb69-af84-4973-89de-6f874849187d/snapshot\"}}"} +{"t": 1794161461.7813044, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"f739ab83-5942-5814-8d97-0ec18fd5e3b2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"103b5cc1-4622-41e2-ab71-c76172dcf970\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:01.775Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c44c611e-6cfa-47de-9a09-692d1170c141\"}}"} +{"t": 1794161461.7814927, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"0c84668b-b04b-5fa6-ae27-0f72fa1bd246\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"103b5cc1-4622-41e2-ab71-c76172dcf970\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:01.775Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c44c611e-6cfa-47de-9a09-692d1170c141\",\"snapshot_url\":\"/api/observations/103b5cc1-4622-41e2-ab71-c76172dcf970/snapshot\"}}"} +{"t": 1794161506.171702, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"80c737e2-ba13-56a5-9194-bf5fa92ba775\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a4cd465-38d6-4270-a2ed-9674a245dfb4\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:46.162Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"65be61ee-c39a-4d3c-816b-4e75006eecb1\"}}"} +{"t": 1794161506.1720078, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"0ca2b485-17ec-5154-ad54-4a39481d09ed\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a4cd465-38d6-4270-a2ed-9674a245dfb4\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:46.162Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"65be61ee-c39a-4d3c-816b-4e75006eecb1\",\"snapshot_url\":\"/api/observations/8a4cd465-38d6-4270-a2ed-9674a245dfb4/snapshot\"}}"} +{"t": 1794161507.4495506, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"449bdaf5-66aa-5be8-918b-a9867a825678\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"addd231b-f70d-4239-a4d5-8d5eb25035f9\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:47.442Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"572b8b72-09a6-4e7c-8b50-a20918c9f264\"}}"} +{"t": 1794161507.4499085, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"02487101-1381-5eba-b2a8-e47efcf385a9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"addd231b-f70d-4239-a4d5-8d5eb25035f9\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:47.442Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"572b8b72-09a6-4e7c-8b50-a20918c9f264\",\"snapshot_url\":\"/api/observations/addd231b-f70d-4239-a4d5-8d5eb25035f9/snapshot\"}}"} +{"t": 1794161508.7291303, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"0e651313-73c3-552c-a700-db13433641a1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8895eee4-b0f0-41c7-8bd5-876bdca8cd53\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:48.722Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"314bb6a1-98c2-49c9-a093-5c3eb089ef4b\"}}"} +{"t": 1794161508.7293472, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"e09d09f3-2ec0-54d1-8d9c-bd087a8f5af9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8895eee4-b0f0-41c7-8bd5-876bdca8cd53\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:48.722Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"314bb6a1-98c2-49c9-a093-5c3eb089ef4b\",\"snapshot_url\":\"/api/observations/8895eee4-b0f0-41c7-8bd5-876bdca8cd53/snapshot\"}}"} +{"t": 1794161510.0136652, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"c6a27a0c-5817-54d8-b39d-4e2c12780061\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7e928156-af15-4b87-b3f3-86ecde0e93c4\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:50.004Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"60712dbe-c12c-4a68-ae44-73627c6ad245\"}}"} +{"t": 1794161510.0139074, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"42ef00b7-26dd-5c0f-ab81-7c53bc226dd7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7e928156-af15-4b87-b3f3-86ecde0e93c4\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:50.004Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"60712dbe-c12c-4a68-ae44-73627c6ad245\",\"snapshot_url\":\"/api/observations/7e928156-af15-4b87-b3f3-86ecde0e93c4/snapshot\"}}"} +{"t": 1794161514.4457557, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"591e08e5-4133-5e51-a1a0-04f33d70efd1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8895eee4-b0f0-41c7-8bd5-876bdca8cd53\",\"revision\":3,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:48.722Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"unavailable\",\"evidence_reason\":\"write_failed\",\"asset_id\":\"314bb6a1-98c2-49c9-a093-5c3eb089ef4b\"}}"} +{"t": 1794161514.4465184, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"d7f1eb0d-2224-5a38-9bf1-4fe747aa45ed\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:11:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7e928156-af15-4b87-b3f3-86ecde0e93c4\",\"revision\":3,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:11:50.004Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"unavailable\",\"evidence_reason\":\"write_failed\",\"asset_id\":\"60712dbe-c12c-4a68-ae44-73627c6ad245\"}}"} +{"t": 1794161574.463073, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"5ac736a9-28d4-5f13-9fdd-b52a687c624e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:12:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a4cd465-38d6-4270-a2ed-9674a245dfb4\",\"revision\":3,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-10-31T18:11:46.162Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"expired\",\"evidence_reason\":\"retention_expired\",\"asset_id\":\"65be61ee-c39a-4d3c-816b-4e75006eecb1\"}}"} +{"t": 1794161574.463511, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"59220580-7946-50e2-b78d-95e19db16f25\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:12:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"addd231b-f70d-4239-a4d5-8d5eb25035f9\",\"revision\":3,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-10-31T18:11:47.442Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"expired\",\"evidence_reason\":\"retention_expired\",\"asset_id\":\"572b8b72-09a6-4e7c-8b50-a20918c9f264\"}}"} +{"t": 1794161588.0575018, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"6de188a4-6547-589c-8344-6f948dd6207b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:13:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ae0150a5-a133-44a6-a4c5-c9e2f62f43fb\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:13:08.049Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f5c96cbd-cb53-404c-ba4d-c19c9808c1d7\"}}"} +{"t": 1794161588.0578296, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"6df9444b-9433-534e-a62f-a3dd05c37f07\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:13:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ae0150a5-a133-44a6-a4c5-c9e2f62f43fb\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:13:08.049Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f5c96cbd-cb53-404c-ba4d-c19c9808c1d7\",\"snapshot_url\":\"/api/observations/ae0150a5-a133-44a6-a4c5-c9e2f62f43fb/snapshot\"}}"} +{"t": 1794161589.33561, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"3d4679e8-c046-580a-8a56-3e6d819f6bf0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:13:09Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ddfeb50c-2bde-40d7-834f-e0080da33755\",\"revision\":1,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:13:09.329Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"87eb1b09-8b45-4a1c-838f-a80eabaaa360\"}}"} +{"t": 1794161589.3359857, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa", "payload": "{\"specversion\":\"1.0\",\"id\":\"9066a0b8-804b-57ce-a63e-449faea22552\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:12e0cfcb-359e-460f-af91-093a2c93724f\",\"subject\":\"camera/1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"time\":\"2026-11-08T18:13:09Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ddfeb50c-2bde-40d7-834f-e0080da33755\",\"revision\":2,\"camera_uuid\":\"1bee0e9d-06d4-4bcc-b958-7fb3d44bc3aa\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:13:09.329Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"87eb1b09-8b45-4a1c-838f-a80eabaaa360\",\"snapshot_url\":\"/api/observations/ddfeb50c-2bde-40d7-834f-e0080da33755/snapshot\"}}"} diff --git a/tests/integration/yawamf_e2e/results/x3-host-20261108-red-nowriteback.json b/tests/integration/yawamf_e2e/results/x3-host-20261108-red-nowriteback.json new file mode 100644 index 000000000..07212d10a --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x3-host-20261108-red-nowriteback.json @@ -0,0 +1,377 @@ +{ + "suite": "INT_01 X1/X3 lightNVR <-> YA-WAMF E2E", + "writeback_enabled": false, + "mode": "host-process", + "started_at": 1794161995.39704, + "finished_at": 1794162245.473003, + "seconds": 250.1, + "versions": { + "lightnvr": { + "commit": "bcf5290f", + "branch": "feat/int01-event-evidence", + "dirty_tracked": true, + "binary": "/home/matteius/opensensor/lightNVR/build-int01-X3/bin/lightnvr" + }, + "yawamf": { + "commit": "1cbbae74", + "branch": "feature/lightnvr-provider" + }, + "mosquitto": "mosquitto version 2.0.22", + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "detector": "loopback HTTP x1-colour-detector 1.0 (light-object-detect wire format; finds the red/blue fixture birds by colour)", + "classifier": "pinned x1-colour-rule-v1 (harness/yawamf_launcher.py replaces ClassifierService.classify_async_live)", + "python": "3.12.12" + }, + "recipe": [ + { + "step": "prereq: MQTT broker", + "outcome": "ok", + "detail": { + "broker": "mosquitto version 2.0.22", + "port": 39075 + } + }, + { + "step": "prereq: lightNVR with [mqtt] block (step 3.1)", + "outcome": "ok", + "detail": { + "version": "0.47.3" + } + }, + { + "step": "step 1-2: detector + evidence per camera", + "outcome": "ok", + "detail": { + "cameras": { + "feeder-bulk": "fbeda394-cd3d-41b8-8a59-a38733dd9f12", + "feeder-east": "4467465e-c408-42fb-a2aa-4bdf4c22a130", + "feeder-kill": "571911f9-77cf-4519-9f1b-54ee9538c73d", + "feeder-moving": "5c506dd1-30e8-4710-a662-749d226d9c8c", + "feeder-outcomes": "a19e729a-0cbb-4e06-935c-e4ee918179e3", + "feeder-pair": "6030b9ea-228c-40c4-8115-1a37338eb78c", + "feeder-wb": "209b0cf7-f2e7-47f7-aa85-b84fe3193c0d", + "feeder-wb-outage": "89c57d00-1ad1-4256-abee-b632c473b193" + }, + "observation_evidence_enabled": true, + "observation_labels": "bird" + } + }, + { + "step": "step 3.2: event route", + "outcome": "ok", + "detail": { + "route": "b2d36b11-223b-47aa-b0c2-920471409e79" + } + }, + { + "step": "step 4.2: create role (recipe body)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.2: create role with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.3: grant policy (no expected_policy_version)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.3: grant policy with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.4: scoped token", + "outcome": "ok", + "detail": { + "principal": { + "writer_namespace": "yawamf" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras_evidence_enabled": { + "209b0cf7-f2e7-47f7-aa85-b84fe3193c0d": true, + "4467465e-c408-42fb-a2aa-4bdf4c22a130": true, + "571911f9-77cf-4519-9f1b-54ee9538c73d": true, + "5c506dd1-30e8-4710-a662-749d226d9c8c": true, + "6030b9ea-228c-40c4-8115-1a37338eb78c": true, + "89c57d00-1ad1-4256-abee-b632c473b193": true, + "a19e729a-0cbb-4e06-935c-e4ee918179e3": true, + "fbeda394-cd3d-41b8-8a59-a38733dd9f12": true + } + } + }, + { + "step": "step 5: YA-WAMF started (MQTT subscribed)", + "outcome": "ok", + "detail": { + "health_version": "2.23.1-feature/lightnvr-provider+1cbbae74", + "classification_writeback": { + "enabled": false, + "running": true, + "outcomes": {}, + "last_outcome": null, + "last_outcome_at": null + } + } + }, + { + "step": "step 6.1: Test lightNVR connection", + "outcome": "ok", + "detail": { + "ok": true, + "results": [ + { + "id": "api_access", + "status": "ok", + "code": "connected", + "message": "Connected to lightNVR 0.47.3.", + "values": { + "product_version": "0.47.3", + "camera_count": 8 + } + }, + { + "id": "event_connectivity", + "status": "ok", + "code": "connected", + "message": "The MQTT broker answered and the lightNVR topic matches.", + "values": { + "topic": "lightnvr" + } + }, + { + "id": "evidence_access", + "status": "ok", + "code": "authorized", + "message": "YA-WAMF may read detection photos.", + "values": {} + }, + { + "id": "features", + "status": "ok", + "code": "listed", + "message": "Available: history, snapshots, annotate. Not available with this source: track_lifecycle, recorded_frame, event_clip, range_clip.", + "values": { + "supported": "history,snapshots,annotate", + "unsupported": "track_lifecycle,recorded_frame,event_clip,range_clip" + } + } + ], + "features": [ + { + "name": "history", + "supported": true, + "reason": null + }, + { + "name": "snapshots", + "supported": true, + "reason": null + }, + { + "name": "annotate", + "supported": true, + "reason": null + }, + { + "name": "track_lifecycle", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "recorded_frame", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "event_clip", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "range_clip", + "supported": false, + "reason": "not_implemented" + } + ], + "latest_observation": { + "state": "none_yet", + "code": null, + "observation_id": null, + "camera_uuid": null, + "camera_name": null, + "label": null, + "observed_at": null, + "photo_state": null, + "photo_reason": null, + "searched_labels": [ + "bird" + ] + } + } + }, + { + "step": "step 6.2: setup probe", + "outcome": "ok", + "detail": { + "status": 200, + "body": { + "items": [], + "next_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiMWZmMzc5NjgtOTIwZS00MGE4LWFlYmQtNzE4NDYzYzA1OWEyIn0", + "has_more": false + } + } + }, + { + "step": "step 6.2: setup probe, camera outside the token scope", + "outcome": "ok", + "detail": { + "status": 403 + } + }, + { + "step": "diagnostics: token cannot read /api/system/info", + "outcome": "ok", + "detail": { + "status": 403 + } + } + ], + "deploy_error": null, + "scenarios": [ + { + "id": "A1", + "title": "identity and labels", + "started_at": 1794162000.0169942, + "evidence": { + "observation": { + "id": "735b1951-6716-4feb-a34f-4a16f02796d7", + "camera_uuid": "4467465e-c408-42fb-a2aa-4bdf4c22a130", + "stream_name_at_capture": "feeder-east", + "label": "bird", + "confidence": 0.899999976158142, + "box": { + "format": "xywh_normalized", + "x": 0.3125, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "revision": 2 + }, + "evidence": { + "asset_id": "fe39f160-04d3-4c09-844d-bae3e76b1add", + "state": "ready", + "reason": null, + "origin": "submitted_image", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 2125, + "captured_at": "2026-11-08T18:20:00.543Z", + "expires_at": null, + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/735b1951-6716-4feb-a34f-4a16f02796d7/snapshot" + }, + "yawamf_row": { + "event": "lnvr:e7ddc82d-b32a-4e7c-83e2-af3893bcec90:735b1951-6716-4feb-a34f-4a16f02796d7", + "observation_id": "735b1951-6716-4feb-a34f-4a16f02796d7", + "source_kind": "lightnvr", + "source_instance": "e7ddc82d-b32a-4e7c-83e2-af3893bcec90", + "camera_uuid": "4467465e-c408-42fb-a2aa-4bdf4c22a130", + "camera_name": "feeder-east", + "scientific_name": "Prunella modularis", + "display_name": "Dunnock", + "score": 0.93, + "intent": "dispatch", + "outcome": "skipped" + }, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/4467465e-c408-42fb-a2aa-4bdf4c22a130", + "mqtt_revisions": { + "1": { + "id": "f462393f-e5e1-5152-b03c-39f2e9a92cd7", + "cause": "created", + "evidence_state": "pending" + }, + "2": { + "id": "63964555-a4bd-5332-bb61-d64ea1c1910d", + "cause": "evidence", + "evidence_state": "ready" + } + }, + "classifier_calls": [ + { + "t": 1794162000.618737, + "camera": "feeder-east", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.9, + 38.3, + 25.7 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ] + }, + "result": "pass", + "seconds": 2.0 + }, + { + "id": "A12", + "title": "write-back: species in both products, replays and stale revisions, owner precedence, no loop", + "started_at": 1794162002.0408356, + "result": "fail", + "error": "TimeoutError: timed out after 120s waiting for the automated write-back on lightNVR", + "traceback": "Traceback (most recent call last):\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 180, in scenario\n entry[\"evidence\"] = fn()\n ^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/p2_scenarios.py\", line 127, in a12\n stored = self.wait_species(obs[\"id\"], lambda r: r[\"kind\"] == \"automated\", 120, \"the automated write-back on lightNVR\")\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/p2_scenarios.py\", line 101, in wait_species\n return wait_for(lambda: (lambda row: row if row and predicate(row) else None)(self.lnvr_species(observation_id)), timeout, 1.0, what)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/harness/components.py\", line 51, in wait_for\n raise TimeoutError(f\"timed out after {timeout:.0f}s waiting for {what}\" + (f\" (last error: {last_error})\" if last_error else \"\"))\nTimeoutError: timed out after 120s waiting for the automated write-back on lightNVR\n", + "seconds": 122.2 + }, + { + "id": "A13", + "title": "write-back survives a lightNVR outage; the YA-WAMF detection stays intact", + "started_at": 1794162124.2596874, + "result": "fail", + "error": "TimeoutError: timed out after 120s waiting for the automated write-back before the outage", + "traceback": "Traceback (most recent call last):\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 180, in scenario\n entry[\"evidence\"] = fn()\n ^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/p2_scenarios.py\", line 247, in a13\n auto = self.wait_species(obs[\"id\"], lambda r: r[\"kind\"] == \"automated\", 120, \"the automated write-back before the outage\")\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/p2_scenarios.py\", line 101, in wait_species\n return wait_for(lambda: (lambda row: row if row and predicate(row) else None)(self.lnvr_species(observation_id)), timeout, 1.0, what)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/harness/components.py\", line 51, in wait_for\n raise TimeoutError(f\"timed out after {timeout:.0f}s waiting for {what}\" + (f\" (last error: {last_error})\" if last_error else \"\"))\nTimeoutError: timed out after 120s waiting for the automated write-back before the outage\n", + "seconds": 121.2 + }, + { + "id": "A7-annotate", + "title": "annotate needs observations.annotate on the capture camera", + "started_at": 1794162245.4441879, + "result": "fail", + "error": "HttpError: HTTP 403: b'{\"error\":\"API token scope exceeds live.view authority\"}'", + "traceback": "Traceback (most recent call last):\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/run_e2e.py\", line 180, in scenario\n entry[\"evidence\"] = fn()\n ^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/p2_scenarios.py\", line 303, in a7_annotate\n other_camera = self.lnvr.api(\"POST\", f\"/api/authorization/users/{self.user_id}/tokens\", {\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/harness/components.py\", line 428, in api\n return http_json(method, f\"{self.base}{route}\", body, headers or self.admin, expect)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/matteius/opensensor/lightNVR/tests/integration/yawamf_e2e/harness/components.py\", line 80, in http_json\n raise HttpError(status, payload, response_headers)\ncomponents.HttpError: HTTP 403: b'{\"error\":\"API token scope exceeds live.view authority\"}'\n", + "seconds": 0.0 + } + ], + "workdir": "/tmp/x3red/work", + "counts": { + "detector_requests": 1470, + "mqtt_messages_captured": 6 + } +} diff --git a/tests/integration/yawamf_e2e/results/x3-host-20261108.classifier.jsonl b/tests/integration/yawamf_e2e/results/x3-host-20261108.classifier.jsonl new file mode 100644 index 000000000..57d91abe5 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x3-host-20261108.classifier.jsonl @@ -0,0 +1,156 @@ +{"t": 1794162514.4532409, "camera": "feeder-east", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162517.0398607, "camera": "feeder-moving", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.4], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162518.3115864, "camera": "feeder-moving", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.4], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162519.592602, "camera": "feeder-moving", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.4], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162520.95077, "camera": "feeder-pair", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162520.9523351, "camera": "feeder-pair", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162537.6820848, "camera": "feeder-wb", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162619.6366515, "camera": "feeder-wb-outage", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162765.6979902, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162765.9634433, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162765.993812, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162765.9974558, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.0604365, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.0655403, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.1278734, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.1330144, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.1904147, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.1968262, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.2586803, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.2632952, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.3247466, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.3295581, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.392617, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.3976507, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.4605463, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.4661186, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.5255792, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.5304155, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.5883815, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.5932162, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.6567895, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.6615396, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.7195773, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.724383, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.787369, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.7919936, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.8501744, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.8552706, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.9143488, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.9186978, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162766.9764144, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162766.9809542, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.0399122, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.0472338, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.1091304, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.1142883, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.1785414, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.1830366, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.248908, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.2544537, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.3139532, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.3197381, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.3805962, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.38545, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.4414327, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.4460537, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.5104203, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.5152304, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.5725389, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.57747, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.6416366, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.6462176, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.7035902, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.7087853, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.7783735, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.7832038, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.85357, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.8583722, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.9233167, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.9278245, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162767.9842994, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162767.9890823, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.0469358, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.0518959, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.1084924, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.1137311, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.1716948, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.1767786, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.2354798, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.2405994, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.299566, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.3042123, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.3627064, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.3679593, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.425508, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.4303913, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.4879482, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.4927518, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.5633805, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.5688403, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.6394722, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.6438825, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.706475, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.7111483, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.7737246, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.7782645, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.8367283, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.8413606, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.9014087, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.9060981, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162768.9884965, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162768.9930172, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162769.0130773, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162769.01832, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162769.0755823, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162769.0804045, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162769.137506, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162769.1421027, "camera": "feeder-bulk", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162770.8943717, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162770.8953397, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162772.1830158, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162772.1851313, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162773.4638057, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162773.4647782, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162777.4300075, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162777.4309983, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162778.707151, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162778.708153, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162779.9873488, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162779.9885323, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162781.2697968, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162781.2707524, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162782.5370414, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162782.5380647, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162783.8208332, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162783.8217833, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162785.096762, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162785.0977206, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162786.3881502, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162786.3891351, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162804.7526405, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162805.2818835, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162806.861037, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162806.8620584, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162808.1441896, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162808.1459527, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162820.8590882, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.4739883, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.493165, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.498012, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.558394, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.5642157, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.6239476, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.6294954, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.6873024, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.6929147, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.7513983, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.7567468, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.8186612, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.8237267, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162821.8825438, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [185.7, 38.5, 25.8], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162821.8870451, "camera": "feeder-kill", "size": [72, 72], "mean_rgb": [28.2, 38.0, 185.0], "red_fraction": 0.0, "blue_fraction": 0.789, "result": "Erithacus rubecula"} +{"t": 1794162867.905786, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162868.478283, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162951.169882, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} +{"t": 1794162952.4442072, "camera": "feeder-outcomes", "size": [72, 72], "mean_rgb": [185.9, 38.3, 25.7], "red_fraction": 0.789, "blue_fraction": 0.0, "result": "Prunella modularis"} diff --git a/tests/integration/yawamf_e2e/results/x3-host-20261108.detector.jsonl b/tests/integration/yawamf_e2e/results/x3-host-20261108.detector.jsonl new file mode 100644 index 000000000..1827d4b92 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x3-host-20261108.detector.jsonl @@ -0,0 +1,87 @@ +{"t": 1794162514.3874485, "key": "a1", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162516.9745882, "key": "a2", "sha256": "d84a6d7aac33817ba45b29fe227186378f221f840f33e34f22feee08e1b466c8", "bytes": 1867, "birds": [{"colour": "red", "x_min": 0.475, "y_min": 0.4166666666666667, "x_max": 0.575, "y_max": 0.5944444444444444, "px": [304, 150, 368, 214]}]} +{"t": 1794162518.252587, "key": "a2", "sha256": "4338a66ac1a2a64a105d98b41aca3b165df6c10bb84c0c66bf68ca5eafbf7baf", "bytes": 1867, "birds": [{"colour": "red", "x_min": 0.85, "y_min": 0.4166666666666667, "x_max": 0.95, "y_max": 0.5944444444444444, "px": [544, 150, 608, 214]}]} +{"t": 1794162519.5330002, "key": "a2", "sha256": "731fadb3aaa07602b894dc532fad34c6522c71347e287107835a190c669c18e9", "bytes": 1867, "birds": [{"colour": "red", "x_min": 0.75, "y_min": 0.4166666666666667, "x_max": 0.85, "y_max": 0.5944444444444444, "px": [480, 150, 544, 214]}]} +{"t": 1794162520.8469899, "key": "a3", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162537.6177752, "key": "a12", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162619.570788, "key": "a13", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162696.0072563, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162697.287044, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162698.5670872, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162699.8463676, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162701.1264222, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162702.4064934, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162703.686656, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162704.967566, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162706.24712, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162707.526919, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162708.8071578, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162710.087109, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162711.3672764, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162712.6461563, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162713.9263678, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162715.2073963, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162716.4873123, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162717.7671826, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162719.0468812, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162720.3265095, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162721.6072283, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162722.8869283, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162724.166262, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162725.4465365, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162726.7271326, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162728.0062099, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162729.2857003, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162730.567296, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162731.8462968, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162733.1274793, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162734.4079099, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162735.6862621, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162736.9671543, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162738.2462661, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162739.5263014, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162740.8072875, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162742.0861318, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162743.3661468, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162744.6468782, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162745.9275017, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162747.206721, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162748.4890716, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162749.7670763, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162751.0475554, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162752.3277395, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162753.6069503, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162754.886625, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162756.166375, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162757.4477289, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162758.7260585, "key": "a5", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162770.8047607, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162772.0846865, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162773.3652744, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162774.6453073, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162777.338181, "key": "a6", "sha256": "666ac51dac4763b68e6fd1043fbd181360cd3a9829aaf970c6f39364dba8f610", "bytes": 2827, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162778.6165066, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162779.8961852, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162781.176079, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162782.456704, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162783.7367191, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162785.015513, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162786.2958205, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162806.777774, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162808.0562851, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162809.3365788, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162810.6176622, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162811.896786, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162813.1764328, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162814.4567032, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162815.7372134, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162817.0160801, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162818.2967305, "key": "a6", "sha256": "ed6b475f6f1becb4beea4370f67ec3a00dfecb52c6ecb279a89b1c2f8224271d", "bytes": 2823, "birds": [{"colour": "red", "x_min": 0.1875, "y_min": 0.3888888888888889, "x_max": 0.2875, "y_max": 0.5666666666666667, "px": [120, 140, 184, 204]}, {"colour": "blue", "x_min": 0.671875, "y_min": 0.4722222222222222, "x_max": 0.771875, "y_max": 0.65, "px": [430, 170, 494, 234]}]} +{"t": 1794162823.4177575, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162824.697144, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162869.6585982, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162870.9379454, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162872.2179549, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162873.4976642, "key": "a8", "sha256": "acde09c9c63ed9a3fadd2b493e4c393c3a2e82c754410e5f5233f1af5b0d456f", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162951.1016555, "key": "a8", "sha256": "7aef40f8db2e08620cf354a13870961c0e18e857c1f07f6c2823738980b4d031", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} +{"t": 1794162952.3814664, "key": "a8", "sha256": "7aef40f8db2e08620cf354a13870961c0e18e857c1f07f6c2823738980b4d031", "bytes": 2125, "birds": [{"colour": "red", "x_min": 0.3125, "y_min": 0.4166666666666667, "x_max": 0.4125, "y_max": 0.5944444444444444, "px": [200, 150, 264, 214]}]} diff --git a/tests/integration/yawamf_e2e/results/x3-host-20261108.json b/tests/integration/yawamf_e2e/results/x3-host-20261108.json new file mode 100644 index 000000000..80637df23 --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x3-host-20261108.json @@ -0,0 +1,1247 @@ +{ + "suite": "INT_01 X1/X3 lightNVR <-> YA-WAMF E2E", + "writeback_enabled": true, + "mode": "host-process", + "started_at": 1794162509.1281445, + "finished_at": 1794162962.9463036, + "seconds": 453.8, + "versions": { + "lightnvr": { + "commit": "bcf5290f", + "branch": "feat/int01-event-evidence", + "dirty_tracked": true, + "binary": "/home/matteius/opensensor/lightNVR/build-int01-X3/bin/lightnvr" + }, + "yawamf": { + "commit": "1cbbae74", + "branch": "feature/lightnvr-provider" + }, + "mosquitto": "mosquitto version 2.0.22", + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "detector": "loopback HTTP x1-colour-detector 1.0 (light-object-detect wire format; finds the red/blue fixture birds by colour)", + "classifier": "pinned x1-colour-rule-v1 (harness/yawamf_launcher.py replaces ClassifierService.classify_async_live)", + "python": "3.12.12" + }, + "recipe": [ + { + "step": "prereq: MQTT broker", + "outcome": "ok", + "detail": { + "broker": "mosquitto version 2.0.22", + "port": 46153 + } + }, + { + "step": "prereq: lightNVR with [mqtt] block (step 3.1)", + "outcome": "ok", + "detail": { + "version": "0.47.3" + } + }, + { + "step": "step 1-2: detector + evidence per camera", + "outcome": "ok", + "detail": { + "cameras": { + "feeder-bulk": "6dab5ccc-193e-40ce-90e9-9ffa8117e722", + "feeder-east": "91eed0e0-2e86-47ce-a734-b5856abd7497", + "feeder-kill": "1755d8d0-c2a6-4a5b-9132-55168a387c08", + "feeder-moving": "d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", + "feeder-outcomes": "542dce9e-dfdd-4f05-a451-6e21c427bd03", + "feeder-pair": "3676ace0-cb75-4a75-adbf-c89de606aaf4", + "feeder-wb": "300c0294-e470-4ef1-9e2f-b04b55c12878", + "feeder-wb-outage": "f73a5a16-8e66-41e7-8ad5-80fcd8c84d20" + }, + "observation_evidence_enabled": true, + "observation_labels": "bird" + } + }, + { + "step": "step 3.2: event route", + "outcome": "ok", + "detail": { + "route": "b6027a13-15e7-4411-85ba-78195f29ce79" + } + }, + { + "step": "step 4.2: create role (recipe body)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.2: create role with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.3: grant policy (no expected_policy_version)", + "outcome": "failed", + "detail": { + "status": 400, + "body": "{\"error\":\"expected_policy_version must be a positive integer\"}" + } + }, + { + "step": "step 4.3: grant policy with expected_policy_version", + "outcome": "ok" + }, + { + "step": "step 4.4: scoped token", + "outcome": "ok", + "detail": { + "principal": { + "writer_namespace": "yawamf" + }, + "delivery": { + "mqtt_enabled": true, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}" + }, + "cameras_evidence_enabled": { + "1755d8d0-c2a6-4a5b-9132-55168a387c08": true, + "300c0294-e470-4ef1-9e2f-b04b55c12878": true, + "3676ace0-cb75-4a75-adbf-c89de606aaf4": true, + "542dce9e-dfdd-4f05-a451-6e21c427bd03": true, + "6dab5ccc-193e-40ce-90e9-9ffa8117e722": true, + "91eed0e0-2e86-47ce-a734-b5856abd7497": true, + "d8e75d4a-f21d-46d3-b153-2d95fd96e9a3": true, + "f73a5a16-8e66-41e7-8ad5-80fcd8c84d20": true + } + } + }, + { + "step": "step 5: YA-WAMF started (MQTT subscribed)", + "outcome": "ok", + "detail": { + "health_version": "2.23.1-feature/lightnvr-provider+1cbbae74", + "classification_writeback": { + "enabled": true, + "running": true, + "outcomes": {}, + "last_outcome": null, + "last_outcome_at": null + } + } + }, + { + "step": "step 6.1: Test lightNVR connection", + "outcome": "ok", + "detail": { + "ok": true, + "results": [ + { + "id": "api_access", + "status": "ok", + "code": "connected", + "message": "Connected to lightNVR 0.47.3.", + "values": { + "product_version": "0.47.3", + "camera_count": 8 + } + }, + { + "id": "event_connectivity", + "status": "ok", + "code": "connected", + "message": "The MQTT broker answered and the lightNVR topic matches.", + "values": { + "topic": "lightnvr" + } + }, + { + "id": "evidence_access", + "status": "ok", + "code": "authorized", + "message": "YA-WAMF may read detection photos.", + "values": {} + }, + { + "id": "features", + "status": "ok", + "code": "listed", + "message": "Available: history, snapshots, annotate. Not available with this source: track_lifecycle, recorded_frame, event_clip, range_clip.", + "values": { + "supported": "history,snapshots,annotate", + "unsupported": "track_lifecycle,recorded_frame,event_clip,range_clip" + } + } + ], + "features": [ + { + "name": "history", + "supported": true, + "reason": null + }, + { + "name": "snapshots", + "supported": true, + "reason": null + }, + { + "name": "annotate", + "supported": true, + "reason": null + }, + { + "name": "track_lifecycle", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "recorded_frame", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "event_clip", + "supported": false, + "reason": "not_implemented" + }, + { + "name": "range_clip", + "supported": false, + "reason": "not_implemented" + } + ], + "latest_observation": { + "state": "none_yet", + "code": null, + "observation_id": null, + "camera_uuid": null, + "camera_name": null, + "label": null, + "observed_at": null, + "photo_state": null, + "photo_reason": null, + "searched_labels": [ + "bird" + ] + } + } + }, + { + "step": "step 6.2: setup probe", + "outcome": "ok", + "detail": { + "status": 200, + "body": { + "items": [], + "next_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiYTk4MjdlZDEtOTIwNC00MzgwLTg3NTctODVkNDAwYzVmYzhhIn0", + "has_more": false + } + } + }, + { + "step": "step 6.2: setup probe, camera outside the token scope", + "outcome": "ok", + "detail": { + "status": 403 + } + }, + { + "step": "diagnostics: token cannot read /api/system/info", + "outcome": "ok", + "detail": { + "status": 403 + } + } + ], + "deploy_error": null, + "scenarios": [ + { + "id": "A1", + "title": "identity and labels", + "started_at": 1794162513.8266811, + "evidence": { + "observation": { + "id": "5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f", + "camera_uuid": "91eed0e0-2e86-47ce-a734-b5856abd7497", + "stream_name_at_capture": "feeder-east", + "label": "bird", + "confidence": 0.899999976158142, + "box": { + "format": "xywh_normalized", + "x": 0.3125, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "revision": 2 + }, + "evidence": { + "asset_id": "8cc6ee43-5309-49de-a7e8-9adb605b7fb4", + "state": "ready", + "reason": null, + "origin": "submitted_image", + "media_type": "image/jpeg", + "width": 640, + "height": 360, + "bytes": 2125, + "captured_at": "2026-11-08T18:28:34.378Z", + "expires_at": null, + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + }, + "snapshot_url": "/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot" + }, + "yawamf_row": { + "event": "lnvr:277b5716-7899-4a09-b7f8-0885e9629983:5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f", + "observation_id": "5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f", + "source_kind": "lightnvr", + "source_instance": "277b5716-7899-4a09-b7f8-0885e9629983", + "camera_uuid": "91eed0e0-2e86-47ce-a734-b5856abd7497", + "camera_name": "feeder-east", + "scientific_name": "Prunella modularis", + "display_name": "Dunnock", + "score": 0.93, + "intent": "dispatch", + "outcome": "skipped" + }, + "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", + "mqtt_revisions": { + "1": { + "id": "9c22823f-a05c-5e38-bd1f-b99215a86eb1", + "cause": "created", + "evidence_state": "pending" + }, + "2": { + "id": "9f4bc15a-f6c2-5c66-a248-700966dd9e7b", + "cause": "evidence", + "evidence_state": "ready" + }, + "3": { + "id": "835d9fac-b5cb-5e04-b973-53eb82f204f3", + "cause": "classification", + "evidence_state": "ready" + } + }, + "classifier_calls": [ + { + "t": 1794162514.4532409, + "camera": "feeder-east", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.9, + 38.3, + 25.7 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ] + }, + "result": "pass", + "seconds": 2.0 + }, + { + "id": "A2", + "title": "moving-bird alignment (HTTP detector)", + "started_at": 1794162515.8482296, + "evidence": { + "detector": "http (loopback x1-colour-detector)", + "observations": [ + { + "id": "3dc85cc1-196b-4e8d-8f18-9a805d20b90e", + "sha256": "d84a6d7aac33817ba45b29fe227186378f221f840f33e34f22feee08e1b466c8", + "box": { + "format": "xywh_normalized", + "x": 0.475, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 304, + 150, + 368, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + { + "id": "c252abcb-be23-4b40-9f54-21130a503cc9", + "sha256": "4338a66ac1a2a64a105d98b41aca3b165df6c10bb84c0c66bf68ca5eafbf7baf", + "box": { + "format": "xywh_normalized", + "x": 0.85, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 544, + 150, + 608, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + }, + { + "id": "5f254536-4ee4-4b8f-8aef-2228bfbdfc00", + "sha256": "731fadb3aaa07602b894dc532fad34c6522c71347e287107835a190c669c18e9", + "box": { + "format": "xywh_normalized", + "x": 0.75, + "y": 0.416667, + "width": 0.1, + "height": 0.177778 + }, + "bird_px": [ + 480, + 150, + 544, + 214 + ], + "transform": { + "mode": "none", + "source_width": 640, + "source_height": 360, + "inference_width": 640, + "inference_height": 360, + "scale_x": 1, + "scale_y": 1, + "pad_x": 0, + "pad_y": 0 + } + } + ], + "bird_x_px_per_frame": [ + 304, + 544, + 480 + ], + "cross_frame_crop_colours": [ + "background", + "background", + "background", + "background", + "background", + "background" + ], + "yawamf_species": [ + "Prunella modularis", + "Prunella modularis", + "Prunella modularis" + ], + "classifier_calls": [ + { + "t": 1794162517.0398607, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794162518.3115864, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + }, + { + "t": 1794162519.592602, + "camera": "feeder-moving", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.4 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ], + "local_detector": "pending: no bird-capable local model drawn on this fixture (see README)" + }, + "result": "pass", + "seconds": 4.1 + }, + { + "id": "A3", + "title": "two birds in one frame", + "started_at": 1794162519.9196844, + "evidence": { + "asset_id": "9b6b285d-fce1-4f05-ad2d-a066e1e60bb6", + "batch_id": "4767590b-62ca-4bb0-a36f-73becd59fded", + "box_colours": { + "220979fd-eb6e-44c2-969e-379cd2bd615b": "red", + "cdd012e3-4cfc-4e86-ab25-d6e1a1817535": "blue" + }, + "yawamf_species": { + "220979fd-eb6e-44c2-969e-379cd2bd615b": "Prunella modularis", + "cdd012e3-4cfc-4e86-ab25-d6e1a1817535": "Erithacus rubecula" + }, + "classifier_calls": [ + { + "t": 1794162520.95077, + "camera": "feeder-pair", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 28.2, + 38.0, + 185.0 + ], + "red_fraction": 0.0, + "blue_fraction": 0.789, + "result": "Erithacus rubecula" + }, + { + "t": 1794162520.9523351, + "camera": "feeder-pair", + "size": [ + 72, + 72 + ], + "mean_rgb": [ + 185.7, + 38.5, + 25.8 + ], + "red_fraction": 0.789, + "blue_fraction": 0.0, + "result": "Prunella modularis" + } + ], + "yawamf_detection_rows": 2 + }, + "result": "pass", + "seconds": 2.0 + }, + { + "id": "A4", + "title": "replay x10 with overlapping API recovery", + "started_at": 1794162521.9474359, + "evidence": { + "messages_replayed": 90, + "distinct_messages": 9, + "observations": 3, + "rows_after": 3, + "extra_classifier_calls": 0, + "intents": { + "220979fd-eb6e-44c2-969e-379cd2bd615b": [ + "dispatch", + "skipped" + ], + "5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f": [ + "dispatch", + "skipped" + ], + "cdd012e3-4cfc-4e86-ab25-d6e1a1817535": [ + "dispatch", + "skipped" + ] + }, + "overlapping_backfill": { + "status": "completed", + "processed": 6, + "new_detections": 0, + "updated": 0, + "skipped": 6, + "errors": 0, + "skipped_reasons": { + "already_exists": 6 + }, + "error_reasons": {}, + "message": "No new detections found, 6 already existed" + } + }, + "result": "pass", + "seconds": 15.3 + }, + { + "id": "A12", + "title": "write-back: species in both products, replays and stale revisions, owner precedence, no loop", + "started_at": 1794162537.2456667, + "evidence": { + "species_in_both": { + "observation_id": "dd086cb3-c382-4518-b6aa-6017c28e3b88", + "yawamf_event": "lnvr:277b5716-7899-4a09-b7f8-0885e9629983:dd086cb3-c382-4518-b6aa-6017c28e3b88", + "yawamf": { + "display_name": "Dunnock", + "scientific_name": "Prunella modularis", + "common_name": "Dunnock", + "score": 0.93, + "manual_tagged": 0 + }, + "lightnvr_row": { + "key": "yawamf/species", + "kind": "automated", + "label": "Dunnock", + "confidence": 0.93, + "annotation_revision": 1, + "writer_namespace": "yawamf", + "input_revision": 2, + "classifier": "yawamf" + }, + "lightnvr_get_classifications": { + "automated": [ + { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "model.tflite", + "label": "Dunnock", + "taxonomy_id": "inat:13988", + "confidence": 0.93, + "classified_at": "2026-11-08T18:28:57.692Z", + "input_asset_id": "f622342b-2cc4-4fcc-bf1f-6507e799c519", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T18:28:57.696Z", + "updated_at": "2026-11-08T18:28:57.696Z" + } + ], + "owner_confirmed": [] + }, + "lightnvr_detection_result": { + "label": "bird", + "confidence": 0.899999976158142, + "observation": { + "id": "dd086cb3-c382-4518-b6aa-6017c28e3b88", + "owner_confirmed": false, + "classification": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": "model.tflite", + "label": "Dunnock", + "taxonomy_id": "inat:13988", + "confidence": 0.93, + "classified_at": "2026-11-08T18:28:57.692Z", + "input_asset_id": "f622342b-2cc4-4fcc-bf1f-6507e799c519", + "input_revision": 2, + "kind": "automated", + "annotation_revision": 1, + "created_at": "2026-11-08T18:28:57.696Z", + "updated_at": "2026-11-08T18:28:57.696Z" + } + } + }, + "writeback_job": { + "status": "succeeded", + "attempt_count": 0, + "retry_after": null, + "last_error": null, + "revision": 0 + } + }, + "no_feedback_loop": { + "classification_message_id": "c50ba2ed-b943-59a3-8076-68db2f0ee56e", + "observation_revision": 3, + "classifier_calls_for_observation": 1, + "intent": { + "notification_intent": "dispatch", + "notification_intent_at": "2026-11-08 18:28:57.682903", + "notification_outcome": "skipped", + "notification_outcome_at": "2026-11-08 18:28:57.684387" + }, + "writeback_job_revision": 0 + }, + "replays_and_stale_revisions": { + "mqtt_messages_replayed": 30, + "identical_put_retries": [ + 200, + 200, + 200, + 200, + 200 + ], + "stale_revision": { + "status": 409, + "code": "revision_conflict", + "current_label": "Dunnock" + }, + "future_revision": { + "status": 409, + "code": "revision_conflict" + }, + "annotations_after": [ + { + "key": "yawamf/species", + "kind": "automated", + "label": "Dunnock", + "confidence": 0.93, + "annotation_revision": 1, + "writer_namespace": "yawamf", + "input_revision": 2, + "classifier": "yawamf" + } + ], + "observation_revision_after": 3 + }, + "owner_correction": { + "patch_response": { + "status": "updated", + "old_species": "Dunnock", + "new_species": "European Robin", + "manual_tagged": true + }, + "yawamf": { + "display_name": "European Robin", + "scientific_name": "Erithacus rubecula", + "common_name": "European Robin", + "manual_tagged": 1 + }, + "lightnvr_row": { + "key": "yawamf/species", + "kind": "owner_confirmed", + "label": "European Robin", + "confidence": 1.0, + "annotation_revision": 2, + "writer_namespace": "yawamf", + "input_revision": 3, + "classifier": "yawamf" + }, + "lightnvr_detection_result": { + "id": "dd086cb3-c382-4518-b6aa-6017c28e3b88", + "owner_confirmed": true, + "classification": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": null, + "label": "European Robin", + "taxonomy_id": "inat:13094", + "confidence": 1, + "classified_at": "2026-11-08T18:29:18.782Z", + "input_asset_id": "f622342b-2cc4-4fcc-bf1f-6507e799c519", + "input_revision": 3, + "kind": "owner_confirmed", + "annotation_revision": 2, + "created_at": "2026-11-08T18:28:57.696Z", + "updated_at": "2026-11-08T18:29:18.787Z" + } + } + }, + "automated_after_owner": { + "lightnvr_put": { + "status": 409, + "code": "owner_decision_locked", + "current_kind": "owner_confirmed" + }, + "yawamf_writeback_job": { + "status": "terminal", + "attempt_count": 1, + "retry_after": null, + "last_error": "dropped:owner_decision_locked", + "revision": 2 + }, + "lightnvr_row_after": { + "key": "yawamf/species", + "kind": "owner_confirmed", + "label": "European Robin", + "confidence": 1.0, + "annotation_revision": 2, + "writer_namespace": "yawamf", + "input_revision": 3, + "classifier": "yawamf" + } + }, + "totals": { + "classifier_calls_for_observation": 1, + "yawamf_rows": 1, + "intent": { + "notification_intent": "dispatch", + "notification_intent_at": "2026-11-08 18:28:57.682903", + "notification_outcome": "skipped", + "notification_outcome_at": "2026-11-08 18:28:57.684387" + }, + "classification_revisions_published": [ + 3, + 4 + ] + } + }, + "result": "pass", + "seconds": 81.6 + }, + { + "id": "A13", + "title": "write-back survives a lightNVR outage; the YA-WAMF detection stays intact", + "started_at": 1794162618.881237, + "evidence": { + "observation_id": "4db73649-60d2-4311-9db3-8b4f352333c6", + "yawamf_event": "lnvr:277b5716-7899-4a09-b7f8-0885e9629983:4db73649-60d2-4311-9db3-8b4f352333c6", + "automated_before_outage": { + "key": "yawamf/species", + "kind": "automated", + "label": "Dunnock", + "confidence": 0.93, + "annotation_revision": 1, + "writer_namespace": "yawamf", + "input_revision": 2, + "classifier": "yawamf" + }, + "during_outage": { + "patch_response": { + "status": "updated", + "new_species": "European Robin", + "manual_tagged": true + }, + "yawamf_detection": { + "display_name": "European Robin", + "scientific_name": "Erithacus rubecula", + "manual_tagged": 1 + }, + "event_api_species": { + "display_name": "European Robin", + "scientific_name": "Erithacus rubecula", + "manual_tagged": true + }, + "writeback_job": { + "status": "retryable", + "attempt_count": 1, + "retry_after": "2026-11-08 18:30:54.316406+00:00", + "last_error": "retry:source_unreachable:request_error", + "revision": 1 + }, + "writeback_job_end_of_outage": { + "status": "retryable", + "attempt_count": 1, + "retry_after": "2026-11-08 18:30:54.316406+00:00", + "last_error": "retry:source_unreachable:request_error", + "revision": 1 + }, + "health_classification_writeback": { + "enabled": true, + "running": true, + "outcomes": { + "succeeded:stored": 9, + "dropped:owner_decision_locked": 1, + "retry:source_unreachable": 1 + }, + "last_outcome": "retry:source_unreachable", + "last_outcome_at": 1794162624.316406 + }, + "seconds_down": 46.8 + }, + "after_restart": { + "lightnvr_row": { + "key": "yawamf/species", + "kind": "owner_confirmed", + "label": "European Robin", + "confidence": 1.0, + "annotation_revision": 2, + "writer_namespace": "yawamf", + "input_revision": 3, + "classifier": "yawamf" + }, + "writeback_job": { + "status": "succeeded", + "attempt_count": 1, + "retry_after": null, + "last_error": null, + "revision": 1 + }, + "converged_seconds_after_restart": 14.0, + "lightnvr_detection_result": { + "id": "4db73649-60d2-4311-9db3-8b4f352333c6", + "owner_confirmed": true, + "classification": { + "key": "yawamf/species", + "writer_namespace": "yawamf", + "classifier": "yawamf", + "model_version": null, + "label": "European Robin", + "taxonomy_id": "inat:13094", + "confidence": 1, + "classified_at": "2026-11-08T18:31:24.327Z", + "input_asset_id": "086ef605-3fb8-4d6f-ac5d-77302a8d1ea8", + "input_revision": 3, + "kind": "owner_confirmed", + "annotation_revision": 2, + "created_at": "2026-11-08T18:30:19.797Z", + "updated_at": "2026-11-08T18:31:24.332Z" + } + } + }, + "classifier_calls_for_observation": 1, + "intent": { + "notification_intent": "dispatch", + "notification_intent_at": "2026-11-08 18:30:19.784367", + "notification_outcome": "skipped", + "notification_outcome_at": "2026-11-08 18:30:19.785593" + } + }, + "result": "pass", + "seconds": 75.7 + }, + { + "id": "A7-annotate", + "title": "annotate needs observations.annotate on the capture camera", + "started_at": 1794162694.5667305, + "evidence": { + "observation_id": "dd086cb3-c382-4518-b6aa-6017c28e3b88", + "without_annotate": { + "existing_key": [ + 403, + { + "error": "Forbidden", + "code": "forbidden" + } + ], + "new_key": [ + 403, + { + "error": "Forbidden", + "code": "forbidden" + } + ] + }, + "annotate_other_camera_scope": { + "writer_namespace": "yawamf-east", + "response": [ + 404, + { + "error": "Observation not found", + "code": "observation_not_found" + } + ] + }, + "with_annotate": { + "create": { + "status": 201, + "changed": true + }, + "retry": { + "status": 200, + "changed": false + } + }, + "rows_unchanged_by_refusals": true, + "audit_latest": [ + { + "outcome": "success", + "result": "noop" + }, + { + "outcome": "success", + "result": "created" + }, + { + "outcome": "denied", + "result": "observation_not_found" + }, + { + "outcome": "denied", + "result": "forbidden" + }, + { + "outcome": "denied", + "result": "forbidden" + }, + { + "outcome": "success", + "result": "updated" + } + ] + }, + "result": "pass", + "seconds": 0.1 + }, + { + "id": "A5", + "title": "broker down for 100 observations, both apps restarted", + "started_at": 1794162694.6781096, + "evidence": { + "observations_created_while_broker_down": 100, + "outbox_states_while_broker_down": { + "delivered": 27, + "pending": 200 + }, + "outbox_states_after": { + "delivered": 327 + }, + "yawamf_rows_while_down": 0, + "yawamf_rows_after_restart": 100, + "converged_seconds_after_restart": 6.6, + "classifier_calls_for_camera": 100, + "outbox": "all 200 revisions delivered after the broker returned", + "intents": { + "suppressed_recovery": 100 + }, + "recovery_status": { + "state": "caught_up", + "source_kind": "lightnvr", + "source_instance": "277b5716-7899-4a09-b7f8-0885e9629983", + "recovery_required": false, + "gap_reason": null, + "cursor_saved": true, + "last_error": null, + "updated_at": "2026-11-08 18:32:49", + "last_run": { + "trigger": "start", + "started_at": 1794162765.630308, + "finished_at": 1794162769.1608427, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 2, + "items": 198, + "outcomes": { + "already_exists": 98, + "admitted": 100 + } + } + } + }, + "result": "pass", + "seconds": 75.2 + }, + { + "id": "A6", + "title": "kill points end to end", + "started_at": 1794162769.8789027, + "evidence": { + "phase1_lightnvr_sigkill": { + "frames_reported_before_kill": 4, + "observations": 24, + "integrity": { + "asset_states": { + "ready": 12 + }, + "final_files": 12, + "tmp_files": 0 + }, + "startup_log": [ + "[2026-11-08 13:32:54.754] [INFO] Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "[2026-11-08 13:32:54.764] [INFO] Observation publication relay started" + ], + "converged_seconds_after_restart": 31.0, + "ready": 24, + "not_ready": {}, + "yawamf_rows": 24 + }, + "phase2_yawamf_sigkill": { + "observations_total": 44, + "ready": 44, + "yawamf_rows": 44, + "classifier_calls_in_phase": 20, + "converged_seconds_after_restart": 3.6, + "integrity": { + "asset_states": { + "ready": 22 + }, + "final_files": 22, + "tmp_files": 0 + } + } + }, + "result": "pass", + "seconds": 52.2 + }, + { + "id": "A8", + "title": "expired, unavailable, temporary and expired-cursor outcomes", + "started_at": 1794162822.0451794, + "evidence": { + "temporary_lightnvr_unreachable": { + "rows_while_unreachable": 0, + "recovery_status_while_unreachable": { + "state": "unreachable", + "source_kind": null, + "source_instance": null, + "recovery_required": false, + "gap_reason": null, + "cursor_saved": false, + "last_error": null, + "updated_at": null, + "last_run": { + "trigger": "start", + "started_at": 1794162832.8314598, + "finished_at": 1794162837.833155, + "result": "unreachable", + "detail": "lightNVR request to /api/integrations/capabilities timed out", + "start": null, + "pages": 0, + "items": 0, + "outcomes": {} + } + }, + "rows_after_resume": 2, + "converged_seconds_after_resume": 16.0 + }, + "expired_and_unavailable": { + "lightnvr": { + "8d26e617-9379-4834-b538-3bbcbf8ae4dd": { + "case": "expired", + "state": "expired", + "reason": "retention_expired", + "snapshot_status": 410, + "snapshot_code": "evidence_expired" + }, + "3ef8e39b-e08a-4a4e-a7b3-f6d99bf907ef": { + "case": "expired", + "state": "expired", + "reason": "retention_expired", + "snapshot_status": 410, + "snapshot_code": "evidence_expired" + }, + "7f586db1-3045-42ab-881a-67410d93347a": { + "case": "unavailable", + "state": "unavailable", + "reason": "write_failed", + "snapshot_status": 404, + "snapshot_code": "evidence_unavailable" + }, + "a88ae5eb-d50d-4c8a-a958-0c91ed44ddab": { + "case": "unavailable", + "state": "unavailable", + "reason": "write_failed", + "snapshot_status": 404, + "snapshot_code": "evidence_unavailable" + } + }, + "recovery_run": { + "trigger": "start", + "started_at": 1794162940.8157122, + "finished_at": 1794162940.825726, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 1, + "items": 6, + "outcomes": { + "already_exists": 2, + "evidence_expired": 2, + "evidence_unavailable": 2 + } + }, + "yawamf_rows": 0, + "backfill": { + "status": "completed", + "processed": 250, + "new_detections": 0, + "updated": 0, + "skipped": 250, + "errors": 0, + "skipped_reasons": { + "already_exists": 246, + "evidence_expired": 2, + "evidence_unavailable": 2 + }, + "error_reasons": {}, + "message": "No new detections found, 246 already existed, 2 with evidence expired, 2 without an evidence image" + } + }, + "expired_cursor_after_restore": { + "old_cursor_response": { + "status": 410, + "body": { + "error": "Cursor is older than retained observation history", + "code": "cursor_expired", + "oldest_cursor": "eyJ2IjoxLCJzZXEiOjAsImVwb2NoIjoiYWRjMjE4YjUtNjQ1Ni00YzZlLTk4YzMtZWQxNDg2MTU3YmJkIn0", + "oldest_observed_at": "2026-10-31T18:34:29.654Z" + } + }, + "recovery_after_restore": { + "state": "caught_up", + "source_kind": "lightnvr", + "source_instance": "277b5716-7899-4a09-b7f8-0885e9629983", + "recovery_required": true, + "gap_reason": "cursor_expired", + "cursor_saved": true, + "last_error": null, + "updated_at": "2026-11-08 18:36:02", + "last_run": { + "trigger": "start", + "started_at": 1794162962.6572747, + "finished_at": 1794162962.7301, + "result": "caught_up", + "detail": null, + "start": "resume", + "pages": 3, + "items": 250, + "outcomes": { + "already_exists": 246, + "evidence_expired": 2, + "evidence_unavailable": 2 + } + } + }, + "diagnostics_entries": [ + { + "source": "source_recovery", + "component": "lightnvr", + "severity": "warning", + "reason_code": "cursor_expired", + "message": "Source history expired before it was recovered; sightings in the gap cannot be recovered" + } + ], + "after_acknowledge": { + "state": "caught_up", + "recovery_required": false, + "gap_reason": null + }, + "observations_lost_by_restore_still_in_yawamf": 2 + } + }, + "result": "pass", + "seconds": 140.9 + } + ], + "workdir": "/tmp/x3full/work", + "counts": { + "detector_requests": 2175, + "mqtt_messages_captured": 603 + } +} diff --git a/tests/integration/yawamf_e2e/results/x3-host-20261108.mqtt-capture.jsonl b/tests/integration/yawamf_e2e/results/x3-host-20261108.mqtt-capture.jsonl new file mode 100644 index 000000000..184a69a4f --- /dev/null +++ b/tests/integration/yawamf_e2e/results/x3-host-20261108.mqtt-capture.jsonl @@ -0,0 +1,603 @@ +{"t": 1794162514.3910394, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162514.3914025, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162514.9575467, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162516.978883, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"e5f7a24d-8e66-5014-9152-c9321951c556\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3dc85cc1-196b-4e8d-8f18-9a805d20b90e\",\"revision\":1,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:36.969Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d625bd21-fb03-4df0-8be8-0b4a35a519a1\"}}"} +{"t": 1794162516.97908, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"f7210ae2-c507-5ab1-9ecd-16e449cf6c59\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3dc85cc1-196b-4e8d-8f18-9a805d20b90e\",\"revision\":2,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:36.969Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d625bd21-fb03-4df0-8be8-0b4a35a519a1\",\"snapshot_url\":\"/api/observations/3dc85cc1-196b-4e8d-8f18-9a805d20b90e/snapshot\"}}"} +{"t": 1794162517.0588088, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"22be98c1-60a6-5a7a-9825-e05e112dc836\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3dc85cc1-196b-4e8d-8f18-9a805d20b90e\",\"revision\":3,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:36.969Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d625bd21-fb03-4df0-8be8-0b4a35a519a1\",\"snapshot_url\":\"/api/observations/3dc85cc1-196b-4e8d-8f18-9a805d20b90e/snapshot\"}}"} +{"t": 1794162518.255319, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"eb1cb7b3-1f29-55d0-b7f0-6dfa0a49fdf3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c252abcb-be23-4b40-9f54-21130a503cc9\",\"revision\":1,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:38.249Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"06834f5e-f4a3-494a-a917-ba7dd068df52\"}}"} +{"t": 1794162518.2556834, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"25d79cb3-7334-5f03-9231-fbbd4c10e31a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c252abcb-be23-4b40-9f54-21130a503cc9\",\"revision\":2,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:38.249Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"06834f5e-f4a3-494a-a917-ba7dd068df52\",\"snapshot_url\":\"/api/observations/c252abcb-be23-4b40-9f54-21130a503cc9/snapshot\"}}"} +{"t": 1794162518.32721, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"9d560447-d099-5ef9-8042-2ea7f4686e2f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c252abcb-be23-4b40-9f54-21130a503cc9\",\"revision\":3,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:38.249Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"06834f5e-f4a3-494a-a917-ba7dd068df52\",\"snapshot_url\":\"/api/observations/c252abcb-be23-4b40-9f54-21130a503cc9/snapshot\"}}"} +{"t": 1794162519.5359335, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"5452866e-cc23-57b1-88f1-c444b2779299\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5f254536-4ee4-4b8f-8aef-2228bfbdfc00\",\"revision\":1,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:39.529Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e92752ba-5c43-414b-a9ab-eb5d5f6e465f\"}}"} +{"t": 1794162519.536395, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"d763ed61-e275-5255-b1d3-d1def131d5e2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5f254536-4ee4-4b8f-8aef-2228bfbdfc00\",\"revision\":2,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:39.529Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e92752ba-5c43-414b-a9ab-eb5d5f6e465f\",\"snapshot_url\":\"/api/observations/5f254536-4ee4-4b8f-8aef-2228bfbdfc00/snapshot\"}}"} +{"t": 1794162519.6080842, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3", "payload": "{\"specversion\":\"1.0\",\"id\":\"119d9970-b975-5ee5-8775-949e5ffb30ec\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"time\":\"2026-11-08T18:28:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5f254536-4ee4-4b8f-8aef-2228bfbdfc00\",\"revision\":3,\"camera_uuid\":\"d8e75d4a-f21d-46d3-b153-2d95fd96e9a3\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:39.529Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e92752ba-5c43-414b-a9ab-eb5d5f6e465f\",\"snapshot_url\":\"/api/observations/5f254536-4ee4-4b8f-8aef-2228bfbdfc00/snapshot\"}}"} +{"t": 1794162520.8509974, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162520.8513172, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162520.851731, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162520.852131, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162520.9715483, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162521.298848, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162521.950739, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162521.9535906, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162521.9566822, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162521.960574, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162521.963865, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162521.9671338, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162521.9713032, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162521.9742618, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162521.9778352, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162521.9810042, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162521.984504, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162521.9879065, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162521.991359, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162521.9947817, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162521.9982736, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.0020208, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.0054479, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.0085323, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.012103, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.016208, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.0195394, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.022827, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.0259433, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.0295687, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.0328887, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.0365732, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.039646, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.043235, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.0466151, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.0502253, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.0536928, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.0572572, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.0611894, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.0640981, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.0673773, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.068657, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.06999, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.0714247, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.075361, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.0786095, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.0817266, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.085185, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.0892444, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.0924072, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.0938578, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.0954304, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.0988364, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.1003242, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.103732, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.108169, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.111004, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.1149602, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.118836, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.122663, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.1257927, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.1291938, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.1325684, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.136571, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.1400747, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.1437957, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.1471045, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.1506732, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.154249, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.1579835, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.1597579, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.1634247, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.1668446, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.1700492, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.1734946, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.1769023, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.1805153, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.1838996, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.1879785, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.1917095, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.1947727, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.1981146, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.2015388, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.2049892, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.2084785, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.21203, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.2152803, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.2166352, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9c22823f-a05c-5e38-bd1f-b99215a86eb1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":1,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\"}}"} +{"t": 1794162522.2181306, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4bc15a-f6c2-5c66-a248-700966dd9e7b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":2,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.2216065, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/91eed0e0-2e86-47ce-a734-b5856abd7497", "payload": "{\"specversion\":\"1.0\",\"id\":\"835d9fac-b5cb-5e04-b973-53eb82f204f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/91eed0e0-2e86-47ce-a734-b5856abd7497\",\"time\":\"2026-11-08T18:28:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f\",\"revision\":3,\"camera_uuid\":\"91eed0e0-2e86-47ce-a734-b5856abd7497\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:34.378Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8cc6ee43-5309-49de-a7e8-9adb605b7fb4\",\"snapshot_url\":\"/api/observations/5eb8a4ff-e7b5-4a71-9ef1-448e4c268f8f/snapshot\"}}"} +{"t": 1794162522.2255468, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"edd72cff-2042-596a-9d2e-eccf93204d08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.2288072, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ce4d278-874c-52e2-96c3-893d3fa6feb9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":1,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\"}}"} +{"t": 1794162522.232255, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"35e7ae4d-7400-5360-9466-2d74744c5fae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.23571, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"80a1ef3c-c4b9-5a99-ada4-b70fb4e014e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":2,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162522.239071, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"04f73eb2-87ca-5f46-9bf6-9323e8576e03\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:40Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"220979fd-eb6e-44c2-969e-379cd2bd615b\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/220979fd-eb6e-44c2-969e-379cd2bd615b/snapshot\"}}"} +{"t": 1794162522.2424774, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/3676ace0-cb75-4a75-adbf-c89de606aaf4", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9de96d5-0913-5095-a695-a6a6e1ac8506\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"time\":\"2026-11-08T18:28:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"cdd012e3-4cfc-4e86-ab25-d6e1a1817535\",\"revision\":3,\"camera_uuid\":\"3676ace0-cb75-4a75-adbf-c89de606aaf4\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:40.843Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9b6b285d-fce1-4f05-ad2d-a066e1e60bb6\",\"snapshot_url\":\"/api/observations/cdd012e3-4cfc-4e86-ab25-d6e1a1817535/snapshot\"}}"} +{"t": 1794162537.6210737, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162537.621437, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162537.6985765, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.303557, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3067276, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3099616, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3133056, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3146315, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.315969, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3173134, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.318644, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3205454, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3237891, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3271377, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.330712, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3342652, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3380132, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3416574, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.344913, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3483827, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3517334, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3551989, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.358602, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3625138, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.365814, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.369163, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3726947, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3761122, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3796496, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.383227, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3865898, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"8535dfbd-7fc5-51a2-8194-3e6c5e225757\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":1,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\"}}"} +{"t": 1794162548.3880336, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"3fc0d4b0-dde9-5be7-a3bb-82dca75c7ba2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":2,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162548.3894694, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"c50ba2ed-b943-59a3-8076-68db2f0ee56e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:28:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":3,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162558.7889845, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"e0ba65c1-71e4-5318-8c76-a98597d5647b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:29:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":4,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162619.5750194, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20", "payload": "{\"specversion\":\"1.0\",\"id\":\"26da8956-b6f4-5693-aa02-9ca15cc75cc7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"time\":\"2026-11-08T18:30:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4db73649-60d2-4311-9db3-8b4f352333c6\",\"revision\":1,\"camera_uuid\":\"f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:30:19.567Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"086ef605-3fb8-4d6f-ac5d-77302a8d1ea8\"}}"} +{"t": 1794162619.5754454, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20", "payload": "{\"specversion\":\"1.0\",\"id\":\"5aa0dea5-f35f-5d8c-992a-1504c2d88bde\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"time\":\"2026-11-08T18:30:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4db73649-60d2-4311-9db3-8b4f352333c6\",\"revision\":2,\"camera_uuid\":\"f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:30:19.567Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"086ef605-3fb8-4d6f-ac5d-77302a8d1ea8\",\"snapshot_url\":\"/api/observations/4db73649-60d2-4311-9db3-8b4f352333c6/snapshot\"}}"} +{"t": 1794162619.799687, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20", "payload": "{\"specversion\":\"1.0\",\"id\":\"d2d0b6b3-d5d7-56d2-b70e-d9b79daa3427\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"time\":\"2026-11-08T18:30:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4db73649-60d2-4311-9db3-8b4f352333c6\",\"revision\":3,\"camera_uuid\":\"f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:30:19.567Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"086ef605-3fb8-4d6f-ac5d-77302a8d1ea8\",\"snapshot_url\":\"/api/observations/4db73649-60d2-4311-9db3-8b4f352333c6/snapshot\"}}"} +{"t": 1794162684.3353033, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20", "payload": "{\"specversion\":\"1.0\",\"id\":\"d6eec8dd-c4d4-5400-9c85-f792dc7f3691\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"time\":\"2026-11-08T18:31:24Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4db73649-60d2-4311-9db3-8b4f352333c6\",\"revision\":4,\"camera_uuid\":\"f73a5a16-8e66-41e7-8ad5-80fcd8c84d20\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:30:19.567Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"086ef605-3fb8-4d6f-ac5d-77302a8d1ea8\",\"snapshot_url\":\"/api/observations/4db73649-60d2-4311-9db3-8b4f352333c6/snapshot\"}}"} +{"t": 1794162694.671141, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/300c0294-e470-4ef1-9e2f-b04b55c12878", "payload": "{\"specversion\":\"1.0\",\"id\":\"15469f69-fffe-5d06-bb15-8221e782c1b4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/300c0294-e470-4ef1-9e2f-b04b55c12878\",\"time\":\"2026-11-08T18:31:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dd086cb3-c382-4518-b6aa-6017c28e3b88\",\"revision\":5,\"camera_uuid\":\"300c0294-e470-4ef1-9e2f-b04b55c12878\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:28:57.614Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f622342b-2cc4-4fcc-bf1f-6507e799c519\",\"snapshot_url\":\"/api/observations/dd086cb3-c382-4518-b6aa-6017c28e3b88/snapshot\"}}"} +{"t": 1794162763.1161888, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"249a4eee-8bbd-57bf-acd1-f3b6da2f7afc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a4c58b8f-ed21-4a27-96a0-4e2877f80b49\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:36.002Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8e29d4c3-3ee4-4930-9c44-6cdec211c8fd\"}}"} +{"t": 1794162763.1166887, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"942356eb-556b-5055-9c26-6d540c00bc33\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1833d16b-8dc3-4bb5-bbd4-b248a2de3804\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:36.002Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8e29d4c3-3ee4-4930-9c44-6cdec211c8fd\"}}"} +{"t": 1794162763.117658, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f4525005-0ea9-5333-af0e-eff8dbd1d4a8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a4c58b8f-ed21-4a27-96a0-4e2877f80b49\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:36.002Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8e29d4c3-3ee4-4930-9c44-6cdec211c8fd\",\"snapshot_url\":\"/api/observations/a4c58b8f-ed21-4a27-96a0-4e2877f80b49/snapshot\"}}"} +{"t": 1794162763.1183536, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"de38d68f-99e7-5aee-8810-55f479fac72f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1833d16b-8dc3-4bb5-bbd4-b248a2de3804\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:36.002Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8e29d4c3-3ee4-4930-9c44-6cdec211c8fd\",\"snapshot_url\":\"/api/observations/1833d16b-8dc3-4bb5-bbd4-b248a2de3804/snapshot\"}}"} +{"t": 1794162763.1187887, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3eebc592-eb72-57e7-b2ca-6c67838cee36\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8860bac2-f96a-4229-9b95-888a6a5d8b48\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:37.282Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"095b9933-1a0b-4964-b35b-2b5661d04b19\"}}"} +{"t": 1794162763.1195188, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"62419022-2f6b-5efd-8773-4dc7060153f5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"42d66c96-ffea-49a1-9572-29523db29321\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:37.282Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"095b9933-1a0b-4964-b35b-2b5661d04b19\"}}"} +{"t": 1794162763.12049, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6fa1d443-edbf-57ee-805a-c290c25dc2c0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8860bac2-f96a-4229-9b95-888a6a5d8b48\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:37.282Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"095b9933-1a0b-4964-b35b-2b5661d04b19\",\"snapshot_url\":\"/api/observations/8860bac2-f96a-4229-9b95-888a6a5d8b48/snapshot\"}}"} +{"t": 1794162763.1213174, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"54caecf1-ead4-57c5-b85f-2d5b44490e6e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"42d66c96-ffea-49a1-9572-29523db29321\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:37.282Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"095b9933-1a0b-4964-b35b-2b5661d04b19\",\"snapshot_url\":\"/api/observations/42d66c96-ffea-49a1-9572-29523db29321/snapshot\"}}"} +{"t": 1794162763.1217608, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3886b482-ed8c-5a81-9c40-1bb21c129a94\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e1ca18aa-a4d7-4f2c-84cc-e7ec8f59467f\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:38.562Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91f292df-ff2a-452e-90c2-a17776e0180f\"}}"} +{"t": 1794162763.1223443, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"31b3a25e-0e8c-5991-8f54-381c1629363c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5e4e7a87-4f22-4d51-b7c3-e030446ef051\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:38.562Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91f292df-ff2a-452e-90c2-a17776e0180f\"}}"} +{"t": 1794162763.1235597, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"865588a0-f801-5171-9503-b19fdef7f72e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e1ca18aa-a4d7-4f2c-84cc-e7ec8f59467f\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:38.562Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91f292df-ff2a-452e-90c2-a17776e0180f\",\"snapshot_url\":\"/api/observations/e1ca18aa-a4d7-4f2c-84cc-e7ec8f59467f/snapshot\"}}"} +{"t": 1794162763.1242826, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fd027f3c-06b5-58dc-aa3f-6da32183e2a9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5e4e7a87-4f22-4d51-b7c3-e030446ef051\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:38.562Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91f292df-ff2a-452e-90c2-a17776e0180f\",\"snapshot_url\":\"/api/observations/5e4e7a87-4f22-4d51-b7c3-e030446ef051/snapshot\"}}"} +{"t": 1794162763.1249301, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9fc032df-72fe-5f28-acd3-1d0b61caa81e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7d58a49b-a6b1-4750-9e30-2a8c90a88e97\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:39.842Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9290e75d-8539-466a-8b39-6b2ffef4cbcc\"}}"} +{"t": 1794162763.1253803, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1517ab7d-eab7-5794-acc8-6a136366b48e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"755c8e57-5617-4fb9-848a-06a0c682fc2d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:39.842Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9290e75d-8539-466a-8b39-6b2ffef4cbcc\"}}"} +{"t": 1794162763.1261477, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"98450a9b-cdf4-5721-ac06-58622896e1d1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7d58a49b-a6b1-4750-9e30-2a8c90a88e97\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:39.842Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9290e75d-8539-466a-8b39-6b2ffef4cbcc\",\"snapshot_url\":\"/api/observations/7d58a49b-a6b1-4750-9e30-2a8c90a88e97/snapshot\"}}"} +{"t": 1794162763.1268167, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8be29429-c6df-574e-b45e-aeedf92ec76a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:39Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"755c8e57-5617-4fb9-848a-06a0c682fc2d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:39.842Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9290e75d-8539-466a-8b39-6b2ffef4cbcc\",\"snapshot_url\":\"/api/observations/755c8e57-5617-4fb9-848a-06a0c682fc2d/snapshot\"}}"} +{"t": 1794162763.1272762, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8a5588eb-38a9-567c-b2d4-b51efdcbce4f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"82178866-a0f9-4123-b041-bc6cd710a834\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:41.122Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7f7eb2b0-14d9-4133-9279-6957ec9b27e8\"}}"} +{"t": 1794162763.1278105, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2390568b-aee8-516d-8c31-668668223112\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6802e572-50ca-4412-a6bc-01bf3d3a5406\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:41.122Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7f7eb2b0-14d9-4133-9279-6957ec9b27e8\"}}"} +{"t": 1794162763.1283543, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"d4f17309-546c-5e53-a86c-5bf81a7651df\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"82178866-a0f9-4123-b041-bc6cd710a834\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:41.122Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7f7eb2b0-14d9-4133-9279-6957ec9b27e8\",\"snapshot_url\":\"/api/observations/82178866-a0f9-4123-b041-bc6cd710a834/snapshot\"}}"} +{"t": 1794162763.1290376, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"57cbde96-f35f-54e1-8333-c0e97239a0f2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6802e572-50ca-4412-a6bc-01bf3d3a5406\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:41.122Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7f7eb2b0-14d9-4133-9279-6957ec9b27e8\",\"snapshot_url\":\"/api/observations/6802e572-50ca-4412-a6bc-01bf3d3a5406/snapshot\"}}"} +{"t": 1794162763.1293619, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"5456932f-3f81-5fff-ae7b-f56193b7724f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c195f1fa-9c7f-4415-9de7-bc1d2d6891c4\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:42.402Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2bac4d25-f8e6-455f-8120-b9d2a0aa7dd6\"}}"} +{"t": 1794162763.129656, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1414c86e-c2ef-58d8-8d89-56753f290ef2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b01d1827-291d-4d60-9d7e-45461de10634\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:42.402Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2bac4d25-f8e6-455f-8120-b9d2a0aa7dd6\"}}"} +{"t": 1794162763.1301816, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9a9ebe13-177b-560e-9d58-782a16fe8a32\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c195f1fa-9c7f-4415-9de7-bc1d2d6891c4\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:42.402Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2bac4d25-f8e6-455f-8120-b9d2a0aa7dd6\",\"snapshot_url\":\"/api/observations/c195f1fa-9c7f-4415-9de7-bc1d2d6891c4/snapshot\"}}"} +{"t": 1794162763.130977, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c1244d10-460e-59b7-bd1c-834dec4e4548\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:42Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b01d1827-291d-4d60-9d7e-45461de10634\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:42.402Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2bac4d25-f8e6-455f-8120-b9d2a0aa7dd6\",\"snapshot_url\":\"/api/observations/b01d1827-291d-4d60-9d7e-45461de10634/snapshot\"}}"} +{"t": 1794162763.131287, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e1bb5e38-ec77-5211-a194-4acb342bfbf4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9bf808fa-4d94-4c1f-807f-aa898da0b184\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:43.682Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1715535d-cbda-42b3-9c44-6c2af472223a\"}}"} +{"t": 1794162763.1316042, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"92730356-f5c1-5871-a2d1-78ae1b5b68d6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8878e354-8ddb-4cb1-a194-7aa4c4e29a85\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:43.682Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1715535d-cbda-42b3-9c44-6c2af472223a\"}}"} +{"t": 1794162763.1321156, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f782cdc9-4ad9-5e1c-9753-ae6aa2388bc9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9bf808fa-4d94-4c1f-807f-aa898da0b184\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:43.682Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1715535d-cbda-42b3-9c44-6c2af472223a\",\"snapshot_url\":\"/api/observations/9bf808fa-4d94-4c1f-807f-aa898da0b184/snapshot\"}}"} +{"t": 1794162763.132782, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0bdfeabc-e918-5478-9929-5d0e9593aaab\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8878e354-8ddb-4cb1-a194-7aa4c4e29a85\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:43.682Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1715535d-cbda-42b3-9c44-6c2af472223a\",\"snapshot_url\":\"/api/observations/8878e354-8ddb-4cb1-a194-7aa4c4e29a85/snapshot\"}}"} +{"t": 1794162763.1330068, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6a15d9cb-73a4-5e08-90fb-25177b18b44b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1d4e5f03-53c5-45b3-957d-2565f48dd24e\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:44.962Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9bbb1b9a-6ba2-41dd-9887-a6faa1bd0767\"}}"} +{"t": 1794162763.1332736, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"12b405d9-ad2d-5aef-b116-de9f6582bcb7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf2f8d70-ca99-4a4c-a4f0-6c39d64084e7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:44.962Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9bbb1b9a-6ba2-41dd-9887-a6faa1bd0767\"}}"} +{"t": 1794162763.1334891, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9ff7438a-1115-5896-9d64-ba47bd7637a8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1d4e5f03-53c5-45b3-957d-2565f48dd24e\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:44.962Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9bbb1b9a-6ba2-41dd-9887-a6faa1bd0767\",\"snapshot_url\":\"/api/observations/1d4e5f03-53c5-45b3-957d-2565f48dd24e/snapshot\"}}"} +{"t": 1794162763.1336699, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c10907d3-6b3a-5672-8bdf-b7ed96c37330\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf2f8d70-ca99-4a4c-a4f0-6c39d64084e7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:44.962Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9bbb1b9a-6ba2-41dd-9887-a6faa1bd0767\",\"snapshot_url\":\"/api/observations/bf2f8d70-ca99-4a4c-a4f0-6c39d64084e7/snapshot\"}}"} +{"t": 1794162763.1338162, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9606324e-4c9a-569e-91ec-666d180945e1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"206eead6-2311-4dbc-84c3-3067f4cbb0bf\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:46.242Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d9a18fdd-7910-4bfc-802d-7865b2c013a5\"}}"} +{"t": 1794162763.1339667, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"aa7f4cd5-932e-5419-8fce-729a9230018e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"553917df-338c-4f13-b77d-ef93d0cf56f8\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:46.242Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d9a18fdd-7910-4bfc-802d-7865b2c013a5\"}}"} +{"t": 1794162763.1341877, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f832fd31-6797-5e12-aaff-33e2fb9d94f9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"206eead6-2311-4dbc-84c3-3067f4cbb0bf\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:46.242Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d9a18fdd-7910-4bfc-802d-7865b2c013a5\",\"snapshot_url\":\"/api/observations/206eead6-2311-4dbc-84c3-3067f4cbb0bf/snapshot\"}}"} +{"t": 1794162763.1343544, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b63dda77-9fce-5bf6-8ef9-3ceb74cc2a09\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"553917df-338c-4f13-b77d-ef93d0cf56f8\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:46.242Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d9a18fdd-7910-4bfc-802d-7865b2c013a5\",\"snapshot_url\":\"/api/observations/553917df-338c-4f13-b77d-ef93d0cf56f8/snapshot\"}}"} +{"t": 1794162763.1345117, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"98af8fed-8667-50e2-95f3-f298455c839c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b260d01d-b39b-4a7e-b803-01d1a7433224\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:47.522Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b3ce2fd8-126a-4d66-821d-e40e78a6723c\"}}"} +{"t": 1794162763.1346614, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6e1fe480-1b7c-5a46-b160-15e23d02bf3c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f1bae83b-68bd-4beb-8da1-e491c32218bd\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:47.522Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b3ce2fd8-126a-4d66-821d-e40e78a6723c\"}}"} +{"t": 1794162763.1348224, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"320bfedb-771e-5f12-8fa3-fe3bc9bb858a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b260d01d-b39b-4a7e-b803-01d1a7433224\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:47.522Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b3ce2fd8-126a-4d66-821d-e40e78a6723c\",\"snapshot_url\":\"/api/observations/b260d01d-b39b-4a7e-b803-01d1a7433224/snapshot\"}}"} +{"t": 1794162763.1349876, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6b65f215-d5ff-5417-b78e-a7f825dc36fa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f1bae83b-68bd-4beb-8da1-e491c32218bd\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:47.522Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b3ce2fd8-126a-4d66-821d-e40e78a6723c\",\"snapshot_url\":\"/api/observations/f1bae83b-68bd-4beb-8da1-e491c32218bd/snapshot\"}}"} +{"t": 1794162763.135139, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"001ad443-6bf4-53c1-a1fc-fe6f9a3c13a3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8ee3bee8-dd7b-4683-9afe-57816668be5b\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:48.802Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"51443a0e-1ef9-450e-9069-9b0d8bfa0037\"}}"} +{"t": 1794162763.135287, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"afb4c168-b968-5dd6-95ad-342d8a6316ec\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"53209dc8-70e7-4567-b68c-b20e426ddc04\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:48.802Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"51443a0e-1ef9-450e-9069-9b0d8bfa0037\"}}"} +{"t": 1794162763.1354406, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9080b17-c583-5995-a4cc-5384be150863\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8ee3bee8-dd7b-4683-9afe-57816668be5b\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:48.802Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"51443a0e-1ef9-450e-9069-9b0d8bfa0037\",\"snapshot_url\":\"/api/observations/8ee3bee8-dd7b-4683-9afe-57816668be5b/snapshot\"}}"} +{"t": 1794162763.1355946, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"024d4770-5b12-51ec-b248-2a634bb19e78\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"53209dc8-70e7-4567-b68c-b20e426ddc04\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:48.802Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"51443a0e-1ef9-450e-9069-9b0d8bfa0037\",\"snapshot_url\":\"/api/observations/53209dc8-70e7-4567-b68c-b20e426ddc04/snapshot\"}}"} +{"t": 1794162763.1357603, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b8efc69d-14b5-5d88-b64f-371672409d16\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2b48e404-e587-453c-83b1-8cefc905a42b\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:50.082Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c0adf959-5abb-4678-a624-4ab34afe6a18\"}}"} +{"t": 1794162763.1359096, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a551eb07-832d-5b3c-812f-fbd13ed8828a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fe38bc8b-d814-416b-889f-815782ea8842\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:50.082Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c0adf959-5abb-4678-a624-4ab34afe6a18\"}}"} +{"t": 1794162763.1360686, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2fe2ba7d-9f8b-59c3-b3aa-25aa254e5a51\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2b48e404-e587-453c-83b1-8cefc905a42b\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:50.082Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c0adf959-5abb-4678-a624-4ab34afe6a18\",\"snapshot_url\":\"/api/observations/2b48e404-e587-453c-83b1-8cefc905a42b/snapshot\"}}"} +{"t": 1794162763.1362321, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"76251f3d-c3c5-5351-8bfe-17f80436c5b8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fe38bc8b-d814-416b-889f-815782ea8842\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:50.082Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c0adf959-5abb-4678-a624-4ab34afe6a18\",\"snapshot_url\":\"/api/observations/fe38bc8b-d814-416b-889f-815782ea8842/snapshot\"}}"} +{"t": 1794162763.1363697, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"daf584e0-2d77-5249-ad7a-044530109d0c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3b9304c1-4650-4f4a-85e7-8fc45a7c769d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:51.362Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"41586bf4-1653-4720-a81a-2117487f0bc7\"}}"} +{"t": 1794162763.1365197, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2b4b3485-772b-5f44-bd4f-c018652a4669\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6edf46d1-43f0-4433-b582-befdf895ddfd\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:51.362Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"41586bf4-1653-4720-a81a-2117487f0bc7\"}}"} +{"t": 1794162763.1366873, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"490629a8-0417-5ea8-a2f8-b16d02b27798\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3b9304c1-4650-4f4a-85e7-8fc45a7c769d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:51.362Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"41586bf4-1653-4720-a81a-2117487f0bc7\",\"snapshot_url\":\"/api/observations/3b9304c1-4650-4f4a-85e7-8fc45a7c769d/snapshot\"}}"} +{"t": 1794162763.136842, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f81538da-eee7-5020-b78f-170cc171e774\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6edf46d1-43f0-4433-b582-befdf895ddfd\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:51.362Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"41586bf4-1653-4720-a81a-2117487f0bc7\",\"snapshot_url\":\"/api/observations/6edf46d1-43f0-4433-b582-befdf895ddfd/snapshot\"}}"} +{"t": 1794162763.1369808, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2527041a-7990-5613-9444-a746e47b3f93\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"30f3c86c-7d47-4d6b-a400-60c957c00bda\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:52.642Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f6b65f82-c3e8-47de-b0dc-4c5ff51b52ce\"}}"} +{"t": 1794162763.1371624, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e45250b8-9693-53fb-bf15-5420296869fc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"943e07b6-2836-403a-8013-76d6c84b51a4\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:52.642Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f6b65f82-c3e8-47de-b0dc-4c5ff51b52ce\"}}"} +{"t": 1794162763.1373417, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a4827320-611d-53ff-a670-db22a12e4c57\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"30f3c86c-7d47-4d6b-a400-60c957c00bda\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:52.642Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f6b65f82-c3e8-47de-b0dc-4c5ff51b52ce\",\"snapshot_url\":\"/api/observations/30f3c86c-7d47-4d6b-a400-60c957c00bda/snapshot\"}}"} +{"t": 1794162763.1375115, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f6a5c435-947f-5a6c-b4e9-a92171cbf41b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"943e07b6-2836-403a-8013-76d6c84b51a4\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:52.642Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f6b65f82-c3e8-47de-b0dc-4c5ff51b52ce\",\"snapshot_url\":\"/api/observations/943e07b6-2836-403a-8013-76d6c84b51a4/snapshot\"}}"} +{"t": 1794162763.1376572, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fa1ad90f-d0f0-5a27-beee-25652a346c62\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"96b649ae-d6cf-4e8a-97c9-de6526651dfd\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:53.922Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"6e09eff9-d7a7-4d42-8d85-a6bb8c1f8312\"}}"} +{"t": 1794162763.1378083, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c1353275-d325-550d-87b5-2b9f031d6b23\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8dd4b0e9-f808-440c-add5-5d9feace8a40\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:53.922Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"6e09eff9-d7a7-4d42-8d85-a6bb8c1f8312\"}}"} +{"t": 1794162763.137956, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fb903815-363c-54ad-80a7-4ec594191aa3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"96b649ae-d6cf-4e8a-97c9-de6526651dfd\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:53.922Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"6e09eff9-d7a7-4d42-8d85-a6bb8c1f8312\",\"snapshot_url\":\"/api/observations/96b649ae-d6cf-4e8a-97c9-de6526651dfd/snapshot\"}}"} +{"t": 1794162763.1381161, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"5352991b-1d00-5d65-89ec-a7890ed699bd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8dd4b0e9-f808-440c-add5-5d9feace8a40\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:53.922Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"6e09eff9-d7a7-4d42-8d85-a6bb8c1f8312\",\"snapshot_url\":\"/api/observations/8dd4b0e9-f808-440c-add5-5d9feace8a40/snapshot\"}}"} +{"t": 1794162763.1382554, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f2b1b105-8fca-5c17-b53b-df6ccd66c042\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b96fd67d-568b-426a-870b-369c122b67e7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:55.202Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7a97882f-a7a5-493f-977c-f5689994695f\"}}"} +{"t": 1794162763.1383953, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7d51edec-e52b-5d4b-b570-e510bf78488a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"05e44032-07d2-421a-ac03-3daddbd1deff\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:55.202Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7a97882f-a7a5-493f-977c-f5689994695f\"}}"} +{"t": 1794162763.1385481, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"d2edc467-f424-58ba-ac81-f8b2787b2c37\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b96fd67d-568b-426a-870b-369c122b67e7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:55.202Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a97882f-a7a5-493f-977c-f5689994695f\",\"snapshot_url\":\"/api/observations/b96fd67d-568b-426a-870b-369c122b67e7/snapshot\"}}"} +{"t": 1794162763.138698, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8c407de3-9f08-5638-8321-6cb4265e7974\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:55Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"05e44032-07d2-421a-ac03-3daddbd1deff\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:55.202Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a97882f-a7a5-493f-977c-f5689994695f\",\"snapshot_url\":\"/api/observations/05e44032-07d2-421a-ac03-3daddbd1deff/snapshot\"}}"} +{"t": 1794162763.138837, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"45d8bccb-e8c2-5219-b679-47c4d5d8abb2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:56Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"80875d90-9c0f-4134-8772-d8d11b3d9b7d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:56.482Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2d50bc9f-8a79-4bea-a13e-4cd4249ebe3c\"}}"} +{"t": 1794162763.1389775, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"dc3488dd-bb52-5c0c-bd7c-721cf42d9235\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:56Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"95dc404c-1326-4ba6-96b1-7a2fa4e6307d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:56.482Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2d50bc9f-8a79-4bea-a13e-4cd4249ebe3c\"}}"} +{"t": 1794162763.1391373, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"cc105d98-f1ab-56c3-849f-3e2edcd5a3b9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:56Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"80875d90-9c0f-4134-8772-d8d11b3d9b7d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:56.482Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2d50bc9f-8a79-4bea-a13e-4cd4249ebe3c\",\"snapshot_url\":\"/api/observations/80875d90-9c0f-4134-8772-d8d11b3d9b7d/snapshot\"}}"} +{"t": 1794162763.1392891, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"cc51e7fc-fdfb-50a3-ba27-377be6bb615e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:56Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"95dc404c-1326-4ba6-96b1-7a2fa4e6307d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:56.482Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2d50bc9f-8a79-4bea-a13e-4cd4249ebe3c\",\"snapshot_url\":\"/api/observations/95dc404c-1326-4ba6-96b1-7a2fa4e6307d/snapshot\"}}"} +{"t": 1794162763.1394362, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"134b3b89-f2f7-5317-b195-1773409a0a70\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"91aa76b8-db62-4c0d-ab97-aebdb21e212b\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:57.762Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3c051f1f-8513-4779-a915-4e98aeb0648f\"}}"} +{"t": 1794162763.139575, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a4f9c712-5bff-570c-ac26-53c4995e198d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"22cb5edf-4e9e-4b18-a4d7-975cd61f989c\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:57.762Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3c051f1f-8513-4779-a915-4e98aeb0648f\"}}"} +{"t": 1794162763.1397245, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9baa7ad6-fd1b-51fc-a93e-9d9187ce2fda\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"91aa76b8-db62-4c0d-ab97-aebdb21e212b\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:57.762Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c051f1f-8513-4779-a915-4e98aeb0648f\",\"snapshot_url\":\"/api/observations/91aa76b8-db62-4c0d-ab97-aebdb21e212b/snapshot\"}}"} +{"t": 1794162763.1398764, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7afdcfb5-7dce-53df-a9d7-df4bb359c24f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"22cb5edf-4e9e-4b18-a4d7-975cd61f989c\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:57.762Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c051f1f-8513-4779-a915-4e98aeb0648f\",\"snapshot_url\":\"/api/observations/22cb5edf-4e9e-4b18-a4d7-975cd61f989c/snapshot\"}}"} +{"t": 1794162763.1400323, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"065981be-ad38-59e8-8f64-1d4c2fd14802\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f75fad0-1dfa-46e3-9164-0ade806bc9ed\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:59.042Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b42043f7-6a63-4e58-b6b7-045da70a44de\"}}"} +{"t": 1794162763.1401882, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ae3a141b-1c72-5340-ab29-8484da63f1ad\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"30209283-9ff0-4396-8c3c-034b4db70d3d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:59.042Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b42043f7-6a63-4e58-b6b7-045da70a44de\"}}"} +{"t": 1794162763.1403456, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"48a96925-8dad-5ab5-a2c1-4475e6aa9fb7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f75fad0-1dfa-46e3-9164-0ade806bc9ed\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:59.042Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b42043f7-6a63-4e58-b6b7-045da70a44de\",\"snapshot_url\":\"/api/observations/7f75fad0-1dfa-46e3-9164-0ade806bc9ed/snapshot\"}}"} +{"t": 1794162763.1405127, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"bd92b46d-c120-5fe2-96aa-3aa82e47d681\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:31:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"30209283-9ff0-4396-8c3c-034b4db70d3d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:59.042Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b42043f7-6a63-4e58-b6b7-045da70a44de\",\"snapshot_url\":\"/api/observations/30209283-9ff0-4396-8c3c-034b4db70d3d/snapshot\"}}"} +{"t": 1794162763.1406522, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e853c763-29a5-55b6-b212-6f3726bdc367\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"76bd602e-b1df-4f3f-b14b-42f91309668d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:00.322Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0ca0e08f-cf6a-42f7-89e8-9c78ff567860\"}}"} +{"t": 1794162763.140796, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2f3e6df4-63cc-584e-9005-c854439e6a87\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9787b28c-664a-42a0-a65b-1a9f0ba70503\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:00.322Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0ca0e08f-cf6a-42f7-89e8-9c78ff567860\"}}"} +{"t": 1794162763.1409485, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"70804e16-e185-50f9-935c-71bc06597812\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"76bd602e-b1df-4f3f-b14b-42f91309668d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:00.322Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ca0e08f-cf6a-42f7-89e8-9c78ff567860\",\"snapshot_url\":\"/api/observations/76bd602e-b1df-4f3f-b14b-42f91309668d/snapshot\"}}"} +{"t": 1794162763.141102, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fdb44094-5f54-5f28-84c5-0bfcfebace12\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9787b28c-664a-42a0-a65b-1a9f0ba70503\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:00.322Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ca0e08f-cf6a-42f7-89e8-9c78ff567860\",\"snapshot_url\":\"/api/observations/9787b28c-664a-42a0-a65b-1a9f0ba70503/snapshot\"}}"} +{"t": 1794162763.141248, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"d8a0c92e-73ee-58f3-8722-c7fbf9906ebf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bc8e5105-30db-4b5c-aeb6-4d69b5da5267\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:01.602Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f5324a07-4c1b-4fd7-ab74-4f3e9c3eb3d0\"}}"} +{"t": 1794162763.141397, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e7c2429e-e8a0-5fbd-9b79-82fc569c703f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"85292301-968e-4626-a603-c98578a0e017\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:01.602Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f5324a07-4c1b-4fd7-ab74-4f3e9c3eb3d0\"}}"} +{"t": 1794162763.1415477, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e1bbc3e0-06c9-56aa-a5f4-96e884f72bf7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bc8e5105-30db-4b5c-aeb6-4d69b5da5267\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:01.602Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f5324a07-4c1b-4fd7-ab74-4f3e9c3eb3d0\",\"snapshot_url\":\"/api/observations/bc8e5105-30db-4b5c-aeb6-4d69b5da5267/snapshot\"}}"} +{"t": 1794162763.141695, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"4e252341-7cec-5f0c-b2ed-9737d8ece9c1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"85292301-968e-4626-a603-c98578a0e017\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:01.602Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f5324a07-4c1b-4fd7-ab74-4f3e9c3eb3d0\",\"snapshot_url\":\"/api/observations/85292301-968e-4626-a603-c98578a0e017/snapshot\"}}"} +{"t": 1794162763.1418233, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8b8952be-9b24-5b09-bb21-c37284a2ef7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"340e1738-0e29-41e0-9c7e-b0021658bef7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:02.882Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"21ce2169-41d1-445a-a096-650a1b914170\"}}"} +{"t": 1794162763.1419566, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"802cb4e3-b3d0-57c7-81f1-11e129b7f4ac\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"66b7c9a9-6701-4ef6-8cff-a94395b1138d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:02.882Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"21ce2169-41d1-445a-a096-650a1b914170\"}}"} +{"t": 1794162763.1420996, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"04c8831e-dae2-5208-8d17-5aae67d9c538\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"340e1738-0e29-41e0-9c7e-b0021658bef7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:02.882Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"21ce2169-41d1-445a-a096-650a1b914170\",\"snapshot_url\":\"/api/observations/340e1738-0e29-41e0-9c7e-b0021658bef7/snapshot\"}}"} +{"t": 1794162763.1422553, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2a221623-6524-53aa-b49e-fd55371233bc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"66b7c9a9-6701-4ef6-8cff-a94395b1138d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:02.882Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"21ce2169-41d1-445a-a096-650a1b914170\",\"snapshot_url\":\"/api/observations/66b7c9a9-6701-4ef6-8cff-a94395b1138d/snapshot\"}}"} +{"t": 1794162763.1424112, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f6b16deb-a4f3-5597-a381-3f4e293da7a5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"68b9e20f-6e03-4b43-b3eb-f70b17edcea5\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:04.162Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5e669462-05d1-415e-9f25-0c363bcc758f\"}}"} +{"t": 1794162763.142566, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3322a0a7-210e-5538-9db4-59831019a400\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2020a2e3-085f-43d8-b2e1-f6d2d366dad1\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:04.162Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5e669462-05d1-415e-9f25-0c363bcc758f\"}}"} +{"t": 1794162763.1427138, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3284a1ec-bd7d-53ad-8405-2095615b726b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"68b9e20f-6e03-4b43-b3eb-f70b17edcea5\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:04.162Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5e669462-05d1-415e-9f25-0c363bcc758f\",\"snapshot_url\":\"/api/observations/68b9e20f-6e03-4b43-b3eb-f70b17edcea5/snapshot\"}}"} +{"t": 1794162763.14286, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3c0fe4cc-2d27-50d3-8801-912cd4c5bd3f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:04Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2020a2e3-085f-43d8-b2e1-f6d2d366dad1\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:04.162Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5e669462-05d1-415e-9f25-0c363bcc758f\",\"snapshot_url\":\"/api/observations/2020a2e3-085f-43d8-b2e1-f6d2d366dad1/snapshot\"}}"} +{"t": 1794162763.142988, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b6927a0d-5311-58cf-9d6a-72d2a0762b08\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b9ec77ba-14ed-42df-bb44-1ff897fef134\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:05.442Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"122a7839-6ca2-4400-9818-3714b7cafab2\"}}"} +{"t": 1794162763.1431248, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b67e638a-00a1-59e6-bc38-a329ae5e961e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"110390a5-63d1-4996-a3d4-74beb938ac15\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:05.442Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"122a7839-6ca2-4400-9818-3714b7cafab2\"}}"} +{"t": 1794162763.1432726, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"361e6182-b7eb-577d-b245-9caf6548209c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b9ec77ba-14ed-42df-bb44-1ff897fef134\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:05.442Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"122a7839-6ca2-4400-9818-3714b7cafab2\",\"snapshot_url\":\"/api/observations/b9ec77ba-14ed-42df-bb44-1ff897fef134/snapshot\"}}"} +{"t": 1794162763.1434152, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"aeff03b0-bf2e-5721-8b65-ca98782b7074\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"110390a5-63d1-4996-a3d4-74beb938ac15\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:05.442Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"122a7839-6ca2-4400-9818-3714b7cafab2\",\"snapshot_url\":\"/api/observations/110390a5-63d1-4996-a3d4-74beb938ac15/snapshot\"}}"} +{"t": 1794162763.1435418, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"4ee850f2-5d35-5ab7-9e3c-a1966cc9e4c3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"51ff312d-763e-4422-bca8-70c8046403a7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:06.722Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"82bf55ec-bdc2-4867-92be-e035a6bffe2f\"}}"} +{"t": 1794162763.1436725, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"547a90e9-e76b-5404-947f-008cedc4eea1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0b3e3427-470e-4142-aa3a-b205457e5117\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:06.722Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"82bf55ec-bdc2-4867-92be-e035a6bffe2f\"}}"} +{"t": 1794162763.1438112, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a545c42-5d98-50f6-b750-e7cb5bf71510\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"51ff312d-763e-4422-bca8-70c8046403a7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:06.722Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"82bf55ec-bdc2-4867-92be-e035a6bffe2f\",\"snapshot_url\":\"/api/observations/51ff312d-763e-4422-bca8-70c8046403a7/snapshot\"}}"} +{"t": 1794162763.1439536, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"45a0f3b9-bb77-58aa-a2f7-6e75c7d621bb\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0b3e3427-470e-4142-aa3a-b205457e5117\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:06.722Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"82bf55ec-bdc2-4867-92be-e035a6bffe2f\",\"snapshot_url\":\"/api/observations/0b3e3427-470e-4142-aa3a-b205457e5117/snapshot\"}}"} +{"t": 1794162763.1440797, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2a86e20-ad30-55a6-8115-bef8660be90e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aee538a8-a51f-4138-ab25-e691c53e913a\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:08.002Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f033d250-0a04-4e17-805e-d982c035f0ac\"}}"} +{"t": 1794162763.1442232, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c76528d5-4811-50fd-bb0d-a5229efa0480\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0895bd0c-7d26-4995-a99d-036b20c16a88\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:08.002Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f033d250-0a04-4e17-805e-d982c035f0ac\"}}"} +{"t": 1794162763.1443624, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a54c73c9-d73c-5a31-a971-048b283134c4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aee538a8-a51f-4138-ab25-e691c53e913a\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:08.002Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f033d250-0a04-4e17-805e-d982c035f0ac\",\"snapshot_url\":\"/api/observations/aee538a8-a51f-4138-ab25-e691c53e913a/snapshot\"}}"} +{"t": 1794162763.144502, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ea984503-238d-5869-b89b-dc5ea2c3693f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:08Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0895bd0c-7d26-4995-a99d-036b20c16a88\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:08.002Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f033d250-0a04-4e17-805e-d982c035f0ac\",\"snapshot_url\":\"/api/observations/0895bd0c-7d26-4995-a99d-036b20c16a88/snapshot\"}}"} +{"t": 1794162763.1446292, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8c1f58e6-0b23-571f-9512-4ecff0819123\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:09Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"511fed72-bf48-4fbc-890c-79009a9a9313\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:09.282Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c9000b2a-3975-4167-a0bb-6600a1e09cb2\"}}"} +{"t": 1794162763.1447587, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2c7f76aa-7018-553f-aedd-323b0261e57a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:09Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"691eed98-6553-4c6a-a237-1d0803462879\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:09.282Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c9000b2a-3975-4167-a0bb-6600a1e09cb2\"}}"} +{"t": 1794162763.1449, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"bf6f9144-4a1c-512f-ad71-c0c1a3eed3e8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:09Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"511fed72-bf48-4fbc-890c-79009a9a9313\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:09.282Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c9000b2a-3975-4167-a0bb-6600a1e09cb2\",\"snapshot_url\":\"/api/observations/511fed72-bf48-4fbc-890c-79009a9a9313/snapshot\"}}"} +{"t": 1794162763.145036, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"653d6c66-644e-5b6d-85dc-c902f042a6fc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:09Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"691eed98-6553-4c6a-a237-1d0803462879\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:09.282Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c9000b2a-3975-4167-a0bb-6600a1e09cb2\",\"snapshot_url\":\"/api/observations/691eed98-6553-4c6a-a237-1d0803462879/snapshot\"}}"} +{"t": 1794162763.1451738, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6fae5c3f-ec3e-5ae4-bd65-32ab3f4d655f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2f1decb1-b9a8-4b3b-96dc-29cd71df01a3\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:10.562Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ebbac472-8266-4885-8fb8-79900c126ba2\"}}"} +{"t": 1794162763.1453073, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6d3d350a-d802-5ff9-bd27-fe8a9d4f0071\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dcf405bb-cb7e-4cd3-98b2-90edbc8823c4\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:10.562Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ebbac472-8266-4885-8fb8-79900c126ba2\"}}"} +{"t": 1794162763.145462, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3f3be980-1b69-598e-9b42-97bc167e1035\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2f1decb1-b9a8-4b3b-96dc-29cd71df01a3\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:10.562Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ebbac472-8266-4885-8fb8-79900c126ba2\",\"snapshot_url\":\"/api/observations/2f1decb1-b9a8-4b3b-96dc-29cd71df01a3/snapshot\"}}"} +{"t": 1794162763.1456118, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"809127cc-9398-5c44-b4e3-85d4d84c470c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:10Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dcf405bb-cb7e-4cd3-98b2-90edbc8823c4\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:10.562Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ebbac472-8266-4885-8fb8-79900c126ba2\",\"snapshot_url\":\"/api/observations/dcf405bb-cb7e-4cd3-98b2-90edbc8823c4/snapshot\"}}"} +{"t": 1794162763.1457386, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a20723ee-b139-54c6-8496-0887fdccbfa8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"31a994cf-27be-4af3-9bb6-f465ae5c5ecd\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:11.842Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b0a18a6c-831f-405d-b2e2-ffd8866e8c9b\"}}"} +{"t": 1794162763.1458676, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"711833b5-9f71-5847-a8cb-a23304f4ec22\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"33c36b10-51e3-4d64-a0ad-4ff2bafe3aeb\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:11.842Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b0a18a6c-831f-405d-b2e2-ffd8866e8c9b\"}}"} +{"t": 1794162763.1460075, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"357bdebf-f558-5968-bc92-bbb682c80da3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"31a994cf-27be-4af3-9bb6-f465ae5c5ecd\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:11.842Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0a18a6c-831f-405d-b2e2-ffd8866e8c9b\",\"snapshot_url\":\"/api/observations/31a994cf-27be-4af3-9bb6-f465ae5c5ecd/snapshot\"}}"} +{"t": 1794162763.1461523, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"3f3f9f42-cf90-5531-9d27-67ffc58d40df\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:11Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"33c36b10-51e3-4d64-a0ad-4ff2bafe3aeb\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:11.842Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0a18a6c-831f-405d-b2e2-ffd8866e8c9b\",\"snapshot_url\":\"/api/observations/33c36b10-51e3-4d64-a0ad-4ff2bafe3aeb/snapshot\"}}"} +{"t": 1794162763.1462808, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"dcacd161-7c13-53e9-a3d3-9b3dd43af7b4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b21d99e-e2d6-4257-9a1c-4ebb7cf7537b\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:13.122Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d8a841d9-7503-4ce0-88b6-7061f3c51567\"}}"} +{"t": 1794162763.1464105, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"db52522d-afa5-5e96-9664-cf73f60d1915\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34c7ba1d-66fa-4a88-8ca3-5a8dd42cc261\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:13.122Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"d8a841d9-7503-4ce0-88b6-7061f3c51567\"}}"} +{"t": 1794162763.1465492, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"59847f67-dccb-5298-842b-81c0a432ea47\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b21d99e-e2d6-4257-9a1c-4ebb7cf7537b\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:13.122Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d8a841d9-7503-4ce0-88b6-7061f3c51567\",\"snapshot_url\":\"/api/observations/4b21d99e-e2d6-4257-9a1c-4ebb7cf7537b/snapshot\"}}"} +{"t": 1794162763.1466804, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"838ffdd7-08a2-5898-ac95-247eceab20ad\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:13Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34c7ba1d-66fa-4a88-8ca3-5a8dd42cc261\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:13.122Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d8a841d9-7503-4ce0-88b6-7061f3c51567\",\"snapshot_url\":\"/api/observations/34c7ba1d-66fa-4a88-8ca3-5a8dd42cc261/snapshot\"}}"} +{"t": 1794162763.1468077, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e9271003-4168-5b71-be9f-2818ab987dac\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:14Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"89836eb6-f65e-4577-836e-b9db4bc3fedd\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:14.402Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ba4e49b9-62d0-4fdc-a076-cb665bc8f2d8\"}}"} +{"t": 1794162763.1469398, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"cf90f74f-b437-5ac7-a4ee-f85872c2bec3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:14Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3cae004f-7f3d-4843-93e0-395eba7f48f3\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:14.402Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"ba4e49b9-62d0-4fdc-a076-cb665bc8f2d8\"}}"} +{"t": 1794162763.147074, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e14a8442-93ce-5792-8503-788c17212fd8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:14Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"89836eb6-f65e-4577-836e-b9db4bc3fedd\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:14.402Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ba4e49b9-62d0-4fdc-a076-cb665bc8f2d8\",\"snapshot_url\":\"/api/observations/89836eb6-f65e-4577-836e-b9db4bc3fedd/snapshot\"}}"} +{"t": 1794162763.1473644, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a6802fe1-ca66-53f7-aa9a-ee86f62abf06\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:14Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3cae004f-7f3d-4843-93e0-395eba7f48f3\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:14.402Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ba4e49b9-62d0-4fdc-a076-cb665bc8f2d8\",\"snapshot_url\":\"/api/observations/3cae004f-7f3d-4843-93e0-395eba7f48f3/snapshot\"}}"} +{"t": 1794162763.1474898, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2282ca98-8600-5dd7-badc-946e61f0d138\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"294510d3-094d-4ce7-8a0b-db9c042345aa\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:15.682Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2c443664-1891-4d3a-ad31-caeab4310752\"}}"} +{"t": 1794162763.1476178, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"4f23f378-632b-501b-bef2-04d0dd1278d7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e0ab529f-329f-4769-903b-919a1a987895\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:15.682Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2c443664-1891-4d3a-ad31-caeab4310752\"}}"} +{"t": 1794162763.1477578, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"61c2e0b0-898e-51a4-829f-2ce0cfb161a8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"294510d3-094d-4ce7-8a0b-db9c042345aa\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:15.682Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2c443664-1891-4d3a-ad31-caeab4310752\",\"snapshot_url\":\"/api/observations/294510d3-094d-4ce7-8a0b-db9c042345aa/snapshot\"}}"} +{"t": 1794162763.1478932, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"151d9f7c-8b1c-55b7-a59b-d09d94e19122\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:15Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e0ab529f-329f-4769-903b-919a1a987895\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:15.682Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2c443664-1891-4d3a-ad31-caeab4310752\",\"snapshot_url\":\"/api/observations/e0ab529f-329f-4769-903b-919a1a987895/snapshot\"}}"} +{"t": 1794162763.1480162, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"77070cdb-654a-5352-b1e7-a918d5135270\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"00c29d36-125a-47f7-844a-49fe56e975fc\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:16.962Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3e6b3c03-13df-4a84-8278-6b0a2f050bcc\"}}"} +{"t": 1794162763.1481516, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"68220bfe-9c77-556f-bb27-c36574a21e60\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fe14e7a2-afa6-4e01-b689-dd53ed892801\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:16.962Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3e6b3c03-13df-4a84-8278-6b0a2f050bcc\"}}"} +{"t": 1794162763.1482906, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"4e1ec357-ccbf-5d0a-91da-c940629987ff\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"00c29d36-125a-47f7-844a-49fe56e975fc\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:16.962Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3e6b3c03-13df-4a84-8278-6b0a2f050bcc\",\"snapshot_url\":\"/api/observations/00c29d36-125a-47f7-844a-49fe56e975fc/snapshot\"}}"} +{"t": 1794162763.1484294, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b9364283-33d8-5cb5-b271-9ea906cbd373\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:16Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fe14e7a2-afa6-4e01-b689-dd53ed892801\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:16.962Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3e6b3c03-13df-4a84-8278-6b0a2f050bcc\",\"snapshot_url\":\"/api/observations/fe14e7a2-afa6-4e01-b689-dd53ed892801/snapshot\"}}"} +{"t": 1794162763.1485658, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ac37e33e-1986-57ce-a552-8c742f22ae0f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6ece1731-aa4a-434a-86ba-198ef4052a44\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:18.242Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"36703f71-741e-45b9-9d1f-8e73b6f959ea\"}}"} +{"t": 1794162763.1486938, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"92e0be7a-932c-5d05-b043-83b789a939d1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c1906f96-a5d8-41cf-850c-e1f0317fa189\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:18.242Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"36703f71-741e-45b9-9d1f-8e73b6f959ea\"}}"} +{"t": 1794162763.1488283, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f159e7de-c314-57f5-b2a2-e2b7394b2933\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6ece1731-aa4a-434a-86ba-198ef4052a44\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:18.242Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"36703f71-741e-45b9-9d1f-8e73b6f959ea\",\"snapshot_url\":\"/api/observations/6ece1731-aa4a-434a-86ba-198ef4052a44/snapshot\"}}"} +{"t": 1794162763.1489613, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"42c8416e-a8f6-5150-9d84-65b61c703e7a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:18Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c1906f96-a5d8-41cf-850c-e1f0317fa189\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:18.242Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"36703f71-741e-45b9-9d1f-8e73b6f959ea\",\"snapshot_url\":\"/api/observations/c1906f96-a5d8-41cf-850c-e1f0317fa189/snapshot\"}}"} +{"t": 1794162763.1490798, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fd96d20b-ea50-534c-b458-ddd2511582ee\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"695737e2-face-44ec-abc8-826d09a7097a\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:19.522Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8175499c-0a81-4385-8b54-3f37c6df3ee1\"}}"} +{"t": 1794162763.1492164, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6fb688ac-84d1-5a08-b73d-21eac294e384\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a496b3b7-3316-4f7f-90f1-f6ae693a9cb0\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:19.522Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8175499c-0a81-4385-8b54-3f37c6df3ee1\"}}"} +{"t": 1794162763.1493483, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"865e9543-1f81-565d-bbfd-ab8dde393387\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"695737e2-face-44ec-abc8-826d09a7097a\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:19.522Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8175499c-0a81-4385-8b54-3f37c6df3ee1\",\"snapshot_url\":\"/api/observations/695737e2-face-44ec-abc8-826d09a7097a/snapshot\"}}"} +{"t": 1794162763.1494768, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"d6056298-b145-5e80-83d4-fb84795a1362\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:19Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a496b3b7-3316-4f7f-90f1-f6ae693a9cb0\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:19.522Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8175499c-0a81-4385-8b54-3f37c6df3ee1\",\"snapshot_url\":\"/api/observations/a496b3b7-3316-4f7f-90f1-f6ae693a9cb0/snapshot\"}}"} +{"t": 1794162763.1495981, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a5a439ab-ff98-5594-80f8-0107e6c47f61\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b6ad043b-f4a9-4385-8aeb-2a80c2aae2dc\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:20.802Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2734a446-1c44-4063-a1f5-2dd826f2fcf8\"}}"} +{"t": 1794162763.1497214, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7a49408d-25bf-5933-8e0f-b5dd1f033eb2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2a316c78-97a8-47a3-aa0c-dacd60775611\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:20.802Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2734a446-1c44-4063-a1f5-2dd826f2fcf8\"}}"} +{"t": 1794162763.149851, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"d0311326-b42c-556b-95e3-9cf11f14497e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b6ad043b-f4a9-4385-8aeb-2a80c2aae2dc\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:20.802Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2734a446-1c44-4063-a1f5-2dd826f2fcf8\",\"snapshot_url\":\"/api/observations/b6ad043b-f4a9-4385-8aeb-2a80c2aae2dc/snapshot\"}}"} +{"t": 1794162763.149982, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"686c576d-32cb-5769-aeea-d9462e630f0b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:20Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2a316c78-97a8-47a3-aa0c-dacd60775611\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:20.802Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2734a446-1c44-4063-a1f5-2dd826f2fcf8\",\"snapshot_url\":\"/api/observations/2a316c78-97a8-47a3-aa0c-dacd60775611/snapshot\"}}"} +{"t": 1794162763.1501422, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f21e677c-605f-562d-97bd-70f855d721a8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b57351a-d545-4ba6-9a9b-de0831809cf7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:22.082Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a00ac837-e7c2-45ee-aec5-0e23faa68776\"}}"} +{"t": 1794162763.150375, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fc06dcd0-d7e4-5c4a-83ad-ba79308c20bc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"11c9a212-f108-4db9-b83c-543a60410029\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:22.082Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"a00ac837-e7c2-45ee-aec5-0e23faa68776\"}}"} +{"t": 1794162763.1505675, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b16c9fd2-7a36-56b4-bc67-47f05b937882\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b57351a-d545-4ba6-9a9b-de0831809cf7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:22.082Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a00ac837-e7c2-45ee-aec5-0e23faa68776\",\"snapshot_url\":\"/api/observations/4b57351a-d545-4ba6-9a9b-de0831809cf7/snapshot\"}}"} +{"t": 1794162763.1507757, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7d8e4cea-25d9-5cc8-8dce-173719639539\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:22Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"11c9a212-f108-4db9-b83c-543a60410029\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:22.082Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a00ac837-e7c2-45ee-aec5-0e23faa68776\",\"snapshot_url\":\"/api/observations/11c9a212-f108-4db9-b83c-543a60410029/snapshot\"}}"} +{"t": 1794162763.150947, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"496ce46a-381b-505c-a4bf-931d946ad318\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a0bdca2-3768-4c5c-a9cf-121a0baa1211\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:23.362Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0ad4c249-dbba-46b1-a0a1-de04e5242939\"}}"} +{"t": 1794162763.1510708, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"fbd3bcbd-641b-54b5-afed-b695758287a7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"45d0ebd1-7eac-43d1-8078-8fbbf1414c6e\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:23.362Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"0ad4c249-dbba-46b1-a0a1-de04e5242939\"}}"} +{"t": 1794162763.151218, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"357243ab-5ab2-520c-9485-d7577687519b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a0bdca2-3768-4c5c-a9cf-121a0baa1211\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:23.362Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ad4c249-dbba-46b1-a0a1-de04e5242939\",\"snapshot_url\":\"/api/observations/8a0bdca2-3768-4c5c-a9cf-121a0baa1211/snapshot\"}}"} +{"t": 1794162763.1513436, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"beba267a-0a6b-5bd9-b9b8-ce7bae5f67e1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:23Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"45d0ebd1-7eac-43d1-8078-8fbbf1414c6e\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:23.362Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ad4c249-dbba-46b1-a0a1-de04e5242939\",\"snapshot_url\":\"/api/observations/45d0ebd1-7eac-43d1-8078-8fbbf1414c6e/snapshot\"}}"} +{"t": 1794162763.151465, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0a503285-2dbd-5482-b8c4-6378bf4b240b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:24Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ab0895ed-907b-474a-b7ba-a7675f319d25\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:24.642Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8f22f65e-d649-4e70-8f9e-1ed49f393ab0\"}}"} +{"t": 1794162763.1515863, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8df38300-1fa0-502e-b993-391dcb91b03d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:24Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1e4d39c4-8f75-48cb-8667-eb086ba83e35\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:24.642Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8f22f65e-d649-4e70-8f9e-1ed49f393ab0\"}}"} +{"t": 1794162763.1517136, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"bdeefcc8-27ca-59eb-a249-cce5f806ed83\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:24Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ab0895ed-907b-474a-b7ba-a7675f319d25\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:24.642Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f22f65e-d649-4e70-8f9e-1ed49f393ab0\",\"snapshot_url\":\"/api/observations/ab0895ed-907b-474a-b7ba-a7675f319d25/snapshot\"}}"} +{"t": 1794162763.1518404, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7486fd3c-f586-58fe-8346-cb103bf80653\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:24Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1e4d39c4-8f75-48cb-8667-eb086ba83e35\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:24.642Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f22f65e-d649-4e70-8f9e-1ed49f393ab0\",\"snapshot_url\":\"/api/observations/1e4d39c4-8f75-48cb-8667-eb086ba83e35/snapshot\"}}"} +{"t": 1794162763.1519613, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2abfb4cc-748b-5540-8960-3a35dc313074\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6a1f8961-c34f-4fc7-8556-bdb9f45ce7c1\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:25.922Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8c45e3f7-ae42-4ab8-98f9-fb3442c2d57e\"}}"} +{"t": 1794162763.1520894, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b102176a-c330-57cb-9910-74ea03ef3de6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ef884f92-d966-4608-8e77-88a23f3fef51\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:25.922Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8c45e3f7-ae42-4ab8-98f9-fb3442c2d57e\"}}"} +{"t": 1794162763.1522188, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"033776c9-450b-55c4-bb30-e6a1271fec61\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6a1f8961-c34f-4fc7-8556-bdb9f45ce7c1\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:25.922Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8c45e3f7-ae42-4ab8-98f9-fb3442c2d57e\",\"snapshot_url\":\"/api/observations/6a1f8961-c34f-4fc7-8556-bdb9f45ce7c1/snapshot\"}}"} +{"t": 1794162763.152346, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e189a355-3998-52fb-824c-0075cc3dd9fa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ef884f92-d966-4608-8e77-88a23f3fef51\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:25.922Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8c45e3f7-ae42-4ab8-98f9-fb3442c2d57e\",\"snapshot_url\":\"/api/observations/ef884f92-d966-4608-8e77-88a23f3fef51/snapshot\"}}"} +{"t": 1794162763.1524675, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8d01b672-ea2c-57bc-8974-e91e3f26e59c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77508e76-4590-436e-a85c-023c5bd346a6\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:27.202Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c35498d5-ca05-4498-8bbd-e7987200cc63\"}}"} +{"t": 1794162763.1526997, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a7ac0056-0fe0-5311-bf6d-a375167475ef\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2af39cbf-fd5c-4fd1-907d-22b956805a18\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:27.202Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"c35498d5-ca05-4498-8bbd-e7987200cc63\"}}"} +{"t": 1794162763.1528986, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a6683103-86ad-5303-a0a2-51ca945972cb\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77508e76-4590-436e-a85c-023c5bd346a6\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:27.202Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c35498d5-ca05-4498-8bbd-e7987200cc63\",\"snapshot_url\":\"/api/observations/77508e76-4590-436e-a85c-023c5bd346a6/snapshot\"}}"} +{"t": 1794162763.1530638, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"93713a1a-9bd4-5f15-ac43-748a40eccd3f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:27Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2af39cbf-fd5c-4fd1-907d-22b956805a18\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:27.202Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c35498d5-ca05-4498-8bbd-e7987200cc63\",\"snapshot_url\":\"/api/observations/2af39cbf-fd5c-4fd1-907d-22b956805a18/snapshot\"}}"} +{"t": 1794162763.1532066, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9e1bd4cc-8048-5118-b311-abf6f580d872\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5ac8099-6d07-4ae6-b4ae-f48f25727146\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:28.482Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"47c11085-8f58-49e1-85a9-550ffbf76249\"}}"} +{"t": 1794162763.15334, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9545e4e-5d3b-5fd6-8c0a-f37abe25a3b7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8da8a9ad-d2ee-49ca-b024-f6e7cfd2c4e5\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:28.482Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"47c11085-8f58-49e1-85a9-550ffbf76249\"}}"} +{"t": 1794162763.1534846, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b725cc73-3b38-5244-b9a1-201df1cdf363\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5ac8099-6d07-4ae6-b4ae-f48f25727146\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:28.482Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"47c11085-8f58-49e1-85a9-550ffbf76249\",\"snapshot_url\":\"/api/observations/c5ac8099-6d07-4ae6-b4ae-f48f25727146/snapshot\"}}"} +{"t": 1794162763.153616, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a822921c-9b53-50cc-bf11-ce2d32decb72\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8da8a9ad-d2ee-49ca-b024-f6e7cfd2c4e5\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:28.482Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"47c11085-8f58-49e1-85a9-550ffbf76249\",\"snapshot_url\":\"/api/observations/8da8a9ad-d2ee-49ca-b024-f6e7cfd2c4e5/snapshot\"}}"} +{"t": 1794162763.153732, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0a23b693-83af-5aba-acf1-f23c53ac5817\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0b5fcb7d-70c6-4f42-a7c7-7f7d7db7bd69\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:29.762Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1d4ea893-f2ae-4a28-8b38-b1102f0dcfe1\"}}"} +{"t": 1794162763.153852, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"175592ed-4707-5e8f-9e12-e95425f1dd14\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"626df9a7-ac17-476c-8b08-6ce22c999bf7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:29.762Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1d4ea893-f2ae-4a28-8b38-b1102f0dcfe1\"}}"} +{"t": 1794162763.1539772, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"09ac98b7-038e-58a0-8c9f-68efa9e61cf7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0b5fcb7d-70c6-4f42-a7c7-7f7d7db7bd69\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:29.762Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1d4ea893-f2ae-4a28-8b38-b1102f0dcfe1\",\"snapshot_url\":\"/api/observations/0b5fcb7d-70c6-4f42-a7c7-7f7d7db7bd69/snapshot\"}}"} +{"t": 1794162763.1541073, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f4de6cf5-9089-54ff-bb8b-a5cc5df182c7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"626df9a7-ac17-476c-8b08-6ce22c999bf7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:29.762Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1d4ea893-f2ae-4a28-8b38-b1102f0dcfe1\",\"snapshot_url\":\"/api/observations/626df9a7-ac17-476c-8b08-6ce22c999bf7/snapshot\"}}"} +{"t": 1794162763.1542284, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"357735a9-ced0-5c4e-ba00-5f254d28730c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c487c5fd-d566-43ea-baa9-2fc18afe0fd2\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:31.042Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b0b0cc5d-812c-4f07-a655-bedc4b09f736\"}}"} +{"t": 1794162763.154347, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ae5c06ee-af1f-5aba-8ccb-3a6a87f46fd0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"099b5103-4abb-4d46-9c7d-777e75fd21dd\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:31.042Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"b0b0cc5d-812c-4f07-a655-bedc4b09f736\"}}"} +{"t": 1794162763.154471, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"28661bd1-7cc9-5b9f-a15e-ade836a70d62\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c487c5fd-d566-43ea-baa9-2fc18afe0fd2\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:31.042Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0b0cc5d-812c-4f07-a655-bedc4b09f736\",\"snapshot_url\":\"/api/observations/c487c5fd-d566-43ea-baa9-2fc18afe0fd2/snapshot\"}}"} +{"t": 1794162763.154591, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"bc5ddae2-611f-5589-91a0-1a03886e683f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"099b5103-4abb-4d46-9c7d-777e75fd21dd\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:31.042Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0b0cc5d-812c-4f07-a655-bedc4b09f736\",\"snapshot_url\":\"/api/observations/099b5103-4abb-4d46-9c7d-777e75fd21dd/snapshot\"}}"} +{"t": 1794162763.154708, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e41a4796-e932-58f6-af5f-5db3ae997747\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f74c2545-750f-472a-9034-e989066ad6fa\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:32.322Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1b052ee4-db06-4dfe-bebe-7b9b07cb9264\"}}"} +{"t": 1794162763.1548347, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0e4a19ea-b4d0-542d-bd30-774587d2800c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"95c0a03c-65a5-4f5f-846b-978e928d8508\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:32.322Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"1b052ee4-db06-4dfe-bebe-7b9b07cb9264\"}}"} +{"t": 1794162763.1549613, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8751dea6-fcb5-5f45-abc2-f4fc1d4c5044\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f74c2545-750f-472a-9034-e989066ad6fa\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:32.322Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1b052ee4-db06-4dfe-bebe-7b9b07cb9264\",\"snapshot_url\":\"/api/observations/f74c2545-750f-472a-9034-e989066ad6fa/snapshot\"}}"} +{"t": 1794162763.1550875, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"05eba353-3983-5b62-adb8-42e385282b28\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"95c0a03c-65a5-4f5f-846b-978e928d8508\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:32.322Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1b052ee4-db06-4dfe-bebe-7b9b07cb9264\",\"snapshot_url\":\"/api/observations/95c0a03c-65a5-4f5f-846b-978e928d8508/snapshot\"}}"} +{"t": 1794162763.1552181, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"cd1ee91b-b447-53f0-af08-122ad162e498\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bce9e906-ac65-4694-9712-3070d28f1623\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:33.602Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f71cef2f-c2a0-4ce3-879d-ee6383931a81\"}}"} +{"t": 1794162763.1553388, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"48bd71c5-e672-5a60-9641-50a66bd31cff\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7d567da4-bb72-4e99-8eb5-3a475809150d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:33.602Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"f71cef2f-c2a0-4ce3-879d-ee6383931a81\"}}"} +{"t": 1794162763.1554704, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"244c33f1-0d6c-53eb-8903-dda75cc18a15\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bce9e906-ac65-4694-9712-3070d28f1623\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:33.602Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f71cef2f-c2a0-4ce3-879d-ee6383931a81\",\"snapshot_url\":\"/api/observations/bce9e906-ac65-4694-9712-3070d28f1623/snapshot\"}}"} +{"t": 1794162763.1555943, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9d2f4488-a05a-59db-9fde-940db255ce6b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7d567da4-bb72-4e99-8eb5-3a475809150d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:33.602Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f71cef2f-c2a0-4ce3-879d-ee6383931a81\",\"snapshot_url\":\"/api/observations/7d567da4-bb72-4e99-8eb5-3a475809150d/snapshot\"}}"} +{"t": 1794162763.1557062, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2200c331-91ec-505f-a5ae-7b57a4cad840\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"68f4f996-3d86-42f3-8499-33b2c74107b7\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:34.882Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"aca02ded-baa9-45bb-a62b-83c5ac7224e3\"}}"} +{"t": 1794162763.1558216, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"447ff5a2-5e8d-5b65-ace3-f4ef2e7807d3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d15d508c-8f8a-483a-a3fa-e4ae7d4d7457\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:34.882Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"aca02ded-baa9-45bb-a62b-83c5ac7224e3\"}}"} +{"t": 1794162763.1559541, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"359fae3b-1298-56cf-a017-7844ce73cbc1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"68f4f996-3d86-42f3-8499-33b2c74107b7\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:34.882Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"aca02ded-baa9-45bb-a62b-83c5ac7224e3\",\"snapshot_url\":\"/api/observations/68f4f996-3d86-42f3-8499-33b2c74107b7/snapshot\"}}"} +{"t": 1794162763.1560712, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"63c6cb5e-9c7f-50c6-9775-e26fc9d5d727\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d15d508c-8f8a-483a-a3fa-e4ae7d4d7457\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:34.882Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"aca02ded-baa9-45bb-a62b-83c5ac7224e3\",\"snapshot_url\":\"/api/observations/d15d508c-8f8a-483a-a3fa-e4ae7d4d7457/snapshot\"}}"} +{"t": 1794162763.1561937, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"af79b6e7-e6cc-52fc-9257-e35c762edb8d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"180892c1-21f0-4f90-84d7-8ea513098c7e\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:36.162Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bfed62d5-a6bc-4e21-bcdd-af7e4c5e7dd5\"}}"} +{"t": 1794162763.1563118, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"66b63aae-1acc-5509-b7d5-2af523faaa30\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f8244c2b-0179-46e1-b193-7cac5878086d\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:36.162Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bfed62d5-a6bc-4e21-bcdd-af7e4c5e7dd5\"}}"} +{"t": 1794162763.1564362, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"32092f05-e2be-5892-a09e-a2a9d92277f8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"180892c1-21f0-4f90-84d7-8ea513098c7e\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:36.162Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bfed62d5-a6bc-4e21-bcdd-af7e4c5e7dd5\",\"snapshot_url\":\"/api/observations/180892c1-21f0-4f90-84d7-8ea513098c7e/snapshot\"}}"} +{"t": 1794162763.156555, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9080a49b-8017-5208-aaba-2aae7410cddb\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:36Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f8244c2b-0179-46e1-b193-7cac5878086d\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:36.162Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bfed62d5-a6bc-4e21-bcdd-af7e4c5e7dd5\",\"snapshot_url\":\"/api/observations/f8244c2b-0179-46e1-b193-7cac5878086d/snapshot\"}}"} +{"t": 1794162763.1566722, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f34ccb8e-62a7-5c97-b372-6dd22e64af7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0445b9a3-c308-4df7-ab0b-8594ecd47074\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:37.442Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"18465779-f7c0-4873-9d9a-db958cfda3fe\"}}"} +{"t": 1794162763.1567922, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"6b3edef9-f6a4-5cdb-8df9-bbc9b5784867\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aaf2e17a-54ec-4870-8789-3e85a715598e\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:37.442Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"18465779-f7c0-4873-9d9a-db958cfda3fe\"}}"} +{"t": 1794162763.1569095, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"38e4c59e-0c3f-5f86-94e9-b9f64874d306\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0445b9a3-c308-4df7-ab0b-8594ecd47074\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:37.442Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"18465779-f7c0-4873-9d9a-db958cfda3fe\",\"snapshot_url\":\"/api/observations/0445b9a3-c308-4df7-ab0b-8594ecd47074/snapshot\"}}"} +{"t": 1794162763.1570363, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1b93419a-031b-5345-8095-ba1b8e51c137\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aaf2e17a-54ec-4870-8789-3e85a715598e\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:37.442Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"18465779-f7c0-4873-9d9a-db958cfda3fe\",\"snapshot_url\":\"/api/observations/aaf2e17a-54ec-4870-8789-3e85a715598e/snapshot\"}}"} +{"t": 1794162763.1571567, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ca352bb-e7eb-560c-9088-05d15cd01d1d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6052d662-fc47-414c-9729-d478d77843b3\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:38.722Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"63e68f16-f66b-460f-a376-f32f54a9df9a\"}}"} +{"t": 1794162763.157276, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1fde3070-6341-5081-961b-e4d0914fdabd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aeaa24bf-1041-49fc-baa9-2cca8c577b3c\",\"revision\":1,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:38.722Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"63e68f16-f66b-460f-a376-f32f54a9df9a\"}}"} +{"t": 1794162763.157397, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"02d7a58b-ceaa-58a3-bc68-96fb6a108993\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6052d662-fc47-414c-9729-d478d77843b3\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:38.722Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"63e68f16-f66b-460f-a376-f32f54a9df9a\",\"snapshot_url\":\"/api/observations/6052d662-fc47-414c-9729-d478d77843b3/snapshot\"}}"} +{"t": 1794162763.1575267, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"85629b5c-a7f1-5704-974d-0d6d52569e7f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aeaa24bf-1041-49fc-baa9-2cca8c577b3c\",\"revision\":2,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:38.722Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"63e68f16-f66b-460f-a376-f32f54a9df9a\",\"snapshot_url\":\"/api/observations/aeaa24bf-1041-49fc-baa9-2cca8c577b3c/snapshot\"}}"} +{"t": 1794162765.986528, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"53fba40d-492f-58ed-928f-311f54411ccb\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:45Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a4c58b8f-ed21-4a27-96a0-4e2877f80b49\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:36.002Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8e29d4c3-3ee4-4930-9c44-6cdec211c8fd\",\"snapshot_url\":\"/api/observations/a4c58b8f-ed21-4a27-96a0-4e2877f80b49/snapshot\"}}"} +{"t": 1794162766.0099134, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"726831ab-eb4a-5f86-b447-effc3feaf30d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1833d16b-8dc3-4bb5-bbd4-b248a2de3804\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:36.002Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8e29d4c3-3ee4-4930-9c44-6cdec211c8fd\",\"snapshot_url\":\"/api/observations/1833d16b-8dc3-4bb5-bbd4-b248a2de3804/snapshot\"}}"} +{"t": 1794162766.0293581, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"00fd18d7-1f6c-5444-8a8b-415a0c897a38\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"42d66c96-ffea-49a1-9572-29523db29321\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:37.282Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"095b9933-1a0b-4964-b35b-2b5661d04b19\",\"snapshot_url\":\"/api/observations/42d66c96-ffea-49a1-9572-29523db29321/snapshot\"}}"} +{"t": 1794162766.0409892, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2a941393-9e26-59a2-af1e-2b81fddbc2d7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8860bac2-f96a-4229-9b95-888a6a5d8b48\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:37.282Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"095b9933-1a0b-4964-b35b-2b5661d04b19\",\"snapshot_url\":\"/api/observations/8860bac2-f96a-4229-9b95-888a6a5d8b48/snapshot\"}}"} +{"t": 1794162766.089067, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0347796c-49b2-5a71-85b0-9723af156ede\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e1ca18aa-a4d7-4f2c-84cc-e7ec8f59467f\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:38.562Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91f292df-ff2a-452e-90c2-a17776e0180f\",\"snapshot_url\":\"/api/observations/e1ca18aa-a4d7-4f2c-84cc-e7ec8f59467f/snapshot\"}}"} +{"t": 1794162766.1032767, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"897b8350-f414-5262-a53a-802bcb90c43c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5e4e7a87-4f22-4d51-b7c3-e030446ef051\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:38.562Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91f292df-ff2a-452e-90c2-a17776e0180f\",\"snapshot_url\":\"/api/observations/5e4e7a87-4f22-4d51-b7c3-e030446ef051/snapshot\"}}"} +{"t": 1794162766.1517148, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1ef471e9-6a91-5e83-89c4-bd95b4e21069\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7d58a49b-a6b1-4750-9e30-2a8c90a88e97\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:39.842Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9290e75d-8539-466a-8b39-6b2ffef4cbcc\",\"snapshot_url\":\"/api/observations/7d58a49b-a6b1-4750-9e30-2a8c90a88e97/snapshot\"}}"} +{"t": 1794162766.1640506, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"18b0b23a-3bb7-56d0-a5b8-68ad0f183879\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"755c8e57-5617-4fb9-848a-06a0c682fc2d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:39.842Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9290e75d-8539-466a-8b39-6b2ffef4cbcc\",\"snapshot_url\":\"/api/observations/755c8e57-5617-4fb9-848a-06a0c682fc2d/snapshot\"}}"} +{"t": 1794162766.218732, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"abea22ff-8891-5995-9749-9079289b67f8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"82178866-a0f9-4123-b041-bc6cd710a834\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:41.122Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7f7eb2b0-14d9-4133-9279-6957ec9b27e8\",\"snapshot_url\":\"/api/observations/82178866-a0f9-4123-b041-bc6cd710a834/snapshot\"}}"} +{"t": 1794162766.2309146, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"013796b9-c75f-583f-ab48-b82399963336\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6802e572-50ca-4412-a6bc-01bf3d3a5406\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:41.122Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7f7eb2b0-14d9-4133-9279-6957ec9b27e8\",\"snapshot_url\":\"/api/observations/6802e572-50ca-4412-a6bc-01bf3d3a5406/snapshot\"}}"} +{"t": 1794162766.2851284, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0f50be0b-64ae-511b-8ed4-3b4e0d5455c9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c195f1fa-9c7f-4415-9de7-bc1d2d6891c4\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:42.402Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2bac4d25-f8e6-455f-8120-b9d2a0aa7dd6\",\"snapshot_url\":\"/api/observations/c195f1fa-9c7f-4415-9de7-bc1d2d6891c4/snapshot\"}}"} +{"t": 1794162766.2969074, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1304c953-75f4-5298-93f2-5b9a5d8a044b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b01d1827-291d-4d60-9d7e-45461de10634\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:42.402Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2bac4d25-f8e6-455f-8120-b9d2a0aa7dd6\",\"snapshot_url\":\"/api/observations/b01d1827-291d-4d60-9d7e-45461de10634/snapshot\"}}"} +{"t": 1794162766.3524697, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"66cfd05e-23f1-52f9-9d57-9efd6b12caa8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9bf808fa-4d94-4c1f-807f-aa898da0b184\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:43.682Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1715535d-cbda-42b3-9c44-6c2af472223a\",\"snapshot_url\":\"/api/observations/9bf808fa-4d94-4c1f-807f-aa898da0b184/snapshot\"}}"} +{"t": 1794162766.3649116, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"197756f5-f1c8-5462-a7c6-ba22d8e3091c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8878e354-8ddb-4cb1-a194-7aa4c4e29a85\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:43.682Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1715535d-cbda-42b3-9c44-6c2af472223a\",\"snapshot_url\":\"/api/observations/8878e354-8ddb-4cb1-a194-7aa4c4e29a85/snapshot\"}}"} +{"t": 1794162766.4219646, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"df9bab27-a8b6-59f2-88ea-b5bee4a2af7a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1d4e5f03-53c5-45b3-957d-2565f48dd24e\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:44.962Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9bbb1b9a-6ba2-41dd-9887-a6faa1bd0767\",\"snapshot_url\":\"/api/observations/1d4e5f03-53c5-45b3-957d-2565f48dd24e/snapshot\"}}"} +{"t": 1794162766.433877, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0ce86a74-6475-57c5-9c13-1885c7797745\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bf2f8d70-ca99-4a4c-a4f0-6c39d64084e7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:44.962Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9bbb1b9a-6ba2-41dd-9887-a6faa1bd0767\",\"snapshot_url\":\"/api/observations/bf2f8d70-ca99-4a4c-a4f0-6c39d64084e7/snapshot\"}}"} +{"t": 1794162766.4851568, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b0d3fc71-4760-5fbf-a47b-ed49305d082a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"206eead6-2311-4dbc-84c3-3067f4cbb0bf\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:46.242Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d9a18fdd-7910-4bfc-802d-7865b2c013a5\",\"snapshot_url\":\"/api/observations/206eead6-2311-4dbc-84c3-3067f4cbb0bf/snapshot\"}}"} +{"t": 1794162766.4997423, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e0bcd50a-c4ca-5fca-bd9c-f79815fbdf25\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"553917df-338c-4f13-b77d-ef93d0cf56f8\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:46.242Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d9a18fdd-7910-4bfc-802d-7865b2c013a5\",\"snapshot_url\":\"/api/observations/553917df-338c-4f13-b77d-ef93d0cf56f8/snapshot\"}}"} +{"t": 1794162766.5477772, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e2e05788-07b5-598e-9db6-d9de9c035dc6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b260d01d-b39b-4a7e-b803-01d1a7433224\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:47.522Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b3ce2fd8-126a-4d66-821d-e40e78a6723c\",\"snapshot_url\":\"/api/observations/b260d01d-b39b-4a7e-b803-01d1a7433224/snapshot\"}}"} +{"t": 1794162766.559389, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9be4d9fc-f7a3-535c-8af7-b10d48c8b550\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f1bae83b-68bd-4beb-8da1-e491c32218bd\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:47.522Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b3ce2fd8-126a-4d66-821d-e40e78a6723c\",\"snapshot_url\":\"/api/observations/f1bae83b-68bd-4beb-8da1-e491c32218bd/snapshot\"}}"} +{"t": 1794162766.616697, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f74d00ad-3fb7-571d-806c-4cc3128ef5c4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8ee3bee8-dd7b-4683-9afe-57816668be5b\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:48.802Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"51443a0e-1ef9-450e-9069-9b0d8bfa0037\",\"snapshot_url\":\"/api/observations/8ee3bee8-dd7b-4683-9afe-57816668be5b/snapshot\"}}"} +{"t": 1794162766.6292472, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"78d8c762-47d6-56ae-a361-1c5037351db7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"53209dc8-70e7-4567-b68c-b20e426ddc04\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:48.802Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"51443a0e-1ef9-450e-9069-9b0d8bfa0037\",\"snapshot_url\":\"/api/observations/53209dc8-70e7-4567-b68c-b20e426ddc04/snapshot\"}}"} +{"t": 1794162766.6796327, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9e0f6071-3f39-5e36-b61d-f257553ae4d1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2b48e404-e587-453c-83b1-8cefc905a42b\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:50.082Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c0adf959-5abb-4678-a624-4ab34afe6a18\",\"snapshot_url\":\"/api/observations/2b48e404-e587-453c-83b1-8cefc905a42b/snapshot\"}}"} +{"t": 1794162766.6930175, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c35d05bd-bae9-5c3a-b324-9712acb98597\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fe38bc8b-d814-416b-889f-815782ea8842\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:50.082Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c0adf959-5abb-4678-a624-4ab34afe6a18\",\"snapshot_url\":\"/api/observations/fe38bc8b-d814-416b-889f-815782ea8842/snapshot\"}}"} +{"t": 1794162766.7463248, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"33d111c7-5dd4-537d-ad33-9edfc7916f5a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3b9304c1-4650-4f4a-85e7-8fc45a7c769d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:51.362Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"41586bf4-1653-4720-a81a-2117487f0bc7\",\"snapshot_url\":\"/api/observations/3b9304c1-4650-4f4a-85e7-8fc45a7c769d/snapshot\"}}"} +{"t": 1794162766.7578986, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9919f3d3-2d8b-5ebe-afec-04165f4a464c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6edf46d1-43f0-4433-b582-befdf895ddfd\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:51.362Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"41586bf4-1653-4720-a81a-2117487f0bc7\",\"snapshot_url\":\"/api/observations/6edf46d1-43f0-4433-b582-befdf895ddfd/snapshot\"}}"} +{"t": 1794162766.8089535, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a7555e57-4acb-5950-b950-acf450cca45f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"30f3c86c-7d47-4d6b-a400-60c957c00bda\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:52.642Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f6b65f82-c3e8-47de-b0dc-4c5ff51b52ce\",\"snapshot_url\":\"/api/observations/30f3c86c-7d47-4d6b-a400-60c957c00bda/snapshot\"}}"} +{"t": 1794162766.8209128, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8ca820fb-32d5-5159-b54e-14cc91da8b85\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"943e07b6-2836-403a-8013-76d6c84b51a4\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:52.642Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f6b65f82-c3e8-47de-b0dc-4c5ff51b52ce\",\"snapshot_url\":\"/api/observations/943e07b6-2836-403a-8013-76d6c84b51a4/snapshot\"}}"} +{"t": 1794162766.8749886, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1804a6c6-89fe-572e-b822-217fdbaae254\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"96b649ae-d6cf-4e8a-97c9-de6526651dfd\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:53.922Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"6e09eff9-d7a7-4d42-8d85-a6bb8c1f8312\",\"snapshot_url\":\"/api/observations/96b649ae-d6cf-4e8a-97c9-de6526651dfd/snapshot\"}}"} +{"t": 1794162766.887883, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"eb01d2fb-e655-5620-b51b-a712e87d3ac6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8dd4b0e9-f808-440c-add5-5d9feace8a40\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:53.922Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"6e09eff9-d7a7-4d42-8d85-a6bb8c1f8312\",\"snapshot_url\":\"/api/observations/8dd4b0e9-f808-440c-add5-5d9feace8a40/snapshot\"}}"} +{"t": 1794162766.9374073, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"097ece89-24d5-562f-aa21-9d55a93a16d0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b96fd67d-568b-426a-870b-369c122b67e7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:55.202Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a97882f-a7a5-493f-977c-f5689994695f\",\"snapshot_url\":\"/api/observations/b96fd67d-568b-426a-870b-369c122b67e7/snapshot\"}}"} +{"t": 1794162766.9490092, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a06b4a21-4b86-5f14-9105-ad5a7900d89b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"05e44032-07d2-421a-ac03-3daddbd1deff\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:55.202Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a97882f-a7a5-493f-977c-f5689994695f\",\"snapshot_url\":\"/api/observations/05e44032-07d2-421a-ac03-3daddbd1deff/snapshot\"}}"} +{"t": 1794162766.9999168, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9b5942d-51a5-5e44-aa48-069f41c7278b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:46Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"80875d90-9c0f-4134-8772-d8d11b3d9b7d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:56.482Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2d50bc9f-8a79-4bea-a13e-4cd4249ebe3c\",\"snapshot_url\":\"/api/observations/80875d90-9c0f-4134-8772-d8d11b3d9b7d/snapshot\"}}"} +{"t": 1794162767.0148437, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"be9e4d4e-4467-51ea-8028-f000059f7a7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"95dc404c-1326-4ba6-96b1-7a2fa4e6307d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:56.482Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2d50bc9f-8a79-4bea-a13e-4cd4249ebe3c\",\"snapshot_url\":\"/api/observations/95dc404c-1326-4ba6-96b1-7a2fa4e6307d/snapshot\"}}"} +{"t": 1794162767.0680737, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9128bee3-5197-5e83-80f1-403ec80309e2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"91aa76b8-db62-4c0d-ab97-aebdb21e212b\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:57.762Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c051f1f-8513-4779-a915-4e98aeb0648f\",\"snapshot_url\":\"/api/observations/91aa76b8-db62-4c0d-ab97-aebdb21e212b/snapshot\"}}"} +{"t": 1794162767.0826638, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"61c62a27-2934-5d24-8daa-1b7dbd35efe8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"22cb5edf-4e9e-4b18-a4d7-975cd61f989c\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:57.762Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3c051f1f-8513-4779-a915-4e98aeb0648f\",\"snapshot_url\":\"/api/observations/22cb5edf-4e9e-4b18-a4d7-975cd61f989c/snapshot\"}}"} +{"t": 1794162767.1395926, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"2cdf565d-aada-5a29-8484-9f3e768ea6c2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f75fad0-1dfa-46e3-9164-0ade806bc9ed\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:59.042Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b42043f7-6a63-4e58-b6b7-045da70a44de\",\"snapshot_url\":\"/api/observations/7f75fad0-1dfa-46e3-9164-0ade806bc9ed/snapshot\"}}"} +{"t": 1794162767.152481, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"8d0e15d9-3e10-53d1-bed7-38fd07c5b182\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"30209283-9ff0-4396-8c3c-034b4db70d3d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:31:59.042Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b42043f7-6a63-4e58-b6b7-045da70a44de\",\"snapshot_url\":\"/api/observations/30209283-9ff0-4396-8c3c-034b4db70d3d/snapshot\"}}"} +{"t": 1794162767.2115939, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ac0d7221-1cc8-5817-96bb-7d0d15937926\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"76bd602e-b1df-4f3f-b14b-42f91309668d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:00.322Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ca0e08f-cf6a-42f7-89e8-9c78ff567860\",\"snapshot_url\":\"/api/observations/76bd602e-b1df-4f3f-b14b-42f91309668d/snapshot\"}}"} +{"t": 1794162767.2231843, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b677e764-d08f-576c-b798-725c8eee205b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9787b28c-664a-42a0-a65b-1a9f0ba70503\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:00.322Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ca0e08f-cf6a-42f7-89e8-9c78ff567860\",\"snapshot_url\":\"/api/observations/9787b28c-664a-42a0-a65b-1a9f0ba70503/snapshot\"}}"} +{"t": 1794162767.2729204, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e5d8b953-b752-563d-80f1-b5c8d7ef831b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bc8e5105-30db-4b5c-aeb6-4d69b5da5267\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:01.602Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f5324a07-4c1b-4fd7-ab74-4f3e9c3eb3d0\",\"snapshot_url\":\"/api/observations/bc8e5105-30db-4b5c-aeb6-4d69b5da5267/snapshot\"}}"} +{"t": 1794162767.2854774, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"63fada53-28b2-5ca5-8dd4-a9a3c8fae53a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"85292301-968e-4626-a603-c98578a0e017\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:01.602Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f5324a07-4c1b-4fd7-ab74-4f3e9c3eb3d0\",\"snapshot_url\":\"/api/observations/85292301-968e-4626-a603-c98578a0e017/snapshot\"}}"} +{"t": 1794162767.3402388, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0e4d9a90-33ce-5072-a3f7-4bfd3e43ef19\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"340e1738-0e29-41e0-9c7e-b0021658bef7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:02.882Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"21ce2169-41d1-445a-a096-650a1b914170\",\"snapshot_url\":\"/api/observations/340e1738-0e29-41e0-9c7e-b0021658bef7/snapshot\"}}"} +{"t": 1794162767.35263, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"12bc6fdb-7744-55e3-8372-ba899b350cf8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"66b7c9a9-6701-4ef6-8cff-a94395b1138d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:02.882Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"21ce2169-41d1-445a-a096-650a1b914170\",\"snapshot_url\":\"/api/observations/66b7c9a9-6701-4ef6-8cff-a94395b1138d/snapshot\"}}"} +{"t": 1794162767.4049096, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"464f13a6-650b-57fe-ad54-06d46c2dd6d4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"68b9e20f-6e03-4b43-b3eb-f70b17edcea5\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:04.162Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5e669462-05d1-415e-9f25-0c363bcc758f\",\"snapshot_url\":\"/api/observations/68b9e20f-6e03-4b43-b3eb-f70b17edcea5/snapshot\"}}"} +{"t": 1794162767.4170344, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7ad49477-8370-5b26-840e-262d38612ca1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2020a2e3-085f-43d8-b2e1-f6d2d366dad1\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:04.162Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5e669462-05d1-415e-9f25-0c363bcc758f\",\"snapshot_url\":\"/api/observations/2020a2e3-085f-43d8-b2e1-f6d2d366dad1/snapshot\"}}"} +{"t": 1794162767.471946, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1cb5f686-5546-57bd-b3cf-0557de69bb8b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b9ec77ba-14ed-42df-bb44-1ff897fef134\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:05.442Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"122a7839-6ca2-4400-9818-3714b7cafab2\",\"snapshot_url\":\"/api/observations/b9ec77ba-14ed-42df-bb44-1ff897fef134/snapshot\"}}"} +{"t": 1794162767.4835398, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"227b021e-7269-5318-b726-1493b970dfd4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"110390a5-63d1-4996-a3d4-74beb938ac15\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:05.442Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"122a7839-6ca2-4400-9818-3714b7cafab2\",\"snapshot_url\":\"/api/observations/110390a5-63d1-4996-a3d4-74beb938ac15/snapshot\"}}"} +{"t": 1794162767.5335333, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"878bea08-8ebc-546d-9f08-a737fa65638a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"51ff312d-763e-4422-bca8-70c8046403a7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:06.722Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"82bf55ec-bdc2-4867-92be-e035a6bffe2f\",\"snapshot_url\":\"/api/observations/51ff312d-763e-4422-bca8-70c8046403a7/snapshot\"}}"} +{"t": 1794162767.5457034, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a8c6b944-8078-5990-8e30-163f4cbe99f8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0b3e3427-470e-4142-aa3a-b205457e5117\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:06.722Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"82bf55ec-bdc2-4867-92be-e035a6bffe2f\",\"snapshot_url\":\"/api/observations/0b3e3427-470e-4142-aa3a-b205457e5117/snapshot\"}}"} +{"t": 1794162767.603372, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"04fb3b3d-bd40-52aa-8fdc-18754809670f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aee538a8-a51f-4138-ab25-e691c53e913a\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:08.002Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f033d250-0a04-4e17-805e-d982c035f0ac\",\"snapshot_url\":\"/api/observations/aee538a8-a51f-4138-ab25-e691c53e913a/snapshot\"}}"} +{"t": 1794162767.6168785, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1751ab43-7897-5564-b064-be17a35bed51\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0895bd0c-7d26-4995-a99d-036b20c16a88\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:08.002Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f033d250-0a04-4e17-805e-d982c035f0ac\",\"snapshot_url\":\"/api/observations/0895bd0c-7d26-4995-a99d-036b20c16a88/snapshot\"}}"} +{"t": 1794162767.664243, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e6881b58-a25b-59bc-b63d-90bdc3275244\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"511fed72-bf48-4fbc-890c-79009a9a9313\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:09.282Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c9000b2a-3975-4167-a0bb-6600a1e09cb2\",\"snapshot_url\":\"/api/observations/511fed72-bf48-4fbc-890c-79009a9a9313/snapshot\"}}"} +{"t": 1794162767.6759615, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"cba3547a-7f0a-5800-8751-a0f889343691\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"691eed98-6553-4c6a-a237-1d0803462879\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:09.282Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c9000b2a-3975-4167-a0bb-6600a1e09cb2\",\"snapshot_url\":\"/api/observations/691eed98-6553-4c6a-a237-1d0803462879/snapshot\"}}"} +{"t": 1794162767.7369854, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a56cbf36-84e9-5e5d-a5ee-0968d08422ae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2f1decb1-b9a8-4b3b-96dc-29cd71df01a3\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:10.562Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ebbac472-8266-4885-8fb8-79900c126ba2\",\"snapshot_url\":\"/api/observations/2f1decb1-b9a8-4b3b-96dc-29cd71df01a3/snapshot\"}}"} +{"t": 1794162767.7496386, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"db36e0f2-aab0-5080-ba1d-236caf46098a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"dcf405bb-cb7e-4cd3-98b2-90edbc8823c4\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:10.562Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ebbac472-8266-4885-8fb8-79900c126ba2\",\"snapshot_url\":\"/api/observations/dcf405bb-cb7e-4cd3-98b2-90edbc8823c4/snapshot\"}}"} +{"t": 1794162767.8119304, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"dcbed724-c33e-530d-8e93-91c496a68cec\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"31a994cf-27be-4af3-9bb6-f465ae5c5ecd\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:11.842Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0a18a6c-831f-405d-b2e2-ffd8866e8c9b\",\"snapshot_url\":\"/api/observations/31a994cf-27be-4af3-9bb6-f465ae5c5ecd/snapshot\"}}"} +{"t": 1794162767.8268151, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"54cbfaba-94b0-55c0-9708-4c10f38180f0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"33c36b10-51e3-4d64-a0ad-4ff2bafe3aeb\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:11.842Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0a18a6c-831f-405d-b2e2-ffd8866e8c9b\",\"snapshot_url\":\"/api/observations/33c36b10-51e3-4d64-a0ad-4ff2bafe3aeb/snapshot\"}}"} +{"t": 1794162767.8839343, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"10e0d7c4-4407-5719-b7ba-524e0a11d3f1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b21d99e-e2d6-4257-9a1c-4ebb7cf7537b\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:13.122Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d8a841d9-7503-4ce0-88b6-7061f3c51567\",\"snapshot_url\":\"/api/observations/4b21d99e-e2d6-4257-9a1c-4ebb7cf7537b/snapshot\"}}"} +{"t": 1794162767.8954465, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"987760de-0df7-5355-9c8d-8a62ef9446d8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"34c7ba1d-66fa-4a88-8ca3-5a8dd42cc261\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:13.122Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"d8a841d9-7503-4ce0-88b6-7061f3c51567\",\"snapshot_url\":\"/api/observations/34c7ba1d-66fa-4a88-8ca3-5a8dd42cc261/snapshot\"}}"} +{"t": 1794162767.9469302, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"1489c534-1512-55a5-b25b-d05afacf4822\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"89836eb6-f65e-4577-836e-b9db4bc3fedd\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:14.402Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ba4e49b9-62d0-4fdc-a076-cb665bc8f2d8\",\"snapshot_url\":\"/api/observations/89836eb6-f65e-4577-836e-b9db4bc3fedd/snapshot\"}}"} +{"t": 1794162767.959303, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c3a37e68-cf91-58f8-9b5b-1c7740b74a86\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:47Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3cae004f-7f3d-4843-93e0-395eba7f48f3\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:14.402Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"ba4e49b9-62d0-4fdc-a076-cb665bc8f2d8\",\"snapshot_url\":\"/api/observations/3cae004f-7f3d-4843-93e0-395eba7f48f3/snapshot\"}}"} +{"t": 1794162768.0085146, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7d52210e-7385-5264-b316-9adc0bb63ed5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"294510d3-094d-4ce7-8a0b-db9c042345aa\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:15.682Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2c443664-1891-4d3a-ad31-caeab4310752\",\"snapshot_url\":\"/api/observations/294510d3-094d-4ce7-8a0b-db9c042345aa/snapshot\"}}"} +{"t": 1794162768.024279, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e772725c-2303-52cd-9338-2e3e2aaa1945\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e0ab529f-329f-4769-903b-919a1a987895\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:15.682Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2c443664-1891-4d3a-ad31-caeab4310752\",\"snapshot_url\":\"/api/observations/e0ab529f-329f-4769-903b-919a1a987895/snapshot\"}}"} +{"t": 1794162768.069726, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0edbd2d5-be83-56bf-a594-bfdd30f5ece7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"00c29d36-125a-47f7-844a-49fe56e975fc\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:16.962Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3e6b3c03-13df-4a84-8278-6b0a2f050bcc\",\"snapshot_url\":\"/api/observations/00c29d36-125a-47f7-844a-49fe56e975fc/snapshot\"}}"} +{"t": 1794162768.0821092, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"85747f2f-f6c4-5906-a663-5d7fdd364842\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fe14e7a2-afa6-4e01-b689-dd53ed892801\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:16.962Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3e6b3c03-13df-4a84-8278-6b0a2f050bcc\",\"snapshot_url\":\"/api/observations/fe14e7a2-afa6-4e01-b689-dd53ed892801/snapshot\"}}"} +{"t": 1794162768.1327639, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"707b071f-1c4e-50f5-88bb-1c7cc0920969\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6ece1731-aa4a-434a-86ba-198ef4052a44\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:18.242Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"36703f71-741e-45b9-9d1f-8e73b6f959ea\",\"snapshot_url\":\"/api/observations/6ece1731-aa4a-434a-86ba-198ef4052a44/snapshot\"}}"} +{"t": 1794162768.1459043, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9c42ca5-ec2d-5a54-8b42-1b56af1495c1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c1906f96-a5d8-41cf-850c-e1f0317fa189\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:18.242Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"36703f71-741e-45b9-9d1f-8e73b6f959ea\",\"snapshot_url\":\"/api/observations/c1906f96-a5d8-41cf-850c-e1f0317fa189/snapshot\"}}"} +{"t": 1794162768.195581, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"b28f8eaf-5426-5570-a1d2-2f6dc5bad736\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"695737e2-face-44ec-abc8-826d09a7097a\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:19.522Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8175499c-0a81-4385-8b54-3f37c6df3ee1\",\"snapshot_url\":\"/api/observations/695737e2-face-44ec-abc8-826d09a7097a/snapshot\"}}"} +{"t": 1794162768.2077258, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"eaad2a6d-5083-5597-8072-2f1ad36f8951\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a496b3b7-3316-4f7f-90f1-f6ae693a9cb0\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:19.522Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8175499c-0a81-4385-8b54-3f37c6df3ee1\",\"snapshot_url\":\"/api/observations/a496b3b7-3316-4f7f-90f1-f6ae693a9cb0/snapshot\"}}"} +{"t": 1794162768.2586076, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0b0266db-d17b-57c5-87bb-76507f386a34\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b6ad043b-f4a9-4385-8aeb-2a80c2aae2dc\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:20.802Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2734a446-1c44-4063-a1f5-2dd826f2fcf8\",\"snapshot_url\":\"/api/observations/b6ad043b-f4a9-4385-8aeb-2a80c2aae2dc/snapshot\"}}"} +{"t": 1794162768.2723713, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ad5fe02e-acf8-59d8-9b61-febe16a2e421\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2a316c78-97a8-47a3-aa0c-dacd60775611\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:20.802Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2734a446-1c44-4063-a1f5-2dd826f2fcf8\",\"snapshot_url\":\"/api/observations/2a316c78-97a8-47a3-aa0c-dacd60775611/snapshot\"}}"} +{"t": 1794162768.3242035, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"db3e031d-0d1c-5aaf-8349-dd7c55c74e40\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4b57351a-d545-4ba6-9a9b-de0831809cf7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:22.082Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a00ac837-e7c2-45ee-aec5-0e23faa68776\",\"snapshot_url\":\"/api/observations/4b57351a-d545-4ba6-9a9b-de0831809cf7/snapshot\"}}"} +{"t": 1794162768.336641, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"90d4a4fb-632e-5b74-8c26-d2c7ca1545e5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"11c9a212-f108-4db9-b83c-543a60410029\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:22.082Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"a00ac837-e7c2-45ee-aec5-0e23faa68776\",\"snapshot_url\":\"/api/observations/11c9a212-f108-4db9-b83c-543a60410029/snapshot\"}}"} +{"t": 1794162768.3862157, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e452a036-60c0-50e8-8a0f-6f9a87fd30f7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a0bdca2-3768-4c5c-a9cf-121a0baa1211\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:23.362Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ad4c249-dbba-46b1-a0a1-de04e5242939\",\"snapshot_url\":\"/api/observations/8a0bdca2-3768-4c5c-a9cf-121a0baa1211/snapshot\"}}"} +{"t": 1794162768.3983731, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0fa59fad-0e58-56d0-a4ba-1b1d08cf586b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"45d0ebd1-7eac-43d1-8078-8fbbf1414c6e\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:23.362Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"0ad4c249-dbba-46b1-a0a1-de04e5242939\",\"snapshot_url\":\"/api/observations/45d0ebd1-7eac-43d1-8078-8fbbf1414c6e/snapshot\"}}"} +{"t": 1794162768.4479392, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f938286-bb27-5bcf-ac1c-5544ccb68db7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ab0895ed-907b-474a-b7ba-a7675f319d25\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:24.642Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f22f65e-d649-4e70-8f9e-1ed49f393ab0\",\"snapshot_url\":\"/api/observations/ab0895ed-907b-474a-b7ba-a7675f319d25/snapshot\"}}"} +{"t": 1794162768.4605837, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"7aa6a356-7964-5a25-974a-d3598e0e7948\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1e4d39c4-8f75-48cb-8667-eb086ba83e35\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:24.642Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f22f65e-d649-4e70-8f9e-1ed49f393ab0\",\"snapshot_url\":\"/api/observations/1e4d39c4-8f75-48cb-8667-eb086ba83e35/snapshot\"}}"} +{"t": 1794162768.5239604, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ea3994c7-9f01-54be-9ab7-7ad00aead852\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6a1f8961-c34f-4fc7-8556-bdb9f45ce7c1\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:25.922Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8c45e3f7-ae42-4ab8-98f9-fb3442c2d57e\",\"snapshot_url\":\"/api/observations/6a1f8961-c34f-4fc7-8556-bdb9f45ce7c1/snapshot\"}}"} +{"t": 1794162768.5354815, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"e536bc2e-35cd-565f-a858-9fac6ffd4af0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ef884f92-d966-4608-8e77-88a23f3fef51\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:25.922Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8c45e3f7-ae42-4ab8-98f9-fb3442c2d57e\",\"snapshot_url\":\"/api/observations/ef884f92-d966-4608-8e77-88a23f3fef51/snapshot\"}}"} +{"t": 1794162768.5987177, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"74c4a496-40d1-50c7-b2ce-7c97db8b87f4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"77508e76-4590-436e-a85c-023c5bd346a6\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:27.202Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c35498d5-ca05-4498-8bbd-e7987200cc63\",\"snapshot_url\":\"/api/observations/77508e76-4590-436e-a85c-023c5bd346a6/snapshot\"}}"} +{"t": 1794162768.6122193, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c629dfb5-a7e9-568b-8c02-ea4f95f85fb0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2af39cbf-fd5c-4fd1-907d-22b956805a18\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:27.202Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"c35498d5-ca05-4498-8bbd-e7987200cc63\",\"snapshot_url\":\"/api/observations/2af39cbf-fd5c-4fd1-907d-22b956805a18/snapshot\"}}"} +{"t": 1794162768.6680808, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a2b108f4-0cc5-5b39-847c-c26368fbd45c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5ac8099-6d07-4ae6-b4ae-f48f25727146\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:28.482Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"47c11085-8f58-49e1-85a9-550ffbf76249\",\"snapshot_url\":\"/api/observations/c5ac8099-6d07-4ae6-b4ae-f48f25727146/snapshot\"}}"} +{"t": 1794162768.6797235, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"40f766fc-f38c-5368-bd5b-affe566ad7a4\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8da8a9ad-d2ee-49ca-b024-f6e7cfd2c4e5\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:28.482Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"47c11085-8f58-49e1-85a9-550ffbf76249\",\"snapshot_url\":\"/api/observations/8da8a9ad-d2ee-49ca-b024-f6e7cfd2c4e5/snapshot\"}}"} +{"t": 1794162768.7335336, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"cdac2b62-9e9c-5d98-b323-6e82baf5d44c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0b5fcb7d-70c6-4f42-a7c7-7f7d7db7bd69\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:29.762Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1d4ea893-f2ae-4a28-8b38-b1102f0dcfe1\",\"snapshot_url\":\"/api/observations/0b5fcb7d-70c6-4f42-a7c7-7f7d7db7bd69/snapshot\"}}"} +{"t": 1794162768.7455087, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"ef2a6f92-a8d4-5f79-b307-73d8c39767b5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"626df9a7-ac17-476c-8b08-6ce22c999bf7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:29.762Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1d4ea893-f2ae-4a28-8b38-b1102f0dcfe1\",\"snapshot_url\":\"/api/observations/626df9a7-ac17-476c-8b08-6ce22c999bf7/snapshot\"}}"} +{"t": 1794162768.796186, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f73ac4f9-a76a-56f6-8ac0-d3398f06ed38\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c487c5fd-d566-43ea-baa9-2fc18afe0fd2\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:31.042Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0b0cc5d-812c-4f07-a655-bedc4b09f736\",\"snapshot_url\":\"/api/observations/c487c5fd-d566-43ea-baa9-2fc18afe0fd2/snapshot\"}}"} +{"t": 1794162768.8080554, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"100836bf-7bf4-5998-8abe-dad3fd12078c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"099b5103-4abb-4d46-9c7d-777e75fd21dd\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:31.042Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"b0b0cc5d-812c-4f07-a655-bedc4b09f736\",\"snapshot_url\":\"/api/observations/099b5103-4abb-4d46-9c7d-777e75fd21dd/snapshot\"}}"} +{"t": 1794162768.8631518, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"608e1056-b60b-5b7b-8f6f-58134e75071a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f74c2545-750f-472a-9034-e989066ad6fa\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:32.322Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1b052ee4-db06-4dfe-bebe-7b9b07cb9264\",\"snapshot_url\":\"/api/observations/f74c2545-750f-472a-9034-e989066ad6fa/snapshot\"}}"} +{"t": 1794162768.8746312, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0f0987b5-0b49-500b-8a09-38ae540e6d12\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"95c0a03c-65a5-4f5f-846b-978e928d8508\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:32.322Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"1b052ee4-db06-4dfe-bebe-7b9b07cb9264\",\"snapshot_url\":\"/api/observations/95c0a03c-65a5-4f5f-846b-978e928d8508/snapshot\"}}"} +{"t": 1794162768.9291809, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"f6b6a64c-48e7-5184-a50b-c13acd9e6556\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bce9e906-ac65-4694-9712-3070d28f1623\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:33.602Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f71cef2f-c2a0-4ce3-879d-ee6383931a81\",\"snapshot_url\":\"/api/observations/bce9e906-ac65-4694-9712-3070d28f1623/snapshot\"}}"} +{"t": 1794162768.9528122, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"221f5b9b-0fe4-5c1c-8860-1f4849e884e3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:48Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7d567da4-bb72-4e99-8eb5-3a475809150d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:33.602Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"f71cef2f-c2a0-4ce3-879d-ee6383931a81\",\"snapshot_url\":\"/api/observations/7d567da4-bb72-4e99-8eb5-3a475809150d/snapshot\"}}"} +{"t": 1794162769.015152, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a256aaeb-ced8-5e67-a143-5b231884a8ee\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"68f4f996-3d86-42f3-8499-33b2c74107b7\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:34.882Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"aca02ded-baa9-45bb-a62b-83c5ac7224e3\",\"snapshot_url\":\"/api/observations/68f4f996-3d86-42f3-8499-33b2c74107b7/snapshot\"}}"} +{"t": 1794162769.0356715, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"a38913a7-92ac-573e-86d5-2a4be835601d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d15d508c-8f8a-483a-a3fa-e4ae7d4d7457\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:34.882Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"aca02ded-baa9-45bb-a62b-83c5ac7224e3\",\"snapshot_url\":\"/api/observations/d15d508c-8f8a-483a-a3fa-e4ae7d4d7457/snapshot\"}}"} +{"t": 1794162769.0477903, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"87e3868f-3163-512b-906d-ed34881d2711\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"180892c1-21f0-4f90-84d7-8ea513098c7e\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:36.162Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bfed62d5-a6bc-4e21-bcdd-af7e4c5e7dd5\",\"snapshot_url\":\"/api/observations/180892c1-21f0-4f90-84d7-8ea513098c7e/snapshot\"}}"} +{"t": 1794162769.0608141, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"4571a61a-9218-53b6-a9d9-a66523b8c545\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f8244c2b-0179-46e1-b193-7cac5878086d\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:36.162Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bfed62d5-a6bc-4e21-bcdd-af7e4c5e7dd5\",\"snapshot_url\":\"/api/observations/f8244c2b-0179-46e1-b193-7cac5878086d/snapshot\"}}"} +{"t": 1794162769.0997772, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"94cd8b9c-598d-53a2-97ca-f0b31e1f981a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"0445b9a3-c308-4df7-ab0b-8594ecd47074\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:37.442Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"18465779-f7c0-4873-9d9a-db958cfda3fe\",\"snapshot_url\":\"/api/observations/0445b9a3-c308-4df7-ab0b-8594ecd47074/snapshot\"}}"} +{"t": 1794162769.1121178, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"0268d7e6-4891-52a3-ad4b-74906ab68d56\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aaf2e17a-54ec-4870-8789-3e85a715598e\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:37.442Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"18465779-f7c0-4873-9d9a-db958cfda3fe\",\"snapshot_url\":\"/api/observations/aaf2e17a-54ec-4870-8789-3e85a715598e/snapshot\"}}"} +{"t": 1794162769.153236, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"d38bc598-c645-5a00-946f-3803d62a83ba\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6052d662-fc47-414c-9729-d478d77843b3\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:38.722Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"63e68f16-f66b-460f-a376-f32f54a9df9a\",\"snapshot_url\":\"/api/observations/6052d662-fc47-414c-9729-d478d77843b3/snapshot\"}}"} +{"t": 1794162769.1653566, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/6dab5ccc-193e-40ce-90e9-9ffa8117e722", "payload": "{\"specversion\":\"1.0\",\"id\":\"c046d16d-0a24-5fc2-b101-a2bf4fac068d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"time\":\"2026-11-08T18:32:49Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"aeaa24bf-1041-49fc-baa9-2cca8c577b3c\",\"revision\":3,\"camera_uuid\":\"6dab5ccc-193e-40ce-90e9-9ffa8117e722\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:38.722Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"63e68f16-f66b-460f-a376-f32f54a9df9a\",\"snapshot_url\":\"/api/observations/aeaa24bf-1041-49fc-baa9-2cca8c577b3c/snapshot\"}}"} +{"t": 1794162770.8088157, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"19a1fa08-87c5-5367-ac19-1bcb2915c523\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"85953dce-b3a8-462f-a784-7f0d01204d52\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:50.801Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e6c10637-5b18-48b3-b75f-066ee101c5ef\"}}"} +{"t": 1794162770.8094246, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"f5c9404e-5983-557f-9592-cf3953f97367\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"944bf5ba-d073-4a72-9c5f-073f89ffcaf5\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:50.801Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e6c10637-5b18-48b3-b75f-066ee101c5ef\"}}"} +{"t": 1794162770.815044, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"6a3b2c5c-ca55-5705-971f-77893a567b67\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"85953dce-b3a8-462f-a784-7f0d01204d52\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:50.801Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e6c10637-5b18-48b3-b75f-066ee101c5ef\",\"snapshot_url\":\"/api/observations/85953dce-b3a8-462f-a784-7f0d01204d52/snapshot\"}}"} +{"t": 1794162770.8155618, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c80d083b-fe9a-5354-a7aa-1ed366dc2eef\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"944bf5ba-d073-4a72-9c5f-073f89ffcaf5\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:50.801Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e6c10637-5b18-48b3-b75f-066ee101c5ef\",\"snapshot_url\":\"/api/observations/944bf5ba-d073-4a72-9c5f-073f89ffcaf5/snapshot\"}}"} +{"t": 1794162770.9111485, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c432a7d6-4400-5444-a6f8-6bdb00c3d2f1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"944bf5ba-d073-4a72-9c5f-073f89ffcaf5\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:50.801Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e6c10637-5b18-48b3-b75f-066ee101c5ef\",\"snapshot_url\":\"/api/observations/944bf5ba-d073-4a72-9c5f-073f89ffcaf5/snapshot\"}}"} +{"t": 1794162770.9229748, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"09e3262b-6083-53c3-b483-6ae1482d6481\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:50Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"85953dce-b3a8-462f-a784-7f0d01204d52\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:50.801Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e6c10637-5b18-48b3-b75f-066ee101c5ef\",\"snapshot_url\":\"/api/observations/85953dce-b3a8-462f-a784-7f0d01204d52/snapshot\"}}"} +{"t": 1794162772.0881393, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5c0e1ed9-d52e-590c-8eb1-66aa3784f400\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"03dd2c20-57fe-4ae6-90db-c205252eeef7\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:52.081Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"95973ea1-debf-4806-9aa9-1dd1c043ef03\"}}"} +{"t": 1794162772.0884387, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"8bd23e1e-905c-5566-a8d1-e1128738e6f5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"088030fa-88d4-4fc2-84c5-646d5ca3292d\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:52.081Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"95973ea1-debf-4806-9aa9-1dd1c043ef03\"}}"} +{"t": 1794162772.0887983, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a0ec06c4-60e6-5455-91e3-9aa81dc5f341\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"03dd2c20-57fe-4ae6-90db-c205252eeef7\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:52.081Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"95973ea1-debf-4806-9aa9-1dd1c043ef03\",\"snapshot_url\":\"/api/observations/03dd2c20-57fe-4ae6-90db-c205252eeef7/snapshot\"}}"} +{"t": 1794162772.0892932, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c9fbd86a-24a0-5652-8d3d-20bb4c771377\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"088030fa-88d4-4fc2-84c5-646d5ca3292d\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:52.081Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"95973ea1-debf-4806-9aa9-1dd1c043ef03\",\"snapshot_url\":\"/api/observations/088030fa-88d4-4fc2-84c5-646d5ca3292d/snapshot\"}}"} +{"t": 1794162772.2014978, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"ea0cc837-ff20-59a9-8fc9-d7ea5ee12857\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"088030fa-88d4-4fc2-84c5-646d5ca3292d\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:52.081Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"95973ea1-debf-4806-9aa9-1dd1c043ef03\",\"snapshot_url\":\"/api/observations/088030fa-88d4-4fc2-84c5-646d5ca3292d/snapshot\"}}"} +{"t": 1794162772.2130835, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9dfcebdd-4e0a-59fa-a0fd-9bd37d654a8a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"03dd2c20-57fe-4ae6-90db-c205252eeef7\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:52.081Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"95973ea1-debf-4806-9aa9-1dd1c043ef03\",\"snapshot_url\":\"/api/observations/03dd2c20-57fe-4ae6-90db-c205252eeef7/snapshot\"}}"} +{"t": 1794162773.3682187, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c021a5a2-a471-511b-953b-ba54eb6e4ec9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1342474c-0088-42e4-91af-926e6f6a6421\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:53.361Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"28c7300a-1d42-435f-9ca7-e103afa37c63\"}}"} +{"t": 1794162773.3684375, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9ce0d95a-3f2f-5dd6-89cc-3c0577219a55\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1fbd9850-fd83-4523-afc1-ef6027725fb5\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:53.361Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"28c7300a-1d42-435f-9ca7-e103afa37c63\"}}"} +{"t": 1794162773.3686385, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"0c190cf8-9037-5ceb-a149-f538b7188388\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1342474c-0088-42e4-91af-926e6f6a6421\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:53.361Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"28c7300a-1d42-435f-9ca7-e103afa37c63\",\"snapshot_url\":\"/api/observations/1342474c-0088-42e4-91af-926e6f6a6421/snapshot\"}}"} +{"t": 1794162773.3688755, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"0395c338-db26-5e85-b52b-c164f2db541b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1fbd9850-fd83-4523-afc1-ef6027725fb5\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:53.361Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"28c7300a-1d42-435f-9ca7-e103afa37c63\",\"snapshot_url\":\"/api/observations/1fbd9850-fd83-4523-afc1-ef6027725fb5/snapshot\"}}"} +{"t": 1794162773.4815147, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"1b879f4c-c004-5bcf-b431-eca9be505d39\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1fbd9850-fd83-4523-afc1-ef6027725fb5\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:53.361Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"28c7300a-1d42-435f-9ca7-e103afa37c63\",\"snapshot_url\":\"/api/observations/1fbd9850-fd83-4523-afc1-ef6027725fb5/snapshot\"}}"} +{"t": 1794162773.4936464, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"728d6aae-7023-5ca5-b222-0ac19980c0f3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:53Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"1342474c-0088-42e4-91af-926e6f6a6421\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:53.361Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"28c7300a-1d42-435f-9ca7-e103afa37c63\",\"snapshot_url\":\"/api/observations/1342474c-0088-42e4-91af-926e6f6a6421/snapshot\"}}"} +{"t": 1794162774.6485674, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"eae6b502-bb0e-59ae-9c8a-cb8c0466df7a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5b2991d6-bc7b-4561-b288-0e6bde091078\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:54.641Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2b36d640-00c5-4534-a695-beff41101f3c\"}}"} +{"t": 1794162774.6487703, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"d463bcea-e517-5b4c-b12e-7a66c60f1dcd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5b31584-be34-46bf-b03b-1b4f0e889a59\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:54.641Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"2b36d640-00c5-4534-a695-beff41101f3c\"}}"} +{"t": 1794162774.6489887, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"ffd90d61-5689-59d1-82b2-7477ce1c8e7d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5b2991d6-bc7b-4561-b288-0e6bde091078\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:54.641Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2b36d640-00c5-4534-a695-beff41101f3c\",\"snapshot_url\":\"/api/observations/5b2991d6-bc7b-4561-b288-0e6bde091078/snapshot\"}}"} +{"t": 1794162774.649313, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"ed6d04ff-01da-5f99-82e5-5211a0137b6f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:54Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5b31584-be34-46bf-b03b-1b4f0e889a59\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:54.641Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2b36d640-00c5-4534-a695-beff41101f3c\",\"snapshot_url\":\"/api/observations/c5b31584-be34-46bf-b03b-1b4f0e889a59/snapshot\"}}"} +{"t": 1794162777.3424656, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"045d5f11-0f1c-5b82-a2b6-9444bfc78c60\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5c945a20-c54f-4172-b4af-5a2926d24081\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:57.332Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5b787ba0-5178-4e0c-81b9-f32f5a688271\"}}"} +{"t": 1794162777.3426924, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"4af13781-272a-5d1c-8d2d-585a95c60fa9\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f3355087-b16f-4d27-ae62-8ed1e319569e\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:57.332Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"5b787ba0-5178-4e0c-81b9-f32f5a688271\"}}"} +{"t": 1794162777.3429399, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"04b5fc7d-4dc8-527c-a772-bdff1a898fbd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5c945a20-c54f-4172-b4af-5a2926d24081\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:57.332Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5b787ba0-5178-4e0c-81b9-f32f5a688271\",\"snapshot_url\":\"/api/observations/5c945a20-c54f-4172-b4af-5a2926d24081/snapshot\"}}"} +{"t": 1794162777.3432155, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"71b51880-1585-5bb6-99e1-6d13ea022e86\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f3355087-b16f-4d27-ae62-8ed1e319569e\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:57.332Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5b787ba0-5178-4e0c-81b9-f32f5a688271\",\"snapshot_url\":\"/api/observations/f3355087-b16f-4d27-ae62-8ed1e319569e/snapshot\"}}"} +{"t": 1794162777.4489505, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"b010a1b5-74fc-5a57-9c0d-5d7a8ab5c35c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5c945a20-c54f-4172-b4af-5a2926d24081\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:57.332Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5b787ba0-5178-4e0c-81b9-f32f5a688271\",\"snapshot_url\":\"/api/observations/5c945a20-c54f-4172-b4af-5a2926d24081/snapshot\"}}"} +{"t": 1794162777.4624171, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c3ad766f-b1c9-54a0-b262-961655cdd30f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:57Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f3355087-b16f-4d27-ae62-8ed1e319569e\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:57.332Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"5b787ba0-5178-4e0c-81b9-f32f5a688271\",\"snapshot_url\":\"/api/observations/f3355087-b16f-4d27-ae62-8ed1e319569e/snapshot\"}}"} +{"t": 1794162778.6198287, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"4c10ff68-2b7b-5bfb-b9cb-de0d76a8eba0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"35c07aac-e3b9-4eff-8199-3e01b331ba75\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:58.612Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"345b8105-1483-49aa-b327-caa998ed2da0\"}}"} +{"t": 1794162778.6200662, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"eb5b7f7a-25c3-5a74-a991-7ae0d97d3b45\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9de78f60-fa0a-4b54-8b44-7d1a5758df5c\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:58.612Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"345b8105-1483-49aa-b327-caa998ed2da0\"}}"} +{"t": 1794162778.6204488, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"926819a5-b290-526e-8f97-949b47f696e6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"35c07aac-e3b9-4eff-8199-3e01b331ba75\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:58.612Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"345b8105-1483-49aa-b327-caa998ed2da0\",\"snapshot_url\":\"/api/observations/35c07aac-e3b9-4eff-8199-3e01b331ba75/snapshot\"}}"} +{"t": 1794162778.6208148, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a57a08a6-63d8-5e97-8164-a0e05b9dea4d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9de78f60-fa0a-4b54-8b44-7d1a5758df5c\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:58.612Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"345b8105-1483-49aa-b327-caa998ed2da0\",\"snapshot_url\":\"/api/observations/9de78f60-fa0a-4b54-8b44-7d1a5758df5c/snapshot\"}}"} +{"t": 1794162778.7252297, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"8581f795-aa64-5069-808e-7773db5d1504\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9de78f60-fa0a-4b54-8b44-7d1a5758df5c\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:58.612Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"345b8105-1483-49aa-b327-caa998ed2da0\",\"snapshot_url\":\"/api/observations/9de78f60-fa0a-4b54-8b44-7d1a5758df5c/snapshot\"}}"} +{"t": 1794162778.7373827, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"77cf4a09-0f5f-5441-aa3e-c354940730fe\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:58Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"35c07aac-e3b9-4eff-8199-3e01b331ba75\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:58.612Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"345b8105-1483-49aa-b327-caa998ed2da0\",\"snapshot_url\":\"/api/observations/35c07aac-e3b9-4eff-8199-3e01b331ba75/snapshot\"}}"} +{"t": 1794162779.899737, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"00c80297-ddff-5726-9f16-ae319db603b6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4d2269b8-8e0e-4576-b608-02510e2b3385\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:59.892Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7a237be9-47cf-4141-a05d-fc507a85a0c6\"}}"} +{"t": 1794162779.8999245, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9f4b825b-4591-5787-b8e7-7829d209c1ab\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"41f7d290-62c8-4bb3-a7ad-2a003ed6a0a4\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:59.892Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7a237be9-47cf-4141-a05d-fc507a85a0c6\"}}"} +{"t": 1794162779.9004037, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5a743b3d-facc-525b-be4a-cee3e16eae2e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4d2269b8-8e0e-4576-b608-02510e2b3385\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:59.892Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a237be9-47cf-4141-a05d-fc507a85a0c6\",\"snapshot_url\":\"/api/observations/4d2269b8-8e0e-4576-b608-02510e2b3385/snapshot\"}}"} +{"t": 1794162779.9007106, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5067501a-0ab0-5173-a0fc-0eb240eccbf8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:32:59Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"41f7d290-62c8-4bb3-a7ad-2a003ed6a0a4\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:59.892Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a237be9-47cf-4141-a05d-fc507a85a0c6\",\"snapshot_url\":\"/api/observations/41f7d290-62c8-4bb3-a7ad-2a003ed6a0a4/snapshot\"}}"} +{"t": 1794162780.0054204, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c22e7253-d601-52ca-998b-ca646d1a12c5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4d2269b8-8e0e-4576-b608-02510e2b3385\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:59.892Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a237be9-47cf-4141-a05d-fc507a85a0c6\",\"snapshot_url\":\"/api/observations/4d2269b8-8e0e-4576-b608-02510e2b3385/snapshot\"}}"} +{"t": 1794162780.0178335, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"ecf2a480-5b14-5a12-a060-ea409de72f0e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:00Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"41f7d290-62c8-4bb3-a7ad-2a003ed6a0a4\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:59.892Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7a237be9-47cf-4141-a05d-fc507a85a0c6\",\"snapshot_url\":\"/api/observations/41f7d290-62c8-4bb3-a7ad-2a003ed6a0a4/snapshot\"}}"} +{"t": 1794162781.179054, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"8d870a28-b876-55ab-b851-250d382bf9d7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"845514ef-23d3-41d3-b2a1-642b4cb52685\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:01.171Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9834ca4f-32c4-4ad5-957d-17278827372e\"}}"} +{"t": 1794162781.1792681, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5a879b87-1c7e-5f04-9c15-a3bf17c40faf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bbabd4e6-ef97-43dd-bca9-b371ed74af17\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:01.171Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"9834ca4f-32c4-4ad5-957d-17278827372e\"}}"} +{"t": 1794162781.1794517, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"6a31325e-b5e3-5c4c-9bba-5a558154beab\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"845514ef-23d3-41d3-b2a1-642b4cb52685\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:01.171Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9834ca4f-32c4-4ad5-957d-17278827372e\",\"snapshot_url\":\"/api/observations/845514ef-23d3-41d3-b2a1-642b4cb52685/snapshot\"}}"} +{"t": 1794162781.1796894, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"31db8a50-e116-5f9f-af8f-28d0526936ad\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bbabd4e6-ef97-43dd-bca9-b371ed74af17\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:01.171Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9834ca4f-32c4-4ad5-957d-17278827372e\",\"snapshot_url\":\"/api/observations/bbabd4e6-ef97-43dd-bca9-b371ed74af17/snapshot\"}}"} +{"t": 1794162781.2875874, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"0cd47853-b842-5921-9931-b09f94329de0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"bbabd4e6-ef97-43dd-bca9-b371ed74af17\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:01.171Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9834ca4f-32c4-4ad5-957d-17278827372e\",\"snapshot_url\":\"/api/observations/bbabd4e6-ef97-43dd-bca9-b371ed74af17/snapshot\"}}"} +{"t": 1794162781.2990563, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"95a2fc46-46d9-51c9-b5a4-b5836e823f53\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:01Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"845514ef-23d3-41d3-b2a1-642b4cb52685\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:01.171Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"9834ca4f-32c4-4ad5-957d-17278827372e\",\"snapshot_url\":\"/api/observations/845514ef-23d3-41d3-b2a1-642b4cb52685/snapshot\"}}"} +{"t": 1794162782.4599943, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"49e525b0-f5f8-5fbd-addd-916e49b2f615\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"94fe5f34-7dab-446b-8e67-a83fedaf80e4\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:02.451Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bb1d31c0-5803-4142-808c-9f604946d177\"}}"} +{"t": 1794162782.4601605, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"fd051615-1062-57b4-bd68-594ce7a0a99d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6bbcb832-cdd6-4f35-9e69-065887f541be\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:02.451Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"bb1d31c0-5803-4142-808c-9f604946d177\"}}"} +{"t": 1794162782.46036, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"4edd6431-262a-5e98-89da-73d40b09fbae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"94fe5f34-7dab-446b-8e67-a83fedaf80e4\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:02.451Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bb1d31c0-5803-4142-808c-9f604946d177\",\"snapshot_url\":\"/api/observations/94fe5f34-7dab-446b-8e67-a83fedaf80e4/snapshot\"}}"} +{"t": 1794162782.4606318, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"3c78836f-4719-56fb-9b01-f9faac8ce2df\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6bbcb832-cdd6-4f35-9e69-065887f541be\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:02.451Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bb1d31c0-5803-4142-808c-9f604946d177\",\"snapshot_url\":\"/api/observations/6bbcb832-cdd6-4f35-9e69-065887f541be/snapshot\"}}"} +{"t": 1794162782.553727, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"f291190c-da48-58dd-a00d-640ad1a5a230\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6bbcb832-cdd6-4f35-9e69-065887f541be\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:02.451Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bb1d31c0-5803-4142-808c-9f604946d177\",\"snapshot_url\":\"/api/observations/6bbcb832-cdd6-4f35-9e69-065887f541be/snapshot\"}}"} +{"t": 1794162782.5658386, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"8bdfe00b-3e98-59fb-9bcc-7b6c173630a0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:02Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"94fe5f34-7dab-446b-8e67-a83fedaf80e4\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:02.451Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"bb1d31c0-5803-4142-808c-9f604946d177\",\"snapshot_url\":\"/api/observations/94fe5f34-7dab-446b-8e67-a83fedaf80e4/snapshot\"}}"} +{"t": 1794162783.7398617, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"6d799dc7-4f48-5da5-92c9-ad90ea47773d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"44b279c5-cd4d-49b0-b56a-b428915e8b1c\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:03.731Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"55721882-9025-4fa9-a5bc-269b557ca2ef\"}}"} +{"t": 1794162783.7400727, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"376c8912-d662-5e55-b469-fdaf7f7b964b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2270c88b-9438-40d0-b084-07585c14548c\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:03.731Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"55721882-9025-4fa9-a5bc-269b557ca2ef\"}}"} +{"t": 1794162783.7402828, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9670d543-6a53-5b20-bd5a-16b5b12ffa5c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"44b279c5-cd4d-49b0-b56a-b428915e8b1c\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:03.731Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"55721882-9025-4fa9-a5bc-269b557ca2ef\",\"snapshot_url\":\"/api/observations/44b279c5-cd4d-49b0-b56a-b428915e8b1c/snapshot\"}}"} +{"t": 1794162783.7405338, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"2d0d834f-ae2d-5faf-b794-a4ff62510117\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2270c88b-9438-40d0-b084-07585c14548c\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:03.731Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"55721882-9025-4fa9-a5bc-269b557ca2ef\",\"snapshot_url\":\"/api/observations/2270c88b-9438-40d0-b084-07585c14548c/snapshot\"}}"} +{"t": 1794162783.8375928, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"e03b30a8-a2bd-5a0f-a6a7-9a03c9a38729\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"44b279c5-cd4d-49b0-b56a-b428915e8b1c\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:03.731Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"55721882-9025-4fa9-a5bc-269b557ca2ef\",\"snapshot_url\":\"/api/observations/44b279c5-cd4d-49b0-b56a-b428915e8b1c/snapshot\"}}"} +{"t": 1794162783.849845, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"bba73733-65c4-56b6-a38b-e2372c085753\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:03Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2270c88b-9438-40d0-b084-07585c14548c\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:03.731Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"55721882-9025-4fa9-a5bc-269b557ca2ef\",\"snapshot_url\":\"/api/observations/2270c88b-9438-40d0-b084-07585c14548c/snapshot\"}}"} +{"t": 1794162785.0186741, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c4ed280a-658e-569f-b44e-50d783e90f01\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2fff4904-14ac-4dea-a670-59042ef4760b\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:05.011Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"14021851-e857-4c5d-a412-d70c6757dd5a\"}}"} +{"t": 1794162785.019053, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5590441f-2456-529b-8b65-e86e39a5e134\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4f05de42-dfe4-49dc-bcf4-d7d01c79a82c\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:05.011Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"14021851-e857-4c5d-a412-d70c6757dd5a\"}}"} +{"t": 1794162785.0193143, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"d3553267-74ae-5a7d-abf0-d3a2229c5c81\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2fff4904-14ac-4dea-a670-59042ef4760b\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:05.011Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"14021851-e857-4c5d-a412-d70c6757dd5a\",\"snapshot_url\":\"/api/observations/2fff4904-14ac-4dea-a670-59042ef4760b/snapshot\"}}"} +{"t": 1794162785.0196116, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"6c3e6383-3e46-5aab-81fb-a26f79475a4e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4f05de42-dfe4-49dc-bcf4-d7d01c79a82c\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:05.011Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"14021851-e857-4c5d-a412-d70c6757dd5a\",\"snapshot_url\":\"/api/observations/4f05de42-dfe4-49dc-bcf4-d7d01c79a82c/snapshot\"}}"} +{"t": 1794162785.11366, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"8702d575-93ed-5668-963c-797046d32091\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"2fff4904-14ac-4dea-a670-59042ef4760b\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:05.011Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"14021851-e857-4c5d-a412-d70c6757dd5a\",\"snapshot_url\":\"/api/observations/2fff4904-14ac-4dea-a670-59042ef4760b/snapshot\"}}"} +{"t": 1794162785.1258545, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"fb9a289c-1115-57a2-a75e-917663a49d8c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:05Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"4f05de42-dfe4-49dc-bcf4-d7d01c79a82c\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:05.011Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"14021851-e857-4c5d-a412-d70c6757dd5a\",\"snapshot_url\":\"/api/observations/4f05de42-dfe4-49dc-bcf4-d7d01c79a82c/snapshot\"}}"} +{"t": 1794162786.2988052, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c8cfac2b-a74a-5610-90f6-22492b08c46f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"06142ab0-1a65-48c3-b33c-d31487eb2aee\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:06.292Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e5c87585-6cfc-47b3-a066-3a97dc4523ab\"}}"} +{"t": 1794162786.2990115, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"83615c29-998c-581f-aeec-155a4ac7166a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c34e3cbc-b1bb-402d-b0a2-c9a0f60658bd\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:06.292Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e5c87585-6cfc-47b3-a066-3a97dc4523ab\"}}"} +{"t": 1794162786.299408, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"26d4e013-4332-5fb5-828a-54b2ce190e7c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"06142ab0-1a65-48c3-b33c-d31487eb2aee\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:06.292Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e5c87585-6cfc-47b3-a066-3a97dc4523ab\",\"snapshot_url\":\"/api/observations/06142ab0-1a65-48c3-b33c-d31487eb2aee/snapshot\"}}"} +{"t": 1794162786.299734, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a5b6a421-60b0-52fe-95bd-4b7dcfa749c0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c34e3cbc-b1bb-402d-b0a2-c9a0f60658bd\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:06.292Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e5c87585-6cfc-47b3-a066-3a97dc4523ab\",\"snapshot_url\":\"/api/observations/c34e3cbc-b1bb-402d-b0a2-c9a0f60658bd/snapshot\"}}"} +{"t": 1794162786.4067047, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"7f02580b-0cdc-5f52-96db-9f576fedced1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c34e3cbc-b1bb-402d-b0a2-c9a0f60658bd\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:06.292Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e5c87585-6cfc-47b3-a066-3a97dc4523ab\",\"snapshot_url\":\"/api/observations/c34e3cbc-b1bb-402d-b0a2-c9a0f60658bd/snapshot\"}}"} +{"t": 1794162786.4193769, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"281017f0-3ee8-5569-9e32-cddef98ee456\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:06Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"06142ab0-1a65-48c3-b33c-d31487eb2aee\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:06.292Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e5c87585-6cfc-47b3-a066-3a97dc4523ab\",\"snapshot_url\":\"/api/observations/06142ab0-1a65-48c3-b33c-d31487eb2aee/snapshot\"}}"} +{"t": 1794162805.2924936, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"0e2f73c9-a5da-513a-bdc3-581d97d6e2ef\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5b2991d6-bc7b-4561-b288-0e6bde091078\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:54.641Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2b36d640-00c5-4534-a695-beff41101f3c\",\"snapshot_url\":\"/api/observations/5b2991d6-bc7b-4561-b288-0e6bde091078/snapshot\"}}"} +{"t": 1794162805.3061614, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"fe8169a5-aefa-5370-82e7-8a7452af0fbc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:25Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"c5b31584-be34-46bf-b03b-1b4f0e889a59\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:32:54.641Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"2b36d640-00c5-4534-a695-beff41101f3c\",\"snapshot_url\":\"/api/observations/c5b31584-be34-46bf-b03b-1b4f0e889a59/snapshot\"}}"} +{"t": 1794162806.7814362, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"3924f85f-5f4d-51c9-b63d-4488eeea0ce1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9f8caa52-7d55-4dc9-a12a-17b27bc6b249\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:26.771Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"90397e4d-7c5c-40dd-814e-21123d97c89e\"}}"} +{"t": 1794162806.7817414, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"be501a3f-46fc-58c4-984e-14fac00c9c49\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f3024814-5a67-4055-81dd-cc269c3462a8\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:26.771Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"90397e4d-7c5c-40dd-814e-21123d97c89e\"}}"} +{"t": 1794162806.782124, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5494a614-d0b5-531a-9391-a723e2e91ce2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9f8caa52-7d55-4dc9-a12a-17b27bc6b249\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:26.771Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"90397e4d-7c5c-40dd-814e-21123d97c89e\",\"snapshot_url\":\"/api/observations/9f8caa52-7d55-4dc9-a12a-17b27bc6b249/snapshot\"}}"} +{"t": 1794162806.7824664, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"d6b9a7be-e524-5543-ac2d-ee87b65e67b6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f3024814-5a67-4055-81dd-cc269c3462a8\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:26.771Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"90397e4d-7c5c-40dd-814e-21123d97c89e\",\"snapshot_url\":\"/api/observations/f3024814-5a67-4055-81dd-cc269c3462a8/snapshot\"}}"} +{"t": 1794162806.8805969, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"77eb7b25-5656-5061-b36d-c08868cc3fb3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"9f8caa52-7d55-4dc9-a12a-17b27bc6b249\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:26.771Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"90397e4d-7c5c-40dd-814e-21123d97c89e\",\"snapshot_url\":\"/api/observations/9f8caa52-7d55-4dc9-a12a-17b27bc6b249/snapshot\"}}"} +{"t": 1794162806.8923664, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"32c9ec17-a83a-54a3-a4ef-f347f11f3ad2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:26Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f3024814-5a67-4055-81dd-cc269c3462a8\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:26.771Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"90397e4d-7c5c-40dd-814e-21123d97c89e\",\"snapshot_url\":\"/api/observations/f3024814-5a67-4055-81dd-cc269c3462a8/snapshot\"}}"} +{"t": 1794162808.0593529, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"bc974964-0a3f-5d63-b140-24c908eb1441\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f0c5fd0f-1b15-4261-971a-c8388a9c9fea\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:28.052Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"dd704579-1384-477b-b93b-1052180719ef\"}}"} +{"t": 1794162808.0596447, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"65c1b7e9-19e4-525e-a828-be19e11a4709\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5cce22b3-b135-43b5-b732-7de4779478f6\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:28.052Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"dd704579-1384-477b-b93b-1052180719ef\"}}"} +{"t": 1794162808.0597558, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"4611ac10-4d3b-5435-82f5-1ad60130b40c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f0c5fd0f-1b15-4261-971a-c8388a9c9fea\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:28.052Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"dd704579-1384-477b-b93b-1052180719ef\",\"snapshot_url\":\"/api/observations/f0c5fd0f-1b15-4261-971a-c8388a9c9fea/snapshot\"}}"} +{"t": 1794162808.0602076, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9da53577-248d-5927-ba4c-cda8475df840\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5cce22b3-b135-43b5-b732-7de4779478f6\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:28.052Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"dd704579-1384-477b-b93b-1052180719ef\",\"snapshot_url\":\"/api/observations/5cce22b3-b135-43b5-b732-7de4779478f6/snapshot\"}}"} +{"t": 1794162808.163802, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"24ffd972-adc7-5250-929b-da2a701ba88f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5cce22b3-b135-43b5-b732-7de4779478f6\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:28.052Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"dd704579-1384-477b-b93b-1052180719ef\",\"snapshot_url\":\"/api/observations/5cce22b3-b135-43b5-b732-7de4779478f6/snapshot\"}}"} +{"t": 1794162808.1774218, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"e0ce30d7-8e29-55fa-af40-7544c7ff7c98\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f0c5fd0f-1b15-4261-971a-c8388a9c9fea\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:28.052Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"dd704579-1384-477b-b93b-1052180719ef\",\"snapshot_url\":\"/api/observations/f0c5fd0f-1b15-4261-971a-c8388a9c9fea/snapshot\"}}"} +{"t": 1794162809.339667, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"6d748578-70e4-5340-b40d-17bc1c0781b0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"08f91ea3-5f60-4b9f-b53e-6e2d5a44add9\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:29.331Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"015dd469-1bd0-48ae-bc0b-61b56d2f3b5e\"}}"} +{"t": 1794162809.3397858, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a89d2982-b882-5564-823a-a8f7e952cafd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"babc9984-8428-4d17-b8fd-7f8f8fcaab93\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:29.331Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"015dd469-1bd0-48ae-bc0b-61b56d2f3b5e\"}}"} +{"t": 1794162809.3400762, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"1260de22-3a78-5d3f-85a0-41462feadab2\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"08f91ea3-5f60-4b9f-b53e-6e2d5a44add9\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:29.331Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"015dd469-1bd0-48ae-bc0b-61b56d2f3b5e\",\"snapshot_url\":\"/api/observations/08f91ea3-5f60-4b9f-b53e-6e2d5a44add9/snapshot\"}}"} +{"t": 1794162809.3403296, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9caea140-df16-5906-892c-8322787ef553\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"babc9984-8428-4d17-b8fd-7f8f8fcaab93\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:29.331Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"015dd469-1bd0-48ae-bc0b-61b56d2f3b5e\",\"snapshot_url\":\"/api/observations/babc9984-8428-4d17-b8fd-7f8f8fcaab93/snapshot\"}}"} +{"t": 1794162810.620952, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"624949a4-b2d7-5428-be46-1934e713cdd3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f2fcf16d-f54a-4230-95ae-79e5d403b80f\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:30.611Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"02cd9d84-22e7-46df-9faa-6f417b940b0b\"}}"} +{"t": 1794162810.621084, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"e59f294b-7af9-534d-80f7-4f1ed7377616\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5fd8d324-a628-4f35-971b-03a914e308d7\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:30.611Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"02cd9d84-22e7-46df-9faa-6f417b940b0b\"}}"} +{"t": 1794162810.6213353, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"1b12f889-db65-51a7-8e64-3abe21674f24\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f2fcf16d-f54a-4230-95ae-79e5d403b80f\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:30.611Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"02cd9d84-22e7-46df-9faa-6f417b940b0b\",\"snapshot_url\":\"/api/observations/f2fcf16d-f54a-4230-95ae-79e5d403b80f/snapshot\"}}"} +{"t": 1794162810.6216302, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a8057554-0b7c-5741-bcb0-d61fd9d45c52\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5fd8d324-a628-4f35-971b-03a914e308d7\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:30.611Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"02cd9d84-22e7-46df-9faa-6f417b940b0b\",\"snapshot_url\":\"/api/observations/5fd8d324-a628-4f35-971b-03a914e308d7/snapshot\"}}"} +{"t": 1794162811.9012825, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"f5fd7429-e377-5d30-a47b-b03cdd632cdf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e74d82d5-9270-45fd-aa6e-4f8ea10e5733\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:31.892Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e082a603-b87e-47af-869c-67472b94492e\"}}"} +{"t": 1794162811.9013484, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"65113c31-7b9b-5be7-9f31-0e42a9aa92b5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7b398aed-8dc3-4904-81c0-f8567434b499\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:31.892Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e082a603-b87e-47af-869c-67472b94492e\"}}"} +{"t": 1794162811.90157, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"e72e79d4-2d67-57f0-be26-20d571b48657\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e74d82d5-9270-45fd-aa6e-4f8ea10e5733\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:31.892Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e082a603-b87e-47af-869c-67472b94492e\",\"snapshot_url\":\"/api/observations/e74d82d5-9270-45fd-aa6e-4f8ea10e5733/snapshot\"}}"} +{"t": 1794162811.9019132, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a284e271-c891-5a94-89a2-214558bf9182\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:31Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7b398aed-8dc3-4904-81c0-f8567434b499\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:31.892Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e082a603-b87e-47af-869c-67472b94492e\",\"snapshot_url\":\"/api/observations/7b398aed-8dc3-4904-81c0-f8567434b499/snapshot\"}}"} +{"t": 1794162813.1799278, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"6facd02a-c3c8-5988-868f-e22a6a669479\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7e779d13-5a0d-4eb9-b5b2-3c49a5357018\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:33.171Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"25421bcc-83d4-42f9-84c8-6a07c592e786\"}}"} +{"t": 1794162813.1801171, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9638dd61-b30a-54ca-8411-fa2ff6c86d97\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b6c39c84-bb0b-4bed-a0b4-ff1a5166fafa\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:33.171Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"25421bcc-83d4-42f9-84c8-6a07c592e786\"}}"} +{"t": 1794162813.1807036, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"2a66e7b5-843e-535f-a27e-1c5c12a8d02c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7e779d13-5a0d-4eb9-b5b2-3c49a5357018\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:33.171Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"25421bcc-83d4-42f9-84c8-6a07c592e786\",\"snapshot_url\":\"/api/observations/7e779d13-5a0d-4eb9-b5b2-3c49a5357018/snapshot\"}}"} +{"t": 1794162813.1810012, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"bf85dbf9-eb0a-5d15-a34f-b5e8570577ae\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b6c39c84-bb0b-4bed-a0b4-ff1a5166fafa\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:33.171Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"25421bcc-83d4-42f9-84c8-6a07c592e786\",\"snapshot_url\":\"/api/observations/b6c39c84-bb0b-4bed-a0b4-ff1a5166fafa/snapshot\"}}"} +{"t": 1794162814.459923, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"31c5e27a-9658-531b-ae86-885d9832647e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fa50adbf-c279-47be-889a-af02e4271835\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:34.451Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"abf0f304-1a68-40ab-b04d-0b5c789bcf62\"}}"} +{"t": 1794162814.4601326, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"df45b39e-abed-5583-825c-2d0b469b3894\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7db4aaeb-3b55-4870-88e0-267e0bb04c33\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:34.451Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"abf0f304-1a68-40ab-b04d-0b5c789bcf62\"}}"} +{"t": 1794162814.4603536, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c52e365b-de4d-5f72-a3cd-dda6a24043fd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fa50adbf-c279-47be-889a-af02e4271835\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:34.451Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"abf0f304-1a68-40ab-b04d-0b5c789bcf62\",\"snapshot_url\":\"/api/observations/fa50adbf-c279-47be-889a-af02e4271835/snapshot\"}}"} +{"t": 1794162814.46065, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"fa8d2d79-ab90-562e-869e-0b8f2092f2a7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:34Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7db4aaeb-3b55-4870-88e0-267e0bb04c33\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:34.451Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"abf0f304-1a68-40ab-b04d-0b5c789bcf62\",\"snapshot_url\":\"/api/observations/7db4aaeb-3b55-4870-88e0-267e0bb04c33/snapshot\"}}"} +{"t": 1794162815.7405374, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"3a3c5bd7-befd-5ee7-abbd-afc7025dfa6b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2b6839a-3cbc-4894-8850-a1400db03d64\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:35.731Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91aeac58-1f55-4336-b0b7-30b045ab1c88\"}}"} +{"t": 1794162815.7407296, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9313e6d-2085-5931-88da-42b24e0d0eaa\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a6e4ad1-9386-4cfa-871d-c1a6916e3774\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:35.731Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"91aeac58-1f55-4336-b0b7-30b045ab1c88\"}}"} +{"t": 1794162815.741284, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"9a6229b7-270d-5a0d-94e9-d550a62eb945\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2b6839a-3cbc-4894-8850-a1400db03d64\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:35.731Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91aeac58-1f55-4336-b0b7-30b045ab1c88\",\"snapshot_url\":\"/api/observations/e2b6839a-3cbc-4894-8850-a1400db03d64/snapshot\"}}"} +{"t": 1794162815.7415457, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"cb85cd51-f2aa-5e16-bf3e-18b55c53b04d\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:35Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a6e4ad1-9386-4cfa-871d-c1a6916e3774\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:35.731Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91aeac58-1f55-4336-b0b7-30b045ab1c88\",\"snapshot_url\":\"/api/observations/8a6e4ad1-9386-4cfa-871d-c1a6916e3774/snapshot\"}}"} +{"t": 1794162817.0191643, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"efe87b35-4279-5b18-96b0-309561c8809c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"25691268-d4bc-42fd-b98d-64b363bec804\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:37.011Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7caad9bc-e679-4159-9fbd-545f2b59164a\"}}"} +{"t": 1794162817.0193822, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c35a7a12-462c-50ed-ab0e-7996d74c5e42\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ecc06721-31ae-4efa-b6e4-b0535b902d39\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:37.011Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"7caad9bc-e679-4159-9fbd-545f2b59164a\"}}"} +{"t": 1794162817.0196342, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"cd03660d-098d-5cc1-858a-7e5e28270290\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"25691268-d4bc-42fd-b98d-64b363bec804\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:37.011Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7caad9bc-e679-4159-9fbd-545f2b59164a\",\"snapshot_url\":\"/api/observations/25691268-d4bc-42fd-b98d-64b363bec804/snapshot\"}}"} +{"t": 1794162817.0199099, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"b13c72b2-959a-5b99-88ac-9b3eafd1a08b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ecc06721-31ae-4efa-b6e4-b0535b902d39\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:37.011Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7caad9bc-e679-4159-9fbd-545f2b59164a\",\"snapshot_url\":\"/api/observations/ecc06721-31ae-4efa-b6e4-b0535b902d39/snapshot\"}}"} +{"t": 1794162818.2997715, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"75d361c9-eb66-59cc-8eec-264c3d754abc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6ecd2116-2e9d-4da2-9d9b-ba6c8d8674b4\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:38.291Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4784bb7f-0763-4777-99c0-71c0611b31ae\"}}"} +{"t": 1794162818.299965, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c53c42bd-b165-56bc-9ce7-c4f3018ff167\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"caea003a-d24a-41ea-8b45-06eef50fa191\",\"revision\":1,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:38.291Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"4784bb7f-0763-4777-99c0-71c0611b31ae\"}}"} +{"t": 1794162818.300186, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"f09c6c08-265f-52e0-b9f1-9fad8c7b98b8\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6ecd2116-2e9d-4da2-9d9b-ba6c8d8674b4\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:38.291Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4784bb7f-0763-4777-99c0-71c0611b31ae\",\"snapshot_url\":\"/api/observations/6ecd2116-2e9d-4da2-9d9b-ba6c8d8674b4/snapshot\"}}"} +{"t": 1794162818.3004048, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"ab9159d4-19b7-5b6f-87c5-966bea225630\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:38Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"caea003a-d24a-41ea-8b45-06eef50fa191\",\"revision\":2,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:38.291Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4784bb7f-0763-4777-99c0-71c0611b31ae\",\"snapshot_url\":\"/api/observations/caea003a-d24a-41ea-8b45-06eef50fa191/snapshot\"}}"} +{"t": 1794162821.4925778, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"5373859b-6faf-5a46-809d-2b403eeeb8c7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"08f91ea3-5f60-4b9f-b53e-6e2d5a44add9\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:29.331Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"015dd469-1bd0-48ae-bc0b-61b56d2f3b5e\",\"snapshot_url\":\"/api/observations/08f91ea3-5f60-4b9f-b53e-6e2d5a44add9/snapshot\"}}"} +{"t": 1794162821.514536, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"f283b321-540f-5ef1-b234-c6689129cbcc\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"babc9984-8428-4d17-b8fd-7f8f8fcaab93\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:29.331Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"015dd469-1bd0-48ae-bc0b-61b56d2f3b5e\",\"snapshot_url\":\"/api/observations/babc9984-8428-4d17-b8fd-7f8f8fcaab93/snapshot\"}}"} +{"t": 1794162821.5272079, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"d533517e-4539-591d-855b-08d312fffb13\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"5fd8d324-a628-4f35-971b-03a914e308d7\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:30.611Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"02cd9d84-22e7-46df-9faa-6f417b940b0b\",\"snapshot_url\":\"/api/observations/5fd8d324-a628-4f35-971b-03a914e308d7/snapshot\"}}"} +{"t": 1794162821.5387232, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"fc85c49f-2b74-5f2b-bed0-8dfc02f402ad\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"f2fcf16d-f54a-4230-95ae-79e5d403b80f\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:30.611Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"02cd9d84-22e7-46df-9faa-6f417b940b0b\",\"snapshot_url\":\"/api/observations/f2fcf16d-f54a-4230-95ae-79e5d403b80f/snapshot\"}}"} +{"t": 1794162821.5820723, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"da7e45c6-ad12-5722-81e9-75012c194034\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e74d82d5-9270-45fd-aa6e-4f8ea10e5733\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:31.892Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e082a603-b87e-47af-869c-67472b94492e\",\"snapshot_url\":\"/api/observations/e74d82d5-9270-45fd-aa6e-4f8ea10e5733/snapshot\"}}"} +{"t": 1794162821.5943289, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"02028d0f-b594-5a3f-928e-56b1cc7440ad\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7b398aed-8dc3-4904-81c0-f8567434b499\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:31.892Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e082a603-b87e-47af-869c-67472b94492e\",\"snapshot_url\":\"/api/observations/7b398aed-8dc3-4904-81c0-f8567434b499/snapshot\"}}"} +{"t": 1794162821.646985, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"d1012891-8762-548b-b75f-bb0b97624ca0\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7e779d13-5a0d-4eb9-b5b2-3c49a5357018\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:33.171Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"25421bcc-83d4-42f9-84c8-6a07c592e786\",\"snapshot_url\":\"/api/observations/7e779d13-5a0d-4eb9-b5b2-3c49a5357018/snapshot\"}}"} +{"t": 1794162821.6594696, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a5da570d-3d7d-5cc9-a406-96997f2aeef5\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b6c39c84-bb0b-4bed-a0b4-ff1a5166fafa\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:33.171Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"25421bcc-83d4-42f9-84c8-6a07c592e786\",\"snapshot_url\":\"/api/observations/b6c39c84-bb0b-4bed-a0b4-ff1a5166fafa/snapshot\"}}"} +{"t": 1794162821.709892, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c54ba290-3cf8-52bf-9dec-6a1f75ca62dd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"fa50adbf-c279-47be-889a-af02e4271835\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:34.451Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"abf0f304-1a68-40ab-b04d-0b5c789bcf62\",\"snapshot_url\":\"/api/observations/fa50adbf-c279-47be-889a-af02e4271835/snapshot\"}}"} +{"t": 1794162821.7227218, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"c45a6b67-7d37-50e2-b684-a7c02760cf4f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7db4aaeb-3b55-4870-88e0-267e0bb04c33\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:34.451Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"abf0f304-1a68-40ab-b04d-0b5c789bcf62\",\"snapshot_url\":\"/api/observations/7db4aaeb-3b55-4870-88e0-267e0bb04c33/snapshot\"}}"} +{"t": 1794162821.778479, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"916d1226-1a4a-549d-8d6a-50e40e86962c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"e2b6839a-3cbc-4894-8850-a1400db03d64\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:35.731Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91aeac58-1f55-4336-b0b7-30b045ab1c88\",\"snapshot_url\":\"/api/observations/e2b6839a-3cbc-4894-8850-a1400db03d64/snapshot\"}}"} +{"t": 1794162821.7898512, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"23e27420-c38a-5aee-93aa-4337bdb020da\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8a6e4ad1-9386-4cfa-871d-c1a6916e3774\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:35.731Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"91aeac58-1f55-4336-b0b7-30b045ab1c88\",\"snapshot_url\":\"/api/observations/8a6e4ad1-9386-4cfa-871d-c1a6916e3774/snapshot\"}}"} +{"t": 1794162821.8414705, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"ff3cb489-1b36-5956-9c62-1573fd80faa7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"25691268-d4bc-42fd-b98d-64b363bec804\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:37.011Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7caad9bc-e679-4159-9fbd-545f2b59164a\",\"snapshot_url\":\"/api/observations/25691268-d4bc-42fd-b98d-64b363bec804/snapshot\"}}"} +{"t": 1794162821.85461, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"dbe620ea-1400-5dec-b2fa-b86b264fd6c1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"ecc06721-31ae-4efa-b6e4-b0535b902d39\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:37.011Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"7caad9bc-e679-4159-9fbd-545f2b59164a\",\"snapshot_url\":\"/api/observations/ecc06721-31ae-4efa-b6e4-b0535b902d39/snapshot\"}}"} +{"t": 1794162821.8975656, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"a85cbf7f-7531-55e3-8e4c-d9afe93d89e7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"6ecd2116-2e9d-4da2-9d9b-ba6c8d8674b4\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:38.291Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4784bb7f-0763-4777-99c0-71c0611b31ae\",\"snapshot_url\":\"/api/observations/6ecd2116-2e9d-4da2-9d9b-ba6c8d8674b4/snapshot\"}}"} +{"t": 1794162821.9089592, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/1755d8d0-c2a6-4a5b-9132-55168a387c08", "payload": "{\"specversion\":\"1.0\",\"id\":\"f53c289d-2e7f-5367-a5fe-ed64d41a13d7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"time\":\"2026-11-08T18:33:41Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"caea003a-d24a-41ea-8b45-06eef50fa191\",\"revision\":3,\"camera_uuid\":\"1755d8d0-c2a6-4a5b-9132-55168a387c08\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:38.291Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"4784bb7f-0763-4777-99c0-71c0611b31ae\",\"snapshot_url\":\"/api/observations/caea003a-d24a-41ea-8b45-06eef50fa191/snapshot\"}}"} +{"t": 1794162823.4219031, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"00d84569-36c7-53fd-a6bd-bbb655295f07\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:33:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b1cb0d2b-e554-400e-8ff1-0a03d922ee46\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:43.413Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"8f3b7d8a-db42-47a2-bffe-1329d8d55f5b\"}}"} +{"t": 1794162823.422258, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"143dd595-4b8f-5118-9822-848fadd0e31a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:33:43Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b1cb0d2b-e554-400e-8ff1-0a03d922ee46\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:43.413Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f3b7d8a-db42-47a2-bffe-1329d8d55f5b\",\"snapshot_url\":\"/api/observations/b1cb0d2b-e554-400e-8ff1-0a03d922ee46/snapshot\"}}"} +{"t": 1794162824.6998694, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"b05d7597-b106-563c-a3d4-b290d4bc494a\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:33:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d6837ba6-ce67-4f3e-bbb4-360b4e4c3c50\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:44.693Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"59aa296d-74ad-4b71-868c-c67a9e7e2af4\"}}"} +{"t": 1794162824.7001007, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"b01dc5aa-4195-5a59-b632-73ff92300c83\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:33:44Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d6837ba6-ce67-4f3e-bbb4-360b4e4c3c50\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:44.693Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"59aa296d-74ad-4b71-868c-c67a9e7e2af4\",\"snapshot_url\":\"/api/observations/d6837ba6-ce67-4f3e-bbb4-360b4e4c3c50/snapshot\"}}"} +{"t": 1794162868.4835272, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"a9e3926f-2b16-5d9e-954a-fcd21f5bf30e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b1cb0d2b-e554-400e-8ff1-0a03d922ee46\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:43.413Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"8f3b7d8a-db42-47a2-bffe-1329d8d55f5b\",\"snapshot_url\":\"/api/observations/b1cb0d2b-e554-400e-8ff1-0a03d922ee46/snapshot\"}}"} +{"t": 1794162868.4975748, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"ae764cdd-1490-53c9-ad93-472b36ac7938\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:28Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"d6837ba6-ce67-4f3e-bbb4-360b4e4c3c50\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:33:44.693Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"59aa296d-74ad-4b71-868c-c67a9e7e2af4\",\"snapshot_url\":\"/api/observations/d6837ba6-ce67-4f3e-bbb4-360b4e4c3c50/snapshot\"}}"} +{"t": 1794162869.6621413, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"a8469d25-a239-5251-9fce-9af27ccff575\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8d26e617-9379-4834-b538-3bbcbf8ae4dd\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:29.654Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"68fec615-7ecc-43f5-8487-bb3d62127606\"}}"} +{"t": 1794162869.6624634, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"f10ee557-1e0d-5ca9-8f5c-b8265cee2a82\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:29Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8d26e617-9379-4834-b538-3bbcbf8ae4dd\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:29.654Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"68fec615-7ecc-43f5-8487-bb3d62127606\",\"snapshot_url\":\"/api/observations/8d26e617-9379-4834-b538-3bbcbf8ae4dd/snapshot\"}}"} +{"t": 1794162870.9407823, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"92818516-06e5-57c7-b3f7-7f2d09278ebd\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3ef8e39b-e08a-4a4e-a7b3-f6d99bf907ef\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:30.934Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"3a96affc-c346-4e64-914e-ed3a1f74328a\"}}"} +{"t": 1794162870.941128, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"a1ee8ae0-3a39-53b3-bfd8-880704414247\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:30Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3ef8e39b-e08a-4a4e-a7b3-f6d99bf907ef\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:30.934Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"3a96affc-c346-4e64-914e-ed3a1f74328a\",\"snapshot_url\":\"/api/observations/3ef8e39b-e08a-4a4e-a7b3-f6d99bf907ef/snapshot\"}}"} +{"t": 1794162872.2212787, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"27f088aa-6745-5a5a-81b2-5c1fc8c321bf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f586db1-3045-42ab-881a-67410d93347a\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:32.214Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"99f1d0f3-401b-4acb-a148-98d24f5bb413\"}}"} +{"t": 1794162872.2215133, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"666b3815-0144-58ae-a30f-7226c2a1dd88\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:32Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f586db1-3045-42ab-881a-67410d93347a\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:32.214Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"99f1d0f3-401b-4acb-a148-98d24f5bb413\",\"snapshot_url\":\"/api/observations/7f586db1-3045-42ab-881a-67410d93347a/snapshot\"}}"} +{"t": 1794162873.5008328, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"c2b24c17-113a-599f-8bb8-3498ebbf9be1\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a88ae5eb-d50d-4c8a-a958-0c91ed44ddab\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:33.494Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"e9d94eed-ff08-4aa7-9c81-b3dd26eefacd\"}}"} +{"t": 1794162873.501142, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"4898e40a-1adc-5df5-8e34-828069ba71d3\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:33Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a88ae5eb-d50d-4c8a-a958-0c91ed44ddab\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:33.494Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"e9d94eed-ff08-4aa7-9c81-b3dd26eefacd\",\"snapshot_url\":\"/api/observations/a88ae5eb-d50d-4c8a-a958-0c91ed44ddab/snapshot\"}}"} +{"t": 1794162877.9749844, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"da0f4928-334b-5f12-b858-a241b485594f\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"7f586db1-3045-42ab-881a-67410d93347a\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:32.214Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"unavailable\",\"evidence_reason\":\"write_failed\",\"asset_id\":\"99f1d0f3-401b-4acb-a148-98d24f5bb413\"}}"} +{"t": 1794162877.9757407, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"594607f3-21f0-573c-807d-a3bdd70914b6\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:34:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"a88ae5eb-d50d-4c8a-a958-0c91ed44ddab\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:34:33.494Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"unavailable\",\"evidence_reason\":\"write_failed\",\"asset_id\":\"e9d94eed-ff08-4aa7-9c81-b3dd26eefacd\"}}"} +{"t": 1794162937.9931586, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"f79cece6-539a-5d45-b676-2da326f6159e\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"8d26e617-9379-4834-b538-3bbcbf8ae4dd\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-10-31T18:34:29.654Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"expired\",\"evidence_reason\":\"retention_expired\",\"asset_id\":\"68fec615-7ecc-43f5-8487-bb3d62127606\"}}"} +{"t": 1794162937.99348, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"0cf2f0ac-2f9e-5799-835a-f8a848438056\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:37Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"3ef8e39b-e08a-4a4e-a7b3-f6d99bf907ef\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-10-31T18:34:30.934Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"expired\",\"evidence_reason\":\"retention_expired\",\"asset_id\":\"3a96affc-c346-4e64-914e-ed3a1f74328a\"}}"} +{"t": 1794162951.106679, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"28419948-2f65-5534-b7e0-f93d351c18ee\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"969ff547-56c9-4969-8c0e-7129c17ee88e\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:35:51.097Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"53b9c16c-bfbc-4923-851a-7805b56016ee\"}}"} +{"t": 1794162951.1069217, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"e0c7fcba-fa0d-5b34-8b34-1fa0d16b21cf\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"969ff547-56c9-4969-8c0e-7129c17ee88e\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:35:51.097Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"53b9c16c-bfbc-4923-851a-7805b56016ee\",\"snapshot_url\":\"/api/observations/969ff547-56c9-4969-8c0e-7129c17ee88e/snapshot\"}}"} +{"t": 1794162951.6912677, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"dab3abd4-6dfb-50dd-801d-e245438ec8e7\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:51Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"969ff547-56c9-4969-8c0e-7129c17ee88e\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:35:51.097Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"53b9c16c-bfbc-4923-851a-7805b56016ee\",\"snapshot_url\":\"/api/observations/969ff547-56c9-4969-8c0e-7129c17ee88e/snapshot\"}}"} +{"t": 1794162952.384278, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"03ff02a5-8648-51bb-8015-5b2ec77aa98c\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b25a2711-ab44-4be8-8bdf-581404ad8bda\",\"revision\":1,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:35:52.377Z\",\"change_cause\":\"created\",\"evidence_state\":\"pending\",\"evidence_reason\":null,\"asset_id\":\"533def3f-b909-4743-982a-96433e5ac66d\"}}"} +{"t": 1794162952.3846457, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"b4763e95-6ed5-5505-8159-f56beb0ac591\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b25a2711-ab44-4be8-8bdf-581404ad8bda\",\"revision\":2,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:35:52.377Z\",\"change_cause\":\"evidence\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"533def3f-b909-4743-982a-96433e5ac66d\",\"snapshot_url\":\"/api/observations/b25a2711-ab44-4be8-8bdf-581404ad8bda/snapshot\"}}"} +{"t": 1794162952.462984, "topic": "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/542dce9e-dfdd-4f05-a451-6e21c427bd03", "payload": "{\"specversion\":\"1.0\",\"id\":\"4ddc5b9a-c227-5366-afbf-e76f3e68e36b\",\"type\":\"io.lightnvr.detection.observation_changed.v1\",\"source\":\"urn:lightnvr:277b5716-7899-4a09-b7f8-0885e9629983\",\"subject\":\"camera/542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"time\":\"2026-11-08T18:35:52Z\",\"datacontenttype\":\"application/json\",\"severity\":\"info\",\"sensitivity\":\"operational\",\"data\":{\"observation_id\":\"b25a2711-ab44-4be8-8bdf-581404ad8bda\",\"revision\":3,\"camera_uuid\":\"542dce9e-dfdd-4f05-a451-6e21c427bd03\",\"label\":\"bird\",\"observed_at\":\"2026-11-08T18:35:52.377Z\",\"change_cause\":\"classification\",\"evidence_state\":\"ready\",\"evidence_reason\":null,\"asset_id\":\"533def3f-b909-4743-982a-96433e5ac66d\",\"snapshot_url\":\"/api/observations/b25a2711-ab44-4be8-8bdf-581404ad8bda/snapshot\"}}"} diff --git a/tests/integration/yawamf_e2e/run_e2e.py b/tests/integration/yawamf_e2e/run_e2e.py new file mode 100644 index 000000000..7a34453c9 --- /dev/null +++ b/tests/integration/yawamf_e2e/run_e2e.py @@ -0,0 +1,818 @@ +#!/usr/bin/env python3 +"""INT_01 X1/X3: lightNVR <-> YA-WAMF end-to-end suite (host-process mode). + +Runs docs/integrations/YA_WAMF.md verbatim against real processes: +Mosquitto 2.x, the lightNVR binary under test, the YA-WAMF backend from its +branch (pinned colour classifier, see harness/yawamf_launcher.py), an HTTP +MPEG-TS feeder source and a loopback HTTP detector. Then it runs A1-A6 and A8 +(X1, P1) and A12, A13 and A7-annotate (X3, P2 write-back; p2_scenarios.py) and +writes results/.json. + +Usage (from the lightNVR repo root; YA-WAMF's venv provides Pillow/numpy): + ../YetAnother-WhosAtMyFeeder/backend/.venv/bin/python tests/integration/yawamf_e2e/run_e2e.py \ + --lightnvr-bin build/bin/lightnvr --yawamf ../YetAnother-WhosAtMyFeeder + +Exit status 0 only when every selected scenario passed. +""" +from __future__ import annotations + +import argparse +import hashlib +import io +import json +import os +import shutil +import subprocess +import sys +import tempfile +import threading +import time +import traceback +import urllib.parse +import uuid +from pathlib import Path +from typing import Any, Callable + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE / "harness")) + +from PIL import Image, ImageStat # noqa: E402 + +from components import ( # noqa: E402 + Detector, FeederServer, LightNvr, Mosquitto, MqttCapture, YaWamf, free_port, http, render_image_scene, render_scene, wait_for, +) +from p2_scenarios import P2Scenarios # noqa: E402 + +OBSERVATION_EVENT = "io.lightnvr.detection.observation_changed.v1" +MESSAGE_NS = uuid.UUID("0bc01005-a431-522b-9124-a114f67d187a") +RED, BLUE = "Prunella modularis", "Erithacus rubecula" +TOPIC_PREFIX = "lightnvr" +SCENE_SECONDS = 300 + +# camera name -> (scene, detector key). Names may hold only letters, digits, ".", "-" and "_". +CAMERAS = { + "feeder-east": ("red_static", "a1"), + "feeder-moving": ("red_moving", "a2"), + "feeder-pair": ("two_birds", "a3"), + "feeder-bulk": ("two_birds", "a5"), + "feeder-kill": ("two_birds", "a6"), + "feeder-outcomes": ("red_static", "a8"), + "feeder-wb": ("red_static", "a12"), + "feeder-wb-outage": ("red_static", "a13"), +} + + +def message_id(source: str, observation: str, revision: int) -> str: + return str(uuid.uuid5(MESSAGE_NS, f"{source}|{observation}|{revision}")) + + +def colour_of(image: Image.Image) -> str: + r, g, b = ImageStat.Stat(image.convert("RGB")).mean + if r > g + 60 and r > b + 60: + return "red" + if b > r + 60 and b > g + 60: + return "blue" + return "background" + + +def crop_box(jpeg: bytes, box: dict) -> Image.Image: + image = Image.open(io.BytesIO(jpeg)).convert("RGB") + w, h = image.size + return image.crop((round(box["x"] * w), round(box["y"] * h), round((box["x"] + box["width"]) * w), round((box["y"] + box["height"]) * h))) + + +class Check(Exception): + pass + + +def expect(condition: bool, message: str) -> None: + if not condition: + raise Check(message) + + +class Suite(P2Scenarios): + def __init__(self, args: argparse.Namespace): + self.args = args + self.work = Path(args.workdir or tempfile.mkdtemp(prefix="x1-yawamf-e2e-")) + self.work.mkdir(parents=True, exist_ok=True) + self.results: list[dict] = [] + self.recipe: list[dict] = [] + self.cameras: dict[str, str] = {} # name -> uuid + self.source_id = "" + self.token = "" + + # ------------------------------------------------------------------ plumbing + + def note(self, step: str, outcome: str, detail: Any = None) -> None: + entry = {"step": step, "outcome": outcome} + if detail is not None: + entry["detail"] = detail + self.recipe.append(entry) + print(f"[recipe] {step}: {outcome}" + (f" {json.dumps(detail, default=str)[:300]}" if detail is not None else ""), flush=True) + + def observations(self, camera: str, principal: dict | None = None) -> list[dict]: + """Every retained observation of a camera, oldest first, by walking the cursor.""" + items: dict[str, dict] = {} + cursor = None + for _ in range(200): + query = {"camera_uuid": self.cameras[camera], "limit": "500"} + if cursor: + query["cursor"] = cursor + page = self.lnvr.api("GET", "/api/observations?" + urllib.parse.urlencode(query), headers=principal) + for item in page["items"]: + items[item["id"]] = item + cursor = page.get("next_cursor") or cursor + if not page.get("has_more"): + break + return list(items.values()) + + def ready(self, camera: str) -> list[dict]: + return [o for o in self.observations(camera) if o["evidence"]["state"] == "ready"] + + def settled(self, camera: str, count: int, timeout: float = 120) -> list[dict]: + """Wait until the camera has >= count observations and none is pending.""" + def done(): + items = self.observations(camera) + return items if len(items) >= count and all(o["evidence"]["state"] != "pending" for o in items) else None + return wait_for(done, timeout, 1.0, f"{count} settled observations on {camera}") + + def yawamf_rows(self, camera: str) -> list[dict]: + return [d for d in self.ya.detections() if d["camera_uuid"] == self.cameras[camera]] + + def wait_rows(self, camera: str, ids: set[str], timeout: float) -> list[dict]: + def done(): + rows = self.yawamf_rows(camera) + return rows if ids <= {r["observation_id"] for r in rows} else None + try: + return wait_for(done, timeout, 1.0, f"YA-WAMF detections for {len(ids)} observations on {camera}") + except TimeoutError: + have = {r["observation_id"] for r in self.yawamf_rows(camera)} + raise Check(f"YA-WAMF is missing {len(ids - have)} of {len(ids)} observations on {camera} after {timeout:.0f}s") + + def classifier_calls(self, camera: str) -> list[dict]: + return [c for c in self.ya.classifier_calls() if c.get("camera") == camera] + + def snapshot(self, observation_id: str, headers: dict | None = None) -> tuple[int, bytes, dict]: + status, response_headers, body = http("GET", f"{self.lnvr.base}/api/observations/{observation_id}/snapshot", headers=headers or self.token_headers) + return status, body, response_headers + + def recovery_status(self) -> dict: + return self.ya.api("GET", "/api/backfill/source-recovery") + + def wait_recovery_run(self, after: float, timeout: float = 120) -> dict: + def done(): + status = self.recovery_status() + run = status.get("last_run") or {} + return status if run.get("started_at", 0) >= after and run.get("finished_at") else None + return wait_for(done, timeout, 1.0, "a YA-WAMF recovery run") + + @property + def token_headers(self) -> dict: + return {"Authorization": f"Bearer {self.token}"} + + def scenario(self, sid: str, title: str, fn: Callable[[], dict]) -> None: + if self.args.scenarios and sid not in self.args.scenarios: + return + print(f"\n=== {sid}: {title}", flush=True) + started = time.time() + entry: dict[str, Any] = {"id": sid, "title": title, "started_at": started} + try: + entry["evidence"] = fn() + entry["result"] = "pass" + except Exception as error: # noqa: BLE001 - recorded per scenario + entry["result"] = "fail" + entry["error"] = f"{type(error).__name__}: {error}" + entry["traceback"] = traceback.format_exc() + print(entry["traceback"], flush=True) + entry["seconds"] = round(time.time() - started, 1) + print(f"=== {sid}: {entry['result'].upper()} in {entry['seconds']}s", flush=True) + self.results.append(entry) + + # ------------------------------------------------------------------ recipe + + def deploy(self) -> None: + a = self.args + mqtt_port, lnvr_port, ya_port = free_port(), free_port(), free_port() + scenes = {name: render_scene(self.work, name, SCENE_SECONDS) for name in {scene for scene, _ in CAMERAS.values()}} + self.local = bool(a.sod_model and a.bird_image) + if self.local: + scenes["bird_local"] = render_image_scene(self.work, "bird_local", Path(a.bird_image).resolve(), SCENE_SECONDS) + self.feeder = FeederServer(scenes, SCENE_SECONDS) + self.detector = Detector(self.work) + self.broker = Mosquitto(self.work, mqtt_port) + self.broker.start() + self.capture = MqttCapture(self.work, mqtt_port, f"{TOPIC_PREFIX}/v1/events/{OBSERVATION_EVENT}/#") + self.capture.start() + self.note("prereq: MQTT broker", "ok", {"broker": self.broker.version(), "port": mqtt_port}) + + lnvr_dir = self.work / "lightnvr" + lnvr_dir.mkdir(exist_ok=True) + self.lnvr = LightNvr(lnvr_dir, Path(a.lightnvr_bin).resolve(), Path(a.web_root).resolve(), lnvr_port, mqtt_port, TOPIC_PREFIX) + self.lnvr.start() + self.note("prereq: lightNVR with [mqtt] block (step 3.1)", "ok", {"version": self.lnvr.version()}) + + # Step 1 + 2: a bird-capable (HTTP) detector on each feeder stream, then evidence per camera. + for name, (scene, key) in CAMERAS.items(): + self.lnvr.api("POST", "/api/streams", {"name": name, "url": self.feeder.url(scene), "enabled": True, "streaming_enabled": False, + "record": False, "detection_based_recording": True, "detection_model": self.detector.url(key), + "detection_threshold": 50, "detection_interval": 1}) + # Step 2, verbatim body (URL-encoded name). + self.lnvr.api("PUT", f"/api/streams/{urllib.parse.quote(name)}", {"observation_evidence_enabled": True, "observation_labels": "bird", + "observation_max_age_days": 7, "observation_max_bytes": 2147483648}, + expect=(200, 202)) + # The PUT answers 202 (applied asynchronously); wait until every camera reports it. + def applied() -> bool: + for name in CAMERAS: + full = self.lnvr.api("GET", f"/api/streams/{urllib.parse.quote(name)}/full") + if not full.get("stream", full).get("observation_evidence_enabled"): + return False + return True + wait_for(applied, 30, 0.5, "evidence settings to apply") + thresholds = {row[0]: row[1] for row in self.lnvr.sql("SELECT name, detection_threshold FROM streams")} + expect(all(abs(thresholds[name] - 0.5) < 1e-6 for name in CAMERAS), f"detection_threshold is a percentage in the API: {thresholds}") + if self.local: + # A2 local variant: a real SOD model on a real bird photo, evidence off until the scenario. + model = Path(a.sod_model).resolve() + (lnvr_dir / "models" / model.name).symlink_to(model) + self.lnvr.api("POST", "/api/streams", {"name": "feeder-local", "url": self.feeder.url("bird_local"), "enabled": True, "streaming_enabled": False, + "record": False, "detection_based_recording": True, "detection_model": model.name, + "detection_threshold": 30, "detection_interval": 2}) + streams = self.lnvr.api("GET", "/api/streams") + streams = streams if isinstance(streams, list) else streams.get("streams", streams) + self.cameras = {s["name"]: s["camera_uuid"] for s in streams if s["name"] in CAMERAS or s["name"] == "feeder-local"} + expect(len(self.cameras) == len(CAMERAS) + int(self.local), f"camera_uuid missing from GET /api/streams: {self.cameras}") + full = self.lnvr.api("GET", f"/api/streams/{urllib.parse.quote('feeder-east')}/full") + stream = full.get("stream", full) + self.note("step 1-2: detector + evidence per camera", "ok", {"cameras": self.cameras, "observation_evidence_enabled": stream.get("observation_evidence_enabled"), + "observation_labels": stream.get("observation_labels")}) + + # Step 3.2: the observation route on mqtt:default, camera_uuid selector, no predicate. + route = self.lnvr.api("POST", "/api/event-routes", { + "name": "YA-WAMF observations", "destination": "mqtt:default", "event_types": [OBSERVATION_EVENT], + "camera_scope": {"type": "selector", "selector": {"version": 1, "expression": {"op": "camera_uuid", "values": list(self.cameras.values())}}}}) + self.note("step 3.2: event route", "ok", {"route": route.get("route", route).get("uuid") if isinstance(route, dict) else route}) + + # Step 4: user -> role -> policy -> token. + user = self.lnvr.api("POST", "/api/auth/users", {"username": "yawamf", "password": "X1-Yawamf-Integration-1!", "role": 2, "is_active": True}) + user_id = user.get("id") or user.get("user", {}).get("id") + roles = self.lnvr.api("GET", "/api/authorization/roles") + role_body = {"name": "YA-WAMF", "description": "YA-WAMF feeder integration", + "actions": ["live.view", "snapshot.create", "observations.annotate"]} + status, _h, body = http("POST", f"{self.lnvr.base}/api/authorization/roles", role_body, self.lnvr.admin) + if status not in (200, 201): + self.note("step 4.2: create role (recipe body)", "failed", {"status": status, "body": body.decode()[:300]}) + role_body["expected_policy_version"] = roles["policy_version"] + role = self.lnvr.api("POST", "/api/authorization/roles", role_body) + self.note("step 4.2: create role with expected_policy_version", "ok") + else: + role = json.loads(body) + self.note("step 4.2: create role (recipe body)", "ok") + role_uuid = role["role"]["uuid"] + self.role_uuid = role_uuid + scope = {"type": "selector", "selector": {"version": 1, "expression": {"op": "camera_uuid", "values": list(self.cameras.values())}}} + policy = self.lnvr.api("GET", f"/api/authorization/users/{user_id}") + grant = {"mode": "policy", "grants": [{"role_uuid": role_uuid, "scope": scope}]} + status, _h, body = http("PUT", f"{self.lnvr.base}/api/authorization/users/{user_id}", grant, self.lnvr.admin) + if status not in (200, 201): + self.note("step 4.3: grant policy (no expected_policy_version)", "failed", {"status": status, "body": body.decode()[:300]}) + grant["expected_policy_version"] = policy["policy_version"] + self.lnvr.api("PUT", f"/api/authorization/users/{user_id}", grant) + self.note("step 4.3: grant policy with expected_policy_version", "ok") + else: + self.note("step 4.3: grant policy", "ok") + issued = self.lnvr.api("POST", f"/api/authorization/users/{user_id}/tokens", { + "description": "YA-WAMF feeder integration", "expires_at": int(time.time()) + 300 * 86400, + "actions": ["live.view", "snapshot.create", "observations.annotate"], "scope": scope}) + self.token = issued["secret"] + self.user_id = user_id + caps = self.lnvr.api("GET", "/api/integrations/capabilities", headers=self.token_headers) + self.source_id = caps["source"]["id"] + self.capabilities = caps + self.note("step 4.4: scoped token", "ok", {"principal": caps.get("principal"), "delivery": caps.get("delivery"), + "cameras_evidence_enabled": {c.get("camera_uuid"): c.get("evidence_enabled") for c in caps.get("cameras", [])}}) + + # Step 5: YA-WAMF env. + ya_dir = self.work / "yawamf" + ya_dir.mkdir(exist_ok=True) + self.ya = YaWamf(ya_dir, Path(a.yawamf).resolve(), ya_port, { + "LIGHTNVR__ENABLED": "true", "LIGHTNVR__API_URL": self.lnvr.base, "LIGHTNVR__TOKEN": self.token, + "LIGHTNVR__MQTT_TOPIC_PREFIX": TOPIC_PREFIX, "LIGHTNVR__CAMERA_UUIDS": ",".join(self.cameras.values()), + "LIGHTNVR__REQUEST_TIMEOUT_SECONDS": "5", + # The recipe's optional P2 step (X3). --no-writeback runs the P1 recipe only. + "LIGHTNVR__WRITEBACK_ENABLED": "false" if a.no_writeback else "true", + "FRIGATE__MQTT_SERVER": "127.0.0.1", "FRIGATE__MQTT_PORT": str(mqtt_port), "FRIGATE__MQTT_AUTH": "false", + }, HERE / "harness" / "yawamf_launcher.py") + self.ya.start() + health = self.ya.api("GET", "/health") + self.note("step 5: YA-WAMF started (MQTT subscribed)", "ok", {"health_version": health.get("version"), + "classification_writeback": health.get("classification_writeback")}) + + # Step 6.1: the connection test. + # What the Settings page sends for the saved values: the address, and no token (keeps the saved one). + test = self.ya.api("POST", "/api/setup/lightnvr/test", {"api_url": self.lnvr.base, "camera_uuids": list(self.cameras.values())}) + self.connection_test = test + self.note("step 6.1: Test lightNVR connection", "ok" if test.get("ok") else "failed", test) + expect(bool(test.get("ok")), f"YA-WAMF connection test not ok: {json.dumps(test)[:800]}") + # Step 6.2: the setup probe by hand with the token. + status, _h, body = http("GET", f"{self.lnvr.base}/api/observations?camera_uuid={self.cameras['feeder-east']}&limit=1", headers=self.token_headers) + self.note("step 6.2: setup probe", "ok" if status == 200 else "failed", {"status": status, "body": json.loads(body)}) + status, _h, _b = http("GET", f"{self.lnvr.base}/api/observations?limit=1&camera_uuid=00000000-0000-4000-8000-00000000dead", headers=self.token_headers) + self.note("step 6.2: setup probe, camera outside the token scope", "ok" if status == 403 else f"unexpected {status}", {"status": status}) + status, _h, _b = http("GET", f"{self.lnvr.base}/api/system/info", headers=self.token_headers) + self.note("diagnostics: token cannot read /api/system/info", "ok" if status in (401, 403) else f"unexpected {status}", {"status": status}) + + # ------------------------------------------------------------------ scenarios + + def a1(self) -> dict: + camera, key = "feeder-east", "a1" + self.detector.arm(key, 1) + items = self.settled(camera, 1, 90) + expect(len(items) == 1, f"expected exactly one observation, got {len(items)}") + obs = items[0] + expect(obs["evidence"]["state"] == "ready", f"evidence {obs['evidence']}") + rows = self.wait_rows(camera, {obs["id"]}, 90) + row = rows[0] + expect(len(rows) == 1, f"{len(rows)} YA-WAMF rows") + expect(row["event"] == f"lnvr:{row['source_instance']}:{obs['id']}", f"event key {row['event']}") + expect(row["source_kind"] == "lightnvr" and row["camera_uuid"] == self.cameras[camera], f"identity {row}") + expect(self.source_id == f"urn:lightnvr:{row['source_instance']}", f"source {self.source_id} vs instance {row['source_instance']}") + expect(row["scientific_name"] == RED and abs(row["score"] - 0.93) < 1e-6, f"species {row}") + expect(obs["label"] == "bird" and obs["camera_uuid"] == self.cameras[camera], f"label {obs['label']}") + # One message ID per revision on the real broker: created(pending), then evidence(ready). + wait_for(lambda: {e["id"] for e in self.capture.envelopes()} >= {message_id(self.source_id, obs["id"], r) for r in (1, 2)}, 60, 1, "both revisions on the broker") + mine = [e for e in self.capture.envelopes() if e["data"]["observation_id"] == obs["id"]] + by_rev = {e["data"]["revision"]: e for e in mine} + expect(by_rev[1]["data"]["change_cause"] == "created" and by_rev[1]["data"]["evidence_state"] == "pending", f"rev1 {by_rev[1]['data']}") + expect(by_rev[2]["data"]["change_cause"] == "evidence" and by_rev[2]["data"]["evidence_state"] == "ready", f"rev2 {by_rev[2]['data']}") + calls = self.classifier_calls(camera) + expect(len(calls) == 1 and calls[0]["result"] == RED, f"classifier calls {calls}") + topic = next(t for t, p in self.capture.raw() if json.loads(p)["data"]["observation_id"] == obs["id"]) + self.a1_obs = obs + return {"observation": {k: obs[k] for k in ("id", "camera_uuid", "stream_name_at_capture", "label", "confidence", "box", "revision")}, + "evidence": obs["evidence"], "yawamf_row": row, "topic": topic, + "mqtt_revisions": {r: {"id": e["id"], "cause": e["data"]["change_cause"], "evidence_state": e["data"]["evidence_state"]} for r, e in sorted(by_rev.items())}, + "classifier_calls": calls} + + def a2(self) -> dict: + camera, key, frames = "feeder-moving", "a2", 3 + self.detector.arm(key, frames) + items = self.settled(camera, frames, 120) + expect(len(items) == frames, f"{len(items)} observations for {frames} armed frames") + reports = {r["sha256"]: r for r in self.detector.reports(key)} + evidence = [] + snapshots = {} + for obs in items: + status, jpeg, _h = self.snapshot(obs["id"]) + expect(status == 200, f"snapshot {status}") + sha = hashlib.sha256(jpeg).hexdigest() + expect(sha in reports, "stored evidence is not byte-equal to any image the detector received") + px = reports[sha]["birds"][0] + want = {"x": px["x_min"], "y": px["y_min"], "width": px["x_max"] - px["x_min"], "height": px["y_max"] - px["y_min"]} + for k in want: + expect(abs(obs["box"][k] - want[k]) < 1e-3, f"box {obs['box']} != detector box {want}") + expect(colour_of(crop_box(jpeg, obs["box"])) == "red", "box does not cover the bird in the stored image") + snapshots[obs["id"]] = jpeg + evidence.append({"id": obs["id"], "sha256": sha, "box": obs["box"], "bird_px": px["px"], "transform": obs["evidence"].get("transform")}) + # The bird moved between inference frames: each box misses the bird in every other frame, + # so a substituted later frame would have been caught above. + xs = [e["bird_px"][0] for e in evidence] + expect(max(xs) - min(xs) >= 64, f"fixture bird did not move between frames: {xs}") + crossed = [] + for e in evidence: + for other in evidence: + if other["id"] != e["id"] and abs(other["bird_px"][0] - e["bird_px"][0]) >= 64: + crossed.append(colour_of(crop_box(snapshots[other["id"]], e["box"]))) + expect(crossed and all(c != "red" for c in crossed), f"cross-frame crops {crossed}") + rows = self.wait_rows(camera, {o["id"] for o in items}, 90) + expect(all(r["scientific_name"] == RED for r in rows) and len(rows) == frames, f"YA-WAMF rows {rows}") + calls = self.classifier_calls(camera) + expect(len(calls) == frames and all(c["result"] == RED for c in calls), f"classifier calls {calls}") + return {"detector": "http (loopback x1-colour-detector)", "observations": evidence, "bird_x_px_per_frame": xs, + "cross_frame_crop_colours": crossed, "yawamf_species": [r["scientific_name"] for r in rows], "classifier_calls": calls, + "local_detector": "pending: no bird-capable local model drawn on this fixture (see README)"} + + def a2_local(self) -> dict: + """A2 with a local SOD model: the stored image is the decoded inference frame (no submitted JPEG to compare), + so alignment is checked against where the bird really is in each stored image.""" + camera = "feeder-local" + def red_box(jpeg: bytes): + import numpy as np + image = np.asarray(Image.open(io.BytesIO(jpeg)).convert("RGB")).astype(int) + mask = (image[..., 0] > 140) & (image[..., 0] > image[..., 1] + 60) & (image[..., 0] > image[..., 2] + 60) + ys, xs = np.nonzero(mask) + h, w = mask.shape + return (xs.min() / w, ys.min() / h, (xs.max() + 1) / w, (ys.max() + 1) / h) if len(xs) > 300 else None + self.lnvr.api("PUT", f"/api/streams/{camera}", {"observation_evidence_enabled": True, "observation_labels": "bird"}, expect=(200, 202)) + items = wait_for(lambda: (lambda x: x if len(x) >= 3 and all(o["evidence"]["state"] != "pending" for o in x) else None)(self.observations(camera)), + 180, 2, "three local-detector observations") + self.lnvr.api("PUT", f"/api/streams/{camera}", {"observation_evidence_enabled": False}, expect=(200, 202)) + items = [o for o in items if o["evidence"]["state"] == "ready"][:3] + expect(len(items) >= 2, f"{len(items)} ready local observations") + evidence, snapshots = [], {} + for obs in items: + status, jpeg, _h = self.snapshot(obs["id"]) + expect(status == 200, f"snapshot {status}") + truth = red_box(jpeg) + expect(truth is not None, "no bird visible in the stored evidence") + b = obs["box"] + ix = max(0.0, min(b["x"] + b["width"], truth[2]) - max(b["x"], truth[0])) + iy = max(0.0, min(b["y"] + b["height"], truth[3]) - max(b["y"], truth[1])) + inter = ix * iy + union = b["width"] * b["height"] + (truth[2] - truth[0]) * (truth[3] - truth[1]) - inter + cx, cy = (truth[0] + truth[2]) / 2, (truth[1] + truth[3]) / 2 + inside = b["x"] <= cx <= b["x"] + b["width"] and b["y"] <= cy <= b["y"] + b["height"] + expect(inside and inter / union >= 0.2, f"local box {b} does not match the bird at {truth}") + snapshots[obs["id"]] = jpeg + evidence.append({"id": obs["id"], "label": obs["label"], "confidence": obs["confidence"], "box": b, "bird_in_stored_image": [round(v, 4) for v in truth], + "iou": round(inter / union, 3), "origin": obs["evidence"]["origin"], "transform": obs["evidence"]["transform"], "detector": obs["detector"]}) + xs = [e["bird_in_stored_image"][0] for e in evidence] + expect(max(xs) - min(xs) >= 0.1, f"bird did not move between the stored frames: {xs}") + expect(all(e["origin"] == "inference_input" for e in evidence), "local evidence is not the inference input") + rows = self.wait_rows(camera, {e["id"] for e in evidence}, 120) + calls = self.classifier_calls(camera) + expect(len(rows) == len(evidence) and all(r["scientific_name"] == RED for r in rows), f"YA-WAMF rows {rows}") + expect(calls and all(c["red_fraction"] >= 0.10 for c in calls), f"YA-WAMF crops miss the bird: {calls}") + return {"detector": f"local SOD {Path(self.args.sod_model).name} (sha256 {hashlib.sha256(Path(self.args.sod_model).read_bytes()).hexdigest()[:16]})", + "bird_image_sha256": hashlib.sha256(Path(self.args.bird_image).read_bytes()).hexdigest(), "observations": evidence, + "yawamf_rows": [{k: r[k] for k in ("observation_id", "scientific_name", "score")} for r in rows], "classifier_calls": calls} + + def a3(self) -> dict: + camera, key = "feeder-pair", "a3" + self.detector.arm(key, 1) + items = self.settled(camera, 2, 90) + expect(len(items) == 2, f"{len(items)} observations") + expect(len({o["batch_id"] for o in items}) == 1 and len({o["evidence"]["asset_id"] for o in items}) == 1, "siblings do not share batch/asset") + expect(sorted(o["object_index"] for o in items) == [0, 1], "object indexes") + status, jpeg, _h = self.snapshot(items[0]["id"]) + expect(status == 200, f"snapshot {status}") + colours = {o["id"]: colour_of(crop_box(jpeg, o["box"])) for o in items} + expect(sorted(colours.values()) == ["blue", "red"], f"box colours {colours}") + rows = self.wait_rows(camera, set(colours), 90) + species = {r["observation_id"]: r["scientific_name"] for r in rows} + expect(len(rows) == 2, f"{len(rows)} rows") + for oid, colour in colours.items(): + expect(species[oid] == (RED if colour == "red" else BLUE), f"{oid}: {colour} bird identified as {species[oid]}") + calls = self.classifier_calls(camera) + expect(len(calls) == 2 and sorted(c["result"] for c in calls) == sorted([RED, BLUE]), f"classifier calls {calls}") + visits = self.ya.sql("SELECT COUNT(*) FROM detections WHERE source_camera_id = ?", (self.cameras[camera],))[0][0] + self.a3_obs = items + return {"asset_id": items[0]["evidence"]["asset_id"], "batch_id": items[0]["batch_id"], "box_colours": colours, + "yawamf_species": species, "classifier_calls": calls, "yawamf_detection_rows": visits} + + def a4(self) -> dict: + ids = {self.a1_obs["id"]} | {o["id"] for o in self.a3_obs} + replay = [(t, p) for t, p in self.capture.raw() if json.loads(p)["data"]["observation_id"] in ids] + expect(len(replay) >= 6, f"captured {len(replay)} messages for A1/A3") + before_rows = {r["observation_id"]: r for r in self.ya.detections() if r["observation_id"] in ids} + before_calls = len(self.ya.classifier_calls()) + backfill: dict[str, Any] = {} + + def recover(): + backfill["response"] = self.ya.api("POST", "/api/backfill", {"date_range": "day"}, timeout=120) + worker = threading.Thread(target=recover) + worker.start() + for round_ in range(10): + for topic, payload in replay: + self.broker.publish(topic, payload) + worker.join(180) + time.sleep(15) # let replays and recovery settle + after_rows = [r for r in self.ya.detections() if r["observation_id"] in ids] + expect(len(after_rows) == len(ids), f"{len(after_rows)} rows for {len(ids)} observations after replay") + expect(len(self.ya.classifier_calls()) == before_calls, f"classifier re-ran: {len(self.ya.classifier_calls()) - before_calls} extra calls") + intents = {r["observation_id"]: (r["intent"], r["outcome"]) for r in after_rows} + expect(all(r["intent"] is not None for r in after_rows), f"missing intent {intents}") + expect(all(before_rows[r["observation_id"]]["intent"] == r["intent"] for r in after_rows), "intent changed by replay") + resp = backfill.get("response") or {} + expect(resp.get("new_detections", 1) == 0, f"recovery created detections: {resp}") + return {"messages_replayed": len(replay) * 10, "distinct_messages": len(replay), "observations": len(ids), "rows_after": len(after_rows), + "extra_classifier_calls": 0, "intents": intents, "overlapping_backfill": resp} + + def a5(self) -> dict: + camera, key, frames = "feeder-bulk", "a5", 50 + before = len(self.observations(camera)) + self.broker.stop() + self.detector.arm(key, frames) + items = wait_for(lambda: (lambda x: x if len(x) >= before + 2 * frames and all(o["evidence"]["state"] != "pending" for o in x) else None)(self.observations(camera)), + 240, 2, "100 observations while the broker is down") + new = items[before:] + ids = {o["id"] for o in new} + expect(len(ids) == 100, f"{len(ids)} observations created with the broker down") + # lightNVR half while the broker is down: changes are relayed into the outbox and wait there. + outbox_while_down = dict(self.lnvr.sql("SELECT state, COUNT(*) FROM event_outbox GROUP BY state")) + rows_during = {r["observation_id"] for r in self.yawamf_rows(camera)} & ids + # Restart both applications, broker back first. + self.ya.stop() + self.lnvr.stop() + self.broker.start() + self.capture.start() # mosquitto_sub exits when its broker goes away + self.lnvr.start() + restart = time.time() + self.ya.start() + rows = self.wait_rows(camera, ids, 300) + converged = round(time.time() - restart, 1) + mine = [r for r in rows if r["observation_id"] in ids] + expect(len(mine) == 100 and len({r["observation_id"] for r in mine}) == 100, f"{len(mine)} rows") + calls = [c for c in self.classifier_calls(camera)] + # lightNVR half: the outbox delivers every revision after the broker returns. + want = {message_id(self.source_id, o, r) for o in ids for r in (1, 2)} + try: + wait_for(lambda: want <= {e["id"] for e in self.capture.envelopes()}, 180, 2, "outbox redelivery of 200 revisions") + outbox = "all 200 revisions delivered after the broker returned" + except TimeoutError: + got = len(want & {e["id"] for e in self.capture.envelopes()}) + outbox = f"{got}/200 revisions seen by the capture subscriber" + status = self.recovery_status() + intents: dict[str, int] = {} + for r in mine: + intents[str(r["intent"])] = intents.get(str(r["intent"]), 0) + 1 + return {"observations_created_while_broker_down": len(ids), "outbox_states_while_broker_down": outbox_while_down, "outbox_states_after": dict(self.lnvr.sql("SELECT state, COUNT(*) FROM event_outbox GROUP BY state")), + "yawamf_rows_while_down": len(rows_during), "yawamf_rows_after_restart": len(mine), "converged_seconds_after_restart": converged, + "classifier_calls_for_camera": len(calls), "outbox": outbox, "intents": intents, "recovery_status": status} + + def _has_table(self, name: str) -> bool: + return bool(self.lnvr.sql("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (name,))) + + def lnvr_integrity(self, camera: str) -> dict: + """A6 invariants on disk and in the DB: no pending, no tmp, ready rows <-> final files.""" + uuid_ = self.cameras[camera] + root = self.lnvr.dir / "recordings" / "observations" / uuid_ + tmp = list(root.rglob("*.jpg.tmp")) if root.exists() else [] + finals = {p.stem for p in root.rglob("*.jpg")} if root.exists() else set() + rows = self.lnvr.sql("SELECT uuid, state, reason, path FROM observation_assets WHERE camera_uuid = ?", (uuid_,)) + ready = {r[0] for r in rows if r[1] == "ready"} + states: dict[str, int] = {} + for r in rows: + states[f"{r[1]}:{r[2]}" if r[2] else r[1]] = states.get(f"{r[1]}:{r[2]}" if r[2] else r[1], 0) + 1 + expect(not tmp, f"unfinished files remain: {tmp}") + expect(not any(r[1] == "pending" for r in rows), "pending asset after restart") + expect(ready <= finals, f"ready rows without a final file: {ready - finals}") + return {"asset_states": states, "final_files": len(finals), "tmp_files": len(tmp)} + + def a6(self) -> dict: + camera, key = "feeder-kill", "a6" + out: dict[str, Any] = {} + # Phase 1: SIGKILL lightNVR while frames are being detected, encoded and relayed. + self.detector.arm(key, 12) + wait_for(lambda: len(self.detector.reports(key)) >= 4, 60, 0.2, "four detected frames") + offset = self.lnvr.log_path.stat().st_size + self.lnvr.kill() + killed_after = len(self.detector.reports(key)) + self.lnvr.start() + lnvr_restart = time.time() + log = self.lnvr.log_since(offset) + # Reconciliation and drain log only when they found work; when present they must come first. + markers = ("Observation reconciliation:", "Observation evidence initialized", "Observation relay drained", "Observation publication relay started") + order = {m: log.find(m) for m in markers} + present = [order[m] for m in markers if order[m] >= 0] + expect(order[markers[1]] >= 0 and order[markers[3]] >= 0 and present == sorted(present), f"startup order markers {order}") + startup_log = [line for line in log.splitlines() if any(m in line for m in markers)] + wait_for(lambda: self.detector.remaining(key) == 0, 120, 1, "remaining frames detected after restart") + items = self.settled(camera, 2, 120) + out["phase1_lightnvr_sigkill"] = {"frames_reported_before_kill": killed_after, "observations": len(items), "integrity": self.lnvr_integrity(camera), + "startup_log": startup_log} + ready_ids = {o["id"] for o in items if o["evidence"]["state"] == "ready"} + rows = self.wait_rows(camera, ready_ids, 240) + out["phase1_lightnvr_sigkill"]["converged_seconds_after_restart"] = round(time.time() - lnvr_restart, 1) + skipped = {o["id"]: o["evidence"] for o in items if o["evidence"]["state"] != "ready"} + expect(not ({r["observation_id"] for r in rows} & set(skipped)), "a detection was admitted for evidence that is not ready") + out["phase1_lightnvr_sigkill"].update({"ready": len(ready_ids), "not_ready": {k: f"{v['state']}:{v.get('reason')}" for k, v in skipped.items()}, + "yawamf_rows": len(rows)}) + # Phase 2: SIGKILL YA-WAMF while it is fetching and classifying. + before = {o["id"] for o in items} + calls_before = len(self.classifier_calls(camera)) + self.detector.arm(key, 10) + wait_for(lambda: len(self.classifier_calls(camera)) >= calls_before + 3, 120, 0.2, "YA-WAMF mid-way through the burst") + self.ya.kill() + wait_for(lambda: self.detector.remaining(key) == 0, 120, 1, "burst finished") + items = self.settled(camera, len(before) + 2, 120) + restart = time.time() + self.ya.start() + ready_ids = {o["id"] for o in items if o["evidence"]["state"] == "ready"} + rows = self.wait_rows(camera, ready_ids, 240) + expect(len(rows) == len({r["observation_id"] for r in rows}), "duplicate rows") + calls = len(self.classifier_calls(camera)) - calls_before + out["phase2_yawamf_sigkill"] = {"observations_total": len(items), "ready": len(ready_ids), "yawamf_rows": len(rows), + "classifier_calls_in_phase": calls, "converged_seconds_after_restart": round(time.time() - restart, 1), + "integrity": self.lnvr_integrity(camera)} + return out + + def a8(self) -> dict: + camera, key = "feeder-outcomes", "a8" + out: dict[str, Any] = {} + # Temporary first (lightNVR frozen), so the later cases do not depend on it. + self.ya.stop() + self.detector.arm(key, 2) + temp = self.settled(camera, 2, 90) + temp_ids = {o["id"] for o in temp} + self.lnvr.proc.send_signal(19) # SIGSTOP: the API hangs, a network-level temporary error + try: + self.ya.start(wait_mqtt=True) + time.sleep(20) + during = self.recovery_status() + rows_during = {r["observation_id"] for r in self.yawamf_rows(camera)} & temp_ids + finally: + self.lnvr.proc.send_signal(18) # SIGCONT + resumed = time.time() + rows = self.wait_rows(camera, temp_ids, 240) + out["temporary_lightnvr_unreachable"] = {"rows_while_unreachable": len(rows_during), "recovery_status_while_unreachable": during, + "rows_after_resume": len({r['observation_id'] for r in rows} & temp_ids), + "converged_seconds_after_resume": round(time.time() - resumed, 1)} + expect(not rows_during, "admitted while lightNVR was unreachable") + + # Expired media and unavailable evidence: created while YA-WAMF is down. + self.ya.stop() + known = {o["id"] for o in self.observations(camera)} + self.detector.arm(key, 2) + expired = [o for o in self.settled(camera, len(known) + 2, 90) if o["id"] not in known] + known |= {o["id"] for o in expired} + self.detector.arm(key, 2) + unavailable = [o for o in self.settled(camera, len(known) + 2, 90) if o["id"] not in known] + expect(len(expired) == 2 and len(unavailable) == 2, f"{len(expired)} / {len(unavailable)}") + self.lnvr.stop() + day = 86400000 + for o in expired: + self.lnvr.sql("UPDATE object_observations SET observed_at_ms = observed_at_ms - ? WHERE uuid = ?", (8 * day, o["id"])) + for o in unavailable: + for (path,) in self.lnvr.sql("SELECT path FROM observation_assets WHERE uuid = ?", (o["evidence"]["asset_id"],)): + full = Path(path) if Path(path).is_absolute() else self.lnvr.dir / "recordings" / path + if not full.exists(): + full = next((self.lnvr.dir / "recordings").rglob(f"{o['evidence']['asset_id']}.jpg")) + full.unlink() + self.lnvr.start() + # Unavailable: startup reconciliation. Expired: the first retention pass (<= ~60 s heartbeat). + def states(): + current = {o["id"]: o for o in self.observations(camera)} + return current if all(current[o["id"]]["evidence"]["state"] == "expired" for o in expired) and \ + all(current[o["id"]]["evidence"]["state"] == "unavailable" for o in unavailable) else None + current = wait_for(states, 180, 2, "expired and unavailable evidence states") + outcomes = {} + for label, group in (("expired", expired), ("unavailable", unavailable)): + for o in group: + status, body, _h = self.snapshot(o["id"]) + outcomes[o["id"]] = {"case": label, "state": current[o["id"]]["evidence"]["state"], "reason": current[o["id"]]["evidence"].get("reason"), + "snapshot_status": status, "snapshot_code": json.loads(body).get("code")} + expect(all(v["snapshot_status"] == 410 and v["snapshot_code"] == "evidence_expired" for v in outcomes.values() if v["case"] == "expired"), f"{outcomes}") + expect(all(v["snapshot_status"] == 404 and v["snapshot_code"] == "evidence_unavailable" for v in outcomes.values() if v["case"] == "unavailable"), f"{outcomes}") + started = time.time() + self.ya.start() + status = self.wait_recovery_run(started - 1, 120) + time.sleep(5) + ids = {o["id"] for o in expired + unavailable} + admitted = {r["observation_id"] for r in self.yawamf_rows(camera)} & ids + expect(not admitted, f"admitted detections for expired/unavailable evidence: {admitted}") + backfill = self.ya.api("POST", "/api/backfill", {"date_range": "month"}, timeout=180) + reasons = backfill.get("skipped_reasons", {}) + expect(reasons.get("evidence_expired", 0) >= 2 and reasons.get("evidence_unavailable", 0) >= 2, f"backfill reasons {backfill}") + out["expired_and_unavailable"] = {"lightnvr": outcomes, "recovery_run": status.get("last_run"), "yawamf_rows": len(admitted), "backfill": backfill} + + # Expired cursor: a lightNVR DB restore (new epoch) under YA-WAMF's stored cursor. + cursor_row = self.ya.sql("SELECT cursor, recovery_state FROM source_cursors WHERE source_kind = 'lightnvr'") + expect(cursor_row and cursor_row[0][0], f"YA-WAMF has no stored cursor: {cursor_row}") + sidecar = self.lnvr.dir / "recordings" / "observations" / ".cursor_head.json" + self.lnvr.stop() + db = self.lnvr.db_path + backup = db.with_name("backup.db") + for suffix in ("", "-wal"): + if Path(f"{db}{suffix}").exists(): + shutil.copyfile(f"{db}{suffix}", f"{backup}{suffix}") + self.lnvr.start() + known = {o["id"] for o in self.observations(camera)} + self.detector.arm(key, 2) + lost = [o for o in self.settled(camera, len(known) + 2, 90) if o["id"] not in known] + lost_ids = {o["id"] for o in lost} + self.wait_rows(camera, {o["id"] for o in lost if o["evidence"]["state"] == "ready"}, 120) + head = self.lnvr.sql("SELECT MAX(seq) FROM observation_changes")[0][0] + wait_for(lambda: sidecar.exists() and json.loads(sidecar.read_text()).get("head_seq") == head, 30, 0.5, "cursor head sidecar") + # Make sure YA-WAMF's stored cursor is from the generation the restore discards. + self.ya.stop() + self.ya.start() + self.wait_recovery_run(time.time() - 30, 120) + self.ya.stop() + old_cursor = self.ya.sql("SELECT cursor FROM source_cursors WHERE source_kind = 'lightnvr'")[0][0] + self.lnvr.stop() + for suffix in ("", "-wal", "-shm"): + Path(f"{db}{suffix}").unlink(missing_ok=True) + for suffix in ("", "-wal"): + if Path(f"{backup}{suffix}").exists(): + shutil.copyfile(f"{backup}{suffix}", f"{db}{suffix}") + offset = self.lnvr.log_path.stat().st_size + self.lnvr.start() + restored_log = self.lnvr.log_since(offset) + expect("epoch regenerated" in restored_log, "lightNVR did not regenerate the epoch after the restore") + status, _h, body = http("GET", f"{self.lnvr.base}/api/observations?cursor={urllib.parse.quote(old_cursor)}", headers=self.token_headers) + expired_cursor = {"status": status, "body": json.loads(body)} + expect(status == 410 and expired_cursor["body"].get("code") == "cursor_expired", f"old cursor {expired_cursor}") + started = time.time() + self.ya.start() + rec = self.wait_recovery_run(started - 1, 120) + snapshot = self.ya.api("GET", "/api/diagnostics/errors?source=source_recovery") + entries = snapshot.get("events") or snapshot.get("items") or [v for v in snapshot.values() if isinstance(v, list)][0] + diagnostics = [{k: e.get(k) for k in ("source", "component", "severity", "reason_code", "message")} for e in entries] + expect(rec.get("recovery_required") and rec.get("gap_reason") == "cursor_expired", f"recovery status {rec}") + expect(diagnostics, "no source_recovery diagnostics entry for the expired cursor") + acknowledged = self.ya.api("POST", "/api/backfill/source-recovery/acknowledge", {}) + expect(not acknowledged.get("recovery_required"), f"acknowledge {acknowledged}") + rows_lost = {r["observation_id"] for r in self.yawamf_rows(camera)} & lost_ids + out["expired_cursor_after_restore"] = {"old_cursor_response": expired_cursor, "recovery_after_restore": rec, "diagnostics_entries": diagnostics, + "after_acknowledge": {k: acknowledged.get(k) for k in ("state", "recovery_required", "gap_reason")}, + "observations_lost_by_restore_still_in_yawamf": len(rows_lost)} + return out + + def _ya_has(self, table: str) -> bool: + return bool(self.ya.sql("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (table,))) + + # ------------------------------------------------------------------ run + + def versions(self) -> dict: + def git(repo: str, *argv: str) -> str: + return subprocess.run(["git", "-C", repo, *argv], capture_output=True, text=True).stdout.strip() + root = str(HERE.parents[2]) + return { + "lightnvr": {"commit": git(root, "rev-parse", "--short", "HEAD"), "branch": git(root, "rev-parse", "--abbrev-ref", "HEAD"), + "dirty_tracked": bool(git(root, "status", "--porcelain", "--untracked-files=no")), "binary": str(Path(self.args.lightnvr_bin).resolve())}, + "yawamf": {"commit": git(self.args.yawamf, "rev-parse", "--short", "HEAD"), "branch": git(self.args.yawamf, "rev-parse", "--abbrev-ref", "HEAD")}, + "mosquitto": self.broker.version() if hasattr(self, "broker") else None, + "ffmpeg": subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True).stdout.splitlines()[0], + "detector": "loopback HTTP x1-colour-detector 1.0 (light-object-detect wire format; finds the red/blue fixture birds by colour)", + "classifier": "pinned x1-colour-rule-v1 (harness/yawamf_launcher.py replaces ClassifierService.classify_async_live)", + "python": sys.version.split()[0], + } + + def run(self) -> int: + started = time.time() + deploy_error = None + try: + self.deploy() + except Exception as error: # noqa: BLE001 + deploy_error = f"{type(error).__name__}: {error}\n{traceback.format_exc()}" + print(deploy_error, flush=True) + if deploy_error is None: + self.scenario("A1", "identity and labels", self.a1) + self.scenario("A2", "moving-bird alignment (HTTP detector)", self.a2) + if self.local: + self.scenario("A2-local", "moving-bird alignment (local SOD detector, real bird photo)", self.a2_local) + self.scenario("A3", "two birds in one frame", self.a3) + self.scenario("A4", "replay x10 with overlapping API recovery", self.a4) + self.scenario("A12", "write-back: species in both products, replays and stale revisions, owner precedence, no loop", self.a12) + self.scenario("A13", "write-back survives a lightNVR outage; the YA-WAMF detection stays intact", self.a13) + self.scenario("A7-annotate", "annotate needs observations.annotate on the capture camera", self.a7_annotate) + self.scenario("A5", "broker down for 100 observations, both apps restarted", self.a5) + self.scenario("A6", "kill points end to end", self.a6) + self.scenario("A8", "expired, unavailable, temporary and expired-cursor outcomes", self.a8) + summary = { + "suite": "INT_01 X1/X3 lightNVR <-> YA-WAMF E2E", "writeback_enabled": not self.args.no_writeback, "mode": "host-process", "started_at": started, + "finished_at": time.time(), "seconds": round(time.time() - started, 1), "versions": self.versions(), + "recipe": self.recipe, "deploy_error": deploy_error, "scenarios": self.results, "workdir": str(self.work), + "counts": {"detector_requests": getattr(self.detector, "requests", None) if hasattr(self, "detector") else None, + "mqtt_messages_captured": len(self.capture.raw()) if hasattr(self, "capture") else None}, + } + self.teardown() + out_dir = Path(self.args.results_dir) + out_dir.mkdir(parents=True, exist_ok=True) + name = self.args.run_name or time.strftime("x1-host-%Y%m%dT%H%M%S") + (out_dir / f"{name}.json").write_text(json.dumps(summary, indent=2, default=str) + "\n") + for evidence in ("mqtt-capture.jsonl", "detector.jsonl", "yawamf/classifier.jsonl"): + source = self.work / evidence + if source.exists(): + shutil.copyfile(source, out_dir / f"{name}.{Path(evidence).name}") + print(f"\nresults: {out_dir / (name + '.json')}") + for r in self.results: + print(f" {r['id']}: {r['result']}" + (f" ({r.get('error')})" if r["result"] != "pass" else "")) + ok = deploy_error is None and self.results and all(r["result"] == "pass" for r in self.results) + return 0 if ok else 1 + + def teardown(self) -> None: + for name in ("ya", "lnvr", "capture", "broker"): + component = getattr(self, name, None) + if component is None: + continue + try: + if name == "lnvr" and component.proc and component.proc.poll() is None: + component.proc.send_signal(18) + component.stop() + except Exception as error: # noqa: BLE001 + print(f"teardown {name}: {error}", flush=True) + for name in ("feeder", "detector"): + component = getattr(self, name, None) + if component is not None: + component.close() + + +def main() -> int: + root = HERE.parents[2] + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--lightnvr-bin", default=os.environ.get("LIGHTNVR_E2E_BIN", str(root / "build" / "bin" / "lightnvr"))) + parser.add_argument("--web-root", default=str(root / "web" / "dist")) + parser.add_argument("--yawamf", default=os.environ.get("YAWAMF_E2E_REPO", str(root.parent / "YetAnother-WhosAtMyFeeder"))) + parser.add_argument("--workdir", default=None) + parser.add_argument("--results-dir", default=str(HERE / "results")) + parser.add_argument("--run-name", default=None) + parser.add_argument("--sod-model", default=None, help="optional: a bird-capable .sod model (e.g. VOC tiny20.sod) for the A2 local variant") + parser.add_argument("--bird-image", default=None, help="optional: a real bird photo for the A2 local variant (not committed; see README)") + parser.add_argument("--no-writeback", action="store_true", help="start YA-WAMF without LIGHTNVR__WRITEBACK_ENABLED (P1 recipe only)") + parser.add_argument("--scenarios", type=lambda s: set(s.split(",")), default=None, help="comma-separated subset, e.g. A1,A3") + return Suite(parser.parse_args()).run() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/performance/observations/README.md b/tests/performance/observations/README.md new file mode 100644 index 000000000..3e1caec78 --- /dev/null +++ b/tests/performance/observations/README.md @@ -0,0 +1,301 @@ +# INT_01 observation performance fixture (C4 / X2) + +The repeatable recording workload behind the INT_01 performance gate +(PRD §8, A11). C4 declares it and captures the **feature-disabled** baseline. +X2 reruns it with evidence enabled and compares the two. + +The C1 contract (`docs/integrations/OBSERVATIONS_CONTRACT.md` §Performance) +should link here rather than copy these values. + +## Files + +| File | Purpose | +| --- | --- | +| `workload.json` | Declared fixture: streams, resolution, fps, GOP, segment length, detector and cadence, window, sampling and retention. CLI flags override it, and every run records the values it actually used. | +| `run-workload.mjs` | Harness. Writes `/results.json`. | +| `metrics.mjs` | Pure aggregation (percentiles, Prometheus parsing, gap counting, repeatability and release-gate checks). Unit tests live in `tests/contracts/tooling/observation-perf-metrics.test.mjs` (`npm run contracts:test-tools`). | +| `compare.mjs` | `repeat A B` (C4: p95 variance < 2%), `gate BASELINE CANDIDATE` (X2: same binary, no new gaps, p95 +≤5%, plus the evidence checks for an enabled candidate; `--advisory KEY`, `--require-saturation`, `--max-submit-seconds S`) and `pool BASE... -- CAND...` (one raw series pooled per mode). | +| `make-bird-clip.sh` | X2's detection-producing clip: the pinned cardinal photo (not committed, CC BY-SA) bouncing across a textured 1280×720 background. | +| `results/` | Committed results. Raw samples are kept so the numbers can be recomputed. | + +## Workload (`int01-observations-v1`) + +- **Streams.** 4 streams, each 1280×720 H.264 at 15 fps with a 15-frame GOP + and no B-frames. +- **Source.** A deterministic 30 s `testsrc2` clip. One always-on + `ffmpeg -re -stream_loop -1 -c copy` per stream publishes the clip into a + standalone go2rtc (`cam0..camN-1`) on loopback. +- **lightNVR.** lightNVR pulls each stream over RTSP/TCP. Its own go2rtc + integration is disabled, as in the contract fixture + (`tests/contracts/fixture-server.mjs`), so recording and detection each + open their own RTSP session. +- **Recording.** Continuous MP4 recording with 10 s segments and no audio. + `storage.max_size = 0`, so no retention sweeps run inside the window. +- **Detector.** A local SOD CNN (`tiny20.sod`, VOC-20) in the unified + detection thread, running every 1 s on every stream. It runs in + annotation-only mode because continuous recording is on. The model is not + vendored because it is 63 MB. Pass `--model`; its SHA-256 is recorded. +- **Timing.** 30 s warm-up, then a 300 s measurement window. +- **Viewers.** None. +- **Storage.** The fixture runs on a real filesystem (`--work-dir`, default + `build/perf-observations/work`). The harness refuses tmpfs, because `/tmp` + on the dev host is tmpfs. + +## Measurements (`results.json`) + +`definitions` in every results file repeats these definitions. + +### Recording-path latency + +lightNVR has no in-process packet-write latency histogram, so the harness uses +two black-box proxies that both sit on the recording path. They are the +headline series for repeatability and for the gate. + +- **`latency.segment_interval_ms`** is the wall-clock time between the + appearance of consecutive segment files for each stream, seen through + inotify. A stall in the writer, in rotation, or in the catalog insert under + the global DB mutex lengthens it. + - `segment_interval_excess_ms` is the same value minus the nominal 10 000 ms. +- **`latency.db_probe_ms`** times `GET /api/recordings?limit=1` with a session + cookie, every 250 ms. That request takes the same global DB mutex as the + recording catalog writes. The plan calls out mutex hold time as an X2 risk. + - A session cookie is used because Basic auth runs PBKDF2 on every request, + and that cost would swamp the measurement. + +### Gaps + +- **`gaps.metrics_recording_gaps_total`** is the change in the in-process + `lightnvr_recording_gaps_total` counter (5 s threshold) over the window. +- **`gaps.catalog_gaps_total`** counts places where a catalog row starts more + than 5 s after the previous row for that stream ends. +- **`gaps.segment_interval_gaps`** counts segment intervals longer than the + nominal 10 s plus 5 s. + +### Resources + +**`resources.lightnvr`** comes from `/proc/` sampled once per second: + +- CPU percent of one core (distribution and average); +- RSS distribution and `VmHWM` high-water mark; +- `write_bytes` delta and rate; +- maximum thread count and maximum fd count. + +The fixture's own cost (go2rtc plus publishers) is reported separately under +`resources.fixture_go2rtc_and_publishers`. + +### Health and detector checks + +- **`recording`** records segment counts against the expected number, the + frame-drop and reconnect deltas, FPS samples and `streams_up_min`. +- **`detector`** counts inferences from `sod_cnn_predict returned` lines in the + log, as inferences per stream per second against the configured cadence. + +A run is marked `invalid` when any of these hold: + +- a stream went down; +- the detector ran below half its configured cadence; +- a probe failed; +- a publisher exited. + +## Run + +```sh +# Fresh Release build against the system FFmpeg (stale /usr/local headers on the dev host break local builds) +PKG_CONFIG_LIBDIR=/usr/lib/x86_64-linux-gnu/pkgconfig:/usr/share/pkgconfig \ + cmake -S . -B build/int01-c4-release -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF +cmake --build build/int01-c4-release --target lightnvr -j + +node tests/performance/observations/run-workload.mjs --model ~/tiny20.sod \ + --output build/perf-observations/disabled-run1 --mode disabled +node tests/performance/observations/run-workload.mjs --model ~/tiny20.sod \ + --output build/perf-observations/disabled-run2 --mode disabled +node tests/performance/observations/compare.mjs repeat \ + build/perf-observations/disabled-run1/results.json build/perf-observations/disabled-run2/results.json +``` + +The harness needs Node 24, ffmpeg/ffprobe with libx264, go2rtc on `PATH` (or +`--go2rtc`), `findmnt`/`lsblk`, and Linux `/proc`. Use `--keep` to keep the +fixture directory (server log, DB, media). + +## X2: rerun with evidence enabled + +1. **Use one binary and one source for both modes.** + - Build the X2 binary. Rerun **disabled** mode on it as well. The + committed C4 baseline proves the harness is repeatable, but the gate + should compare the two modes on the same binary. + - Synthetic `testsrc2` content gives **no VOC detections**. The detector + runs, but it produces no observations, so evidence work would never + trigger. + - X2 must pass a clip that produces detections, for example the plan's + recorded bird fixture, with `--source FILE`. Run both modes on that clip. + Its SHA-256 goes into `workload.source_sha256`, and `compare.mjs` refuses + to compare runs whose workloads differ. +2. **Enable the feature with the hooks only.** Do not edit the harness for + this. + - `--stream-extra stream.json` is merged into every `POST /api/streams` + body, for example the L9 per-camera evidence opt-in. + - `--config-extra extra.ini` is appended to the generated `lightnvr.ini`, + for example the C1 queue or byte budgets. + - Both are recorded in `results.feature`. Use `--mode enabled`. + - `--system-setting KEY=VALUE` (repeatable) writes a `system_settings` row + and restarts lightNVR once before streams are added. The global queue + budget is read only at startup, so the saturation run uses + `--system-setting observation_global_queue_bytes=4194304`. +3. **Extra X2 evidence (automatic).** + - `results.feature_metrics` holds the window's L10 counters (accepted, + dropped by reason, ready, failures, discards) and the run's + high-water marks. + - It also holds the configured limits, the gauge maxima over every scrape, + and the producer submit-latency histogram + (`lightnvr_observation_submit_latency_seconds`). + - `results.feature_db` is read from the fixture DB after shutdown: + observations, assets by `state:reason`, and detections. +4. **Gate.** Run `compare.mjs gate disabled.json enabled.json`. It fails on: + - a different binary or workload; + - any additional gap; + - a p95 increase above 5% in a gating headline series; + - any `evidenceGate` check, which requires all of these: + - detections, evidence and DB observations really happened; + - high-water marks are within the configured limits; + - no untracked drops, and the DB holds at least the window's drops by + reason; + - no encode, write or DB failures; + - the producer submit maximum is under 1 ms (`--max-submit-seconds`). + + `--require-saturation` also requires `queue_full` drops. + +## Committed baseline (C4, 2026-11-08, feature disabled) + +The data is in `results/2026-11-08-disabled-run{1,2}.json`, and +`results/2026-11-08-disabled-repeatability.json` holds the output of +`compare.mjs repeat`. + +**Host** + +- AMD Ryzen 9 7950X, 32 logical cores, `powersave` governor. +- 128 GB RAM, kernel 7.0.0-38-generic, Ubuntu 26.04.1. +- Storage: ext4 on a Samsung 990 PRO NVMe. +- ffmpeg 8.0.1, go2rtc 1.9.14. + +**Workload:** 4 × 1280×720 at 15 fps, 10 s segments, SOD `tiny20.sod` every +1 s, 30 s warm-up, 300 s window. + +| Series | Run 1 | Run 2 | Variance | +| --- | --- | --- | --- | +| `segment_interval_ms` p95 | 10035.3 | 10037.9 | 0.03% (pass) | +| `db_probe_ms` p95 | 11.95 | 12.32 | **3.02% (fails the 2% criterion)** | +| Recording gaps (metrics / catalog / interval) | 0 / 0 / 0 | 0 / 0 / 0 | | +| Segments in window (expected 120) | 120 | 120 | | +| Detector inferences per stream per second (configured 1) | 1.003 | 1.003 | | +| lightNVR CPU, average percent of one core | 144.2 | 146.7 | | +| RSS high-water (KiB) | 875 932 | 874 988 | | +| Disk write rate (B/s) | 2.33 M | 2.33 M | | + +**Caveats on these numbers** + +- **The DB probe did not meet the 2% criterion.** The dev host was shared with + other parallel agents during the runs. The 1-minute load average rose from + about 3.8 to 9.25 during run 2. The HTTP round-trip p95 at about 12 ms is + sensitive to that. + - The segment-interval series is stable. + - Before X2 relies on the DB-probe series at a 5% threshold, rerun on a + quiet host, or on the dedicated perf host, with `--seconds 600` or more. + Confirm its variance first. If it stays above 2%, treat it as advisory + and gate only on segment interval plus gaps. Record that decision in C1. +- **Binary provenance.** `build/int01-c4-release/bin/lightnvr` was built at + 08:11 from `c46bff1c` (fresh Release, system FFmpeg 8.0.1) and + `binary_sha256` identifies it. + - `build.revision` in the JSON is the checkout at run time (`3c61e235`). + Parallel tasks committed docs-only changes in between. The tree may also + have held other agents' uncommitted, unwired edits. + - No observation code was compiled in, so the feature is absent. + +## X2 result (2026-11-08, A11): pass + +The data is in `results/2026-11-08-x2-*.json`, and `results/2026-11-08-x2-gate.json` +holds every gate, the pooled comparisons, disabled repeatability, and two runs +showing the gate fails at impossible thresholds (exit 1). + +**Setup** + +- **Binary:** `build-int01-X2/bin/lightnvr` from `9ed02bb4`. It is a Release + build with LiteRT on, MQTT and SOD, built against the apt FFmpeg 8.0.1 + headers through a symlink include dir. +- **Clip:** `make-bird-clip.sh` (sha256 `5bf26bbc…`; see the results). +- **Host:** the same as C4. The 1-minute load average was 2.8–7.8. The + user's own lightNVR, a uvicorn, Mosquitto, a `llama-server` using one core, + and other desktop work were running. +- **Runs:** 600 s each, recorded in this order: + 1. disabled-run1; + 2. enabled-defaults; + 3. enabled-saturated; + 4. disabled-run2; + 5. disabled-run3; + 6. enabled-defaults2; + 7. enabled-saturated2. + +```sh +S=/path/to/scratch +tests/performance/observations/make-bird-clip.sh cardinal.jpg $S/bird.mp4 +echo '{"observation_evidence_enabled": true, "observation_max_bytes": 67108864}' > $S/stream.json +COMMON="--binary build-int01-X2/bin/lightnvr --model ~/tiny20.sod --source $S/bird.mp4 --seconds 600 --warmup 30" +node tests/performance/observations/run-workload.mjs $COMMON --mode disabled --output build/perf-observations/x2-disabled +node tests/performance/observations/run-workload.mjs $COMMON --mode enabled --stream-extra $S/stream.json --output build/perf-observations/x2-enabled +node tests/performance/observations/run-workload.mjs $COMMON --mode enabled --stream-extra $S/stream.json \ + --system-setting observation_global_queue_bytes=4194304 --output build/perf-observations/x2-saturated +node tests/performance/observations/compare.mjs gate build/perf-observations/x2-disabled/results.json \ + build/perf-observations/x2-enabled/results.json --advisory latency.db_probe_ms.p95 +node tests/performance/observations/compare.mjs gate build/perf-observations/x2-disabled/results.json \ + build/perf-observations/x2-saturated/results.json --advisory latency.db_probe_ms.p95 --require-saturation +``` + +| | Disabled (3 runs) | Enabled, defaults (2) | Enabled, saturated 4 MiB (2) | +| --- | --- | --- | --- | +| Detections per window / observations in DB | 2,408 / 0 | 2,407–2,408 / 2,524–2,532 | 2,404 / 2,529–2,532 | +| Accepted / `queue_full` / untracked | — | 2,407–2,408 / 0 / 0 | 1,063–1,073 / 1,331–1,341 / 0 | +| Recording gaps (3 sources) | 0 | 0 | 0 | +| `segment_interval_ms` p95 | 10008.9–10016.1 | 10008.5–10013.5 | 10009.3–10012.9 | +| `db_probe_ms` p95 (advisory) | 12.74–13.40 | 13.99–14.83 | 13.70–14.23 | +| Pooled `db_probe_ms` p95 vs disabled | 13.13 | 14.43 (+9.9%) | 13.98 (+6.5%) | +| Submit latency: max / p99 bucket / mean | — | 341–396 µs / ≤ 50 µs / 4.1–4.4 µs | 365–405 µs / ≤ 50 µs / 4.3–4.4 µs | +| Queue high-water: items / bytes (limit) | — | 4 / 11.1 MB (64 / 32 MiB) | 1 / 2.77 MB (64 / 4 MiB) | +| Per-camera high-water (limit 8) / drop ring max (256) | — | 1 / 0 | 1 / 2 | +| Rejected frames: deferred / inline / pending max | — | 0 / 0 / 0 | 1,331–1,341 / 0 / 5.5 MB | +| lightNVR CPU (% of one core, avg) | 148.9–155.5 | 154.1–158.0 | 151.0–154.9 | +| RSS high-water | 873–888 MB | 891–892 MB | 882–929 MB | +| Disk write rate | 2.06 MB/s | 3.29–3.32 MB/s | 2.87 MB/s | +| Evidence retained per camera (budget 64 MiB) | — | 44.8–45.0 MB | 16.7–24.0 MB | + +**Gate decision.** + +- `db_probe_ms` p95 fails its own 2% repeatability. Across disabled pairs it + varied by 2.61%, 5.03% and 2.42%. Following C4's rule it is + `--advisory`: reported, not gating. +- With it advisory, all four gates pass: + - disabled-run1 against enabled-defaults and enabled-saturated; + - disabled-run3 against enabled-defaults2 and enabled-saturated2. +- Without `--advisory`, the second pair still passes (+4.4% and +2.2%), but + the first fails (+13.4% and +8.8%). That pair ran during a host load spike + (load average about 7). +- **The pooled +9.9% / +6.5% DB-probe tail is a recorded finding**, not + noise to ignore. + - The median is unchanged (8.1–8.4 ms). + - The likely cause is the evidence worker's extra WAL traffic. It causes + more frequent SQLite autocheckpoints, and those run inside commits that + hold the global DB mutex. + - Shrinking admission cannot remove it, because drops still insert an + `unavailable` row. The saturated mode accepted 56% fewer frames and only + moved +9.9% to +6.5%. + - Recording itself, measured by segment interval and gaps, is unaffected. + - Follow-up: run checkpoints outside the global mutex, or batch the + worker's commits. + +**Finding fixed in `9ed02bb4`.** In the first saturated smoke runs, about 8% +of submissions (and 10 of 120 in the window) took 1–4.4 ms. The cause was +`free()` of the rejected 2.7 MB frame on the detection thread, which is an +munmap. Rejected frames are now released by the worker. + +**Not exercised.** Media eviction: the clip's JPEGs are about 100 KB, and a +camera retained at most 45 MB against the 64 MiB minimum budget. +`budget_exhausted` needs protected assets. Both are covered by the L4 and +L11 tests. diff --git a/tests/performance/observations/compare.mjs b/tests/performance/observations/compare.mjs new file mode 100644 index 000000000..442560a83 --- /dev/null +++ b/tests/performance/observations/compare.mjs @@ -0,0 +1,80 @@ +#!/usr/bin/env node +/** + * Compare observation workload results. + * + * node compare.mjs repeat RUN_A.json RUN_B.json # C4: same mode, p95 variance < 2% + * node compare.mjs gate BASELINE.json CANDIDATE.json # X2: no new gaps, p95 +<=5% + * [--max-pct N] [--require-saturation] [--max-submit-seconds S] [--advisory KEY]... + * node compare.mjs pool BASE.json... -- CAND.json... [--series db_probe_ms] [--q 95] [--max-pct N] + * + * --advisory KEY reports a headline p95 (e.g. latency.db_probe_ms.p95) without + * gating on it: C4's rule for a series that fails its own 2% repeatability. + * pool pools one raw sample series across runs per mode (metrics.mjs + * pooledComparison); it is the steadier view of an advisory series. + * + * gate also requires the same binary, and for an enabled candidate applies the + * A11 evidence checks (metrics.mjs evidenceGate): evidence really happened, + * queue limits held, drops carry reasons, submit never waited on I/O. + * Exits non-zero when the check fails or the workloads differ. + */ +import {readFile} from 'node:fs/promises'; +import {repeatability, releaseGate, evidenceGate, workloadFingerprint, pooledComparison, HEADLINE_P95} from './metrics.mjs'; + +const [command, a, b, ...rest] = process.argv.slice(2); +if (command === 'pool') { + const argv = process.argv.slice(3); + const named = new Map(); + const files = []; + for (let i = 0; i < argv.length; i++) { + if (argv[i].startsWith('--') && argv[i] !== '--') named.set(argv[i], argv[++i]); + else files.push(argv[i]); + } + const split = files.indexOf('--'); + if (split < 1 || split === files.length - 1) { console.error('usage: compare.mjs pool BASE.json... -- CAND.json... [--series S] [--q N] [--max-pct N]'); process.exit(2); } + const load = list => Promise.all(list.map(async f => JSON.parse(await readFile(f, 'utf8')))); + const [base, cand] = [await load(files.slice(0, split)), await load(files.slice(split + 1))]; + const all = [...base, ...cand]; + const sameWorkload = all.every(r => r.status === 'completed' && workloadFingerprint(r) === workloadFingerprint(all[0])); + const sameBinary = all.every(r => r.build?.binary_sha256 === all[0].build?.binary_sha256); + const result = pooledComparison(base, cand, named.get('--series') || 'db_probe_ms', Number(named.get('--q') || 95), + named.has('--max-pct') ? Number(named.get('--max-pct')) : 5); + Object.assign(result, {baseline_files: files.slice(0, split), candidate_files: files.slice(split + 1), same_workload: sameWorkload, same_binary: sameBinary}); + console.log(JSON.stringify(result, null, 2)); + process.exit(result.pass && sameWorkload && sameBinary ? 0 : 1); +} +if (!['repeat', 'gate'].includes(command) || !a || !b) { + console.error('usage: compare.mjs repeat|gate A.json B.json [--max-pct N] [--require-saturation] [--max-submit-seconds S]'); + process.exit(2); +} +const opt = name => { const i = rest.indexOf(name); return i < 0 ? undefined : Number(rest[i + 1]); }; +const limit = opt('--max-pct'); +const maxSubmit = opt('--max-submit-seconds'); +const [runA, runB] = await Promise.all([a, b].map(async file => JSON.parse(await readFile(file, 'utf8')))); +for (const [file, run] of [[a, runA], [b, runB]]) { + if (run.status !== 'completed') { console.error(`${file}: status ${run.status}; not comparable`); process.exit(1); } +} +const sameWorkload = workloadFingerprint(runA) === workloadFingerprint(runB); +const advisory = rest.flatMap((x, i) => (x === '--advisory' ? [rest[i + 1]] : [])); +const unknown = advisory.filter(k => !HEADLINE_P95.includes(k)); +if (unknown.length) { console.error(`--advisory: not a headline series: ${unknown.join(', ')}`); process.exit(2); } +const gateOptions = {...(limit === undefined ? {} : {maxIncreasePct: limit})}; +const result = command === 'repeat' + ? repeatability(runA, runB, limit === undefined ? {} : {maxPct: limit}) + : releaseGate(runA, runB, {...gateOptions, keys: HEADLINE_P95.filter(k => !advisory.includes(k))}); +if (command === 'gate' && advisory.length) result.advisory = releaseGate(runA, runB, {...gateOptions, keys: advisory}).checks + .filter(c => advisory.includes(c.key)).map(c => ({...c, gating: false})); +result.same_workload = sameWorkload; +if (command === 'repeat' && runA.mode !== runB.mode) result.same_workload = false; +let pass = result.pass && result.same_workload; +if (command === 'gate') { + result.same_binary = Boolean(runA.build?.binary_sha256) && runA.build.binary_sha256 === runB.build?.binary_sha256; + pass = pass && result.same_binary; + if (runB.mode === 'enabled') { + result.evidence = evidenceGate(runA, runB, {requireSaturation: rest.includes('--require-saturation'), + ...(maxSubmit === undefined ? {} : {maxSubmitSeconds: maxSubmit})}); + pass = pass && result.evidence.pass; + } + result.verdict = pass ? 'pass' : 'fail'; +} +console.log(JSON.stringify(result, null, 2)); +process.exit(pass ? 0 : 1); diff --git a/tests/performance/observations/make-bird-clip.sh b/tests/performance/observations/make-bird-clip.sh new file mode 100755 index 000000000..df0d96084 --- /dev/null +++ b/tests/performance/observations/make-bird-clip.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# X2 detection-producing clip for the INT_01 performance workload. +# +# tests/performance/observations/make-bird-clip.sh CARDINAL.jpg OUT.mp4 +# +# Renders a real bird photo moving across a static, lightly textured 1280x720 +# background as H.264 matching workload.json (15 fps, 15-frame GOP, no +# B-frames, yuv420p, 30 s), so SOD VOC tiny20.sod reports "bird" on every +# sampled frame and evidence is produced at the detector cadence. +# +# The photo is NOT committed: it is CC BY-SA 4.0, which does not fit this +# repository. Use the same file X1 pinned (tests/integration/yawamf_e2e/README.md): +# Wikimedia Commons File:Northern_cardinal_male_(84894).jpg, 330 px thumbnail +# https://upload.wikimedia.org/wikipedia/commons/thumb/9/97/Northern_cardinal_male_%2884894%29.jpg/330px-Northern_cardinal_male_%2884894%29.jpg +# sha256 85bf66b1c51ad1aeb08d3d90f1a39a59f6cb6161d91f7e7b51d4d187847f3cdf +# The script refuses another file unless BIRD_CLIP_ANY_IMAGE=1. +# +# Output is deterministic for a given photo and ffmpeg build (fixed noise seed, +# single-threaded x264); run-workload.mjs records its sha256 either way. +set -euo pipefail + +PHOTO_SHA256=85bf66b1c51ad1aeb08d3d90f1a39a59f6cb6161d91f7e7b51d4d187847f3cdf +if [ $# -ne 2 ]; then + echo "usage: $0 CARDINAL.jpg OUT.mp4" >&2 + exit 2 +fi +photo=$1 +out=$2 +actual=$(sha256sum "$photo" | cut -d' ' -f1) +if [ "$actual" != "$PHOTO_SHA256" ] && [ "${BIRD_CLIP_ANY_IMAGE:-0}" != 1 ]; then + echo "$photo: sha256 $actual is not the pinned cardinal photo ($PHOTO_SHA256); set BIRD_CLIP_ANY_IMAGE=1 to override" >&2 + exit 1 +fi +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +# Static texture (a fixed-seed noise frame, slightly blurred): realistic JPEG +# sizes for the evidence byte budgets without a temporally noisy recording. +ffmpeg -nostdin -hide_banner -loglevel error -y \ + -f lavfi -i "color=c=0x6f7d5c:s=1280x720:d=1,noise=alls=28:allf=u:all_seed=8675309,gblur=sigma=1.2" \ + -frames:v 1 "$work/background.png" + +# The bird (360 px high) bounces across the frame at 160 px/s and bobs +# vertically, always fully in frame. +ffmpeg -nostdin -hide_banner -loglevel error -y \ + -loop 1 -framerate 15 -i "$work/background.png" \ + -loop 1 -framerate 15 -i "$photo" \ + -filter_complex "[1:v]scale=-2:360,format=yuv420p[bird];[0:v]format=yuv420p[bg];[bg][bird]overlay=x='abs(mod(t*160\,2*(W-w))-(W-w))':y='(H-h)/2+120*sin(t*0.9)':shortest=0,scale=out_range=tv,format=yuv420p[v]" \ + -map "[v]" -t 30 -r 15 -an -c:v libx264 -threads 1 -preset ultrafast -pix_fmt yuv420p \ + -g 15 -keyint_min 15 -sc_threshold 0 -bf 0 -movflags +faststart "$out" + +sha256sum "$out" diff --git a/tests/performance/observations/metrics.mjs b/tests/performance/observations/metrics.mjs new file mode 100644 index 000000000..099043951 --- /dev/null +++ b/tests/performance/observations/metrics.mjs @@ -0,0 +1,376 @@ +/** + * Pure aggregation helpers for the INT_01 observation performance workload. + * No I/O here: everything is unit-tested by + * tests/contracts/tooling/observation-perf-metrics.test.mjs. + */ + +/** Linear-interpolated percentile (Hyndman–Fan type 7, same as numpy default). */ +export function percentile(values, p) { + if (!Array.isArray(values) || values.length === 0) return null; + if (!(p >= 0 && p <= 100)) throw new Error(`percentile out of range: ${p}`); + const sorted = values.filter(Number.isFinite).sort((a, b) => a - b); + if (sorted.length === 0) return null; + const rank = (p / 100) * (sorted.length - 1); + const lo = Math.floor(rank), hi = Math.ceil(rank); + return sorted[lo] + (sorted[hi] - sorted[lo]) * (rank - lo); +} + +/** Distribution summary used for every latency/interval series in results.json. */ +export function summarize(values) { + const finite = (values || []).filter(Number.isFinite); + if (finite.length === 0) return {count: 0, min: null, p50: null, p95: null, p99: null, max: null, mean: null}; + const round = v => Math.round(v * 1000) / 1000; + return { + count: finite.length, + min: round(Math.min(...finite)), + p50: round(percentile(finite, 50)), + p95: round(percentile(finite, 95)), + p99: round(percentile(finite, 99)), + max: round(Math.max(...finite)), + mean: round(finite.reduce((a, b) => a + b, 0) / finite.length), + }; +} + +/** Parse Prometheus text exposition (the format served by GET /api/metrics). */ +export function parsePrometheus(text) { + const out = new Map(); + for (const raw of String(text).split('\n')) { + const line = raw.trim(); + if (!line || line.startsWith('#')) continue; + const m = line.match(/^([a-zA-Z_:][a-zA-Z0-9_:]*)(\{([^}]*)\})?\s+(\S+)/); + if (!m) continue; + const labels = {}; + if (m[3]) for (const pair of m[3].matchAll(/([a-zA-Z_][a-zA-Z0-9_]*)="((?:[^"\\]|\\.)*)"/g)) labels[pair[1]] = pair[2]; + const value = Number(m[4]); + if (!out.has(m[1])) out.set(m[1], []); + out.get(m[1]).push({labels, value}); + } + return out; +} + +/** Sum a metric across label sets, optionally filtered by a label predicate. */ +export function sumMetric(parsed, name, filter = () => true) { + const rows = parsed.get(name); + if (!rows) return null; + return rows.filter(r => filter(r.labels)).reduce((a, r) => a + r.value, 0); +} + +/** Per-label-value map of one metric, e.g. {Load0: 0, Load1: 2} for recording gaps by stream. */ +export function metricByLabel(parsed, name, label = 'stream') { + const out = {}; + for (const r of parsed.get(name) || []) out[r.labels[label]] = r.value; + return out; +} + +/** + * Interval between consecutive segment-file creations for each stream. + * events: [{stream, tMs}] (tMs = monotonic first-seen time of a new segment file). + * The first segment of each stream has no predecessor and yields no interval. + */ +export function segmentIntervals(events) { + const byStream = new Map(); + for (const e of events) { + if (!byStream.has(e.stream)) byStream.set(e.stream, []); + byStream.get(e.stream).push(e.tMs); + } + const perStream = {}, all = []; + for (const [stream, times] of byStream) { + times.sort((a, b) => a - b); + const intervals = []; + for (let i = 1; i < times.length; i++) intervals.push(times[i] - times[i - 1]); + perStream[stream] = intervals; + all.push(...intervals); + } + return {perStream, all}; +} + +/** Count intervals that exceed the nominal segment length by more than thresholdMs. */ +export function countIntervalGaps(intervalsMs, nominalMs, thresholdMs) { + return intervalsMs.filter(v => v > nominalMs + thresholdMs).length; +} + +/** + * Gaps in the persisted recording catalog: consecutive rows of one stream whose + * start_time is more than thresholdSec after the previous row's end_time. + * rows: [{stream, start_time, end_time}] with unix-second timestamps. + */ +export function catalogGaps(rows, thresholdSec) { + const byStream = new Map(); + for (const r of rows) { + if (!byStream.has(r.stream)) byStream.set(r.stream, []); + byStream.get(r.stream).push(r); + } + const perStream = {}, gaps = []; + for (const [stream, list] of byStream) { + list.sort((a, b) => a.start_time - b.start_time); + let count = 0; + for (let i = 1; i < list.length; i++) { + const gap = list[i].start_time - list[i - 1].end_time; + if (gap > thresholdSec) { count++; gaps.push({stream, after_id: list[i - 1].id, gap_seconds: gap}); } + } + perStream[stream] = count; + } + return {total: gaps.length, perStream, gaps}; +} + +/** + * Process resource rates from /proc samples ordered by time. + * sample: {tMs, cpuTicks, rssKiB, peakRssKiB, writeBytes, threads, fds} + */ +export function resourceSummary(samples, clockTicksPerSec = 100) { + if (samples.length < 2) return null; + const first = samples[0], last = samples[samples.length - 1]; + const seconds = (last.tMs - first.tMs) / 1000; + const cpuPct = []; + for (let i = 1; i < samples.length; i++) { + const dt = (samples[i].tMs - samples[i - 1].tMs) / 1000; + if (dt > 0) cpuPct.push(((samples[i].cpuTicks - samples[i - 1].cpuTicks) / clockTicksPerSec) / dt * 100); + } + const round = v => (v == null ? null : Math.round(v * 1000) / 1000); + return { + window_seconds: round(seconds), + cpu_percent_one_core: summarize(cpuPct), + cpu_percent_one_core_avg: round(((last.cpuTicks - first.cpuTicks) / clockTicksPerSec) / seconds * 100), + rss_kib: summarize(samples.map(s => s.rssKiB)), + rss_high_water_kib: Math.max(...samples.map(s => s.peakRssKiB).filter(Number.isFinite)), + disk_write_bytes: last.writeBytes - first.writeBytes, + disk_write_bytes_per_sec: round((last.writeBytes - first.writeBytes) / seconds), + threads_max: Math.max(...samples.map(s => s.threads).filter(Number.isFinite)), + fds_max: Math.max(...samples.map(s => s.fds).filter(Number.isFinite)), + }; +} + +/** Relative difference |a-b| / mean(a,b) in percent; null if undefined. */ +export function relativeDifferencePct(a, b) { + if (!Number.isFinite(a) || !Number.isFinite(b)) return null; + const mean = (a + b) / 2; + if (mean === 0) return a === b ? 0 : null; + return Math.abs(a - b) / Math.abs(mean) * 100; +} + +/** Fetch a dotted path such as "latency.segment_interval_ms.p95". */ +export function pick(obj, dotted) { + return dotted.split('.').reduce((o, k) => (o == null ? undefined : o[k]), obj); +} + +/** Headline p95 series that C4 repeatability and the X2 gate are evaluated on. */ +export const HEADLINE_P95 = Object.freeze([ + 'latency.segment_interval_ms.p95', + 'latency.db_probe_ms.p95', +]); + +/** + * C4 repeatability: two runs on the same host and mode must have p95 variance + * under maxPct on every headline series. + */ +export function repeatability(runA, runB, {maxPct = 2, keys = HEADLINE_P95} = {}) { + const checks = keys.map(key => { + const a = pick(runA, key), b = pick(runB, key); + const variancePct = relativeDifferencePct(a, b); + return {key, a, b, variance_pct: variancePct == null ? null : Math.round(variancePct * 1000) / 1000, + pass: variancePct != null && variancePct < maxPct}; + }); + return {max_pct: maxPct, pass: checks.every(c => c.pass), checks}; +} + +/** + * X2 gate (PRD §8): candidate (evidence enabled) vs baseline (disabled), same + * host/workload. No additional recording gaps; each headline p95 rises by at + * most maxIncreasePct. + */ +export function releaseGate(baseline, candidate, {maxIncreasePct = 5, keys = HEADLINE_P95} = {}) { + const checks = keys.map(key => { + const b = pick(baseline, key), c = pick(candidate, key); + const raw = Number.isFinite(b) && Number.isFinite(c) && b > 0 ? (c - b) / b * 100 : null; + const increasePct = raw == null ? null : Math.round(raw * 1000) / 1000; // avoid 5.000000001 > 5 + return {key, baseline: b, candidate: c, increase_pct: increasePct, pass: increasePct != null && increasePct <= maxIncreasePct}; + }); + for (const key of ['gaps.metrics_recording_gaps_total', 'gaps.catalog_gaps_total', 'gaps.segment_interval_gaps']) { + const b = pick(baseline, key), c = pick(candidate, key); + checks.push({key, baseline: b, candidate: c, pass: Number.isFinite(b) && Number.isFinite(c) && c <= b}); + } + return {max_increase_pct: maxIncreasePct, pass: checks.every(c => c.pass), checks}; +} + +/** Workloads must match for a comparison to be meaningful. */ +export function workloadFingerprint(result) { + const w = result.workload || {}; + return JSON.stringify({ + streams: w.streams, width: w.width, height: w.height, fps: w.fps, gop: w.gop, + segment_seconds: w.segment_seconds, duration_seconds: w.duration_seconds, + detector: w.detector && {model_sha256: w.detector.model_sha256, interval_seconds: w.detector.interval_seconds}, + source_sha256: w.source_sha256, cpu: result.host?.cpu_model, cores: result.host?.logical_cores, + storage_device: result.host?.storage?.device, + }); +} + +// ---------- X2: evidence-enabled saturation gate (A11) ---------- + +/** Prometheus histogram {buckets: [{le, count}] cumulative and ascending, count, sum}; null when absent. */ +export function histogram(parsed, name) { + const rows = (parsed.get(`${name}_bucket`) || []) + .map(r => ({le: r.labels.le === '+Inf' ? Infinity : Number(r.labels.le), count: r.value})) + .filter(b => !Number.isNaN(b.le)).sort((a, b) => a.le - b.le); + if (rows.length === 0) return null; + return {buckets: rows, count: sumMetric(parsed, `${name}_count`) ?? rows.at(-1).count, sum: sumMetric(parsed, `${name}_sum`)}; +} + +/** end - start, bucket by bucket (a measurement window of a cumulative histogram). */ +export function histogramDelta(start, end) { + if (!end) return null; + if (!start) return end; + const before = new Map(start.buckets.map(b => [b.le, b.count])); + return { + buckets: end.buckets.map(b => ({le: b.le, count: b.count - (before.get(b.le) ?? 0)})), + count: end.count - start.count, + sum: Number.isFinite(end.sum) && Number.isFinite(start.sum) ? end.sum - start.sum : null, + }; +} + +/** Upper bound of the first bucket holding quantile q (0..1): a conservative estimate. */ +export function histogramUpperQuantile(h, q) { + if (!h || !(h.count > 0)) return null; + const target = q * h.count; + for (const b of h.buckets) if (b.count >= target) return b.le; + return Infinity; +} + +const DROP_REASONS = ['queue_full', 'budget_exhausted', 'disabled', 'untracked']; +const FAILURE_REASONS = ['encode_failed', 'write_failed', 'db_error', 'disabled', 'interrupted']; +const SUBMIT_HISTOGRAM = 'lightnvr_observation_submit_latency_seconds'; + +/** + * L10 observation families over a measurement window: counter deltas + * (start..end), per-process-run high-water gauges and configured limits (end), + * and gauge maxima over every scrape. Null when the binary has no L10 metrics. + */ +export function observationFeatureSummary(start, end, samples = []) { + if (!end.get('lightnvr_observation_accepted_total')) return null; + const at = (p, name, filter) => sumMetric(p, name, filter); + const delta = (name, filter) => (at(end, name, filter) ?? NaN) - (at(start, name, filter) ?? 0); + const all = [start, ...samples, end]; + const maxOf = name => { const v = all.map(p => at(p, name)).filter(Number.isFinite); return v.length ? Math.max(...v) : null; }; + const scoped = (name, scope) => at(end, name, l => l.scope === scope); + const latency = histogramDelta(histogram(start, SUBMIT_HISTOGRAM), histogram(end, SUBMIT_HISTOGRAM)); + const round = v => (Number.isFinite(v) ? Math.round(v * 1e9) / 1e9 : v ?? null); + const leText = le => (Number.isFinite(le) ? le : '+Inf'); + return { + accepted: delta('lightnvr_observation_accepted_total'), + dropped: Object.fromEntries(DROP_REASONS.map(r => [r, delta('lightnvr_observation_dropped_total', l => l.reason === r)])), + ready: delta('lightnvr_observation_evidence_ready_total'), + failures: Object.fromEntries(FAILURE_REASONS.map(r => [r, delta('lightnvr_observation_evidence_failures_total', l => l.reason === r)])), + unavailable_submitted: delta('lightnvr_observation_evidence_unavailable_total'), + bytes_written: delta('lightnvr_observation_evidence_bytes_written_total'), + high_water_items: at(end, 'lightnvr_observation_evidence_high_water_items'), + high_water_bytes: at(end, 'lightnvr_observation_evidence_high_water_bytes'), + camera_high_water_items: at(end, 'lightnvr_observation_evidence_camera_high_water_items'), + limits: { + items: scoped('lightnvr_observation_evidence_limit_items', 'global'), + camera_items: scoped('lightnvr_observation_evidence_limit_items', 'camera'), + bytes: scoped('lightnvr_observation_evidence_limit_bytes', 'global'), + camera_bytes: scoped('lightnvr_observation_evidence_limit_bytes', 'camera'), + discard_bytes: scoped('lightnvr_observation_evidence_limit_bytes', 'discard'), + }, + discards: { + deferred: delta('lightnvr_observation_evidence_discards_total', l => l.mode === 'deferred'), + inline: delta('lightnvr_observation_evidence_discards_total', l => l.mode === 'inline'), + }, + discard_pending_bytes_max: maxOf('lightnvr_observation_evidence_discard_pending_bytes'), + backlog_items_max: maxOf('lightnvr_observation_evidence_backlog_items'), + backlog_bytes_max: maxOf('lightnvr_observation_evidence_backlog_bytes'), + drop_ring_depth_max: maxOf('lightnvr_observation_evidence_drop_ring_depth'), + budget_exhausted_cameras_max: maxOf('lightnvr_observation_evidence_budget_exhausted_cameras'), + relay_lag_seconds_max: maxOf('lightnvr_observation_relay_lag_seconds'), + queue_allocated_end: at(end, 'lightnvr_observation_evidence_queue_allocated'), + worker_running_end: at(end, 'lightnvr_observation_evidence_worker_running'), + retained_bytes_by_camera_end: metricByLabel(end, 'lightnvr_observation_evidence_retained_bytes', 'camera_uuid'), + budget_bytes_by_camera_end: metricByLabel(end, 'lightnvr_observation_evidence_budget_bytes', 'camera_uuid'), + history_observations_end: at(end, 'lightnvr_observation_history_observations'), + publications_end: metricByLabel(end, 'lightnvr_observation_publications', 'state'), + submit_latency: latency && { + count: latency.count, + sum_seconds: round(latency.sum), + mean_seconds: latency.count > 0 && Number.isFinite(latency.sum) ? round(latency.sum / latency.count) : null, + max_seconds: at(end, 'lightnvr_observation_submit_latency_max_seconds'), + p50_upper_seconds: leText(histogramUpperQuantile(latency, 0.5)), + p99_upper_seconds: leText(histogramUpperQuantile(latency, 0.99)), + p999_upper_seconds: leText(histogramUpperQuantile(latency, 0.999)), + buckets: latency.buckets.map(b => ({le: leText(b.le), count: b.count})), + max_note: 'max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta', + }, + }; +} + +/** Contract §4.3 bounded reasons, by asset state. */ +export const ASSET_REASONS = Object.freeze({ + unavailable: ['queue_full', 'budget_exhausted', 'encode_failed', 'write_failed', 'interrupted', 'no_matching_media', 'disabled'], + expired: ['retention_expired', 'deleted'], +}); + +/** rows: [{state, reason, n}] from observation_assets GROUP BY state, reason. */ +export function assetReasonSummary(rows) { + const byStateReason = {}; + let withoutReason = 0; + const unknown = new Set(); + for (const {state, reason, n} of rows) { + byStateReason[reason ? `${state}:${reason}` : state] = (byStateReason[reason ? `${state}:${reason}` : state] || 0) + n; + if (ASSET_REASONS[state]) { + if (!reason) withoutReason += n; + else if (!ASSET_REASONS[state].includes(reason)) unknown.add(reason); + } + } + return {by_state_reason: byStateReason, unavailable_or_expired_without_reason: withoutReason, unknown_reasons: [...unknown].sort()}; +} + +/** + * X2 A11 evidence checks on an enabled run (the recording-path checks are + * releaseGate): detections and evidence really happened, queue limits held, + * every drop carries a bounded reason (none untracked, the DB holds at least + * the window's drops by reason), no post-admission failures, and the producer + * submit never exceeded maxSubmitSeconds (it never waits on I/O). + * requireSaturation additionally demands that admission refused work. + */ +export function evidenceGate(baseline, candidate, {requireSaturation = false, maxSubmitSeconds = 0.001} = {}) { + const f = candidate.feature_metrics || {}; + const lim = f.limits || {}; + const assets = candidate.feature_db?.assets || {by_state_reason: {}}; + const le = (v, limit) => Number.isFinite(v) && Number.isFinite(limit) && v <= limit; + const checks = []; + const add = (key, pass, value, limit) => checks.push({key, value: value ?? null, limit: limit ?? null, pass: Boolean(pass)}); + const b = baseline.feature_metrics; + add('baseline.feature_inactive', b && b.accepted === 0 && !b.queue_allocated_end, b && {accepted: b.accepted, queue_allocated: b.queue_allocated_end}); + add('evidence.detections', candidate.detector?.detections_in_window > 0, candidate.detector?.detections_in_window); + add('evidence.ready', f.accepted > 0 && f.ready > 0, {accepted: f.accepted, ready: f.ready}); + add('evidence.db_observations', candidate.feature_db?.observations > 0, candidate.feature_db?.observations); + add('limits.items', le(f.high_water_items, lim.items) && le(f.backlog_items_max, lim.items), {high_water: f.high_water_items, backlog_max: f.backlog_items_max}, lim.items); + add('limits.bytes', le(f.high_water_bytes, lim.bytes) && le(f.backlog_bytes_max ?? 0, lim.bytes), {high_water: f.high_water_bytes, backlog_max: f.backlog_bytes_max}, lim.bytes); + add('limits.camera_items', le(f.camera_high_water_items, lim.camera_items), f.camera_high_water_items, lim.camera_items); + add('drops.untracked', f.dropped?.untracked === 0, f.dropped?.untracked, 0); + const recorded = ['queue_full', 'budget_exhausted'].every(r => (assets.by_state_reason[`unavailable:${r}`] || 0) >= (f.dropped?.[r] ?? Infinity)); + add('drops.reasons_recorded', recorded && assets.unavailable_or_expired_without_reason === 0 && (assets.unknown_reasons || []).length === 0, + {window_drops: f.dropped, db: assets.by_state_reason, without_reason: assets.unavailable_or_expired_without_reason, unknown: assets.unknown_reasons}); + const failed = f.failures ? f.failures.encode_failed + f.failures.write_failed + f.failures.db_error : NaN; + add('evidence.failures', failed === 0, f.failures, 0); + add('submit.count', f.submit_latency?.count > 0, f.submit_latency?.count); + add('submit.max_seconds', Number.isFinite(f.submit_latency?.max_seconds) && f.submit_latency.max_seconds < maxSubmitSeconds, + f.submit_latency?.max_seconds, maxSubmitSeconds); + if (requireSaturation) add('saturation.queue_full_drops', f.dropped?.queue_full > 0, f.dropped?.queue_full); + return {require_saturation: requireSaturation, max_submit_seconds: maxSubmitSeconds, pass: checks.every(c => c.pass), checks}; +} + +/** + * Pool one raw sample series (results.samples[series]) across several runs per + * mode and compare percentile q: a steadier view than any single pair when + * host load drifts between runs (X2 used it for the advisory DB probe). + */ +export function pooledComparison(baselineRuns, candidateRuns, series = 'db_probe_ms', q = 95, maxIncreasePct = 5) { + const pool = runs => runs.flatMap(r => r.samples?.[series] || []).filter(Number.isFinite); + const b = pool(baselineRuns), c = pool(candidateRuns); + const pb = percentile(b, q), pc = percentile(c, q); + const raw = Number.isFinite(pb) && Number.isFinite(pc) && pb > 0 ? (pc - pb) / pb * 100 : null; + const increasePct = raw == null ? null : Math.round(raw * 1000) / 1000; + return {series, percentile: q, max_increase_pct: maxIncreasePct, + baseline: {runs: baselineRuns.length, ...summarize(b)}, candidate: {runs: candidateRuns.length, ...summarize(c)}, + [`baseline_p${q}`]: pb, [`candidate_p${q}`]: pc, increase_pct: increasePct, + pass: increasePct != null && increasePct <= maxIncreasePct}; +} diff --git a/tests/performance/observations/results/2026-11-08-disabled-repeatability.json b/tests/performance/observations/results/2026-11-08-disabled-repeatability.json new file mode 100644 index 000000000..532e6ef7c --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-disabled-repeatability.json @@ -0,0 +1,21 @@ +{ + "max_pct": 2, + "pass": false, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "a": 10035.298, + "b": 10037.886, + "variance_pct": 0.026, + "pass": true + }, + { + "key": "latency.db_probe_ms.p95", + "a": 11.954, + "b": 12.321, + "variance_pct": 3.024, + "pass": false + } + ], + "same_workload": true +} diff --git a/tests/performance/observations/results/2026-11-08-disabled-run1.json b/tests/performance/observations/results/2026-11-08-disabled-run1.json new file mode 100644 index 000000000..d87f103d2 --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-disabled-run1.json @@ -0,0 +1,4427 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "disabled", + "status": "completed", + "started_at": "2026-11-08T13:17:14.045Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 3.9, + 5.79, + 5.11 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "3c61e2352e053058d7090918fc520ed518d26f8c", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "91863e3b60104ef9eef8762c93c3ad29f33a9e89a5d1673c26af6cb0831cb9ed", + "binary": "build/int01-c4-release/bin/lightnvr", + "binary_sha256": "10ee4b926268599adea5415e768cadd6a288a7699f93856ab39d4f00121be381", + "harness_sha256": "ae926aa53e061acaca4971b71a55a82a1f589c15c2fedaef0e1aeecc2576edc6", + "metrics_sha256": "cd66a4fb252cb0b7066233d1983ba72bb5743aa6aee807c88ef34d68e8292a13" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 300, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "synthetic: ffmpeg lavfi testsrc2, 30 s clip, published into go2rtc by one always-on ffmpeg -re -stream_loop -1 -c copy per stream", + "source_sha256": "a5b16133b02d8bd0121e9865e5eefaa975f7e9caffb5ffe6d4f3922dcda0e92d", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": {}, + "config_extra": null + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-tu3fQd", + "detector": { + "inferences_in_window": 1204, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 0, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "latency": { + "segment_interval_ms": { + "count": 120, + "min": 9951.579, + "p50": 10000.895, + "p95": 10035.298, + "p99": 10040.242, + "max": 10041.762, + "mean": 9998.814 + }, + "segment_interval_excess_ms": { + "count": 120, + "min": -48.421, + "p50": 0.895, + "p95": 35.298, + "p99": 40.242, + "max": 41.762, + "mean": -1.186 + }, + "db_probe_ms": { + "count": 1160, + "min": 6.914, + "p50": 8.005, + "p95": 11.954, + "p99": 15.809, + "max": 25.244, + "mean": 8.605 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 30, + "min": 9958.116, + "p50": 10000.223, + "p95": 10033.025, + "p99": 10034.943, + "max": 10035.296, + "mean": 9998.692 + }, + "Load1": { + "count": 30, + "min": 9956.501, + "p50": 10001.605, + "p95": 10036.685, + "p99": 10039.795, + "max": 10040.357, + "mean": 9999.633 + }, + "Load2": { + "count": 30, + "min": 9957.612, + "p50": 10001.378, + "p95": 10035.498, + "p99": 10041.18, + "max": 10041.762, + "mean": 9998.638 + }, + "Load3": { + "count": 30, + "min": 9951.579, + "p50": 10000.216, + "p95": 10033.092, + "p99": 10036.609, + "max": 10037.132, + "mean": 9998.293 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 120, + "expected_segments_in_window": 120, + "metrics_segments_delta": 120, + "metrics_bytes_written_delta": 679473680, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 124, + "fps_samples": { + "count": 240, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.1, + "mean": 15.1 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 299.76, + "cpu_percent_one_core": { + "count": 257, + "min": 105.666, + "p50": 133.157, + "p95": 191.938, + "p99": 205.431, + "max": 224.76, + "mean": 143.702 + }, + "cpu_percent_one_core_avg": 144.162, + "rss_kib": { + "count": 258, + "min": 830692, + "p50": 833832, + "p95": 858174.4, + "p99": 858448.12, + "max": 860900, + "mean": 840543.194 + }, + "rss_high_water_kib": 875932, + "disk_write_bytes": 697847808, + "disk_write_bytes_per_sec": 2328019.891, + "threads_max": 33, + "fds_max": 37 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.681, + "processes": 5 + }, + "storage_bytes_end": 750615594, + "loadavg_at_end": [ + 3.62, + 4.16, + 4.57 + ] + }, + "window": { + "start": "2026-11-08T13:17:53.901Z", + "end": "2026-11-08T13:22:54.967Z", + "seconds": 301.07 + }, + "samples": { + "proc": [ + { + "tMs": 9, + "cpuTicks": 4349, + "rssKiB": 853564, + "peakRssKiB": 866280, + "threads": 33, + "fds": 35, + "writeBytes": 69464064 + }, + { + "tMs": 1184, + "cpuTicks": 4525, + "rssKiB": 830692, + "peakRssKiB": 866280, + "threads": 33, + "fds": 35, + "writeBytes": 72134656 + }, + { + "tMs": 2321, + "cpuTicks": 4650, + "rssKiB": 831284, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 74657792 + }, + { + "tMs": 3448, + "cpuTicks": 4788, + "rssKiB": 831384, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 77910016 + }, + { + "tMs": 4587, + "cpuTicks": 4924, + "rssKiB": 831684, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 80379904 + }, + { + "tMs": 5723, + "cpuTicks": 5059, + "rssKiB": 832084, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 82968576 + }, + { + "tMs": 6903, + "cpuTicks": 5252, + "rssKiB": 856452, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 85688320 + }, + { + "tMs": 8065, + "cpuTicks": 5451, + "rssKiB": 856632, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 88276992 + }, + { + "tMs": 9197, + "cpuTicks": 5600, + "rssKiB": 832488, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 90886144 + }, + { + "tMs": 10321, + "cpuTicks": 5735, + "rssKiB": 832792, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 93573120 + }, + { + "tMs": 11442, + "cpuTicks": 5868, + "rssKiB": 832812, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 96071680 + }, + { + "tMs": 12574, + "cpuTicks": 6015, + "rssKiB": 832840, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 98586624 + }, + { + "tMs": 13718, + "cpuTicks": 6151, + "rssKiB": 832860, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 101830656 + }, + { + "tMs": 14910, + "cpuTicks": 6351, + "rssKiB": 857224, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 104599552 + }, + { + "tMs": 16075, + "cpuTicks": 6562, + "rssKiB": 857548, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 107044864 + }, + { + "tMs": 17230, + "cpuTicks": 6721, + "rssKiB": 833392, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 109580288 + }, + { + "tMs": 18363, + "cpuTicks": 6854, + "rssKiB": 833220, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 112177152 + }, + { + "tMs": 19483, + "cpuTicks": 6998, + "rssKiB": 833224, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 114757632 + }, + { + "tMs": 20596, + "cpuTicks": 7129, + "rssKiB": 833272, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 117350400 + }, + { + "tMs": 21733, + "cpuTicks": 7271, + "rssKiB": 833280, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 119767040 + }, + { + "tMs": 22919, + "cpuTicks": 7471, + "rssKiB": 857452, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 123187200 + }, + { + "tMs": 24086, + "cpuTicks": 7684, + "rssKiB": 857228, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 125755392 + }, + { + "tMs": 25243, + "cpuTicks": 7839, + "rssKiB": 832828, + "peakRssKiB": 872084, + "threads": 33, + "fds": 35, + "writeBytes": 128507904 + }, + { + "tMs": 26388, + "cpuTicks": 7980, + "rssKiB": 833056, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 131072000 + }, + { + "tMs": 27530, + "cpuTicks": 8125, + "rssKiB": 832828, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 133656576 + }, + { + "tMs": 28694, + "cpuTicks": 8306, + "rssKiB": 833056, + "peakRssKiB": 872896, + "threads": 33, + "fds": 36, + "writeBytes": 136257536 + }, + { + "tMs": 30016, + "cpuTicks": 8603, + "rssKiB": 857588, + "peakRssKiB": 872896, + "threads": 33, + "fds": 36, + "writeBytes": 139235328 + }, + { + "tMs": 31303, + "cpuTicks": 8888, + "rssKiB": 833432, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 142053376 + }, + { + "tMs": 32448, + "cpuTicks": 9030, + "rssKiB": 833432, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 144773120 + }, + { + "tMs": 33602, + "cpuTicks": 9192, + "rssKiB": 833560, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 147968000 + }, + { + "tMs": 34734, + "cpuTicks": 9342, + "rssKiB": 833560, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 150413312 + }, + { + "tMs": 35934, + "cpuTicks": 9554, + "rssKiB": 857312, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 153309184 + }, + { + "tMs": 37101, + "cpuTicks": 9767, + "rssKiB": 857408, + "peakRssKiB": 872896, + "threads": 33, + "fds": 35, + "writeBytes": 155910144 + }, + { + "tMs": 38247, + "cpuTicks": 9926, + "rssKiB": 833140, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 158609408 + }, + { + "tMs": 39393, + "cpuTicks": 10071, + "rssKiB": 833236, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 161136640 + }, + { + "tMs": 40526, + "cpuTicks": 10214, + "rssKiB": 833260, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 163807232 + }, + { + "tMs": 41710, + "cpuTicks": 10361, + "rssKiB": 833260, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 166432768 + }, + { + "tMs": 42875, + "cpuTicks": 10550, + "rssKiB": 857432, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 169771008 + }, + { + "tMs": 44051, + "cpuTicks": 10772, + "rssKiB": 857432, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 172437504 + }, + { + "tMs": 45203, + "cpuTicks": 10939, + "rssKiB": 833496, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 174981120 + }, + { + "tMs": 46356, + "cpuTicks": 11084, + "rssKiB": 833668, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 177430528 + }, + { + "tMs": 47487, + "cpuTicks": 11233, + "rssKiB": 833668, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 179978240 + }, + { + "tMs": 48632, + "cpuTicks": 11366, + "rssKiB": 833668, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 182579200 + }, + { + "tMs": 49764, + "cpuTicks": 11514, + "rssKiB": 833668, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 185147392 + }, + { + "tMs": 50977, + "cpuTicks": 11735, + "rssKiB": 858056, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 188055552 + }, + { + "tMs": 52140, + "cpuTicks": 11942, + "rssKiB": 833752, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 190504960 + }, + { + "tMs": 53295, + "cpuTicks": 12090, + "rssKiB": 833884, + "peakRssKiB": 873472, + "threads": 33, + "fds": 36, + "writeBytes": 193720320 + }, + { + "tMs": 54449, + "cpuTicks": 12235, + "rssKiB": 833752, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 196468736 + }, + { + "tMs": 55605, + "cpuTicks": 12366, + "rssKiB": 833752, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 199053312 + }, + { + "tMs": 56749, + "cpuTicks": 12500, + "rssKiB": 833884, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 201617408 + }, + { + "tMs": 57940, + "cpuTicks": 12706, + "rssKiB": 857924, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 204451840 + }, + { + "tMs": 59100, + "cpuTicks": 12896, + "rssKiB": 857924, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 206999552 + }, + { + "tMs": 60246, + "cpuTicks": 13038, + "rssKiB": 833764, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 209641472 + }, + { + "tMs": 61388, + "cpuTicks": 13185, + "rssKiB": 833764, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 212160512 + }, + { + "tMs": 62526, + "cpuTicks": 13321, + "rssKiB": 833896, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 214724608 + }, + { + "tMs": 63668, + "cpuTicks": 13474, + "rssKiB": 833732, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 217919488 + }, + { + "tMs": 64837, + "cpuTicks": 13639, + "rssKiB": 857728, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 220516352 + }, + { + "tMs": 66037, + "cpuTicks": 13860, + "rssKiB": 857732, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 223236096 + }, + { + "tMs": 67198, + "cpuTicks": 14028, + "rssKiB": 833560, + "peakRssKiB": 873472, + "threads": 33, + "fds": 35, + "writeBytes": 225951744 + }, + { + "tMs": 68356, + "cpuTicks": 14156, + "rssKiB": 833560, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 228519936 + }, + { + "tMs": 69489, + "cpuTicks": 14286, + "rssKiB": 833560, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 231100416 + }, + { + "tMs": 70620, + "cpuTicks": 14435, + "rssKiB": 833356, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 233758720 + }, + { + "tMs": 71763, + "cpuTicks": 14571, + "rssKiB": 833580, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 236208128 + }, + { + "tMs": 72956, + "cpuTicks": 14784, + "rssKiB": 857752, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 239833088 + }, + { + "tMs": 74125, + "cpuTicks": 14979, + "rssKiB": 833580, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 242520064 + }, + { + "tMs": 75282, + "cpuTicks": 15114, + "rssKiB": 833592, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 245043200 + }, + { + "tMs": 76436, + "cpuTicks": 15250, + "rssKiB": 833368, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 247508992 + }, + { + "tMs": 77582, + "cpuTicks": 15384, + "rssKiB": 833368, + "peakRssKiB": 874392, + "threads": 33, + "fds": 36, + "writeBytes": 250167296 + }, + { + "tMs": 78718, + "cpuTicks": 15523, + "rssKiB": 833592, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 252805120 + }, + { + "tMs": 79936, + "cpuTicks": 15728, + "rssKiB": 857764, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 255606784 + }, + { + "tMs": 81090, + "cpuTicks": 15924, + "rssKiB": 857764, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 258134016 + }, + { + "tMs": 82238, + "cpuTicks": 16072, + "rssKiB": 833368, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 260714496 + }, + { + "tMs": 83372, + "cpuTicks": 16215, + "rssKiB": 833592, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 263954432 + }, + { + "tMs": 84512, + "cpuTicks": 16357, + "rssKiB": 833592, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 266514432 + }, + { + "tMs": 85640, + "cpuTicks": 16495, + "rssKiB": 833592, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 269111296 + }, + { + "tMs": 86751, + "cpuTicks": 16637, + "rssKiB": 833592, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 271548416 + }, + { + "tMs": 87955, + "cpuTicks": 16850, + "rssKiB": 857764, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 274382848 + }, + { + "tMs": 89115, + "cpuTicks": 17069, + "rssKiB": 857764, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 277041152 + }, + { + "tMs": 90252, + "cpuTicks": 17228, + "rssKiB": 833624, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 279547904 + }, + { + "tMs": 91375, + "cpuTicks": 17376, + "rssKiB": 833624, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 282103808 + }, + { + "tMs": 92504, + "cpuTicks": 17521, + "rssKiB": 833624, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 284520448 + }, + { + "tMs": 93622, + "cpuTicks": 17679, + "rssKiB": 833624, + "peakRssKiB": 874392, + "threads": 33, + "fds": 36, + "writeBytes": 287698944 + }, + { + "tMs": 94740, + "cpuTicks": 17831, + "rssKiB": 833400, + "peakRssKiB": 874392, + "threads": 33, + "fds": 35, + "writeBytes": 290148352 + }, + { + "tMs": 95946, + "cpuTicks": 18052, + "rssKiB": 857808, + "peakRssKiB": 874392, + "threads": 33, + "fds": 36, + "writeBytes": 293015552 + }, + { + "tMs": 97126, + "cpuTicks": 18268, + "rssKiB": 833636, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 295776256 + }, + { + "tMs": 98261, + "cpuTicks": 18419, + "rssKiB": 833636, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 298340352 + }, + { + "tMs": 99396, + "cpuTicks": 18566, + "rssKiB": 833636, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 300916736 + }, + { + "tMs": 100520, + "cpuTicks": 18717, + "rssKiB": 833644, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 303583232 + }, + { + "tMs": 101646, + "cpuTicks": 18857, + "rssKiB": 833644, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 305967104 + }, + { + "tMs": 102776, + "cpuTicks": 19011, + "rssKiB": 846124, + "peakRssKiB": 875008, + "threads": 33, + "fds": 32, + "writeBytes": 308609024 + }, + { + "tMs": 104017, + "cpuTicks": 19254, + "rssKiB": 857816, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 312041472 + }, + { + "tMs": 105199, + "cpuTicks": 19458, + "rssKiB": 833652, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 314728448 + }, + { + "tMs": 106381, + "cpuTicks": 19609, + "rssKiB": 833428, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 317329408 + }, + { + "tMs": 107543, + "cpuTicks": 19771, + "rssKiB": 833652, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 319881216 + }, + { + "tMs": 108693, + "cpuTicks": 19929, + "rssKiB": 833428, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 322478080 + }, + { + "tMs": 109889, + "cpuTicks": 20108, + "rssKiB": 857600, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 325296128 + }, + { + "tMs": 111082, + "cpuTicks": 20331, + "rssKiB": 857604, + "peakRssKiB": 875008, + "threads": 33, + "fds": 36, + "writeBytes": 327950336 + }, + { + "tMs": 112257, + "cpuTicks": 20488, + "rssKiB": 833204, + "peakRssKiB": 875008, + "threads": 33, + "fds": 36, + "writeBytes": 330539008 + }, + { + "tMs": 113419, + "cpuTicks": 20643, + "rssKiB": 833432, + "peakRssKiB": 875008, + "threads": 33, + "fds": 36, + "writeBytes": 333774848 + }, + { + "tMs": 114593, + "cpuTicks": 20794, + "rssKiB": 833432, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 336474112 + }, + { + "tMs": 115748, + "cpuTicks": 20938, + "rssKiB": 833436, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 339079168 + }, + { + "tMs": 116955, + "cpuTicks": 21139, + "rssKiB": 857964, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 341897216 + }, + { + "tMs": 118122, + "cpuTicks": 21324, + "rssKiB": 833792, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 344645632 + }, + { + "tMs": 119300, + "cpuTicks": 21459, + "rssKiB": 833792, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 347164672 + }, + { + "tMs": 120470, + "cpuTicks": 21599, + "rssKiB": 833792, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 349782016 + }, + { + "tMs": 121626, + "cpuTicks": 21793, + "rssKiB": 833792, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 352333824 + }, + { + "tMs": 122833, + "cpuTicks": 21956, + "rssKiB": 857832, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 355872768 + }, + { + "tMs": 124033, + "cpuTicks": 22187, + "rssKiB": 857832, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 358453248 + }, + { + "tMs": 125207, + "cpuTicks": 22391, + "rssKiB": 833664, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 361115648 + }, + { + "tMs": 126352, + "cpuTicks": 22547, + "rssKiB": 833664, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 363716608 + }, + { + "tMs": 127506, + "cpuTicks": 22692, + "rssKiB": 833664, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 366391296 + }, + { + "tMs": 128669, + "cpuTicks": 22839, + "rssKiB": 833664, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 368947200 + }, + { + "tMs": 129850, + "cpuTicks": 23006, + "rssKiB": 857836, + "peakRssKiB": 875008, + "threads": 33, + "fds": 36, + "writeBytes": 371838976 + }, + { + "tMs": 131057, + "cpuTicks": 23229, + "rssKiB": 857840, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 374591488 + }, + { + "tMs": 132230, + "cpuTicks": 23390, + "rssKiB": 833668, + "peakRssKiB": 875008, + "threads": 33, + "fds": 35, + "writeBytes": 377044992 + }, + { + "tMs": 133388, + "cpuTicks": 23539, + "rssKiB": 833668, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 380407808 + }, + { + "tMs": 134546, + "cpuTicks": 23682, + "rssKiB": 833576, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 382959616 + }, + { + "tMs": 135692, + "cpuTicks": 23847, + "rssKiB": 833672, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 385449984 + }, + { + "tMs": 136863, + "cpuTicks": 24017, + "rssKiB": 857844, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 388169728 + }, + { + "tMs": 138060, + "cpuTicks": 24246, + "rssKiB": 857844, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 390881280 + }, + { + "tMs": 139242, + "cpuTicks": 24410, + "rssKiB": 833804, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 393629696 + }, + { + "tMs": 140423, + "cpuTicks": 24556, + "rssKiB": 833808, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 396238848 + }, + { + "tMs": 141637, + "cpuTicks": 24717, + "rssKiB": 833676, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 398819328 + }, + { + "tMs": 142832, + "cpuTicks": 24904, + "rssKiB": 857624, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 402243584 + }, + { + "tMs": 144032, + "cpuTicks": 25128, + "rssKiB": 857848, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 404938752 + }, + { + "tMs": 145185, + "cpuTicks": 25299, + "rssKiB": 833676, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 407633920 + }, + { + "tMs": 146338, + "cpuTicks": 25430, + "rssKiB": 833452, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 410255360 + }, + { + "tMs": 147483, + "cpuTicks": 25557, + "rssKiB": 833452, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 412852224 + }, + { + "tMs": 148645, + "cpuTicks": 25706, + "rssKiB": 833716, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 415596544 + }, + { + "tMs": 149829, + "cpuTicks": 25857, + "rssKiB": 857888, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 418164736 + }, + { + "tMs": 151045, + "cpuTicks": 26104, + "rssKiB": 857932, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 420970496 + }, + { + "tMs": 152213, + "cpuTicks": 26267, + "rssKiB": 833760, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 423542784 + }, + { + "tMs": 153364, + "cpuTicks": 26408, + "rssKiB": 833760, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 426901504 + }, + { + "tMs": 154515, + "cpuTicks": 26558, + "rssKiB": 833760, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 429498368 + }, + { + "tMs": 155692, + "cpuTicks": 26697, + "rssKiB": 833760, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 432119808 + }, + { + "tMs": 156921, + "cpuTicks": 26908, + "rssKiB": 857932, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 435060736 + }, + { + "tMs": 158106, + "cpuTicks": 27138, + "rssKiB": 857932, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 437768192 + }, + { + "tMs": 159285, + "cpuTicks": 27304, + "rssKiB": 833496, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 440373248 + }, + { + "tMs": 160456, + "cpuTicks": 27448, + "rssKiB": 833732, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 443162624 + }, + { + "tMs": 161602, + "cpuTicks": 27598, + "rssKiB": 833732, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 445644800 + }, + { + "tMs": 162742, + "cpuTicks": 27748, + "rssKiB": 833732, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 448159744 + }, + { + "tMs": 163966, + "cpuTicks": 27965, + "rssKiB": 857904, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 451645440 + }, + { + "tMs": 165128, + "cpuTicks": 28176, + "rssKiB": 857904, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 454275072 + }, + { + "tMs": 166277, + "cpuTicks": 28321, + "rssKiB": 833736, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 456773632 + }, + { + "tMs": 167428, + "cpuTicks": 28458, + "rssKiB": 833736, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 459321344 + }, + { + "tMs": 168577, + "cpuTicks": 28612, + "rssKiB": 833736, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 462065664 + }, + { + "tMs": 169755, + "cpuTicks": 28756, + "rssKiB": 833736, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 464633856 + }, + { + "tMs": 170971, + "cpuTicks": 28978, + "rssKiB": 857912, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 467509248 + }, + { + "tMs": 172163, + "cpuTicks": 29210, + "rssKiB": 833740, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 469995520 + }, + { + "tMs": 173322, + "cpuTicks": 29358, + "rssKiB": 833740, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 473374720 + }, + { + "tMs": 174456, + "cpuTicks": 29508, + "rssKiB": 833740, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 475971584 + }, + { + "tMs": 175600, + "cpuTicks": 29672, + "rssKiB": 833744, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 478560256 + }, + { + "tMs": 176750, + "cpuTicks": 29839, + "rssKiB": 833744, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 481128448 + }, + { + "tMs": 177984, + "cpuTicks": 30070, + "rssKiB": 857916, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 484110336 + }, + { + "tMs": 179130, + "cpuTicks": 30279, + "rssKiB": 857916, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 486662144 + }, + { + "tMs": 180295, + "cpuTicks": 30439, + "rssKiB": 833744, + "peakRssKiB": 875072, + "threads": 33, + "fds": 35, + "writeBytes": 489152512 + }, + { + "tMs": 181453, + "cpuTicks": 30581, + "rssKiB": 833744, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 491782144 + }, + { + "tMs": 182625, + "cpuTicks": 30728, + "rssKiB": 833744, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 494387200 + }, + { + "tMs": 183809, + "cpuTicks": 30904, + "rssKiB": 857916, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 497762304 + }, + { + "tMs": 185004, + "cpuTicks": 31141, + "rssKiB": 857916, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 500436992 + }, + { + "tMs": 186174, + "cpuTicks": 31349, + "rssKiB": 833780, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 503189504 + }, + { + "tMs": 187345, + "cpuTicks": 31493, + "rssKiB": 833780, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 505761792 + }, + { + "tMs": 188486, + "cpuTicks": 31625, + "rssKiB": 833780, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 508325888 + }, + { + "tMs": 189634, + "cpuTicks": 31759, + "rssKiB": 833556, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 510906368 + }, + { + "tMs": 190823, + "cpuTicks": 31909, + "rssKiB": 860900, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 513822720 + }, + { + "tMs": 192020, + "cpuTicks": 32134, + "rssKiB": 857952, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 516374528 + }, + { + "tMs": 193219, + "cpuTicks": 32344, + "rssKiB": 833556, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 519761920 + }, + { + "tMs": 194387, + "cpuTicks": 32491, + "rssKiB": 833688, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 522412032 + }, + { + "tMs": 195527, + "cpuTicks": 32651, + "rssKiB": 833556, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 524922880 + }, + { + "tMs": 196679, + "cpuTicks": 32801, + "rssKiB": 833556, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 527425536 + }, + { + "tMs": 197874, + "cpuTicks": 32979, + "rssKiB": 857952, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 530243584 + }, + { + "tMs": 199075, + "cpuTicks": 33207, + "rssKiB": 858128, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 532992000 + }, + { + "tMs": 200250, + "cpuTicks": 33396, + "rssKiB": 833780, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 535687168 + }, + { + "tMs": 201407, + "cpuTicks": 33552, + "rssKiB": 833780, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 538140672 + }, + { + "tMs": 202567, + "cpuTicks": 33700, + "rssKiB": 833956, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 540631040 + }, + { + "tMs": 203722, + "cpuTicks": 33845, + "rssKiB": 833956, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 544026624 + }, + { + "tMs": 204947, + "cpuTicks": 34062, + "rssKiB": 857952, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 546877440 + }, + { + "tMs": 206154, + "cpuTicks": 34307, + "rssKiB": 833956, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 549584896 + }, + { + "tMs": 207316, + "cpuTicks": 34465, + "rssKiB": 833956, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 552312832 + }, + { + "tMs": 208478, + "cpuTicks": 34623, + "rssKiB": 833956, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 554897408 + }, + { + "tMs": 209612, + "cpuTicks": 34759, + "rssKiB": 833956, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 557391872 + }, + { + "tMs": 210764, + "cpuTicks": 34899, + "rssKiB": 833992, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 559923200 + }, + { + "tMs": 211999, + "cpuTicks": 35132, + "rssKiB": 857988, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 562855936 + }, + { + "tMs": 213170, + "cpuTicks": 35321, + "rssKiB": 833816, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 566112256 + }, + { + "tMs": 214335, + "cpuTicks": 35462, + "rssKiB": 833992, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 568696832 + }, + { + "tMs": 215471, + "cpuTicks": 35599, + "rssKiB": 833992, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 571310080 + }, + { + "tMs": 216621, + "cpuTicks": 35745, + "rssKiB": 833816, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 573911040 + }, + { + "tMs": 217796, + "cpuTicks": 35889, + "rssKiB": 833816, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 576585728 + }, + { + "tMs": 219032, + "cpuTicks": 36134, + "rssKiB": 857988, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 579428352 + }, + { + "tMs": 220190, + "cpuTicks": 36312, + "rssKiB": 833816, + "peakRssKiB": 875152, + "threads": 33, + "fds": 35, + "writeBytes": 582160384 + }, + { + "tMs": 221359, + "cpuTicks": 36471, + "rssKiB": 834108, + "peakRssKiB": 875152, + "threads": 33, + "fds": 37, + "writeBytes": 584732672 + }, + { + "tMs": 222527, + "cpuTicks": 36625, + "rssKiB": 834108, + "peakRssKiB": 875152, + "threads": 33, + "fds": 37, + "writeBytes": 587366400 + }, + { + "tMs": 223695, + "cpuTicks": 36769, + "rssKiB": 834108, + "peakRssKiB": 875152, + "threads": 33, + "fds": 37, + "writeBytes": 590589952 + }, + { + "tMs": 224895, + "cpuTicks": 36957, + "rssKiB": 858280, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 593379328 + }, + { + "tMs": 226068, + "cpuTicks": 37160, + "rssKiB": 858284, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 595922944 + }, + { + "tMs": 227225, + "cpuTicks": 37323, + "rssKiB": 834112, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 598487040 + }, + { + "tMs": 228414, + "cpuTicks": 37458, + "rssKiB": 834112, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 601214976 + }, + { + "tMs": 229583, + "cpuTicks": 37600, + "rssKiB": 834112, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 603955200 + }, + { + "tMs": 230727, + "cpuTicks": 37731, + "rssKiB": 834112, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 606523392 + }, + { + "tMs": 231929, + "cpuTicks": 37933, + "rssKiB": 858268, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 609202176 + }, + { + "tMs": 233098, + "cpuTicks": 38143, + "rssKiB": 858496, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 612401152 + }, + { + "tMs": 234262, + "cpuTicks": 38285, + "rssKiB": 834324, + "peakRssKiB": 875152, + "threads": 33, + "fds": 36, + "writeBytes": 615129088 + }, + { + "tMs": 235414, + "cpuTicks": 38419, + "rssKiB": 834324, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 617709568 + }, + { + "tMs": 236582, + "cpuTicks": 38563, + "rssKiB": 834324, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 620425216 + }, + { + "tMs": 237741, + "cpuTicks": 38696, + "rssKiB": 834324, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 623026176 + }, + { + "tMs": 238977, + "cpuTicks": 38933, + "rssKiB": 858496, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 625975296 + }, + { + "tMs": 240158, + "cpuTicks": 39138, + "rssKiB": 834096, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 628424704 + }, + { + "tMs": 241317, + "cpuTicks": 39288, + "rssKiB": 834096, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 631115776 + }, + { + "tMs": 242464, + "cpuTicks": 39437, + "rssKiB": 834096, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 633683968 + }, + { + "tMs": 243614, + "cpuTicks": 39596, + "rssKiB": 833852, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 637042688 + }, + { + "tMs": 244771, + "cpuTicks": 39734, + "rssKiB": 833852, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 639709184 + }, + { + "tMs": 245986, + "cpuTicks": 39963, + "rssKiB": 858024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 642498560 + }, + { + "tMs": 247171, + "cpuTicks": 40167, + "rssKiB": 833852, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 645181440 + }, + { + "tMs": 248334, + "cpuTicks": 40304, + "rssKiB": 833852, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 647782400 + }, + { + "tMs": 249501, + "cpuTicks": 40447, + "rssKiB": 833852, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 650362880 + }, + { + "tMs": 250660, + "cpuTicks": 40584, + "rssKiB": 833852, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 653189120 + }, + { + "tMs": 251831, + "cpuTicks": 40740, + "rssKiB": 858252, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 655732736 + }, + { + "tMs": 253038, + "cpuTicks": 40974, + "rssKiB": 858252, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 659185664 + }, + { + "tMs": 254207, + "cpuTicks": 41143, + "rssKiB": 834000, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 661770240 + }, + { + "tMs": 255372, + "cpuTicks": 41277, + "rssKiB": 834000, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 664367104 + }, + { + "tMs": 256544, + "cpuTicks": 41431, + "rssKiB": 833904, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 666894336 + }, + { + "tMs": 257699, + "cpuTicks": 41575, + "rssKiB": 834000, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 669458432 + }, + { + "tMs": 258897, + "cpuTicks": 41773, + "rssKiB": 858172, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 672329728 + }, + { + "tMs": 260077, + "cpuTicks": 41993, + "rssKiB": 858172, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 675016704 + }, + { + "tMs": 261246, + "cpuTicks": 42164, + "rssKiB": 834000, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 677654528 + }, + { + "tMs": 262393, + "cpuTicks": 42309, + "rssKiB": 834224, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 680075264 + }, + { + "tMs": 263553, + "cpuTicks": 42457, + "rssKiB": 834216, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 683352064 + }, + { + "tMs": 264698, + "cpuTicks": 42598, + "rssKiB": 834216, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 685948928 + }, + { + "tMs": 265891, + "cpuTicks": 42778, + "rssKiB": 858164, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 688775168 + }, + { + "tMs": 267059, + "cpuTicks": 42987, + "rssKiB": 858164, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 691486720 + }, + { + "tMs": 268231, + "cpuTicks": 43161, + "rssKiB": 833992, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 694091776 + }, + { + "tMs": 269381, + "cpuTicks": 43309, + "rssKiB": 833992, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 696750080 + }, + { + "tMs": 270547, + "cpuTicks": 43455, + "rssKiB": 834140, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 699305984 + }, + { + "tMs": 271672, + "cpuTicks": 43593, + "rssKiB": 834140, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 701841408 + }, + { + "tMs": 272853, + "cpuTicks": 43758, + "rssKiB": 858084, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 705339392 + }, + { + "tMs": 274020, + "cpuTicks": 43965, + "rssKiB": 858004, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 707809280 + }, + { + "tMs": 275183, + "cpuTicks": 44155, + "rssKiB": 833832, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 710443008 + }, + { + "tMs": 276318, + "cpuTicks": 44288, + "rssKiB": 833832, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 713097216 + }, + { + "tMs": 277459, + "cpuTicks": 44429, + "rssKiB": 833832, + "peakRssKiB": 875932, + "threads": 33, + "fds": 37, + "writeBytes": 715665408 + }, + { + "tMs": 278623, + "cpuTicks": 44552, + "rssKiB": 833832, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 718229504 + }, + { + "tMs": 279805, + "cpuTicks": 44714, + "rssKiB": 858188, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 721084416 + }, + { + "tMs": 281035, + "cpuTicks": 44958, + "rssKiB": 858412, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 723845120 + }, + { + "tMs": 282203, + "cpuTicks": 45131, + "rssKiB": 834240, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 726433792 + }, + { + "tMs": 283371, + "cpuTicks": 45272, + "rssKiB": 834240, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 729657344 + }, + { + "tMs": 284529, + "cpuTicks": 45424, + "rssKiB": 834016, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 732352512 + }, + { + "tMs": 285668, + "cpuTicks": 45564, + "rssKiB": 834024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 734863360 + }, + { + "tMs": 286830, + "cpuTicks": 45730, + "rssKiB": 858196, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 737452032 + }, + { + "tMs": 288044, + "cpuTicks": 45953, + "rssKiB": 858196, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 740298752 + }, + { + "tMs": 289217, + "cpuTicks": 46125, + "rssKiB": 834024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 742899712 + }, + { + "tMs": 290380, + "cpuTicks": 46275, + "rssKiB": 833796, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 745574400 + }, + { + "tMs": 291533, + "cpuTicks": 46421, + "rssKiB": 833796, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 748085248 + }, + { + "tMs": 292659, + "cpuTicks": 46560, + "rssKiB": 833928, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 750555136 + }, + { + "tMs": 293844, + "cpuTicks": 46725, + "rssKiB": 858196, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 754069504 + }, + { + "tMs": 295083, + "cpuTicks": 46983, + "rssKiB": 857968, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 756809728 + }, + { + "tMs": 296240, + "cpuTicks": 47131, + "rssKiB": 834024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 759459840 + }, + { + "tMs": 297395, + "cpuTicks": 47275, + "rssKiB": 834024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 762040320 + }, + { + "tMs": 298604, + "cpuTicks": 47432, + "rssKiB": 834024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 764858368 + }, + { + "tMs": 299769, + "cpuTicks": 47563, + "rssKiB": 834024, + "peakRssKiB": 875932, + "threads": 33, + "fds": 36, + "writeBytes": 767311872 + } + ], + "db_probe_ms": [ + 8.736, + 8.886, + 7.512, + 11.594, + 7.859, + 8.529, + 7.401, + 13.491, + 7.967, + 7.598, + 8.475, + 10.351, + 7.688, + 7.18, + 8.834, + 11.861, + 7.737, + 7.825, + 7.322, + 8.401, + 7.401, + 7.111, + 7.705, + 8.719, + 7.405, + 7.28, + 7.647, + 8.013, + 7.62, + 7.508, + 7.389, + 7.554, + 7.892, + 7.069, + 10.913, + 10.014, + 7.633, + 7.9, + 11.397, + 8.387, + 8.035, + 7.664, + 8.783, + 7.566, + 7.417, + 8.004, + 8.134, + 8.749, + 7.683, + 8.428, + 9.396, + 7.627, + 7.023, + 7.605, + 9.203, + 7.179, + 6.959, + 7.358, + 9.529, + 7.47, + 7.194, + 7.597, + 7.619, + 7.757, + 7.395, + 7.872, + 7.813, + 9.136, + 7.902, + 9.536, + 7.967, + 7.265, + 7.759, + 9.774, + 7.469, + 7.347, + 8.035, + 7.844, + 7.557, + 7.409, + 8.895, + 8.36, + 7.297, + 7.519, + 7.614, + 9.485, + 8.326, + 7.265, + 7.507, + 10.105, + 8.119, + 7.149, + 8.367, + 8.166, + 7.629, + 7.586, + 8.384, + 7.546, + 7.581, + 7.774, + 10.816, + 8.386, + 7.298, + 7.926, + 11.667, + 9.401, + 7.42, + 8.606, + 17.628, + 8.576, + 10.844, + 7.683, + 25.22, + 11.934, + 11.952, + 9.634, + 13.521, + 14.211, + 11.388, + 11.717, + 15.932, + 12.149, + 7.947, + 9.094, + 9.291, + 7.172, + 9.541, + 10.746, + 8.678, + 7.343, + 8.456, + 9.099, + 7.662, + 7.911, + 7.056, + 8.881, + 7.04, + 7.481, + 7.457, + 7.718, + 7.996, + 7.398, + 8.659, + 7.379, + 7.924, + 7.55, + 15.761, + 7.952, + 9.685, + 8.089, + 8.734, + 10.142, + 7.384, + 8.163, + 8.396, + 7.461, + 9.509, + 10.389, + 10.052, + 7.489, + 7.498, + 7.221, + 9.525, + 8.825, + 7.42, + 7.676, + 10.288, + 8.13, + 8.99, + 7.092, + 9.287, + 7.895, + 7.257, + 7.197, + 8.134, + 8.938, + 7.744, + 10.989, + 8.652, + 7.547, + 7.996, + 8.587, + 8.04, + 7.387, + 8.422, + 9.307, + 7.578, + 7.094, + 7.695, + 10.609, + 7.667, + 7.347, + 7.359, + 9.822, + 7.473, + 7.485, + 8.128, + 8.24, + 8.488, + 7.488, + 7.124, + 7.586, + 8.767, + 7.672, + 15.877, + 9.094, + 7.037, + 8.176, + 11.046, + 8.216, + 7.287, + 7.856, + 11.451, + 8.012, + 7.902, + 7.316, + 9.434, + 7.952, + 7.383, + 7.187, + 10.052, + 7.609, + 7.365, + 7.334, + 8.401, + 7.941, + 7.322, + 7.422, + 7.68, + 8.591, + 7.174, + 7.312, + 9.938, + 8.24, + 8.152, + 13.548, + 8.685, + 7.158, + 8.503, + 10.378, + 7.81, + 8.404, + 8.82, + 11.453, + 7.683, + 7.296, + 7.471, + 9.534, + 7.659, + 7.342, + 7.494, + 11.372, + 7.425, + 7.273, + 7.684, + 8.205, + 8.451, + 7.592, + 6.951, + 7.758, + 8.07, + 8.281, + 7.454, + 7.738, + 7.468, + 8.173, + 11.51, + 7.598, + 7.363, + 11.31, + 13.18, + 7.999, + 7.414, + 7.148, + 9.36, + 7.39, + 8.246, + 7.912, + 9.596, + 8.558, + 8.504, + 7.348, + 9.129, + 7.574, + 8.107, + 7.369, + 8.402, + 8.031, + 7.519, + 7.237, + 8.108, + 8.055, + 7.667, + 8.928, + 9.782, + 7.191, + 7.82, + 9.502, + 7.545, + 7.372, + 6.914, + 11.795, + 7.853, + 8.411, + 8.39, + 12.574, + 8.058, + 8.898, + 8.32, + 7.505, + 8.023, + 7.705, + 7.817, + 12.906, + 7.884, + 8.012, + 7.942, + 9.074, + 8.582, + 7.78, + 7.312, + 12.627, + 7.313, + 7.64, + 12.382, + 8.123, + 9.519, + 9.191, + 9.063, + 7.603, + 7.128, + 7.164, + 10.329, + 9.237, + 7.828, + 7.054, + 10.034, + 11.289, + 7.493, + 7.043, + 7.746, + 7.829, + 7.358, + 6.925, + 7.827, + 9.835, + 7.131, + 6.966, + 7.382, + 7.334, + 8.67, + 9.217, + 7.541, + 7.166, + 7.737, + 11.537, + 7.812, + 8.128, + 8.751, + 11.327, + 7.938, + 7.179, + 8, + 13.033, + 7.892, + 7.07, + 7.351, + 13.186, + 7.362, + 7.28, + 6.977, + 9.644, + 7.839, + 7.179, + 7.462, + 7.646, + 7.977, + 7.355, + 7.614, + 7.829, + 8.483, + 8.391, + 9.541, + 10.101, + 7.239, + 9.352, + 10.216, + 7.893, + 7.315, + 8.228, + 10.75, + 7.642, + 7.578, + 7.791, + 9.612, + 8.523, + 7.451, + 7.112, + 10.912, + 8.067, + 7.237, + 8.177, + 9.411, + 7.659, + 7.75, + 8.002, + 11.105, + 8.119, + 8.022, + 10.118, + 8.328, + 7.327, + 9.432, + 13.168, + 8.015, + 7.472, + 7.587, + 11.184, + 8.223, + 7.423, + 7.958, + 8.523, + 7.539, + 7.588, + 7.611, + 13.016, + 7.99, + 7.577, + 8.392, + 7.912, + 11.338, + 7.73, + 7.622, + 8.213, + 9.213, + 8.68, + 7.375, + 9.129, + 8.424, + 8.228, + 11.621, + 8.303, + 7.314, + 7.329, + 13.953, + 8.127, + 7.429, + 7.881, + 10.038, + 8.491, + 7.695, + 7.29, + 9.043, + 8.72, + 7.309, + 7.506, + 9.083, + 8.312, + 8.369, + 8.192, + 8.272, + 8.391, + 8.465, + 7.319, + 8.062, + 7.101, + 7.536, + 9.957, + 17.41, + 7.382, + 7.951, + 10.404, + 11.534, + 8.182, + 7.579, + 10.539, + 10.284, + 7.492, + 7.588, + 8.086, + 11.045, + 7.998, + 7.58, + 8.343, + 7.828, + 9.921, + 7.864, + 15.462, + 10.808, + 11.705, + 7.862, + 11.991, + 9.475, + 10.196, + 11.864, + 8.108, + 7.666, + 7.84, + 12.221, + 8.194, + 7.592, + 7.767, + 8.905, + 8.613, + 8.393, + 7.501, + 8.738, + 7.56, + 8.522, + 7.66, + 8.452, + 9.144, + 8.65, + 7.593, + 9.046, + 7.367, + 8.603, + 7.646, + 8.076, + 7.496, + 7.326, + 9.984, + 7.646, + 7.246, + 7.049, + 12.464, + 8.677, + 7.433, + 7.056, + 13.932, + 9.801, + 7.454, + 6.973, + 15.632, + 7.555, + 8.625, + 7.203, + 7.884, + 9.361, + 9.544, + 8.486, + 12.286, + 7.156, + 8.477, + 10.714, + 8.846, + 7.881, + 8.122, + 8.07, + 8.14, + 8.564, + 7.349, + 9.306, + 9.313, + 8.602, + 7.283, + 7.818, + 9.126, + 8.951, + 7.653, + 8.298, + 7.815, + 9.782, + 7.783, + 8.209, + 7.558, + 8.563, + 8.059, + 8.078, + 7.641, + 7.935, + 8.397, + 8.5, + 7.484, + 7.227, + 7.659, + 9.882, + 9.511, + 7.643, + 7.111, + 10.758, + 9.805, + 7.799, + 15.058, + 7.968, + 8.375, + 7.702, + 11.528, + 7.611, + 9.398, + 7.408, + 13.897, + 7.624, + 7.373, + 8.948, + 8.497, + 7.68, + 7.137, + 8.253, + 7.654, + 7.458, + 7.009, + 8.331, + 9.678, + 8.909, + 7.195, + 8.023, + 9.666, + 8.877, + 7.583, + 9.783, + 9.867, + 7.895, + 7.812, + 10.954, + 8.131, + 10.541, + 7.676, + 11.941, + 7.457, + 7.248, + 8.454, + 8.848, + 8.686, + 8.115, + 9.45, + 9.489, + 9.006, + 7.173, + 7.267, + 9.322, + 7.422, + 7.494, + 7.722, + 9.302, + 7.506, + 7.657, + 7.277, + 7.937, + 8.053, + 7.494, + 10.133, + 7.783, + 7.572, + 7.806, + 8.917, + 7.628, + 7.084, + 7.769, + 8.662, + 8.119, + 7.714, + 9.326, + 10.313, + 7.615, + 7.14, + 7.374, + 11.812, + 7.665, + 7.986, + 7.169, + 7.686, + 8.009, + 7.183, + 7.423, + 8.18, + 10.186, + 8.065, + 7.771, + 8.925, + 7.381, + 7.835, + 10.13, + 8.294, + 7.741, + 10.596, + 15.057, + 8.326, + 7.349, + 7.421, + 10.812, + 9.183, + 8.382, + 7.57, + 9.888, + 7.664, + 8.122, + 13.974, + 8.336, + 8.465, + 8.011, + 7.038, + 7.55, + 9.218, + 8.179, + 7.084, + 8, + 8.207, + 10.304, + 9.877, + 10.921, + 7.8, + 8.422, + 12.553, + 8.383, + 9.112, + 7.819, + 12.887, + 17.559, + 7.688, + 7.449, + 8.825, + 8.434, + 8.015, + 7.802, + 8.97, + 8.773, + 7.273, + 7.185, + 8.556, + 8.518, + 8.011, + 7.358, + 9.159, + 7.499, + 7.783, + 8.166, + 8.194, + 7.348, + 7.216, + 8.19, + 8.006, + 7.684, + 7.368, + 10.645, + 7.867, + 7.383, + 8.192, + 9.393, + 10.525, + 7.533, + 7.613, + 8.932, + 9.73, + 8.04, + 7.681, + 9.086, + 7.742, + 8.104, + 7.75, + 8.775, + 7.567, + 9.12, + 8.181, + 8.386, + 7.336, + 7.446, + 10.077, + 8.105, + 8.385, + 7.198, + 9.618, + 8.308, + 7.487, + 7.177, + 12.136, + 9.084, + 7.317, + 7.769, + 8.406, + 9.323, + 8.285, + 7.656, + 10.612, + 8.119, + 9.189, + 7.23, + 11.168, + 7.177, + 8.219, + 7.691, + 7.508, + 10.916, + 7.459, + 8.765, + 9.078, + 7.497, + 7.299, + 11.149, + 9.177, + 7.424, + 7.172, + 11.193, + 10.223, + 7.271, + 8.628, + 9.592, + 10.634, + 7.708, + 7.517, + 8.091, + 8.159, + 7.765, + 7.367, + 9.28, + 7.505, + 7.533, + 7.859, + 8.246, + 7.388, + 7.633, + 7.963, + 8.541, + 7.09, + 8.554, + 7.407, + 9.234, + 7.29, + 7.516, + 12.91, + 7.676, + 8.984, + 7.49, + 14.254, + 9.265, + 8.264, + 7.604, + 9.153, + 7.482, + 7.372, + 8.064, + 8.222, + 7.65, + 7.327, + 8.372, + 12.571, + 7.177, + 7.343, + 10.085, + 13.444, + 7.241, + 7.265, + 7.312, + 9.452, + 7.691, + 7.269, + 10.662, + 8.203, + 8.41, + 7.332, + 9.053, + 9.042, + 15.727, + 8.002, + 12.689, + 7.69, + 8.773, + 8.121, + 9.025, + 8.88, + 7.946, + 8.258, + 9.295, + 7.492, + 7.425, + 7.266, + 8.225, + 7.096, + 7.291, + 6.921, + 13.642, + 7.611, + 7.13, + 9.935, + 7.636, + 8.346, + 7.04, + 8.768, + 9.965, + 8.09, + 7.807, + 9.981, + 8.391, + 7.178, + 7.617, + 7.929, + 7.72, + 7.323, + 7.472, + 10.806, + 7.187, + 7.35, + 8.008, + 8.138, + 7.413, + 7.593, + 9.118, + 9.476, + 9.064, + 7.03, + 7.527, + 9.202, + 7.947, + 7.097, + 8.848, + 11.24, + 8.008, + 7.993, + 7.808, + 7.71, + 8.278, + 8.278, + 10.341, + 7.669, + 7.28, + 7.242, + 9.055, + 9.139, + 7.222, + 7.331, + 10.15, + 8.848, + 7.383, + 7.275, + 7.644, + 7.934, + 7.5, + 6.962, + 8.206, + 7.667, + 8.016, + 7.304, + 9.2, + 7.037, + 8.278, + 9.817, + 7.7, + 8.343, + 8.047, + 13.07, + 8.05, + 7.673, + 7.694, + 10.231, + 8.142, + 7.471, + 7.195, + 12.798, + 8.37, + 6.957, + 7.635, + 8.391, + 7.647, + 7.558, + 7.313, + 7.722, + 9.776, + 7.893, + 7.812, + 7.938, + 7.839, + 8.393, + 7.676, + 8.149, + 7.405, + 8.999, + 11.021, + 7.873, + 7.572, + 8.051, + 11.033, + 7.841, + 8.282, + 7.047, + 10.844, + 9.808, + 8.535, + 7.092, + 9.576, + 7.396, + 7.888, + 7.24, + 9.782, + 8.063, + 8.348, + 7.327, + 7.878, + 7.284, + 8.104, + 8.152, + 11.409, + 7.384, + 7.923, + 8.808, + 7.703, + 8.954, + 7.951, + 8.569, + 8.136, + 7.629, + 7.577, + 15.738, + 9.361, + 7.396, + 7.182, + 10.867, + 8.095, + 7.95, + 7.488, + 9.259, + 7.628, + 7.963, + 7.232, + 8.185, + 8.574, + 9.1, + 8.662, + 9.864, + 7.634, + 7.455, + 7.639, + 7.788, + 7.716, + 7.321, + 9.732, + 8.972, + 7.827, + 7.439, + 13.152, + 7.784, + 7.562, + 7.488, + 14.203, + 15.576, + 9.833, + 7.344, + 8.955, + 7.755, + 7.934, + 7.308, + 8.187, + 7.691, + 10.581, + 7.877, + 7.482, + 7.972, + 7.504, + 8.02, + 11.076, + 7.433, + 7.573, + 9.737, + 8.314, + 8.048, + 7.256, + 8.542, + 10.552, + 7.456, + 6.964, + 13.859, + 17.306, + 7.431, + 7.791, + 10.023, + 7.374, + 7.881, + 7.182, + 7.793, + 7.479, + 7.845, + 7.806, + 7.831, + 7.604, + 7.741, + 8.044, + 7.911, + 8.788, + 8.997, + 7.807, + 8.519, + 7.641, + 7.673, + 10.97, + 10.566, + 7.431, + 7.653, + 7.805, + 11.747, + 8.094, + 7.089, + 8.985, + 9.173, + 12.549, + 8.622, + 16.061, + 7.606, + 7.427, + 8.091, + 10.139, + 7.392, + 7.114, + 7.65, + 8.525, + 7.666, + 7.443, + 7.748, + 8.03, + 7.475, + 7.737, + 8.551, + 10.259, + 7.698, + 7.737, + 9.28, + 9.888, + 8.121, + 7.715, + 11.689, + 9.817, + 9.088, + 7.842, + 8.431, + 7.444, + 7.446, + 7.962, + 9.737, + 7.799, + 7.453, + 8.641, + 8.027, + 8.921, + 7.632, + 9.599, + 9.801, + 7.922, + 7.431, + 7.874, + 8.392, + 21.273, + 7.535, + 14.55, + 8.442, + 8.836, + 8.001, + 9.422, + 8.032, + 11.203, + 19.36, + 9.048, + 7.723, + 8.102, + 25.244, + 17.637, + 8.335, + 7.329, + 7.631, + 8.089, + 7.61, + 8.698, + 8.292 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081723_abbeb364-c4bd-4df1-a228-55251dfab8cd.mp4", + "tMs": -27428 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081723_e1847de7-4dfd-454e-b462-00600d617a26.mp4", + "tMs": -27428 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081723_edced301-1aa0-42a9-bcba-30dfa6df3087.mp4", + "tMs": -27428 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081723_fe35bc3a-a542-431d-8e97-e7f669fde8f1.mp4", + "tMs": -27428 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081736_d99a355a-dd01-43cb-b596-e4d6e68f035d.mp4", + "tMs": -17216 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081736_1d539b97-9ba0-43fe-af5c-d50b7567be94.mp4", + "tMs": -17201 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081736_639d096b-fc93-4fde-bd2c-c45bfc365420.mp4", + "tMs": -17172 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081736_e27a7540-afee-43ac-854b-618318dbd7da.mp4", + "tMs": -17167 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081746_73f270b8-e885-4a11-b909-917989430f09.mp4", + "tMs": -7171 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081746_125fd35a-35ad-428e-9be7-f40bcdaf5cb5.mp4", + "tMs": -7163 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081746_9b12d2af-5050-4477-bcdf-136296d23ee0.mp4", + "tMs": -7161 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081746_a32d79b4-b925-469e-9419-17a980b001d2.mp4", + "tMs": -7161 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081756_e8d8dfba-70a9-4891-8740-9eb173720fbc.mp4", + "tMs": 2790 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081756_1cae44d3-0d0e-4fc4-94a1-cdf3020159ff.mp4", + "tMs": 2795 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081756_db283d78-71d4-48a2-8a27-71a18230f40d.mp4", + "tMs": 2795 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081756_3667c773-13e2-47a6-be1f-9b1314fc0a59.mp4", + "tMs": 2795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081806_b7a2668a-a09e-4fde-9870-cb402fd50100.mp4", + "tMs": 12782 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081806_c54d935d-0252-40a0-9d68-cb29503a1090.mp4", + "tMs": 12787 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081806_e94b76de-8b8c-4a3c-b90d-6a262af58558.mp4", + "tMs": 12825 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081806_34bb29f7-6fa9-46ab-8a7e-d1f083280821.mp4", + "tMs": 12831 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081816_e697208c-a129-43fb-b8fc-97eae00ac2a7.mp4", + "tMs": 22786 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081816_8d3dd5d2-d620-45d0-a594-c0a97f67e291.mp4", + "tMs": 22786 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081816_99ab8736-3682-45a9-b2f3-2006658d7816.mp4", + "tMs": 22789 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081816_3e98b1a9-fed9-4577-ba1f-adc74c675c70.mp4", + "tMs": 22827 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081826_67f73561-28c5-4166-8f46-3e9a2adf2e83.mp4", + "tMs": 32787 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081826_d2c687eb-7b82-4adf-aad7-d7b71f30a9fe.mp4", + "tMs": 32791 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081826_75409745-ef4e-471f-893d-65c7819d7594.mp4", + "tMs": 32794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081826_573e0c76-02bc-4e01-b90e-446a95761327.mp4", + "tMs": 32794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081836_1560175c-204a-4aaa-a742-35eea84159a4.mp4", + "tMs": 42786 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081836_20a45a6d-cf55-43dd-85f0-33c59f278746.mp4", + "tMs": 42796 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081836_d42715bc-6e58-43df-910a-3bcafe81faef.mp4", + "tMs": 42796 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081836_c71fdb12-27fb-4e8d-a3bb-18e0edb52b4a.mp4", + "tMs": 42798 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081846_5ef341d9-2465-4f34-8a3d-19a4160c761e.mp4", + "tMs": 52786 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081846_0051ec0b-8179-460a-89e8-654dfe0168a7.mp4", + "tMs": 52794 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081846_9f7c3ce9-7ca4-4e77-9f6c-448833c62132.mp4", + "tMs": 52795 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081846_f0fbda9a-0811-4e7f-b950-762aecc6eb70.mp4", + "tMs": 52795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081856_23d0ea1a-e666-4e1b-830e-af1cbca57da1.mp4", + "tMs": 62786 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081856_2cce034a-371a-4fad-90a1-f2c20b0ab2f8.mp4", + "tMs": 62787 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081856_f27307a0-433e-4348-bf07-381c39544c03.mp4", + "tMs": 62790 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081856_6ad5159f-bf4b-4e31-b2d8-0bd73c29317a.mp4", + "tMs": 62794 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081906_507f460e-2cfe-4635-8f4e-08314e2d1c6c.mp4", + "tMs": 72793 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081906_df4ad2c4-5a3b-4a04-88c7-0e66db77f040.mp4", + "tMs": 72796 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081906_59153250-28fa-4b9d-be64-f746fa816259.mp4", + "tMs": 72796 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081906_1398c0c9-d1ac-4ded-acc6-d9822422b6f3.mp4", + "tMs": 72824 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081916_88377563-d53f-4d63-b6d7-af7d4e456cd7.mp4", + "tMs": 82782 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081916_672a8367-caaa-4633-82d5-ed9b90078d32.mp4", + "tMs": 82789 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081916_6b77430f-8e72-49f8-83fc-fb24c2759524.mp4", + "tMs": 82790 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081916_13642508-df29-4b19-934c-a6a188358934.mp4", + "tMs": 82823 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081926_fdd8c71c-82e4-43d6-bbc5-901d97d1f732.mp4", + "tMs": 92795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081926_8b9ae886-2434-4402-b1f4-10efb361324b.mp4", + "tMs": 92798 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081926_4a1c9cf5-fccd-449d-b753-3496c0d6be25.mp4", + "tMs": 92798 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081926_fc2e58d4-beb1-4d0b-9585-626db39ce0b5.mp4", + "tMs": 92800 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081936_2625c22e-22d4-4572-86ae-4553ff863e24.mp4", + "tMs": 102784 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081936_0d9f92e4-8115-43d9-ab07-b045aff9f825.mp4", + "tMs": 102787 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081936_ef8edf4c-5cb0-46be-a7a3-075fbda51e6c.mp4", + "tMs": 102790 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081936_37edf153-eee0-4819-bea9-9b3c16104e26.mp4", + "tMs": 102825 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081946_76bd3c1b-fd2b-4518-abc6-cb2de416ca47.mp4", + "tMs": 112788 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081946_0c46d459-71bf-4f3a-a0ab-519ee97e2043.mp4", + "tMs": 112791 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081946_1ae7d69b-c140-449a-9555-baa2a38eec78.mp4", + "tMs": 112794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081946_6d0261c3-43e4-4c20-9fe1-493c16ac470a.mp4", + "tMs": 112794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_081956_b0c75926-a77c-44dc-a5a1-9d2f4972d262.mp4", + "tMs": 122786 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_081956_91d88695-26f0-4664-a341-1403927739d3.mp4", + "tMs": 122787 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_081956_33ab1811-0915-4048-b909-6f8d47448920.mp4", + "tMs": 122791 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_081956_94aaa5c8-7d97-42fd-8e5b-80b217d35530.mp4", + "tMs": 122823 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082006_206d8820-22ff-498f-90f5-3675f815249e.mp4", + "tMs": 132788 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082006_7e753e63-7cbe-4ba7-9f1d-4622d2d09beb.mp4", + "tMs": 132790 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082006_f4982a90-216d-45f6-bdfa-728b67847208.mp4", + "tMs": 132793 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082006_cefb1920-7dce-45da-9020-7361fa528b74.mp4", + "tMs": 132794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082016_ddcaab81-6531-4929-bd53-557faf13cec3.mp4", + "tMs": 142792 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082016_053cff7f-cfa6-4639-8939-b6dfb2bc2475.mp4", + "tMs": 142793 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082016_0faf9f5e-777d-4296-a014-865d85d5923c.mp4", + "tMs": 142795 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082016_ff9a8d21-4d46-4c02-a398-edf4e00b8071.mp4", + "tMs": 142825 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082026_5846a253-c9d5-4dd7-8d25-53c0021fa619.mp4", + "tMs": 152785 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082026_e12c4997-a7c7-4364-856e-5e57595b5834.mp4", + "tMs": 152795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082026_32c7d069-357d-4e4e-a64d-4a70f26845c1.mp4", + "tMs": 152797 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082026_2feea7b8-8e3d-4745-9342-7287c5bc6d57.mp4", + "tMs": 152827 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082036_a815e149-4bd5-4b47-b959-5e720544cd4e.mp4", + "tMs": 162785 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082036_4e08bd3e-6907-4eb6-a7d5-f145e5556545.mp4", + "tMs": 162788 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082036_6103088f-5574-40cf-8398-cbc763907b44.mp4", + "tMs": 162827 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082036_a766d038-e2d1-411c-a5c4-7933faec2722.mp4", + "tMs": 162832 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082046_83c6c7b9-4848-40d7-845f-1715f31cbf4f.mp4", + "tMs": 172783 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082046_faf1b01a-d301-4a1a-b1a6-e877c5a1efea.mp4", + "tMs": 172788 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082046_f3905a5e-44b0-4c1c-b91d-df29d8e5d205.mp4", + "tMs": 172823 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082046_064954bd-4b43-4527-a46a-650d8d5525ea.mp4", + "tMs": 172828 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082056_98d38e61-37f4-4261-923f-f849bb11e974.mp4", + "tMs": 182786 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082056_095308bd-2479-41c7-a448-956090d6ea2e.mp4", + "tMs": 182792 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082056_884251b2-3da4-4548-802b-0eda9fa67cdf.mp4", + "tMs": 182792 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082056_0b719f60-4a17-458f-b0f9-e5be4a8fa483.mp4", + "tMs": 182823 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082106_94e2a7b0-29c6-41e7-a425-25e786e9d709.mp4", + "tMs": 192788 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082106_be7e4a4c-7315-4994-903f-cd6907d9c861.mp4", + "tMs": 192791 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082106_2ef7918d-7c9b-4e09-8517-63e6d15c937e.mp4", + "tMs": 192797 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082106_0daaafdd-913a-4189-8a60-643fa7063409.mp4", + "tMs": 192797 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082116_c7288f7d-06e1-43df-8be1-4d1eec5d82c3.mp4", + "tMs": 202782 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082116_61d1bee2-66db-4826-9de8-6cbe3e494d9a.mp4", + "tMs": 202825 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082116_afcc6e2d-c087-43e9-8321-200c7875d687.mp4", + "tMs": 202830 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082116_1cdd0e04-97e0-4fac-9958-ee1355aea793.mp4", + "tMs": 202830 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082126_c84969ba-e02a-4878-bdf1-9ca245535735.mp4", + "tMs": 212783 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082126_f2e15fb6-1b47-4280-9463-95c8f4b1fcc4.mp4", + "tMs": 212793 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082126_ec909b4a-b339-47f0-916a-482f2e783e97.mp4", + "tMs": 212823 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082126_fa78ed67-ea2a-4d5b-85cb-5d3d367ecf2d.mp4", + "tMs": 212834 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082136_08b0d6ec-75ef-4a96-b4a3-a072754c5ea6.mp4", + "tMs": 222787 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082136_cd3c9773-f484-4e38-8c16-fbf94e3aa18d.mp4", + "tMs": 222794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082136_4a4b64f8-98e3-435e-a908-9b393522e833.mp4", + "tMs": 222795 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082136_94f635e9-2b7a-4e69-aedf-1183b5d4e15c.mp4", + "tMs": 222795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082146_c89c11a7-bfd4-49da-9db5-5e5acc575914.mp4", + "tMs": 232786 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082146_872425ff-0eaf-4877-bbe0-db60933122c4.mp4", + "tMs": 232796 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082146_6dbcbc35-9f33-4b66-932c-f9bef99c2583.mp4", + "tMs": 232796 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082146_faae139d-9b17-4900-a89d-95009ae48c94.mp4", + "tMs": 232796 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082156_800ee7a7-e02b-4728-b18e-0a999788202a.mp4", + "tMs": 242788 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082156_af5f8f15-06d9-4964-90ce-c70df119a30d.mp4", + "tMs": 242789 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082156_8b9db3f5-1456-4956-b55d-eba675c91e0d.mp4", + "tMs": 242803 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082156_87d32761-7bfc-41d3-a261-b7a37345c5cd.mp4", + "tMs": 242804 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082206_8dafdc25-0111-43c5-a33f-1ad62df53b03.mp4", + "tMs": 252788 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082206_d90ff51e-6b04-484f-bb07-2d38be41080b.mp4", + "tMs": 252794 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082206_34f11f0a-bca8-4a7e-9b11-62ec1f2c88e1.mp4", + "tMs": 252795 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082206_4496747b-c1bf-4e0a-969a-7d20c8faa0bd.mp4", + "tMs": 252795 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082216_9dbafeb8-6c80-4f20-8220-9edb4a150b4f.mp4", + "tMs": 262783 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082216_6c31c91b-d6e7-491f-9eb1-1962c377121e.mp4", + "tMs": 262789 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082216_a3effaff-5d3e-4309-a7a5-1a127235d3af.mp4", + "tMs": 262791 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082216_665f447d-aa60-438c-bc7d-3c1bfb5bc75e.mp4", + "tMs": 262825 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082226_af937ead-ab95-4269-8690-f5f1ceba712d.mp4", + "tMs": 272785 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082226_bdd35172-8064-4f7d-aa8b-ddf350e2eea6.mp4", + "tMs": 272788 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082226_e802a1f8-1172-4fb9-b8b3-bdd553ab5da9.mp4", + "tMs": 272823 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082226_0783068d-c166-424e-ad10-875aada5fa37.mp4", + "tMs": 272830 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082236_1ae469ad-d656-4d18-ac83-a32b026addd2.mp4", + "tMs": 282787 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082236_0b213ed4-a4f8-407d-8c29-19d9ba0877a6.mp4", + "tMs": 282788 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082236_901a80d0-dbde-4825-9be6-af6b3df70897.mp4", + "tMs": 282789 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082236_fe370ac5-5598-426d-8a38-23b3ff039536.mp4", + "tMs": 282826 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082246_fa7ae36e-0b9c-4b42-bd89-30c27040a4ec.mp4", + "tMs": 292788 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082246_fbf4d9f6-fe40-4829-b11b-3fe99a0ecd3c.mp4", + "tMs": 292790 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082246_f5956fa9-d032-4cde-8f8a-b057d7876050.mp4", + "tMs": 292796 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082246_07854cbc-ac9b-472a-bb8c-ad355e4d7aab.mp4", + "tMs": 292828 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082256_c3b360aa-6e88-42f3-a231-547f669210b7.mp4", + "tMs": 302833 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082256_765519e9-b388-4c6e-9850-3d81ca5d02aa.mp4", + "tMs": 302840 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082256_16c3b769-a46c-4706-9216-175667791fe8.mp4", + "tMs": 302841 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082256_2e2f72de-5e57-4f12-b283-962895932e54.mp4", + "tMs": 302843 + } + ] + }, + "problems": [], + "finished_at": "2026-11-08T13:23:02.214Z" +} diff --git a/tests/performance/observations/results/2026-11-08-disabled-run2.json b/tests/performance/observations/results/2026-11-08-disabled-run2.json new file mode 100644 index 000000000..1a9054488 --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-disabled-run2.json @@ -0,0 +1,4427 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "disabled", + "status": "completed", + "started_at": "2026-11-08T13:23:04.751Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 3.76, + 4.17, + 4.57 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "5fcc894385fc54186e37005dca0d9a24ba2a5119", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "d6752a1dd7c1d831637d5da570763d2c0c8317ad9ab1727dc2c3c8cc978bb23a", + "binary": "build/int01-c4-release/bin/lightnvr", + "binary_sha256": "10ee4b926268599adea5415e768cadd6a288a7699f93856ab39d4f00121be381", + "harness_sha256": "ae926aa53e061acaca4971b71a55a82a1f589c15c2fedaef0e1aeecc2576edc6", + "metrics_sha256": "cd66a4fb252cb0b7066233d1983ba72bb5743aa6aee807c88ef34d68e8292a13" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 300, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "synthetic: ffmpeg lavfi testsrc2, 30 s clip, published into go2rtc by one always-on ffmpeg -re -stream_loop -1 -c copy per stream", + "source_sha256": "a5b16133b02d8bd0121e9865e5eefaa975f7e9caffb5ffe6d4f3922dcda0e92d", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": {}, + "config_extra": null + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-78ejlI", + "detector": { + "inferences_in_window": 1204, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 0, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "latency": { + "segment_interval_ms": { + "count": 120, + "min": 9940.122, + "p50": 10000.224, + "p95": 10037.886, + "p99": 10045.457, + "max": 10054.216, + "mean": 10000.258 + }, + "segment_interval_excess_ms": { + "count": 120, + "min": -59.878, + "p50": 0.224, + "p95": 37.886, + "p99": 45.457, + "max": 54.216, + "mean": 0.258 + }, + "db_probe_ms": { + "count": 1160, + "min": 6.987, + "p50": 8.144, + "p95": 12.321, + "p99": 16.508, + "max": 26.073, + "mean": 8.76 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 30, + "min": 9957.592, + "p50": 9999.19, + "p95": 10042.547, + "p99": 10045.04, + "max": 10045.54, + "mean": 10000.06 + }, + "Load1": { + "count": 30, + "min": 9940.122, + "p50": 10002.57, + "p95": 10041.452, + "p99": 10051.573, + "max": 10054.216, + "mean": 10001.17 + }, + "Load2": { + "count": 30, + "min": 9947.556, + "p50": 10003.486, + "p95": 10037.652, + "p99": 10037.818, + "max": 10037.862, + "mean": 9999.889 + }, + "Load3": { + "count": 30, + "min": 9941.47, + "p50": 9999.589, + "p95": 10035.666, + "p99": 10037.905, + "max": 10038.349, + "mean": 9999.914 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 120, + "expected_segments_in_window": 120, + "metrics_segments_delta": 120, + "metrics_bytes_written_delta": 679473680, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 124, + "fps_samples": { + "count": 240, + "min": 15, + "p50": 15.1, + "p95": 15.2, + "p99": 15.2, + "max": 15.3, + "mean": 15.105 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 299.405, + "cpu_percent_one_core": { + "count": 257, + "min": 110.443, + "p50": 137.521, + "p95": 190.389, + "p99": 204.372, + "max": 214.452, + "mean": 146.34 + }, + "cpu_percent_one_core_avg": 146.694, + "rss_kib": { + "count": 258, + "min": 819000, + "p50": 821806, + "p95": 846112.6, + "p99": 846468, + "max": 857536, + "mean": 829172.233 + }, + "rss_high_water_kib": 874988, + "disk_write_bytes": 697143296, + "disk_write_bytes_per_sec": 2328430.101, + "threads_max": 33, + "fds_max": 38 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.669, + "processes": 5 + }, + "storage_bytes_end": 749462028, + "loadavg_at_end": [ + 9.25, + 7.5, + 5.96 + ] + }, + "window": { + "start": "2026-11-08T13:23:44.633Z", + "end": "2026-11-08T13:28:45.197Z", + "seconds": 300.56 + }, + "samples": { + "proc": [ + { + "tMs": 9, + "cpuTicks": 4654, + "rssKiB": 841640, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 69484544 + }, + { + "tMs": 1195, + "cpuTicks": 4861, + "rssKiB": 819000, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 72122368 + }, + { + "tMs": 2347, + "cpuTicks": 5016, + "rssKiB": 819592, + "peakRssKiB": 868792, + "threads": 33, + "fds": 36, + "writeBytes": 74706944 + }, + { + "tMs": 3467, + "cpuTicks": 5168, + "rssKiB": 819860, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 77901824 + }, + { + "tMs": 4589, + "cpuTicks": 5312, + "rssKiB": 819980, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 80343040 + }, + { + "tMs": 5719, + "cpuTicks": 5475, + "rssKiB": 820516, + "peakRssKiB": 868792, + "threads": 33, + "fds": 36, + "writeBytes": 82980864 + }, + { + "tMs": 6934, + "cpuTicks": 5704, + "rssKiB": 844608, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 85831680 + }, + { + "tMs": 8105, + "cpuTicks": 5938, + "rssKiB": 844692, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 88367104 + }, + { + "tMs": 9258, + "cpuTicks": 6100, + "rssKiB": 820700, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 91082752 + }, + { + "tMs": 10402, + "cpuTicks": 6248, + "rssKiB": 820824, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 93736960 + }, + { + "tMs": 11535, + "cpuTicks": 6406, + "rssKiB": 821040, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 96235520 + }, + { + "tMs": 12670, + "cpuTicks": 6564, + "rssKiB": 820892, + "peakRssKiB": 868792, + "threads": 33, + "fds": 35, + "writeBytes": 98738176 + }, + { + "tMs": 13863, + "cpuTicks": 6747, + "rssKiB": 844868, + "peakRssKiB": 869868, + "threads": 33, + "fds": 35, + "writeBytes": 102203392 + }, + { + "tMs": 15072, + "cpuTicks": 6987, + "rssKiB": 844912, + "peakRssKiB": 869868, + "threads": 33, + "fds": 37, + "writeBytes": 104906752 + }, + { + "tMs": 16216, + "cpuTicks": 7168, + "rssKiB": 820752, + "peakRssKiB": 870332, + "threads": 33, + "fds": 37, + "writeBytes": 107311104 + }, + { + "tMs": 17364, + "cpuTicks": 7317, + "rssKiB": 820536, + "peakRssKiB": 870332, + "threads": 33, + "fds": 38, + "writeBytes": 109891584 + }, + { + "tMs": 18498, + "cpuTicks": 7459, + "rssKiB": 820536, + "peakRssKiB": 870332, + "threads": 33, + "fds": 37, + "writeBytes": 112492544 + }, + { + "tMs": 19624, + "cpuTicks": 7609, + "rssKiB": 820540, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 115056640 + }, + { + "tMs": 20773, + "cpuTicks": 7770, + "rssKiB": 820552, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 117665792 + }, + { + "tMs": 21993, + "cpuTicks": 7999, + "rssKiB": 844952, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 120434688 + }, + { + "tMs": 23157, + "cpuTicks": 8213, + "rssKiB": 844952, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 123650048 + }, + { + "tMs": 24296, + "cpuTicks": 8365, + "rssKiB": 820780, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 126230528 + }, + { + "tMs": 25451, + "cpuTicks": 8510, + "rssKiB": 820800, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 128954368 + }, + { + "tMs": 26617, + "cpuTicks": 8666, + "rssKiB": 820800, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 131485696 + }, + { + "tMs": 27775, + "cpuTicks": 8802, + "rssKiB": 820804, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 134119424 + }, + { + "tMs": 28984, + "cpuTicks": 9019, + "rssKiB": 844748, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 137068544 + }, + { + "tMs": 30157, + "cpuTicks": 9239, + "rssKiB": 820724, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 139558912 + }, + { + "tMs": 31313, + "cpuTicks": 9395, + "rssKiB": 820952, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 142225408 + }, + { + "tMs": 32472, + "cpuTicks": 9543, + "rssKiB": 820952, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 144793600 + }, + { + "tMs": 33619, + "cpuTicks": 9699, + "rssKiB": 820952, + "peakRssKiB": 870332, + "threads": 33, + "fds": 36, + "writeBytes": 147976192 + }, + { + "tMs": 34750, + "cpuTicks": 9836, + "rssKiB": 820956, + "peakRssKiB": 872932, + "threads": 33, + "fds": 36, + "writeBytes": 150388736 + }, + { + "tMs": 35936, + "cpuTicks": 10040, + "rssKiB": 845144, + "peakRssKiB": 872932, + "threads": 33, + "fds": 36, + "writeBytes": 153329664 + }, + { + "tMs": 37124, + "cpuTicks": 10287, + "rssKiB": 844920, + "peakRssKiB": 872932, + "threads": 33, + "fds": 36, + "writeBytes": 156061696 + }, + { + "tMs": 38281, + "cpuTicks": 10454, + "rssKiB": 820744, + "peakRssKiB": 872932, + "threads": 33, + "fds": 36, + "writeBytes": 158588928 + }, + { + "tMs": 39439, + "cpuTicks": 10607, + "rssKiB": 820972, + "peakRssKiB": 872932, + "threads": 33, + "fds": 36, + "writeBytes": 161193984 + }, + { + "tMs": 40573, + "cpuTicks": 10770, + "rssKiB": 821280, + "peakRssKiB": 872932, + "threads": 33, + "fds": 36, + "writeBytes": 163852288 + }, + { + "tMs": 41706, + "cpuTicks": 10918, + "rssKiB": 821280, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 166301696 + }, + { + "tMs": 42922, + "cpuTicks": 11120, + "rssKiB": 845452, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 169926656 + }, + { + "tMs": 44093, + "cpuTicks": 11337, + "rssKiB": 845452, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 172457984 + }, + { + "tMs": 45247, + "cpuTicks": 11495, + "rssKiB": 821292, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 175128576 + }, + { + "tMs": 46375, + "cpuTicks": 11645, + "rssKiB": 821292, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 177451008 + }, + { + "tMs": 47498, + "cpuTicks": 11799, + "rssKiB": 821292, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 180002816 + }, + { + "tMs": 48650, + "cpuTicks": 11937, + "rssKiB": 821292, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 182747136 + }, + { + "tMs": 49816, + "cpuTicks": 12105, + "rssKiB": 845240, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 185417728 + }, + { + "tMs": 51043, + "cpuTicks": 12368, + "rssKiB": 845488, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 188108800 + }, + { + "tMs": 52217, + "cpuTicks": 12563, + "rssKiB": 821316, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 190656512 + }, + { + "tMs": 53379, + "cpuTicks": 12710, + "rssKiB": 821540, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 193998848 + }, + { + "tMs": 54530, + "cpuTicks": 12860, + "rssKiB": 821540, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 196599808 + }, + { + "tMs": 55689, + "cpuTicks": 12999, + "rssKiB": 821692, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 199200768 + }, + { + "tMs": 56872, + "cpuTicks": 13179, + "rssKiB": 845864, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 202035200 + }, + { + "tMs": 58042, + "cpuTicks": 13395, + "rssKiB": 845836, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 204615680 + }, + { + "tMs": 59209, + "cpuTicks": 13581, + "rssKiB": 821664, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 207302656 + }, + { + "tMs": 60353, + "cpuTicks": 13725, + "rssKiB": 821316, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 209793024 + }, + { + "tMs": 61483, + "cpuTicks": 13887, + "rssKiB": 821316, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 212324352 + }, + { + "tMs": 62697, + "cpuTicks": 14034, + "rssKiB": 821316, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 215035904 + }, + { + "tMs": 63891, + "cpuTicks": 14220, + "rssKiB": 845488, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 218451968 + }, + { + "tMs": 65063, + "cpuTicks": 14455, + "rssKiB": 845416, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 221044736 + }, + { + "tMs": 66216, + "cpuTicks": 14620, + "rssKiB": 821468, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 223657984 + }, + { + "tMs": 67405, + "cpuTicks": 14762, + "rssKiB": 821472, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 226369536 + }, + { + "tMs": 68542, + "cpuTicks": 14902, + "rssKiB": 821472, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 228978688 + }, + { + "tMs": 69683, + "cpuTicks": 15070, + "rssKiB": 821472, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 231485440 + }, + { + "tMs": 70867, + "cpuTicks": 15232, + "rssKiB": 845680, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 234483712 + }, + { + "tMs": 72058, + "cpuTicks": 15454, + "rssKiB": 845680, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 237019136 + }, + { + "tMs": 73250, + "cpuTicks": 15633, + "rssKiB": 821508, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 240480256 + }, + { + "tMs": 74416, + "cpuTicks": 15773, + "rssKiB": 821508, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 243048448 + }, + { + "tMs": 75575, + "cpuTicks": 15908, + "rssKiB": 821280, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 245612544 + }, + { + "tMs": 76719, + "cpuTicks": 16055, + "rssKiB": 821280, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 248152064 + }, + { + "tMs": 77903, + "cpuTicks": 16245, + "rssKiB": 845452, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 250900480 + }, + { + "tMs": 79071, + "cpuTicks": 16454, + "rssKiB": 845712, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 253644800 + }, + { + "tMs": 80213, + "cpuTicks": 16606, + "rssKiB": 821548, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 256155648 + }, + { + "tMs": 81369, + "cpuTicks": 16753, + "rssKiB": 821548, + "peakRssKiB": 873016, + "threads": 33, + "fds": 36, + "writeBytes": 258686976 + }, + { + "tMs": 82515, + "cpuTicks": 16893, + "rssKiB": 821324, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 261287936 + }, + { + "tMs": 83658, + "cpuTicks": 17035, + "rssKiB": 821548, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 264474624 + }, + { + "tMs": 84834, + "cpuTicks": 17207, + "rssKiB": 845720, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 267268096 + }, + { + "tMs": 86032, + "cpuTicks": 17437, + "rssKiB": 845512, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 269959168 + }, + { + "tMs": 87202, + "cpuTicks": 17631, + "rssKiB": 821564, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 272687104 + }, + { + "tMs": 88368, + "cpuTicks": 17791, + "rssKiB": 821696, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 275283968 + }, + { + "tMs": 89539, + "cpuTicks": 17957, + "rssKiB": 821564, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 277860352 + }, + { + "tMs": 90714, + "cpuTicks": 18112, + "rssKiB": 821592, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 280588288 + }, + { + "tMs": 91897, + "cpuTicks": 18288, + "rssKiB": 845764, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 283242496 + }, + { + "tMs": 93037, + "cpuTicks": 18499, + "rssKiB": 845764, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 286486528 + }, + { + "tMs": 94210, + "cpuTicks": 18662, + "rssKiB": 821592, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 289075200 + }, + { + "tMs": 95367, + "cpuTicks": 18795, + "rssKiB": 821724, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 291647488 + }, + { + "tMs": 96529, + "cpuTicks": 18956, + "rssKiB": 821724, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 294371328 + }, + { + "tMs": 97700, + "cpuTicks": 19105, + "rssKiB": 821592, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 296943616 + }, + { + "tMs": 98952, + "cpuTicks": 19326, + "rssKiB": 845764, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 299917312 + }, + { + "tMs": 100142, + "cpuTicks": 19550, + "rssKiB": 845900, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 302649344 + }, + { + "tMs": 101322, + "cpuTicks": 19699, + "rssKiB": 821696, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 305344512 + }, + { + "tMs": 102467, + "cpuTicks": 19878, + "rssKiB": 821696, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 307859456 + }, + { + "tMs": 103619, + "cpuTicks": 20029, + "rssKiB": 821468, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 311099392 + }, + { + "tMs": 104768, + "cpuTicks": 20187, + "rssKiB": 821396, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 313634816 + }, + { + "tMs": 105970, + "cpuTicks": 20404, + "rssKiB": 845800, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 316346368 + }, + { + "tMs": 107123, + "cpuTicks": 20621, + "rssKiB": 845444, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 318996480 + }, + { + "tMs": 108289, + "cpuTicks": 20782, + "rssKiB": 821204, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 321593344 + }, + { + "tMs": 109433, + "cpuTicks": 20934, + "rssKiB": 821432, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 324177920 + }, + { + "tMs": 110577, + "cpuTicks": 21086, + "rssKiB": 821208, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 326856704 + }, + { + "tMs": 111731, + "cpuTicks": 21230, + "rssKiB": 821472, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 329285632 + }, + { + "tMs": 112916, + "cpuTicks": 21434, + "rssKiB": 845644, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 332779520 + }, + { + "tMs": 114093, + "cpuTicks": 21656, + "rssKiB": 845644, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 335343616 + }, + { + "tMs": 115238, + "cpuTicks": 21820, + "rssKiB": 821488, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 337948672 + }, + { + "tMs": 116361, + "cpuTicks": 21973, + "rssKiB": 821488, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 340500480 + }, + { + "tMs": 117488, + "cpuTicks": 22112, + "rssKiB": 821488, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 343101440 + }, + { + "tMs": 118623, + "cpuTicks": 22257, + "rssKiB": 821488, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 345698304 + }, + { + "tMs": 119783, + "cpuTicks": 22406, + "rssKiB": 857536, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 348336128 + }, + { + "tMs": 121006, + "cpuTicks": 22652, + "rssKiB": 845720, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 351019008 + }, + { + "tMs": 122163, + "cpuTicks": 22854, + "rssKiB": 821544, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 353628160 + }, + { + "tMs": 123285, + "cpuTicks": 23011, + "rssKiB": 821544, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 356847616 + }, + { + "tMs": 124410, + "cpuTicks": 23167, + "rssKiB": 821544, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 359313408 + }, + { + "tMs": 125549, + "cpuTicks": 23316, + "rssKiB": 821548, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 361873408 + }, + { + "tMs": 126686, + "cpuTicks": 23459, + "rssKiB": 821548, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 364474368 + }, + { + "tMs": 127857, + "cpuTicks": 23634, + "rssKiB": 845492, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 367259648 + }, + { + "tMs": 129034, + "cpuTicks": 23844, + "rssKiB": 845492, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 369840128 + }, + { + "tMs": 130195, + "cpuTicks": 24028, + "rssKiB": 821320, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 372535296 + }, + { + "tMs": 131349, + "cpuTicks": 24182, + "rssKiB": 821584, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 375156736 + }, + { + "tMs": 132503, + "cpuTicks": 24327, + "rssKiB": 821584, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 377638912 + }, + { + "tMs": 133655, + "cpuTicks": 24486, + "rssKiB": 821584, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 380981248 + }, + { + "tMs": 134786, + "cpuTicks": 24639, + "rssKiB": 841668, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 383524864 + }, + { + "tMs": 136053, + "cpuTicks": 24900, + "rssKiB": 846112, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 386375680 + }, + { + "tMs": 137224, + "cpuTicks": 25093, + "rssKiB": 821940, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 388911104 + }, + { + "tMs": 138369, + "cpuTicks": 25232, + "rssKiB": 821812, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 391512064 + }, + { + "tMs": 139508, + "cpuTicks": 25379, + "rssKiB": 821584, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 394092544 + }, + { + "tMs": 140657, + "cpuTicks": 25529, + "rssKiB": 821584, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 396812288 + }, + { + "tMs": 141825, + "cpuTicks": 25679, + "rssKiB": 851528, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 399351808 + }, + { + "tMs": 143039, + "cpuTicks": 25922, + "rssKiB": 845524, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 402665472 + }, + { + "tMs": 144190, + "cpuTicks": 26108, + "rssKiB": 821460, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 405385216 + }, + { + "tMs": 145315, + "cpuTicks": 26254, + "rssKiB": 821720, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 407932928 + }, + { + "tMs": 146454, + "cpuTicks": 26394, + "rssKiB": 821720, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 410562560 + }, + { + "tMs": 147594, + "cpuTicks": 26552, + "rssKiB": 821720, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 413159424 + }, + { + "tMs": 148744, + "cpuTicks": 26690, + "rssKiB": 821720, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 415756288 + }, + { + "tMs": 149960, + "cpuTicks": 26914, + "rssKiB": 845704, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 418447360 + }, + { + "tMs": 151133, + "cpuTicks": 27115, + "rssKiB": 845704, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 421101568 + }, + { + "tMs": 152289, + "cpuTicks": 27264, + "rssKiB": 821532, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 423686144 + }, + { + "tMs": 153438, + "cpuTicks": 27401, + "rssKiB": 821660, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 427048960 + }, + { + "tMs": 154584, + "cpuTicks": 27545, + "rssKiB": 821660, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 429649920 + }, + { + "tMs": 155737, + "cpuTicks": 27678, + "rssKiB": 821660, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 432250880 + }, + { + "tMs": 156931, + "cpuTicks": 27862, + "rssKiB": 845824, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 435142656 + }, + { + "tMs": 158101, + "cpuTicks": 28074, + "rssKiB": 845824, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 437678080 + }, + { + "tMs": 159253, + "cpuTicks": 28216, + "rssKiB": 821652, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 440356864 + }, + { + "tMs": 160395, + "cpuTicks": 28349, + "rssKiB": 821760, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 442994688 + }, + { + "tMs": 161552, + "cpuTicks": 28484, + "rssKiB": 821760, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 445530112 + }, + { + "tMs": 162708, + "cpuTicks": 28637, + "rssKiB": 821760, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 448028672 + }, + { + "tMs": 163898, + "cpuTicks": 28838, + "rssKiB": 845932, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 451526656 + }, + { + "tMs": 165094, + "cpuTicks": 29065, + "rssKiB": 845932, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 454176768 + }, + { + "tMs": 166279, + "cpuTicks": 29249, + "rssKiB": 821768, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 456781824 + }, + { + "tMs": 167464, + "cpuTicks": 29402, + "rssKiB": 821664, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 459444224 + }, + { + "tMs": 168635, + "cpuTicks": 29556, + "rssKiB": 821664, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 462086144 + }, + { + "tMs": 169836, + "cpuTicks": 29730, + "rssKiB": 845836, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 464908288 + }, + { + "tMs": 171067, + "cpuTicks": 29980, + "rssKiB": 845848, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 467709952 + }, + { + "tMs": 172229, + "cpuTicks": 30163, + "rssKiB": 821676, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 470142976 + }, + { + "tMs": 173382, + "cpuTicks": 30323, + "rssKiB": 821676, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 473530368 + }, + { + "tMs": 174523, + "cpuTicks": 30468, + "rssKiB": 821676, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 476131328 + }, + { + "tMs": 175681, + "cpuTicks": 30647, + "rssKiB": 821676, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 478691328 + }, + { + "tMs": 176882, + "cpuTicks": 30831, + "rssKiB": 845580, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 481525760 + }, + { + "tMs": 178070, + "cpuTicks": 31054, + "rssKiB": 845760, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 484253696 + }, + { + "tMs": 179224, + "cpuTicks": 31238, + "rssKiB": 821588, + "peakRssKiB": 873208, + "threads": 33, + "fds": 37, + "writeBytes": 486789120 + }, + { + "tMs": 180377, + "cpuTicks": 31382, + "rssKiB": 821588, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 489373696 + }, + { + "tMs": 181539, + "cpuTicks": 31527, + "rssKiB": 821588, + "peakRssKiB": 873208, + "threads": 33, + "fds": 36, + "writeBytes": 491978752 + }, + { + "tMs": 182683, + "cpuTicks": 31662, + "rssKiB": 821812, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 494469120 + }, + { + "tMs": 183888, + "cpuTicks": 31844, + "rssKiB": 845988, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 497963008 + }, + { + "tMs": 185066, + "cpuTicks": 32054, + "rssKiB": 845988, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 500592640 + }, + { + "tMs": 186219, + "cpuTicks": 32220, + "rssKiB": 821812, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 503201792 + }, + { + "tMs": 187368, + "cpuTicks": 32347, + "rssKiB": 821812, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 505769984 + }, + { + "tMs": 188527, + "cpuTicks": 32503, + "rssKiB": 821812, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 508485632 + }, + { + "tMs": 189677, + "cpuTicks": 32651, + "rssKiB": 821668, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 511070208 + }, + { + "tMs": 190873, + "cpuTicks": 32826, + "rssKiB": 845840, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 513961984 + }, + { + "tMs": 192068, + "cpuTicks": 33050, + "rssKiB": 845744, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 516534272 + }, + { + "tMs": 193233, + "cpuTicks": 33236, + "rssKiB": 821572, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 519770112 + }, + { + "tMs": 194369, + "cpuTicks": 33385, + "rssKiB": 821668, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 522301440 + }, + { + "tMs": 195508, + "cpuTicks": 33556, + "rssKiB": 821800, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 524828672 + }, + { + "tMs": 196664, + "cpuTicks": 33697, + "rssKiB": 821800, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 527396864 + }, + { + "tMs": 197840, + "cpuTicks": 33865, + "rssKiB": 846064, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 530116608 + }, + { + "tMs": 199034, + "cpuTicks": 34085, + "rssKiB": 846064, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 532869120 + }, + { + "tMs": 200218, + "cpuTicks": 34313, + "rssKiB": 821668, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 535519232 + }, + { + "tMs": 201366, + "cpuTicks": 34479, + "rssKiB": 821892, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 538046464 + }, + { + "tMs": 202514, + "cpuTicks": 34638, + "rssKiB": 821668, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 540643328 + }, + { + "tMs": 203643, + "cpuTicks": 34794, + "rssKiB": 821668, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 543854592 + }, + { + "tMs": 204773, + "cpuTicks": 34939, + "rssKiB": 821668, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 546340864 + }, + { + "tMs": 205977, + "cpuTicks": 35163, + "rssKiB": 845968, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 549261312 + }, + { + "tMs": 207162, + "cpuTicks": 35376, + "rssKiB": 821796, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 551882752 + }, + { + "tMs": 208299, + "cpuTicks": 35527, + "rssKiB": 821796, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 554483712 + }, + { + "tMs": 209452, + "cpuTicks": 35685, + "rssKiB": 821796, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 557142016 + }, + { + "tMs": 210609, + "cpuTicks": 35829, + "rssKiB": 821704, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 559652864 + }, + { + "tMs": 211753, + "cpuTicks": 35972, + "rssKiB": 821700, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 562188288 + }, + { + "tMs": 212955, + "cpuTicks": 36184, + "rssKiB": 845872, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 565694464 + }, + { + "tMs": 214130, + "cpuTicks": 36397, + "rssKiB": 845872, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 568279040 + }, + { + "tMs": 215275, + "cpuTicks": 36565, + "rssKiB": 821700, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 570810368 + }, + { + "tMs": 216428, + "cpuTicks": 36704, + "rssKiB": 821712, + "peakRssKiB": 874988, + "threads": 33, + "fds": 37, + "writeBytes": 573493248 + }, + { + "tMs": 217587, + "cpuTicks": 36848, + "rssKiB": 821936, + "peakRssKiB": 874988, + "threads": 33, + "fds": 37, + "writeBytes": 576139264 + }, + { + "tMs": 218732, + "cpuTicks": 36993, + "rssKiB": 821936, + "peakRssKiB": 874988, + "threads": 33, + "fds": 37, + "writeBytes": 578744320 + }, + { + "tMs": 219937, + "cpuTicks": 37212, + "rssKiB": 845884, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 581545984 + }, + { + "tMs": 221127, + "cpuTicks": 37427, + "rssKiB": 821712, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 584335360 + }, + { + "tMs": 222286, + "cpuTicks": 37562, + "rssKiB": 821752, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 586805248 + }, + { + "tMs": 223441, + "cpuTicks": 37706, + "rssKiB": 821752, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 590016512 + }, + { + "tMs": 224567, + "cpuTicks": 37853, + "rssKiB": 821752, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 592564224 + }, + { + "tMs": 225701, + "cpuTicks": 38009, + "rssKiB": 821752, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 595054592 + }, + { + "tMs": 226888, + "cpuTicks": 38194, + "rssKiB": 845924, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 597774336 + }, + { + "tMs": 228070, + "cpuTicks": 38411, + "rssKiB": 846148, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 600485888 + }, + { + "tMs": 229237, + "cpuTicks": 38567, + "rssKiB": 821976, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 603090944 + }, + { + "tMs": 230400, + "cpuTicks": 38711, + "rssKiB": 821752, + "peakRssKiB": 874988, + "threads": 33, + "fds": 37, + "writeBytes": 605810688 + }, + { + "tMs": 231557, + "cpuTicks": 38842, + "rssKiB": 821756, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 608284672 + }, + { + "tMs": 232717, + "cpuTicks": 38996, + "rssKiB": 821716, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 610848768 + }, + { + "tMs": 233953, + "cpuTicks": 39227, + "rssKiB": 845852, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 614383616 + }, + { + "tMs": 235143, + "cpuTicks": 39448, + "rssKiB": 845984, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 617115648 + }, + { + "tMs": 236314, + "cpuTicks": 39600, + "rssKiB": 821812, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 619847680 + }, + { + "tMs": 237476, + "cpuTicks": 39747, + "rssKiB": 821812, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 622444544 + }, + { + "tMs": 238665, + "cpuTicks": 39901, + "rssKiB": 821944, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 625188864 + }, + { + "tMs": 239804, + "cpuTicks": 40066, + "rssKiB": 846116, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 627744768 + }, + { + "tMs": 240996, + "cpuTicks": 40288, + "rssKiB": 846060, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 630370304 + }, + { + "tMs": 242166, + "cpuTicks": 40494, + "rssKiB": 821888, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 632971264 + }, + { + "tMs": 243395, + "cpuTicks": 40656, + "rssKiB": 821888, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 636641280 + }, + { + "tMs": 244535, + "cpuTicks": 40796, + "rssKiB": 821888, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 639107072 + }, + { + "tMs": 245698, + "cpuTicks": 40946, + "rssKiB": 821888, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 641703936 + }, + { + "tMs": 246916, + "cpuTicks": 41143, + "rssKiB": 846060, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 644669440 + }, + { + "tMs": 248109, + "cpuTicks": 41370, + "rssKiB": 846060, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 647274496 + }, + { + "tMs": 249267, + "cpuTicks": 41521, + "rssKiB": 821848, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 649957376 + }, + { + "tMs": 250438, + "cpuTicks": 41670, + "rssKiB": 822076, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 652595200 + }, + { + "tMs": 251601, + "cpuTicks": 41805, + "rssKiB": 822076, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 655192064 + }, + { + "tMs": 252752, + "cpuTicks": 41939, + "rssKiB": 821848, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 657739776 + }, + { + "tMs": 253964, + "cpuTicks": 42155, + "rssKiB": 846020, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 661204992 + }, + { + "tMs": 255135, + "cpuTicks": 42358, + "rssKiB": 821980, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 663834624 + }, + { + "tMs": 256297, + "cpuTicks": 42511, + "rssKiB": 822076, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 666333184 + }, + { + "tMs": 257448, + "cpuTicks": 42650, + "rssKiB": 822208, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 669028352 + }, + { + "tMs": 258596, + "cpuTicks": 42780, + "rssKiB": 822076, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 671625216 + }, + { + "tMs": 259722, + "cpuTicks": 42924, + "rssKiB": 821968, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 674189312 + }, + { + "tMs": 260924, + "cpuTicks": 43130, + "rssKiB": 846236, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 676978688 + }, + { + "tMs": 262082, + "cpuTicks": 43331, + "rssKiB": 846012, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 679411712 + }, + { + "tMs": 263243, + "cpuTicks": 43495, + "rssKiB": 821840, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 682676224 + }, + { + "tMs": 264398, + "cpuTicks": 43629, + "rssKiB": 821840, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 685383680 + }, + { + "tMs": 265553, + "cpuTicks": 43769, + "rssKiB": 822064, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 687960064 + }, + { + "tMs": 266693, + "cpuTicks": 43913, + "rssKiB": 822064, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 690528256 + }, + { + "tMs": 267866, + "cpuTicks": 44087, + "rssKiB": 846236, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 693346304 + }, + { + "tMs": 269054, + "cpuTicks": 44314, + "rssKiB": 846236, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 696057856 + }, + { + "tMs": 270222, + "cpuTicks": 44500, + "rssKiB": 822176, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 698589184 + }, + { + "tMs": 271364, + "cpuTicks": 44645, + "rssKiB": 822176, + "peakRssKiB": 874988, + "threads": 33, + "fds": 37, + "writeBytes": 701132800 + }, + { + "tMs": 272531, + "cpuTicks": 44792, + "rssKiB": 821948, + "peakRssKiB": 874988, + "threads": 33, + "fds": 38, + "writeBytes": 703844352 + }, + { + "tMs": 273672, + "cpuTicks": 44938, + "rssKiB": 821948, + "peakRssKiB": 874988, + "threads": 33, + "fds": 37, + "writeBytes": 707026944 + }, + { + "tMs": 274843, + "cpuTicks": 45097, + "rssKiB": 846120, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 709648384 + }, + { + "tMs": 276037, + "cpuTicks": 45316, + "rssKiB": 846308, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 712368128 + }, + { + "tMs": 277196, + "cpuTicks": 45496, + "rssKiB": 821912, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 715087872 + }, + { + "tMs": 278355, + "cpuTicks": 45628, + "rssKiB": 822136, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 717651968 + }, + { + "tMs": 279503, + "cpuTicks": 45759, + "rssKiB": 821912, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 720232448 + }, + { + "tMs": 280662, + "cpuTicks": 45895, + "rssKiB": 821916, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 723030016 + }, + { + "tMs": 281861, + "cpuTicks": 46069, + "rssKiB": 846468, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 725741568 + }, + { + "tMs": 283084, + "cpuTicks": 46311, + "rssKiB": 846468, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 729116672 + }, + { + "tMs": 284250, + "cpuTicks": 46471, + "rssKiB": 822276, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 731811840 + }, + { + "tMs": 285405, + "cpuTicks": 46607, + "rssKiB": 822048, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 734298112 + }, + { + "tMs": 286560, + "cpuTicks": 46746, + "rssKiB": 822048, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 736792576 + }, + { + "tMs": 287709, + "cpuTicks": 46898, + "rssKiB": 822048, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 739393536 + }, + { + "tMs": 288911, + "cpuTicks": 47083, + "rssKiB": 846220, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 742322176 + }, + { + "tMs": 290070, + "cpuTicks": 47278, + "rssKiB": 846220, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 744902656 + }, + { + "tMs": 291240, + "cpuTicks": 47434, + "rssKiB": 821824, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 747413504 + }, + { + "tMs": 292389, + "cpuTicks": 47569, + "rssKiB": 821824, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 749998080 + }, + { + "tMs": 293552, + "cpuTicks": 47712, + "rssKiB": 821824, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 753233920 + }, + { + "tMs": 294699, + "cpuTicks": 47854, + "rssKiB": 821824, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 755830784 + }, + { + "tMs": 295909, + "cpuTicks": 48053, + "rssKiB": 845992, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 758665216 + }, + { + "tMs": 297081, + "cpuTicks": 48264, + "rssKiB": 846032, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 761376768 + }, + { + "tMs": 298243, + "cpuTicks": 48430, + "rssKiB": 821860, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 764018688 + }, + { + "tMs": 299414, + "cpuTicks": 48575, + "rssKiB": 821860, + "peakRssKiB": 874988, + "threads": 33, + "fds": 36, + "writeBytes": 766627840 + } + ], + "db_probe_ms": [ + 9.091, + 9.367, + 8.199, + 13.796, + 8.109, + 10.832, + 7.991, + 8.688, + 8.685, + 7.662, + 8.791, + 15.162, + 8.712, + 7.94, + 8.995, + 13.353, + 7.905, + 7.194, + 8.664, + 11.19, + 8.257, + 7.962, + 7.395, + 10.61, + 7.889, + 7.447, + 7.19, + 8.162, + 8.629, + 8.2, + 12.911, + 8.169, + 8.668, + 10.411, + 12.735, + 7.881, + 8.354, + 9.62, + 8.635, + 7.785, + 7.668, + 8.69, + 8.349, + 9.438, + 8.586, + 8.391, + 8.691, + 7.281, + 8.396, + 8.418, + 10.102, + 7.558, + 7.54, + 8.636, + 8.952, + 7.717, + 7.634, + 8.51, + 10.141, + 8.636, + 7.76, + 10.427, + 7.601, + 8.586, + 7.657, + 11.302, + 8.63, + 7.748, + 9.014, + 9.309, + 7.697, + 7.407, + 7.685, + 8.935, + 8.134, + 7.442, + 7.785, + 9.88, + 8.75, + 7.747, + 7.678, + 9.043, + 7.666, + 7.671, + 7.421, + 7.885, + 8.571, + 8.522, + 9.315, + 9.064, + 9.445, + 7.43, + 11.25, + 8.075, + 8.361, + 7.909, + 8.298, + 7.815, + 7.427, + 8.897, + 8.671, + 7.982, + 7.199, + 8.995, + 8.871, + 7.51, + 7.245, + 7.794, + 10.084, + 7.995, + 8.893, + 9.365, + 8.087, + 8.76, + 7.634, + 7.688, + 13.973, + 9.138, + 7.759, + 8.115, + 8.116, + 7.933, + 8.328, + 10.754, + 8.499, + 7.398, + 10.495, + 10.294, + 8.984, + 7.326, + 9.874, + 8.817, + 8.153, + 8.529, + 7.281, + 14.767, + 8.376, + 7.599, + 7.215, + 11.582, + 8.536, + 7.991, + 7.761, + 10.246, + 9.57, + 10.423, + 13.451, + 8.799, + 9.589, + 8.885, + 15.27, + 8.583, + 7.48, + 8.803, + 9.577, + 12.387, + 7.695, + 8.654, + 11.073, + 7.478, + 7.581, + 7.996, + 9.39, + 7.181, + 8.274, + 7.224, + 9.363, + 8.058, + 7.461, + 7.152, + 8.042, + 8.342, + 7.248, + 9.303, + 11.099, + 8.373, + 7.726, + 7.874, + 8.508, + 8.476, + 7.919, + 9.458, + 9.005, + 9.285, + 8.354, + 10.197, + 7.669, + 10.405, + 7.926, + 10.122, + 8.903, + 7.98, + 7.493, + 11.724, + 8.339, + 7.323, + 8.579, + 7.828, + 8.487, + 7.624, + 7.599, + 8.101, + 8.324, + 9.557, + 9.822, + 11.35, + 8.329, + 9.796, + 12.343, + 7.881, + 7.491, + 8.41, + 9.642, + 9.222, + 7.359, + 7.613, + 14.024, + 7.975, + 7.529, + 8.016, + 10.234, + 7.889, + 8.433, + 7.798, + 7.964, + 8.994, + 17.269, + 11.358, + 8.026, + 9.669, + 8.265, + 8.627, + 8.08, + 8.004, + 8.532, + 10.848, + 10.609, + 7.413, + 10.682, + 22.016, + 11.433, + 8.358, + 7.666, + 14.325, + 7.528, + 7.23, + 13.658, + 9.046, + 19.919, + 15.678, + 7.299, + 7.599, + 8.759, + 7.34, + 9.031, + 7.433, + 9.385, + 9.229, + 12.122, + 8.124, + 7.173, + 8.131, + 9.403, + 7.605, + 8.133, + 8.352, + 9.811, + 8.213, + 10.301, + 7.492, + 9.232, + 7.992, + 7.906, + 8.079, + 9.926, + 9.128, + 7.716, + 7.818, + 7.806, + 11.022, + 8.846, + 24.845, + 12.861, + 8.51, + 7.882, + 11.625, + 7.735, + 7.887, + 8.064, + 12.069, + 7.637, + 7.444, + 8.163, + 8.523, + 7.357, + 7.718, + 7.383, + 8.019, + 7.969, + 7.256, + 7.498, + 11.415, + 8.424, + 7.344, + 7.406, + 8.385, + 7.77, + 7.151, + 13.848, + 9.518, + 9.464, + 8.16, + 9.198, + 7.675, + 7.14, + 8.124, + 12.158, + 10.287, + 8.413, + 9.567, + 10.504, + 7.202, + 8.509, + 9.279, + 9.81, + 7.776, + 8.283, + 7.185, + 10.732, + 7.331, + 7.49, + 7.459, + 8.221, + 8.255, + 7.506, + 8.446, + 8.241, + 8.845, + 7.609, + 11.764, + 8.664, + 8.722, + 12.228, + 12.331, + 9.737, + 7.973, + 11.009, + 10.128, + 12.505, + 7.815, + 7.494, + 8.663, + 7.547, + 8.399, + 7.582, + 11.209, + 7.765, + 8.29, + 7.904, + 9.506, + 8.49, + 7.257, + 7.839, + 8.099, + 8.963, + 8.525, + 7.787, + 8.213, + 7.15, + 7.683, + 12.801, + 7.847, + 7.341, + 7.748, + 16.63, + 8.489, + 7.217, + 7.129, + 17.24, + 7.821, + 7.794, + 7.953, + 9.427, + 8.996, + 7.433, + 7.94, + 9.303, + 7.847, + 8.034, + 8.551, + 8.281, + 7.906, + 9.183, + 19.692, + 8.273, + 7.337, + 9.166, + 11.365, + 8.271, + 7.145, + 9.241, + 15.997, + 7.631, + 7.347, + 7.548, + 10.683, + 11.155, + 7.824, + 7.728, + 8.258, + 8.258, + 7.7, + 7.522, + 8.034, + 7.522, + 7.871, + 7.665, + 8.467, + 7.295, + 9.216, + 10.987, + 8.11, + 7.442, + 9.88, + 9.772, + 9.782, + 8.2, + 7.625, + 10.027, + 7.769, + 7.655, + 7.233, + 12.862, + 7.643, + 8.74, + 7.8, + 14.353, + 8.913, + 7.406, + 7.416, + 7.858, + 8.852, + 7.519, + 7.399, + 14.337, + 10.036, + 7.939, + 12.849, + 9.64, + 7.487, + 8.456, + 12.213, + 10.921, + 7.225, + 9.557, + 11.301, + 14.54, + 7.805, + 9.288, + 10.956, + 7.941, + 7.381, + 7.567, + 10.529, + 7.605, + 7.354, + 7.184, + 7.553, + 7.803, + 7.488, + 7.21, + 7.625, + 8.671, + 8.388, + 7.96, + 9.413, + 7.698, + 8.236, + 16.435, + 7.895, + 7.418, + 8.277, + 9.865, + 7.623, + 8.675, + 8.45, + 9.093, + 7.933, + 7.382, + 7.133, + 12.79, + 7.292, + 8.391, + 7.143, + 9.897, + 7.552, + 7.132, + 7.157, + 7.625, + 7.895, + 7.384, + 7.807, + 7.944, + 7.388, + 8.278, + 13.238, + 8.086, + 7.537, + 8.816, + 12.304, + 8.353, + 7.57, + 10.122, + 11.272, + 8.511, + 7.176, + 7.552, + 12.321, + 7.652, + 7.149, + 7.119, + 10.193, + 10.469, + 7.971, + 7.669, + 8.184, + 7.826, + 7.036, + 8.093, + 7.502, + 7.674, + 7.608, + 7.997, + 8.529, + 7.334, + 7.905, + 8.41, + 8.597, + 7.459, + 7.974, + 11.367, + 10.807, + 7.876, + 8.065, + 11.398, + 7.95, + 7.331, + 9.941, + 12.972, + 8.103, + 8.981, + 7.624, + 9.724, + 8.274, + 7.017, + 7.298, + 8.101, + 7.853, + 7.828, + 7.33, + 14.736, + 7.588, + 8.392, + 9.993, + 7.715, + 7.528, + 8.196, + 10.278, + 7.917, + 7.278, + 7.373, + 13.944, + 7.857, + 7.307, + 7.316, + 10.418, + 7.628, + 7.437, + 7.247, + 8.644, + 7.899, + 9.762, + 7.341, + 8.472, + 8.141, + 7.932, + 7.217, + 7.492, + 10.145, + 8.909, + 7.202, + 9.483, + 7.886, + 8.233, + 10.103, + 8.306, + 7.467, + 7.099, + 10.516, + 7.661, + 7.1, + 7.603, + 8.932, + 8.048, + 7.366, + 7.756, + 8.793, + 8.521, + 7.108, + 7.551, + 8.708, + 7.976, + 7.917, + 7.102, + 7.616, + 7.539, + 8.761, + 7.108, + 8.645, + 14.811, + 10.081, + 11.182, + 8.105, + 7.561, + 7.854, + 10.952, + 11.161, + 7.351, + 8.536, + 10.811, + 13.363, + 10.059, + 7.885, + 10.086, + 9.813, + 7.566, + 7.711, + 12.199, + 9.003, + 9.076, + 9.361, + 9.593, + 7.71, + 8.894, + 7.252, + 11.511, + 7.201, + 8.501, + 13.137, + 8.332, + 7.481, + 7.634, + 14.127, + 8.332, + 7.248, + 7.276, + 16.011, + 10.245, + 7.518, + 7.317, + 9.532, + 7.84, + 8.488, + 8.012, + 7.581, + 7.55, + 8.868, + 8.274, + 9.445, + 7.739, + 7.629, + 9.275, + 7.563, + 7.525, + 7.303, + 8.787, + 9.362, + 23.509, + 7.628, + 13.209, + 11.773, + 7.411, + 7.547, + 8.281, + 8.919, + 7.206, + 7.396, + 8.815, + 9.21, + 7.551, + 8.907, + 8.161, + 9.676, + 8.007, + 7.295, + 7.754, + 7.799, + 7.999, + 7.921, + 7.533, + 7.142, + 7.359, + 8.385, + 7.77, + 7.09, + 8.51, + 12.956, + 9.624, + 7.335, + 7.444, + 9.943, + 8.94, + 7.126, + 7.231, + 8.864, + 8.547, + 7.198, + 7.146, + 8.821, + 7.338, + 7.901, + 7.516, + 8.165, + 7.253, + 9.181, + 8.238, + 8.105, + 8.747, + 7.978, + 8.143, + 8.116, + 7.251, + 7.114, + 7.682, + 9.998, + 7.29, + 7.357, + 8.649, + 9.93, + 7.62, + 7.395, + 9.129, + 8.122, + 8.448, + 8.399, + 9.051, + 8.102, + 7.879, + 7.562, + 8.605, + 8.564, + 8.705, + 9.876, + 8.461, + 7.621, + 7.542, + 8.975, + 8.392, + 7.427, + 7.869, + 7.788, + 8.328, + 7.865, + 7.79, + 8.885, + 8.644, + 7.712, + 8.451, + 10.216, + 8.29, + 8.173, + 7.115, + 12.53, + 8.567, + 7.622, + 8.024, + 11.251, + 7.826, + 8.294, + 8.066, + 8.501, + 7.949, + 8.763, + 8.069, + 9.099, + 9.295, + 7.078, + 8.12, + 7.773, + 7.129, + 7.215, + 7.294, + 9.273, + 7.345, + 7.913, + 10.649, + 12.034, + 9.185, + 9.122, + 10.86, + 8.047, + 7.708, + 7.603, + 9.862, + 8.844, + 9.351, + 10.266, + 8.849, + 7.908, + 8.151, + 8.177, + 8.685, + 7.964, + 7.32, + 8.038, + 8.821, + 7.372, + 7.534, + 7.324, + 8.614, + 7.066, + 8.128, + 10.362, + 10.16, + 7.959, + 7.213, + 12.034, + 8.205, + 7.952, + 7.269, + 9.699, + 8.345, + 23.835, + 7.639, + 9.027, + 7.977, + 7.517, + 9.41, + 8.183, + 7.489, + 7.273, + 9.228, + 8.05, + 7.451, + 7.815, + 9.449, + 9.1, + 7.927, + 7.525, + 7.561, + 7.607, + 8.355, + 7.456, + 9.639, + 8.416, + 8.685, + 7.16, + 10.013, + 7.78, + 10.296, + 8.504, + 11.748, + 7.728, + 7.582, + 7.338, + 10.394, + 7.973, + 7.464, + 8.713, + 9.633, + 7.679, + 7.857, + 8.024, + 9.242, + 7.431, + 7.157, + 6.987, + 8.626, + 7.221, + 7.365, + 9.625, + 9.008, + 8.216, + 7.097, + 9.897, + 8.27, + 7.782, + 7.176, + 8.755, + 10.756, + 7.185, + 7.836, + 8, + 7.912, + 8.28, + 8.836, + 9.629, + 9.355, + 8.237, + 8.91, + 14.726, + 7.75, + 9.813, + 7.884, + 11.199, + 8.583, + 7.671, + 7.761, + 9.629, + 8.357, + 10.47, + 10.259, + 7.807, + 8.138, + 9.287, + 12.713, + 8.102, + 8.607, + 10.435, + 9.431, + 8.001, + 7.51, + 7.322, + 10.503, + 7.652, + 7.936, + 7.333, + 11.853, + 7.943, + 7.525, + 7.574, + 8.805, + 8.553, + 7.886, + 7.341, + 7.522, + 11.459, + 8.741, + 10.474, + 9.099, + 7.896, + 8.408, + 10.684, + 8.226, + 7.708, + 9.373, + 26.073, + 8.955, + 7.465, + 7.511, + 11.685, + 8.358, + 7.613, + 7.551, + 8.824, + 7.787, + 7.591, + 7.324, + 10.161, + 9.692, + 8.105, + 7.826, + 8.128, + 7.286, + 9.959, + 11.053, + 8.377, + 7.982, + 7.958, + 8.648, + 9.315, + 7.632, + 7.866, + 9.036, + 8.843, + 9.069, + 7.772, + 10.358, + 8.124, + 8.56, + 7.38, + 8.059, + 8.04, + 7.478, + 7.473, + 7.813, + 9.694, + 7.716, + 7.018, + 9.562, + 7.582, + 7.997, + 7.603, + 8.143, + 7.624, + 9.06, + 9.945, + 8.064, + 7.507, + 7.029, + 9.9, + 7.795, + 7.651, + 7.143, + 12.715, + 8.528, + 7.21, + 7.458, + 8.819, + 8.33, + 7.851, + 7.313, + 8.775, + 8.87, + 8.556, + 7.138, + 9.054, + 7.866, + 8.099, + 9.041, + 7.573, + 7.506, + 8.28, + 7.286, + 8.361, + 7.309, + 7.073, + 16.614, + 12.003, + 7.884, + 7.527, + 11.389, + 9.561, + 7.501, + 7.604, + 9.301, + 8.649, + 7.964, + 7.751, + 9.035, + 8.147, + 8.328, + 7.944, + 10.874, + 7.518, + 8.498, + 7.195, + 10.541, + 9.405, + 8.347, + 7.634, + 9.184, + 7.436, + 8.423, + 12.664, + 8.94, + 7.139, + 7.091, + 11.759, + 8.441, + 7.262, + 7.364, + 10.867, + 8.957, + 7.136, + 7.343, + 8.436, + 9.733, + 7.635, + 8.186, + 8.223, + 7.915, + 7.917, + 8.202, + 7.721, + 7.442, + 8.071, + 7.494, + 8.294, + 7.266, + 7.828, + 7.775, + 8.143, + 8.053, + 7.566, + 18.839, + 8.35, + 7.478, + 8.937, + 12.645, + 8.988, + 7.41, + 8.416, + 9.842, + 9.855, + 8.221, + 7.077, + 9.374, + 7.55, + 7.896, + 7.449, + 7.735, + 7.077, + 10.357, + 7.751, + 9.202, + 7.423, + 8.038, + 7.952, + 7.646, + 7.187, + 7.292, + 10.233, + 9.003, + 7.168, + 9.7, + 9.135, + 8.249, + 8.251, + 7.348, + 8.224, + 8.466, + 7.912, + 7.843, + 8.958, + 7.647, + 7.93, + 7.344, + 8.058, + 8.145, + 9.111, + 8.127, + 10.811, + 7.61, + 7.255, + 7.905, + 7.746, + 7.154, + 7.591, + 8.493, + 8.815, + 7.269, + 8.092, + 9.926, + 11.295, + 8.118, + 7.947, + 9.557, + 8.526, + 8.073, + 8.171, + 11.865, + 7.886, + 10.436, + 7.871, + 13.204, + 8.105, + 8.345, + 8.275, + 8.045 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082314_892a970c-3535-4ea8-9b0a-039120d4e955.mp4", + "tMs": -27423 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082314_9fbcf38f-ad22-43c9-ba00-9d19444d4e13.mp4", + "tMs": -27423 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082314_6d948736-40ba-43a1-95f2-eef1cbcd0279.mp4", + "tMs": -27423 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082314_8569d1a1-41ae-4d0c-8983-2d3a3b3d5abf.mp4", + "tMs": -27421 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082327_69f1b350-1569-47c9-a69d-a05b243b2fd2.mp4", + "tMs": -17214 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082327_8b6e09ba-7d1f-48e6-a4ab-6331646af2b4.mp4", + "tMs": -17193 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082327_0cd00c70-67c1-42af-a537-71ddaeccbbaf.mp4", + "tMs": -17193 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082327_96b76102-a7dd-4918-bc3c-bc874b7d2fec.mp4", + "tMs": -17163 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082337_5c8c7dc2-56c7-42ba-a442-240abb26d96e.mp4", + "tMs": -7213 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082337_ddb72841-6d1b-4b32-aa13-7cd3b6d24d53.mp4", + "tMs": -7204 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082337_76328f57-cd94-4387-9ce5-9508eaec172f.mp4", + "tMs": -7204 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082337_5f656848-3e31-466f-8ed9-075c337f531e.mp4", + "tMs": -7203 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082347_493e1235-19e7-4a21-b008-4c04c46fbbe9.mp4", + "tMs": 2793 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082347_e76ba4e7-4225-451f-8e67-71d3197c2eb6.mp4", + "tMs": 2795 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082347_0f07488e-c41f-48c9-a40f-0f56baed093f.mp4", + "tMs": 2800 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082347_9f848513-e98c-409a-8098-4b1df23e7af4.mp4", + "tMs": 2830 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082357_7f30721a-93df-4ebc-a39e-64b328fbb72e.mp4", + "tMs": 12787 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082357_a7046895-8a25-48c6-b7de-dc864cece144.mp4", + "tMs": 12801 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082357_3d182213-705f-4beb-b753-26ebd1eaada4.mp4", + "tMs": 12810 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082357_50071686-21b7-4b8c-8a8d-a642e549abf2.mp4", + "tMs": 12832 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082407_ab51a66d-43ce-42cb-8af2-50c32fa249c6.mp4", + "tMs": 22788 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082407_9e901399-8eb6-4425-8db8-1ae467f3b80c.mp4", + "tMs": 22793 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082407_446d83a8-b5c3-423c-9509-9947eb07e44e.mp4", + "tMs": 22794 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082407_e2a86935-76fd-4c73-ac65-67a4414f2b26.mp4", + "tMs": 22830 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082417_b2b5fca9-b472-4615-971d-fe105ea4e93a.mp4", + "tMs": 32792 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082417_79244809-95aa-4af6-bee2-2ef849932675.mp4", + "tMs": 32799 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082417_034a2585-6e1b-46cd-b673-a7908bf9f95f.mp4", + "tMs": 32801 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082417_264fb62b-ad56-4550-8bdb-89d7820de892.mp4", + "tMs": 32831 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082427_fb48220d-1368-4448-96dc-1212f3fa28e3.mp4", + "tMs": 42791 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082427_4a236f0b-5b47-474e-9759-48d3d7f1f7e4.mp4", + "tMs": 42792 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082427_cb9c8457-13aa-4490-b6b1-abc7849abcbc.mp4", + "tMs": 42803 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082427_3d3e2ec9-8b04-4381-9f0c-25052863f078.mp4", + "tMs": 42827 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082437_e8463006-43eb-400b-8f1b-0c8e51c00a9e.mp4", + "tMs": 52791 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082437_66d1e12a-f2fa-417a-be8c-2033b4f58ee8.mp4", + "tMs": 52794 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082437_097c6b95-9137-469d-b8da-ec723fca7152.mp4", + "tMs": 52795 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082437_eaa44e24-bad6-4c69-8678-e7c96a47b198.mp4", + "tMs": 52828 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082447_c7eb983e-5601-46d6-85b8-ab805ec58a7a.mp4", + "tMs": 62788 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082447_812f4560-d258-4edc-a979-22a5090bcb54.mp4", + "tMs": 62796 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082447_e763e93a-e5d5-45ca-8b44-c55e6d4e51ed.mp4", + "tMs": 62804 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082447_09014412-13f7-46cb-bd82-bda46a8b0be7.mp4", + "tMs": 62804 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082457_4a0c7c83-ac0f-4d1f-b0c8-fed8a625f083.mp4", + "tMs": 72801 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082457_5b28a393-5737-403d-b67e-2082d3fa7f1b.mp4", + "tMs": 72805 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082457_8a2ff276-0069-4797-a9db-0235a9cbbb93.mp4", + "tMs": 72806 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082457_9634cbbd-e55b-49db-8478-7c09c58714b9.mp4", + "tMs": 72834 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082507_da64288f-9684-4e7a-a60a-58ca103b9b1a.mp4", + "tMs": 82786 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082507_990875ca-d4da-4b6b-b51f-896a64911aa9.mp4", + "tMs": 82799 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082507_4c33524e-61e9-4b3b-b668-79c821eb3a74.mp4", + "tMs": 82807 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082507_edffa158-bc39-46a8-a668-3f01eca84b30.mp4", + "tMs": 82831 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082517_561ceda9-3f9c-4439-ab93-1a26933f5308.mp4", + "tMs": 92786 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082517_3a5df57e-5f26-4fee-95cb-13de7d3b3555.mp4", + "tMs": 92795 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082517_ac726065-6215-40f0-9b56-4315b9a7d879.mp4", + "tMs": 92795 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082517_2e6a25af-60ac-4137-b4d5-e239f0abc948.mp4", + "tMs": 92829 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082527_647e9c26-b9ce-4329-8699-ebeaad7e016b.mp4", + "tMs": 102796 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082527_9e26ac22-9341-44b9-a2c2-adbcb190b46b.mp4", + "tMs": 102806 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082527_261c7e92-5776-4a84-a6d1-8ddbf79f3b86.mp4", + "tMs": 102806 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082527_af1ebbf7-073f-46ed-9e51-8414a20eafba.mp4", + "tMs": 102807 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082537_e2bcccf9-c524-4e39-80b3-21e28bb848a3.mp4", + "tMs": 112789 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082537_47bb625e-cd9c-4747-818d-8ad0992518cd.mp4", + "tMs": 112795 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082537_3a6f730e-6c33-4743-9a7b-45fe98b96fa7.mp4", + "tMs": 112795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082537_07b8a496-540b-416b-9daa-20e5ee068b2f.mp4", + "tMs": 112798 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082547_39ab1e3e-0aaa-43ab-b5db-246e014ba57a.mp4", + "tMs": 122798 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082547_bd2bfdd0-2023-4492-9a23-4c1374d051e3.mp4", + "tMs": 122798 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082547_6ee43265-55f4-49f0-92ef-026d6efb8855.mp4", + "tMs": 122805 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082547_f59354a6-edb2-4315-906d-4d7283afb3a4.mp4", + "tMs": 122833 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082557_b503cfbe-839c-4f6e-83d4-422e59f25597.mp4", + "tMs": 132795 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082557_795348e2-0a44-4797-8e86-6f032d5da633.mp4", + "tMs": 132801 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082557_37a3a182-9794-4069-bf0c-29a103eccc8d.mp4", + "tMs": 132803 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082557_2451f01f-8187-4564-852f-1e795dbcdc0d.mp4", + "tMs": 132805 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082607_07f384fc-458e-48f3-9720-9e572dc7495a.mp4", + "tMs": 142788 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082607_514ea130-25c5-4cd1-80db-496441563ada.mp4", + "tMs": 142798 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082607_39de555c-1d6e-4729-b62a-357521f4b2b5.mp4", + "tMs": 142798 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082607_1668ec48-cbad-486d-8b70-86517c8cf9bc.mp4", + "tMs": 142800 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082617_3b1117b3-6232-4e5a-92e9-30a9e6764a31.mp4", + "tMs": 152787 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082617_5afddb55-8984-456d-9256-482908f6cd98.mp4", + "tMs": 152792 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082617_488d7138-50d3-4371-a614-612b5f5cb4ef.mp4", + "tMs": 152829 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082617_f887a067-434c-4b0b-819f-783a49d54372.mp4", + "tMs": 152834 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082627_b905370b-e154-41ff-89f0-4c141ada9e5c.mp4", + "tMs": 162789 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082627_e941b24e-289a-4146-87c5-fc65fab026c5.mp4", + "tMs": 162795 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082627_b796ceb4-47de-4842-a682-6821498e100e.mp4", + "tMs": 162795 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082627_b3bebcf0-40e1-43d8-b82a-4b8c10df4168.mp4", + "tMs": 162802 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082637_107183ec-50e3-43a8-8810-af3dc0c9f5d0.mp4", + "tMs": 172787 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082637_5db3b125-4af2-4340-a2ff-886e7c623587.mp4", + "tMs": 172798 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082637_bf23dac8-c99d-47bf-af7d-bd7a9a533827.mp4", + "tMs": 172798 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082637_aef5c872-daea-41ab-8c15-849cd8324125.mp4", + "tMs": 172830 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082647_c9d2ab8c-81fa-4cc0-9b35-759ebf3f92e1.mp4", + "tMs": 182789 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082647_41f2aac0-13b1-4395-87a5-9f94c163d59d.mp4", + "tMs": 182793 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082647_a648056a-7f94-44fe-a556-f6bc5a8c2671.mp4", + "tMs": 182833 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082647_479fc358-f42e-4e27-94b0-8010c11153b0.mp4", + "tMs": 182843 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082657_e493b60e-3cb6-4977-b0e1-397f1b22c214.mp4", + "tMs": 192788 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082657_c6e3b551-716c-4574-9f38-3536f0038f5d.mp4", + "tMs": 192799 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082657_398291ac-bd09-4fc6-9391-1c848cc0bb9b.mp4", + "tMs": 192799 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082657_266e0145-1aaa-4953-941b-023ca1f88871.mp4", + "tMs": 192799 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082707_71fd2161-0107-44f3-829d-ae166d9c36e4.mp4", + "tMs": 202790 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082707_6d84250e-4565-4b91-85cc-12abeaff3096.mp4", + "tMs": 202793 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082707_331c95d3-9032-41e9-9641-a06952111d89.mp4", + "tMs": 202796 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082707_b9bb80e1-b5ac-440e-99bd-df71ddc989d8.mp4", + "tMs": 202808 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082717_797776c6-cd1b-4e24-bf4f-f6e6785cf6ac.mp4", + "tMs": 212789 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082717_a8433084-d85b-4bd0-9062-7187aaea7a27.mp4", + "tMs": 212798 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082717_feb6034f-86ff-4cd7-a4d1-de9c1414b62e.mp4", + "tMs": 212800 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082717_be366822-835f-4535-bcd5-ac52cb35f545.mp4", + "tMs": 212831 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082727_86caea0d-0bc6-4869-8213-5402df3cbbce.mp4", + "tMs": 222785 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082727_82c1eff9-99e6-4216-b57a-523c10048803.mp4", + "tMs": 222792 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082727_6ee0729c-7be3-4b20-885f-5899da470256.mp4", + "tMs": 222833 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082727_aa12c5ed-97e3-4730-b741-787ef4b1b301.mp4", + "tMs": 222837 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082737_de8b324b-9537-424b-acdb-d854cefc7837.mp4", + "tMs": 232786 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082737_007901c1-7e6d-4801-8111-ec066f9766fd.mp4", + "tMs": 232800 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082737_2eff5a1e-aebb-4be3-8812-a9bd92f20c00.mp4", + "tMs": 232803 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082737_d9ff43ba-df78-4f4e-be37-ba89736da420.mp4", + "tMs": 232816 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082747_1deb3c52-84b2-4487-8b76-1ac0a25870b2.mp4", + "tMs": 242786 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082747_0ca44a6b-f9cf-4750-a3bb-0347ddad8236.mp4", + "tMs": 242840 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082747_655f158c-7041-491d-bba2-eba2c1e57b2f.mp4", + "tMs": 242854 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082747_77b6c6e5-a392-42ed-9501-fb67a8e9e3f9.mp4", + "tMs": 242854 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082757_6c1e4fd7-a026-44a5-8232-0d05c94dda26.mp4", + "tMs": 252788 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082757_91dbb421-5bde-4bb8-8787-5c296409b725.mp4", + "tMs": 252794 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082757_3494fb2c-3d30-4569-b985-4e9ca578d225.mp4", + "tMs": 252795 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082757_cc441233-85d2-455e-9146-3c1ca2d9c86d.mp4", + "tMs": 252830 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082807_fb88cd59-6020-4bdf-be60-52e655d9cba9.mp4", + "tMs": 262788 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082807_4770b83d-db97-4c0c-bc9b-7b9f0586e0f0.mp4", + "tMs": 262793 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082807_9f0ce791-15dd-4623-9825-7708bfd163fa.mp4", + "tMs": 262793 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082807_53956469-0a4c-4dc2-bf90-3534b63e879a.mp4", + "tMs": 262830 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082817_19208593-da11-48b7-8b34-3c51fa579025.mp4", + "tMs": 272786 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082817_0c08e811-d772-40cb-bba4-61ccbe889551.mp4", + "tMs": 272796 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082817_90f16c52-e3f2-4626-9e93-fd177dc8c044.mp4", + "tMs": 272797 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082817_188cc722-f135-4a2c-b810-fb81f8203918.mp4", + "tMs": 272833 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082827_452243a2-7cc6-4790-9703-bfd0b7d46bbb.mp4", + "tMs": 282786 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082827_a8e695a5-67e7-4440-a3f4-60a0690a9b7e.mp4", + "tMs": 282800 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082827_70eeaa80-8450-470d-8362-1f0983047212.mp4", + "tMs": 282828 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082827_138e9421-bd85-4e0a-9468-bf850a58658f.mp4", + "tMs": 282835 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082837_2328af76-515a-4065-8402-06f3cb40a8e5.mp4", + "tMs": 292789 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082837_770b8fbb-45f3-4ab9-8507-5b956e68ed6a.mp4", + "tMs": 292794 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082837_24e2015b-d000-456d-88f9-0c9be2d095f4.mp4", + "tMs": 292794 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082837_5068b3f0-38d3-454c-af68-091aa5d0cdb2.mp4", + "tMs": 292831 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_082847_4ee70b1c-1267-49f3-8d8c-3c396f1d8add.mp4", + "tMs": 302787 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_082847_da96ae1e-0639-4a95-989a-72a021a63846.mp4", + "tMs": 302794 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_082847_d67f3389-ca7d-4638-91c2-8a34f811d13e.mp4", + "tMs": 302794 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_082847_0d068896-6590-4611-9e2e-8a1ea9a49de3.mp4", + "tMs": 302835 + } + ] + }, + "problems": [], + "finished_at": "2026-11-08T13:28:52.417Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-disabled-run1.json b/tests/performance/observations/results/2026-11-08-x2-disabled-run1.json new file mode 100644 index 000000000..49d48e38f --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-disabled-run1.json @@ -0,0 +1,8670 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "disabled", + "status": "completed", + "started_at": "2026-11-08T18:55:03.554Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 3.04, + 3.61, + 3.46 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": {}, + "config_extra": null, + "system_settings": {} + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-W6Bpqh", + "detector": { + "inferences_in_window": 2408, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 2408, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 0, + "dropped": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 0, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 0, + "high_water_items": 0, + "high_water_bytes": 0, + "camera_high_water_items": 0, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 33554432, + "camera_bytes": 33554432, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 0, + "inline": 0 + }, + "discard_pending_bytes_max": 0, + "backlog_items_max": 0, + "backlog_bytes_max": 0, + "drop_ring_depth_max": 0, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0, + "queue_allocated_end": 0, + "worker_running_end": 0, + "retained_bytes_by_camera_end": {}, + "budget_bytes_by_camera_end": {}, + "history_observations_end": 0, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 0, + "sum_seconds": 0, + "mean_seconds": null, + "max_seconds": 0, + "p50_upper_seconds": "+Inf", + "p99_upper_seconds": "+Inf", + "p999_upper_seconds": "+Inf", + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 0 + }, + { + "le": 0.00001, + "count": 0 + }, + { + "le": 0.00005, + "count": 0 + }, + { + "le": 0.0001, + "count": 0 + }, + { + "le": 0.00025, + "count": 0 + }, + { + "le": 0.0005, + "count": 0 + }, + { + "le": 0.001, + "count": 0 + }, + { + "le": 0.01, + "count": 0 + }, + { + "le": "+Inf", + "count": 0 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "window_unix": { + "start": 1794164142, + "end": 1794164744 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9981.227, + "p50": 10000.015, + "p95": 10008.926, + "p99": 10015.841, + "max": 10021.819, + "mean": 9999.999 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -18.773, + "p50": 0.015, + "p95": 8.926, + "p99": 15.841, + "max": 21.819, + "mean": -0.001 + }, + "db_probe_ms": { + "count": 2318, + "min": 6.814, + "p50": 8.185, + "p95": 13.081, + "p99": 16.334, + "max": 30.734, + "mean": 8.875 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9994.132, + "p50": 10000.059, + "p95": 10004.136, + "p99": 10005.603, + "max": 10005.82, + "mean": 10000 + }, + "Load1": { + "count": 60, + "min": 9981.227, + "p50": 10000.144, + "p95": 10012.062, + "p99": 10015.699, + "max": 10017.871, + "mean": 10000.008 + }, + "Load2": { + "count": 60, + "min": 9985.755, + "p50": 9999.415, + "p95": 10012.551, + "p99": 10017.298, + "max": 10021.819, + "mean": 9999.985 + }, + "Load3": { + "count": 60, + "min": 9987.043, + "p50": 10000.123, + "p95": 10009.645, + "p99": 10015.052, + "max": 10016.896, + "mean": 10000.001 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032217280, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.2, + "mean": 15.101 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 598.853, + "cpu_percent_one_core": { + "count": 519, + "min": 114.124, + "p50": 138.107, + "p95": 192.269, + "p99": 201.921, + "max": 216.543, + "mean": 148.599 + }, + "cpu_percent_one_core_avg": 148.945, + "rss_kib": { + "count": 520, + "min": 858540, + "p50": 862860, + "p95": 887072, + "p99": 887348.2, + "max": 887596, + "mean": 869818.477 + }, + "rss_high_water_kib": 887596, + "disk_write_bytes": 1231781888, + "disk_write_bytes_per_sec": 2056902.248, + "threads_max": 34, + "fds_max": 38 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.132, + "processes": 5 + }, + "storage_bytes_end": 1086895166, + "loadavg_at_end": [ + 7.23, + 7.02, + 5.52 + ] + }, + "window": { + "start": "2026-11-08T18:55:42.752Z", + "end": "2026-11-08T19:05:43.916Z", + "seconds": 601.16 + }, + "samples": { + "proc": [ + { + "tMs": 12, + "cpuTicks": 4582, + "rssKiB": 881400, + "peakRssKiB": 881400, + "threads": 34, + "fds": 36, + "writeBytes": 60612608 + }, + { + "tMs": 1164, + "cpuTicks": 4721, + "rssKiB": 858540, + "peakRssKiB": 877632, + "threads": 34, + "fds": 36, + "writeBytes": 62603264 + }, + { + "tMs": 2268, + "cpuTicks": 4862, + "rssKiB": 858904, + "peakRssKiB": 877892, + "threads": 34, + "fds": 36, + "writeBytes": 64425984 + }, + { + "tMs": 3399, + "cpuTicks": 5011, + "rssKiB": 859152, + "peakRssKiB": 877904, + "threads": 34, + "fds": 36, + "writeBytes": 68055040 + }, + { + "tMs": 4529, + "cpuTicks": 5163, + "rssKiB": 859304, + "peakRssKiB": 877912, + "threads": 34, + "fds": 36, + "writeBytes": 69910528 + }, + { + "tMs": 5656, + "cpuTicks": 5308, + "rssKiB": 859708, + "peakRssKiB": 878316, + "threads": 34, + "fds": 36, + "writeBytes": 71782400 + }, + { + "tMs": 6853, + "cpuTicks": 5531, + "rssKiB": 883952, + "peakRssKiB": 883952, + "threads": 34, + "fds": 36, + "writeBytes": 74924032 + }, + { + "tMs": 8018, + "cpuTicks": 5735, + "rssKiB": 884024, + "peakRssKiB": 884024, + "threads": 34, + "fds": 36, + "writeBytes": 77045760 + }, + { + "tMs": 9176, + "cpuTicks": 5896, + "rssKiB": 859904, + "peakRssKiB": 878384, + "threads": 34, + "fds": 36, + "writeBytes": 79093760 + }, + { + "tMs": 10314, + "cpuTicks": 6037, + "rssKiB": 860248, + "peakRssKiB": 878528, + "threads": 34, + "fds": 36, + "writeBytes": 80965632 + }, + { + "tMs": 11441, + "cpuTicks": 6179, + "rssKiB": 860264, + "peakRssKiB": 878932, + "threads": 34, + "fds": 36, + "writeBytes": 82817024 + }, + { + "tMs": 12555, + "cpuTicks": 6319, + "rssKiB": 860288, + "peakRssKiB": 879020, + "threads": 34, + "fds": 36, + "writeBytes": 84705280 + }, + { + "tMs": 13671, + "cpuTicks": 6468, + "rssKiB": 860288, + "peakRssKiB": 879020, + "threads": 34, + "fds": 36, + "writeBytes": 88973312 + }, + { + "tMs": 14880, + "cpuTicks": 6702, + "rssKiB": 884464, + "peakRssKiB": 884464, + "threads": 34, + "fds": 36, + "writeBytes": 91480064 + }, + { + "tMs": 16032, + "cpuTicks": 6912, + "rssKiB": 884788, + "peakRssKiB": 884788, + "threads": 34, + "fds": 36, + "writeBytes": 93454336 + }, + { + "tMs": 17164, + "cpuTicks": 7065, + "rssKiB": 860304, + "peakRssKiB": 879316, + "threads": 34, + "fds": 36, + "writeBytes": 95449088 + }, + { + "tMs": 18296, + "cpuTicks": 7204, + "rssKiB": 860312, + "peakRssKiB": 879316, + "threads": 34, + "fds": 36, + "writeBytes": 97304576 + }, + { + "tMs": 19441, + "cpuTicks": 7343, + "rssKiB": 860320, + "peakRssKiB": 879316, + "threads": 34, + "fds": 36, + "writeBytes": 99176448 + }, + { + "tMs": 20561, + "cpuTicks": 7495, + "rssKiB": 860380, + "peakRssKiB": 879316, + "threads": 34, + "fds": 36, + "writeBytes": 101036032 + }, + { + "tMs": 21701, + "cpuTicks": 7637, + "rssKiB": 884552, + "peakRssKiB": 884552, + "threads": 34, + "fds": 36, + "writeBytes": 104116224 + }, + { + "tMs": 22877, + "cpuTicks": 7859, + "rssKiB": 884552, + "peakRssKiB": 884552, + "threads": 34, + "fds": 36, + "writeBytes": 107708416 + }, + { + "tMs": 24023, + "cpuTicks": 8067, + "rssKiB": 884552, + "peakRssKiB": 884552, + "threads": 34, + "fds": 36, + "writeBytes": 109887488 + }, + { + "tMs": 25154, + "cpuTicks": 8217, + "rssKiB": 860408, + "peakRssKiB": 879368, + "threads": 34, + "fds": 36, + "writeBytes": 111939584 + }, + { + "tMs": 26274, + "cpuTicks": 8366, + "rssKiB": 860732, + "peakRssKiB": 879368, + "threads": 34, + "fds": 36, + "writeBytes": 113803264 + }, + { + "tMs": 27401, + "cpuTicks": 8520, + "rssKiB": 861056, + "peakRssKiB": 879536, + "threads": 34, + "fds": 36, + "writeBytes": 115695616 + }, + { + "tMs": 28519, + "cpuTicks": 8668, + "rssKiB": 861056, + "peakRssKiB": 879536, + "threads": 34, + "fds": 36, + "writeBytes": 117510144 + }, + { + "tMs": 29643, + "cpuTicks": 8825, + "rssKiB": 861056, + "peakRssKiB": 879536, + "threads": 34, + "fds": 36, + "writeBytes": 119324672 + }, + { + "tMs": 30833, + "cpuTicks": 9049, + "rssKiB": 885996, + "peakRssKiB": 885996, + "threads": 34, + "fds": 37, + "writeBytes": 122478592 + }, + { + "tMs": 31989, + "cpuTicks": 9258, + "rssKiB": 886000, + "peakRssKiB": 886000, + "threads": 34, + "fds": 37, + "writeBytes": 124383232 + }, + { + "tMs": 33122, + "cpuTicks": 9436, + "rssKiB": 861828, + "peakRssKiB": 880172, + "threads": 34, + "fds": 37, + "writeBytes": 128249856 + }, + { + "tMs": 34244, + "cpuTicks": 9594, + "rssKiB": 861832, + "peakRssKiB": 880320, + "threads": 34, + "fds": 37, + "writeBytes": 130101248 + }, + { + "tMs": 35359, + "cpuTicks": 9750, + "rssKiB": 861852, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 132050944 + }, + { + "tMs": 36479, + "cpuTicks": 9908, + "rssKiB": 861852, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 133935104 + }, + { + "tMs": 37597, + "cpuTicks": 10063, + "rssKiB": 861852, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 135794688 + }, + { + "tMs": 38764, + "cpuTicks": 10260, + "rssKiB": 885376, + "peakRssKiB": 885376, + "threads": 34, + "fds": 37, + "writeBytes": 138989568 + }, + { + "tMs": 39959, + "cpuTicks": 10510, + "rssKiB": 885700, + "peakRssKiB": 885700, + "threads": 34, + "fds": 37, + "writeBytes": 140861440 + }, + { + "tMs": 41102, + "cpuTicks": 10702, + "rssKiB": 861568, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 143286272 + }, + { + "tMs": 42220, + "cpuTicks": 10856, + "rssKiB": 861568, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 145162240 + }, + { + "tMs": 43335, + "cpuTicks": 11022, + "rssKiB": 861568, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 148750336 + }, + { + "tMs": 44456, + "cpuTicks": 11174, + "rssKiB": 861568, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 150593536 + }, + { + "tMs": 45574, + "cpuTicks": 11333, + "rssKiB": 861596, + "peakRssKiB": 880424, + "threads": 34, + "fds": 37, + "writeBytes": 152510464 + }, + { + "tMs": 46717, + "cpuTicks": 11510, + "rssKiB": 885768, + "peakRssKiB": 885768, + "threads": 34, + "fds": 37, + "writeBytes": 155709440 + }, + { + "tMs": 47925, + "cpuTicks": 11763, + "rssKiB": 885768, + "peakRssKiB": 885768, + "threads": 34, + "fds": 37, + "writeBytes": 157560832 + }, + { + "tMs": 49072, + "cpuTicks": 11963, + "rssKiB": 861596, + "peakRssKiB": 880576, + "threads": 34, + "fds": 37, + "writeBytes": 159645696 + }, + { + "tMs": 50196, + "cpuTicks": 12120, + "rssKiB": 861652, + "peakRssKiB": 880576, + "threads": 34, + "fds": 37, + "writeBytes": 161542144 + }, + { + "tMs": 51316, + "cpuTicks": 12271, + "rssKiB": 861332, + "peakRssKiB": 880576, + "threads": 34, + "fds": 37, + "writeBytes": 163307520 + }, + { + "tMs": 52440, + "cpuTicks": 12413, + "rssKiB": 861332, + "peakRssKiB": 880576, + "threads": 34, + "fds": 37, + "writeBytes": 165175296 + }, + { + "tMs": 53556, + "cpuTicks": 12566, + "rssKiB": 861560, + "peakRssKiB": 880576, + "threads": 34, + "fds": 37, + "writeBytes": 168857600 + }, + { + "tMs": 54678, + "cpuTicks": 12707, + "rssKiB": 873700, + "peakRssKiB": 880576, + "threads": 34, + "fds": 37, + "writeBytes": 171941888 + }, + { + "tMs": 55852, + "cpuTicks": 12922, + "rssKiB": 885840, + "peakRssKiB": 885840, + "threads": 34, + "fds": 37, + "writeBytes": 174153728 + }, + { + "tMs": 57012, + "cpuTicks": 13132, + "rssKiB": 861992, + "peakRssKiB": 880908, + "threads": 34, + "fds": 37, + "writeBytes": 176091136 + }, + { + "tMs": 58131, + "cpuTicks": 13267, + "rssKiB": 861992, + "peakRssKiB": 880944, + "threads": 34, + "fds": 37, + "writeBytes": 178077696 + }, + { + "tMs": 59248, + "cpuTicks": 13420, + "rssKiB": 861992, + "peakRssKiB": 881092, + "threads": 34, + "fds": 37, + "writeBytes": 179929088 + }, + { + "tMs": 60367, + "cpuTicks": 13566, + "rssKiB": 862020, + "peakRssKiB": 881200, + "threads": 34, + "fds": 37, + "writeBytes": 181772288 + }, + { + "tMs": 61487, + "cpuTicks": 13715, + "rssKiB": 862020, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 183640064 + }, + { + "tMs": 62620, + "cpuTicks": 13857, + "rssKiB": 862020, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 185450496 + }, + { + "tMs": 63805, + "cpuTicks": 14059, + "rssKiB": 886192, + "peakRssKiB": 886192, + "threads": 34, + "fds": 37, + "writeBytes": 190390272 + }, + { + "tMs": 64970, + "cpuTicks": 14260, + "rssKiB": 886192, + "peakRssKiB": 886192, + "threads": 34, + "fds": 37, + "writeBytes": 192294912 + }, + { + "tMs": 66104, + "cpuTicks": 14424, + "rssKiB": 862020, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 194383872 + }, + { + "tMs": 67242, + "cpuTicks": 14561, + "rssKiB": 862020, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 196255744 + }, + { + "tMs": 68370, + "cpuTicks": 14713, + "rssKiB": 861696, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 198135808 + }, + { + "tMs": 69501, + "cpuTicks": 14864, + "rssKiB": 861696, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 200044544 + }, + { + "tMs": 70637, + "cpuTicks": 15017, + "rssKiB": 861700, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 201912320 + }, + { + "tMs": 71820, + "cpuTicks": 15217, + "rssKiB": 886196, + "peakRssKiB": 886196, + "threads": 34, + "fds": 37, + "writeBytes": 205361152 + }, + { + "tMs": 72981, + "cpuTicks": 15429, + "rssKiB": 867196, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 209002496 + }, + { + "tMs": 74099, + "cpuTicks": 15575, + "rssKiB": 862024, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 211046400 + }, + { + "tMs": 75241, + "cpuTicks": 15715, + "rssKiB": 862048, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 212926464 + }, + { + "tMs": 76378, + "cpuTicks": 15857, + "rssKiB": 862048, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 214806528 + }, + { + "tMs": 77513, + "cpuTicks": 16009, + "rssKiB": 862048, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 216649728 + }, + { + "tMs": 78640, + "cpuTicks": 16152, + "rssKiB": 862048, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 218525696 + }, + { + "tMs": 79816, + "cpuTicks": 16351, + "rssKiB": 886220, + "peakRssKiB": 886220, + "threads": 34, + "fds": 37, + "writeBytes": 221696000 + }, + { + "tMs": 80968, + "cpuTicks": 16551, + "rssKiB": 886224, + "peakRssKiB": 886224, + "threads": 34, + "fds": 37, + "writeBytes": 223129600 + }, + { + "tMs": 82122, + "cpuTicks": 16712, + "rssKiB": 862052, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 225591296 + }, + { + "tMs": 83261, + "cpuTicks": 16867, + "rssKiB": 861728, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 229228544 + }, + { + "tMs": 84404, + "cpuTicks": 17012, + "rssKiB": 861728, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 231120896 + }, + { + "tMs": 85522, + "cpuTicks": 17164, + "rssKiB": 861740, + "peakRssKiB": 881604, + "threads": 34, + "fds": 38, + "writeBytes": 233021440 + }, + { + "tMs": 86642, + "cpuTicks": 17312, + "rssKiB": 861740, + "peakRssKiB": 881604, + "threads": 34, + "fds": 38, + "writeBytes": 235184128 + }, + { + "tMs": 87862, + "cpuTicks": 17555, + "rssKiB": 885912, + "peakRssKiB": 885912, + "threads": 34, + "fds": 38, + "writeBytes": 238366720 + }, + { + "tMs": 89027, + "cpuTicks": 17770, + "rssKiB": 885912, + "peakRssKiB": 885912, + "threads": 34, + "fds": 38, + "writeBytes": 240336896 + }, + { + "tMs": 90177, + "cpuTicks": 17932, + "rssKiB": 861448, + "peakRssKiB": 881604, + "threads": 34, + "fds": 38, + "writeBytes": 242352128 + }, + { + "tMs": 91301, + "cpuTicks": 18090, + "rssKiB": 861476, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 244203520 + }, + { + "tMs": 92436, + "cpuTicks": 18243, + "rssKiB": 861480, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 246054912 + }, + { + "tMs": 93562, + "cpuTicks": 18397, + "rssKiB": 861804, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 249663488 + }, + { + "tMs": 94708, + "cpuTicks": 18561, + "rssKiB": 885976, + "peakRssKiB": 885976, + "threads": 34, + "fds": 37, + "writeBytes": 252833792 + }, + { + "tMs": 95888, + "cpuTicks": 18780, + "rssKiB": 885980, + "peakRssKiB": 885980, + "threads": 34, + "fds": 37, + "writeBytes": 254754816 + }, + { + "tMs": 97057, + "cpuTicks": 18992, + "rssKiB": 861808, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 256704512 + }, + { + "tMs": 98179, + "cpuTicks": 19146, + "rssKiB": 861808, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 258678784 + }, + { + "tMs": 99300, + "cpuTicks": 19288, + "rssKiB": 861808, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 260567040 + }, + { + "tMs": 100426, + "cpuTicks": 19432, + "rssKiB": 861812, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 262459392 + }, + { + "tMs": 101553, + "cpuTicks": 19578, + "rssKiB": 861812, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 264364032 + }, + { + "tMs": 102707, + "cpuTicks": 19737, + "rssKiB": 886308, + "peakRssKiB": 886308, + "threads": 34, + "fds": 37, + "writeBytes": 269701120 + }, + { + "tMs": 103893, + "cpuTicks": 19965, + "rssKiB": 886308, + "peakRssKiB": 886308, + "threads": 34, + "fds": 37, + "writeBytes": 271536128 + }, + { + "tMs": 105032, + "cpuTicks": 20172, + "rssKiB": 886308, + "peakRssKiB": 886308, + "threads": 34, + "fds": 38, + "writeBytes": 273506304 + }, + { + "tMs": 106183, + "cpuTicks": 20334, + "rssKiB": 862136, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 275570688 + }, + { + "tMs": 107318, + "cpuTicks": 20478, + "rssKiB": 862136, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 277426176 + }, + { + "tMs": 108450, + "cpuTicks": 20633, + "rssKiB": 862136, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 279281664 + }, + { + "tMs": 109592, + "cpuTicks": 20798, + "rssKiB": 862136, + "peakRssKiB": 881604, + "threads": 34, + "fds": 37, + "writeBytes": 281174016 + }, + { + "tMs": 110775, + "cpuTicks": 21001, + "rssKiB": 886308, + "peakRssKiB": 886308, + "threads": 34, + "fds": 37, + "writeBytes": 283443200 + }, + { + "tMs": 111941, + "cpuTicks": 21224, + "rssKiB": 886308, + "peakRssKiB": 886308, + "threads": 34, + "fds": 37, + "writeBytes": 286142464 + }, + { + "tMs": 113083, + "cpuTicks": 21417, + "rssKiB": 861812, + "peakRssKiB": 881676, + "threads": 34, + "fds": 37, + "writeBytes": 289955840 + }, + { + "tMs": 114218, + "cpuTicks": 21561, + "rssKiB": 861812, + "peakRssKiB": 881676, + "threads": 34, + "fds": 37, + "writeBytes": 291876864 + }, + { + "tMs": 115368, + "cpuTicks": 21707, + "rssKiB": 861716, + "peakRssKiB": 881676, + "threads": 34, + "fds": 37, + "writeBytes": 293765120 + }, + { + "tMs": 116503, + "cpuTicks": 21859, + "rssKiB": 862036, + "peakRssKiB": 881676, + "threads": 34, + "fds": 37, + "writeBytes": 295624704 + }, + { + "tMs": 117622, + "cpuTicks": 22008, + "rssKiB": 862360, + "peakRssKiB": 881704, + "threads": 34, + "fds": 37, + "writeBytes": 297803776 + }, + { + "tMs": 118804, + "cpuTicks": 22218, + "rssKiB": 886532, + "peakRssKiB": 886532, + "threads": 34, + "fds": 37, + "writeBytes": 300957696 + }, + { + "tMs": 119954, + "cpuTicks": 22431, + "rssKiB": 886532, + "peakRssKiB": 886532, + "threads": 34, + "fds": 37, + "writeBytes": 302796800 + }, + { + "tMs": 121115, + "cpuTicks": 22622, + "rssKiB": 862376, + "peakRssKiB": 881892, + "threads": 34, + "fds": 37, + "writeBytes": 304893952 + }, + { + "tMs": 122263, + "cpuTicks": 22755, + "rssKiB": 862376, + "peakRssKiB": 881960, + "threads": 34, + "fds": 37, + "writeBytes": 306753536 + }, + { + "tMs": 123398, + "cpuTicks": 22911, + "rssKiB": 862376, + "peakRssKiB": 881960, + "threads": 34, + "fds": 37, + "writeBytes": 310382592 + }, + { + "tMs": 124513, + "cpuTicks": 23069, + "rssKiB": 862700, + "peakRssKiB": 882464, + "threads": 34, + "fds": 37, + "writeBytes": 312295424 + }, + { + "tMs": 125632, + "cpuTicks": 23228, + "rssKiB": 862712, + "peakRssKiB": 882612, + "threads": 34, + "fds": 37, + "writeBytes": 314195968 + }, + { + "tMs": 126836, + "cpuTicks": 23443, + "rssKiB": 886884, + "peakRssKiB": 886884, + "threads": 34, + "fds": 37, + "writeBytes": 317345792 + }, + { + "tMs": 128011, + "cpuTicks": 23668, + "rssKiB": 886884, + "peakRssKiB": 886884, + "threads": 34, + "fds": 37, + "writeBytes": 319332352 + }, + { + "tMs": 129186, + "cpuTicks": 23823, + "rssKiB": 862712, + "peakRssKiB": 882672, + "threads": 34, + "fds": 37, + "writeBytes": 321462272 + }, + { + "tMs": 130347, + "cpuTicks": 23974, + "rssKiB": 862728, + "peakRssKiB": 882672, + "threads": 34, + "fds": 37, + "writeBytes": 323411968 + }, + { + "tMs": 131494, + "cpuTicks": 24130, + "rssKiB": 862728, + "peakRssKiB": 882772, + "threads": 34, + "fds": 37, + "writeBytes": 325304320 + }, + { + "tMs": 132649, + "cpuTicks": 24271, + "rssKiB": 862728, + "peakRssKiB": 882772, + "threads": 34, + "fds": 37, + "writeBytes": 327188480 + }, + { + "tMs": 133848, + "cpuTicks": 24494, + "rssKiB": 886900, + "peakRssKiB": 886900, + "threads": 34, + "fds": 37, + "writeBytes": 332460032 + }, + { + "tMs": 134996, + "cpuTicks": 24689, + "rssKiB": 886352, + "peakRssKiB": 886352, + "threads": 34, + "fds": 37, + "writeBytes": 334331904 + }, + { + "tMs": 136141, + "cpuTicks": 24840, + "rssKiB": 862180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 336510976 + }, + { + "tMs": 137288, + "cpuTicks": 24987, + "rssKiB": 862180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 338391040 + }, + { + "tMs": 138418, + "cpuTicks": 25125, + "rssKiB": 862180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 340262912 + }, + { + "tMs": 139555, + "cpuTicks": 25270, + "rssKiB": 861856, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 342155264 + }, + { + "tMs": 140730, + "cpuTicks": 25439, + "rssKiB": 886028, + "peakRssKiB": 886028, + "threads": 34, + "fds": 37, + "writeBytes": 344457216 + }, + { + "tMs": 141917, + "cpuTicks": 25666, + "rssKiB": 886028, + "peakRssKiB": 886028, + "threads": 34, + "fds": 37, + "writeBytes": 347172864 + }, + { + "tMs": 143088, + "cpuTicks": 25883, + "rssKiB": 861856, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 351047680 + }, + { + "tMs": 144232, + "cpuTicks": 26043, + "rssKiB": 861856, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 353038336 + }, + { + "tMs": 145370, + "cpuTicks": 26189, + "rssKiB": 861884, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 354988032 + }, + { + "tMs": 146512, + "cpuTicks": 26338, + "rssKiB": 861884, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 357249024 + }, + { + "tMs": 147631, + "cpuTicks": 26489, + "rssKiB": 861884, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 359108608 + }, + { + "tMs": 148843, + "cpuTicks": 26705, + "rssKiB": 886056, + "peakRssKiB": 886056, + "threads": 34, + "fds": 37, + "writeBytes": 362270720 + }, + { + "tMs": 150009, + "cpuTicks": 26919, + "rssKiB": 885768, + "peakRssKiB": 885768, + "threads": 34, + "fds": 37, + "writeBytes": 364220416 + }, + { + "tMs": 151157, + "cpuTicks": 27082, + "rssKiB": 861624, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 366243840 + }, + { + "tMs": 152295, + "cpuTicks": 27243, + "rssKiB": 861948, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 368087040 + }, + { + "tMs": 153444, + "cpuTicks": 27398, + "rssKiB": 861948, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 371699712 + }, + { + "tMs": 154582, + "cpuTicks": 27543, + "rssKiB": 862272, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 373592064 + }, + { + "tMs": 155752, + "cpuTicks": 27721, + "rssKiB": 886452, + "peakRssKiB": 886452, + "threads": 34, + "fds": 37, + "writeBytes": 376868864 + }, + { + "tMs": 156942, + "cpuTicks": 27951, + "rssKiB": 886452, + "peakRssKiB": 886452, + "threads": 34, + "fds": 37, + "writeBytes": 378736640 + }, + { + "tMs": 158089, + "cpuTicks": 28153, + "rssKiB": 862280, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 380833792 + }, + { + "tMs": 159234, + "cpuTicks": 28297, + "rssKiB": 862280, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 382754816 + }, + { + "tMs": 160366, + "cpuTicks": 28444, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 384663552 + }, + { + "tMs": 161522, + "cpuTicks": 28589, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 386621440 + }, + { + "tMs": 162702, + "cpuTicks": 28752, + "rssKiB": 886456, + "peakRssKiB": 886456, + "threads": 34, + "fds": 37, + "writeBytes": 391954432 + }, + { + "tMs": 163879, + "cpuTicks": 28971, + "rssKiB": 886456, + "peakRssKiB": 886456, + "threads": 34, + "fds": 37, + "writeBytes": 393789440 + }, + { + "tMs": 165032, + "cpuTicks": 29161, + "rssKiB": 861864, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 395853824 + }, + { + "tMs": 166177, + "cpuTicks": 29316, + "rssKiB": 861636, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 397934592 + }, + { + "tMs": 167332, + "cpuTicks": 29464, + "rssKiB": 861636, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 399843328 + }, + { + "tMs": 168474, + "cpuTicks": 29605, + "rssKiB": 861636, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 401727488 + }, + { + "tMs": 169627, + "cpuTicks": 29758, + "rssKiB": 861636, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 403636224 + }, + { + "tMs": 170805, + "cpuTicks": 29950, + "rssKiB": 885808, + "peakRssKiB": 885808, + "threads": 34, + "fds": 37, + "writeBytes": 405950464 + }, + { + "tMs": 171969, + "cpuTicks": 30171, + "rssKiB": 885808, + "peakRssKiB": 885808, + "threads": 34, + "fds": 37, + "writeBytes": 408645632 + }, + { + "tMs": 173141, + "cpuTicks": 30350, + "rssKiB": 861636, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 412549120 + }, + { + "tMs": 174283, + "cpuTicks": 30513, + "rssKiB": 861636, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 414457856 + }, + { + "tMs": 175433, + "cpuTicks": 30658, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 416358400 + }, + { + "tMs": 176571, + "cpuTicks": 30804, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 418623488 + }, + { + "tMs": 177767, + "cpuTicks": 30993, + "rssKiB": 886456, + "peakRssKiB": 886456, + "threads": 34, + "fds": 37, + "writeBytes": 421797888 + }, + { + "tMs": 178943, + "cpuTicks": 31209, + "rssKiB": 886456, + "peakRssKiB": 886456, + "threads": 34, + "fds": 37, + "writeBytes": 423686144 + }, + { + "tMs": 180083, + "cpuTicks": 31374, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 425730048 + }, + { + "tMs": 181242, + "cpuTicks": 31518, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 427585536 + }, + { + "tMs": 182392, + "cpuTicks": 31672, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 429486080 + }, + { + "tMs": 183526, + "cpuTicks": 31815, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 433127424 + }, + { + "tMs": 184665, + "cpuTicks": 31964, + "rssKiB": 862284, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 435011584 + }, + { + "tMs": 185862, + "cpuTicks": 32186, + "rssKiB": 886456, + "peakRssKiB": 886456, + "threads": 34, + "fds": 38, + "writeBytes": 438222848 + }, + { + "tMs": 187001, + "cpuTicks": 32393, + "rssKiB": 886132, + "peakRssKiB": 886132, + "threads": 34, + "fds": 38, + "writeBytes": 440135680 + }, + { + "tMs": 188149, + "cpuTicks": 32552, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 442298368 + }, + { + "tMs": 189305, + "cpuTicks": 32692, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 444198912 + }, + { + "tMs": 190474, + "cpuTicks": 32851, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 446087168 + }, + { + "tMs": 191620, + "cpuTicks": 33006, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 448376832 + }, + { + "tMs": 192827, + "cpuTicks": 33223, + "rssKiB": 886132, + "peakRssKiB": 886132, + "threads": 34, + "fds": 37, + "writeBytes": 453361664 + }, + { + "tMs": 193990, + "cpuTicks": 33441, + "rssKiB": 886132, + "peakRssKiB": 886132, + "threads": 34, + "fds": 37, + "writeBytes": 455245824 + }, + { + "tMs": 195146, + "cpuTicks": 33590, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 457326592 + }, + { + "tMs": 196292, + "cpuTicks": 33736, + "rssKiB": 862184, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 459259904 + }, + { + "tMs": 197432, + "cpuTicks": 33889, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 461172736 + }, + { + "tMs": 198564, + "cpuTicks": 34042, + "rssKiB": 861960, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 463024128 + }, + { + "tMs": 199700, + "cpuTicks": 34215, + "rssKiB": 886356, + "peakRssKiB": 886356, + "threads": 34, + "fds": 37, + "writeBytes": 466231296 + }, + { + "tMs": 200901, + "cpuTicks": 34451, + "rssKiB": 886140, + "peakRssKiB": 886140, + "threads": 34, + "fds": 37, + "writeBytes": 467419136 + }, + { + "tMs": 202060, + "cpuTicks": 34655, + "rssKiB": 862192, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 470188032 + }, + { + "tMs": 203202, + "cpuTicks": 34809, + "rssKiB": 861968, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 473862144 + }, + { + "tMs": 204329, + "cpuTicks": 34951, + "rssKiB": 862420, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 475774976 + }, + { + "tMs": 205461, + "cpuTicks": 35103, + "rssKiB": 861968, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 478097408 + }, + { + "tMs": 206592, + "cpuTicks": 35241, + "rssKiB": 861968, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 479989760 + }, + { + "tMs": 207796, + "cpuTicks": 35436, + "rssKiB": 885816, + "peakRssKiB": 885816, + "threads": 34, + "fds": 37, + "writeBytes": 483151872 + }, + { + "tMs": 208961, + "cpuTicks": 35659, + "rssKiB": 886140, + "peakRssKiB": 886140, + "threads": 34, + "fds": 37, + "writeBytes": 485015552 + }, + { + "tMs": 210111, + "cpuTicks": 35851, + "rssKiB": 862004, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 487182336 + }, + { + "tMs": 211237, + "cpuTicks": 36006, + "rssKiB": 862004, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 489111552 + }, + { + "tMs": 212359, + "cpuTicks": 36162, + "rssKiB": 862004, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 490971136 + }, + { + "tMs": 213507, + "cpuTicks": 36320, + "rssKiB": 862004, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 494583808 + }, + { + "tMs": 214644, + "cpuTicks": 36465, + "rssKiB": 862008, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 496467968 + }, + { + "tMs": 215874, + "cpuTicks": 36699, + "rssKiB": 886180, + "peakRssKiB": 886180, + "threads": 34, + "fds": 37, + "writeBytes": 499740672 + }, + { + "tMs": 217037, + "cpuTicks": 36909, + "rssKiB": 862332, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 501841920 + }, + { + "tMs": 218198, + "cpuTicks": 37054, + "rssKiB": 862332, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 503721984 + }, + { + "tMs": 219353, + "cpuTicks": 37206, + "rssKiB": 862332, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 505622528 + }, + { + "tMs": 220496, + "cpuTicks": 37352, + "rssKiB": 862332, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 508002304 + }, + { + "tMs": 221651, + "cpuTicks": 37493, + "rssKiB": 862332, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 509906944 + }, + { + "tMs": 222840, + "cpuTicks": 37705, + "rssKiB": 886504, + "peakRssKiB": 886504, + "threads": 34, + "fds": 37, + "writeBytes": 514924544 + }, + { + "tMs": 224012, + "cpuTicks": 37927, + "rssKiB": 886504, + "peakRssKiB": 886504, + "threads": 34, + "fds": 37, + "writeBytes": 516837376 + }, + { + "tMs": 225158, + "cpuTicks": 38075, + "rssKiB": 862332, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 518905856 + }, + { + "tMs": 226286, + "cpuTicks": 38232, + "rssKiB": 862336, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 520867840 + }, + { + "tMs": 227425, + "cpuTicks": 38385, + "rssKiB": 862336, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 522739712 + }, + { + "tMs": 228571, + "cpuTicks": 38520, + "rssKiB": 862012, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 524652544 + }, + { + "tMs": 229711, + "cpuTicks": 38674, + "rssKiB": 886184, + "peakRssKiB": 886184, + "threads": 34, + "fds": 37, + "writeBytes": 527876096 + }, + { + "tMs": 230923, + "cpuTicks": 38907, + "rssKiB": 886184, + "peakRssKiB": 886184, + "threads": 34, + "fds": 37, + "writeBytes": 529162240 + }, + { + "tMs": 232081, + "cpuTicks": 39076, + "rssKiB": 861688, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 531906560 + }, + { + "tMs": 233223, + "cpuTicks": 39220, + "rssKiB": 861916, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 535572480 + }, + { + "tMs": 234363, + "cpuTicks": 39378, + "rssKiB": 862236, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 537890816 + }, + { + "tMs": 235513, + "cpuTicks": 39527, + "rssKiB": 862236, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 539811840 + }, + { + "tMs": 236653, + "cpuTicks": 39681, + "rssKiB": 862236, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 541696000 + }, + { + "tMs": 237831, + "cpuTicks": 39883, + "rssKiB": 886408, + "peakRssKiB": 886408, + "threads": 34, + "fds": 37, + "writeBytes": 544890880 + }, + { + "tMs": 238981, + "cpuTicks": 40093, + "rssKiB": 886732, + "peakRssKiB": 886732, + "threads": 34, + "fds": 37, + "writeBytes": 546721792 + }, + { + "tMs": 240123, + "cpuTicks": 40259, + "rssKiB": 862560, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 548835328 + }, + { + "tMs": 241256, + "cpuTicks": 40410, + "rssKiB": 862560, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 550723584 + }, + { + "tMs": 242390, + "cpuTicks": 40546, + "rssKiB": 862560, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 552624128 + }, + { + "tMs": 243533, + "cpuTicks": 40687, + "rssKiB": 862560, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 556220416 + }, + { + "tMs": 244672, + "cpuTicks": 40842, + "rssKiB": 862560, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 558796800 + }, + { + "tMs": 245897, + "cpuTicks": 41077, + "rssKiB": 886736, + "peakRssKiB": 886736, + "threads": 34, + "fds": 38, + "writeBytes": 561430528 + }, + { + "tMs": 247031, + "cpuTicks": 41264, + "rssKiB": 884344, + "peakRssKiB": 884344, + "threads": 34, + "fds": 37, + "writeBytes": 563437568 + }, + { + "tMs": 248188, + "cpuTicks": 41417, + "rssKiB": 862564, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 565444608 + }, + { + "tMs": 249325, + "cpuTicks": 41573, + "rssKiB": 862564, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 567726080 + }, + { + "tMs": 250457, + "cpuTicks": 41727, + "rssKiB": 861692, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 569589760 + }, + { + "tMs": 251593, + "cpuTicks": 41879, + "rssKiB": 861692, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 571514880 + }, + { + "tMs": 252749, + "cpuTicks": 42068, + "rssKiB": 886188, + "peakRssKiB": 886188, + "threads": 34, + "fds": 37, + "writeBytes": 576524288 + }, + { + "tMs": 253919, + "cpuTicks": 42296, + "rssKiB": 886188, + "peakRssKiB": 886188, + "threads": 34, + "fds": 37, + "writeBytes": 578347008 + }, + { + "tMs": 255074, + "cpuTicks": 42507, + "rssKiB": 862016, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 580526080 + }, + { + "tMs": 256202, + "cpuTicks": 42660, + "rssKiB": 862016, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 582500352 + }, + { + "tMs": 257341, + "cpuTicks": 42817, + "rssKiB": 862016, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 584441856 + }, + { + "tMs": 258474, + "cpuTicks": 42963, + "rssKiB": 862016, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 586387456 + }, + { + "tMs": 259618, + "cpuTicks": 43114, + "rssKiB": 862016, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 588337152 + }, + { + "tMs": 260794, + "cpuTicks": 43307, + "rssKiB": 886512, + "peakRssKiB": 886512, + "threads": 34, + "fds": 37, + "writeBytes": 590618624 + }, + { + "tMs": 261965, + "cpuTicks": 43537, + "rssKiB": 886512, + "peakRssKiB": 886512, + "threads": 34, + "fds": 37, + "writeBytes": 593408000 + }, + { + "tMs": 263107, + "cpuTicks": 43709, + "rssKiB": 862340, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 597663744 + }, + { + "tMs": 264232, + "cpuTicks": 43868, + "rssKiB": 862340, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 599613440 + }, + { + "tMs": 265351, + "cpuTicks": 44029, + "rssKiB": 862340, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 601460736 + }, + { + "tMs": 266480, + "cpuTicks": 44183, + "rssKiB": 862340, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 603328512 + }, + { + "tMs": 267602, + "cpuTicks": 44338, + "rssKiB": 862340, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 605188096 + }, + { + "tMs": 268787, + "cpuTicks": 44538, + "rssKiB": 886836, + "peakRssKiB": 886836, + "threads": 34, + "fds": 37, + "writeBytes": 608333824 + }, + { + "tMs": 269938, + "cpuTicks": 44741, + "rssKiB": 886984, + "peakRssKiB": 886984, + "threads": 34, + "fds": 37, + "writeBytes": 610246656 + }, + { + "tMs": 271109, + "cpuTicks": 44916, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 612388864 + }, + { + "tMs": 272249, + "cpuTicks": 45053, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 614219776 + }, + { + "tMs": 273391, + "cpuTicks": 45209, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 617877504 + }, + { + "tMs": 274527, + "cpuTicks": 45342, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 619761664 + }, + { + "tMs": 275677, + "cpuTicks": 45494, + "rssKiB": 873340, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 622706688 + }, + { + "tMs": 276903, + "cpuTicks": 45739, + "rssKiB": 887012, + "peakRssKiB": 887012, + "threads": 34, + "fds": 37, + "writeBytes": 624906240 + }, + { + "tMs": 278109, + "cpuTicks": 45946, + "rssKiB": 862840, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 627556352 + }, + { + "tMs": 279276, + "cpuTicks": 46091, + "rssKiB": 862840, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 629481472 + }, + { + "tMs": 280431, + "cpuTicks": 46235, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 631365632 + }, + { + "tMs": 281585, + "cpuTicks": 46381, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 633335808 + }, + { + "tMs": 282781, + "cpuTicks": 46587, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 34, + "fds": 37, + "writeBytes": 638365696 + }, + { + "tMs": 283948, + "cpuTicks": 46814, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 34, + "fds": 37, + "writeBytes": 640229376 + }, + { + "tMs": 285110, + "cpuTicks": 46990, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 642412544 + }, + { + "tMs": 286261, + "cpuTicks": 47138, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 644329472 + }, + { + "tMs": 287398, + "cpuTicks": 47290, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 646258688 + }, + { + "tMs": 288535, + "cpuTicks": 47434, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 648212480 + }, + { + "tMs": 289707, + "cpuTicks": 47600, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 34, + "fds": 37, + "writeBytes": 651493376 + }, + { + "tMs": 290890, + "cpuTicks": 47832, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 34, + "fds": 37, + "writeBytes": 652713984 + }, + { + "tMs": 292063, + "cpuTicks": 48050, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 655921152 + }, + { + "tMs": 293192, + "cpuTicks": 48213, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 659582976 + }, + { + "tMs": 294346, + "cpuTicks": 48369, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 661532672 + }, + { + "tMs": 295487, + "cpuTicks": 48518, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 663461888 + }, + { + "tMs": 296622, + "cpuTicks": 48656, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 665378816 + }, + { + "tMs": 297794, + "cpuTicks": 48856, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 34, + "fds": 37, + "writeBytes": 668557312 + }, + { + "tMs": 298966, + "cpuTicks": 49079, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 34, + "fds": 37, + "writeBytes": 670445568 + }, + { + "tMs": 300112, + "cpuTicks": 49268, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 672526336 + }, + { + "tMs": 301256, + "cpuTicks": 49425, + "rssKiB": 862516, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 674390016 + }, + { + "tMs": 302387, + "cpuTicks": 49597, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 676335616 + }, + { + "tMs": 303519, + "cpuTicks": 49750, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 679976960 + }, + { + "tMs": 304647, + "cpuTicks": 49912, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 682364928 + }, + { + "tMs": 305859, + "cpuTicks": 50149, + "rssKiB": 887012, + "peakRssKiB": 887012, + "threads": 34, + "fds": 37, + "writeBytes": 685670400 + }, + { + "tMs": 307037, + "cpuTicks": 50382, + "rssKiB": 887012, + "peakRssKiB": 887012, + "threads": 34, + "fds": 37, + "writeBytes": 687517696 + }, + { + "tMs": 308186, + "cpuTicks": 50552, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 689672192 + }, + { + "tMs": 309328, + "cpuTicks": 50703, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 691621888 + }, + { + "tMs": 310468, + "cpuTicks": 50865, + "rssKiB": 863160, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 693493760 + }, + { + "tMs": 311611, + "cpuTicks": 51008, + "rssKiB": 863160, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 695414784 + }, + { + "tMs": 312815, + "cpuTicks": 51224, + "rssKiB": 887332, + "peakRssKiB": 887332, + "threads": 34, + "fds": 37, + "writeBytes": 700416000 + }, + { + "tMs": 313972, + "cpuTicks": 51430, + "rssKiB": 887332, + "peakRssKiB": 887332, + "threads": 34, + "fds": 37, + "writeBytes": 702337024 + }, + { + "tMs": 315119, + "cpuTicks": 51601, + "rssKiB": 863160, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 704618496 + }, + { + "tMs": 316268, + "cpuTicks": 51744, + "rssKiB": 863160, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 706588672 + }, + { + "tMs": 317404, + "cpuTicks": 51897, + "rssKiB": 863160, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 708485120 + }, + { + "tMs": 318552, + "cpuTicks": 52047, + "rssKiB": 863160, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 710856704 + }, + { + "tMs": 319716, + "cpuTicks": 52217, + "rssKiB": 887332, + "peakRssKiB": 887332, + "threads": 34, + "fds": 37, + "writeBytes": 714076160 + }, + { + "tMs": 320882, + "cpuTicks": 52426, + "rssKiB": 887008, + "peakRssKiB": 887008, + "threads": 34, + "fds": 37, + "writeBytes": 715317248 + }, + { + "tMs": 322042, + "cpuTicks": 52609, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 718032896 + }, + { + "tMs": 323181, + "cpuTicks": 52749, + "rssKiB": 862836, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 721690624 + }, + { + "tMs": 324367, + "cpuTicks": 52900, + "rssKiB": 862832, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 723628032 + }, + { + "tMs": 325505, + "cpuTicks": 53052, + "rssKiB": 862832, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 725512192 + }, + { + "tMs": 326659, + "cpuTicks": 53191, + "rssKiB": 862832, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 727425024 + }, + { + "tMs": 327867, + "cpuTicks": 53419, + "rssKiB": 887004, + "peakRssKiB": 887004, + "threads": 34, + "fds": 37, + "writeBytes": 730644480 + }, + { + "tMs": 329022, + "cpuTicks": 53615, + "rssKiB": 886972, + "peakRssKiB": 886972, + "threads": 34, + "fds": 38, + "writeBytes": 732639232 + }, + { + "tMs": 330159, + "cpuTicks": 53771, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 734687232 + }, + { + "tMs": 331308, + "cpuTicks": 53917, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 736567296 + }, + { + "tMs": 332439, + "cpuTicks": 54078, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 738443264 + }, + { + "tMs": 333588, + "cpuTicks": 54222, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 742481920 + }, + { + "tMs": 334764, + "cpuTicks": 54404, + "rssKiB": 886708, + "peakRssKiB": 886708, + "threads": 34, + "fds": 37, + "writeBytes": 745709568 + }, + { + "tMs": 335932, + "cpuTicks": 54618, + "rssKiB": 886708, + "peakRssKiB": 886708, + "threads": 34, + "fds": 37, + "writeBytes": 747614208 + }, + { + "tMs": 337093, + "cpuTicks": 54796, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 749711360 + }, + { + "tMs": 338242, + "cpuTicks": 54941, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 751579136 + }, + { + "tMs": 339411, + "cpuTicks": 55092, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 753463296 + }, + { + "tMs": 340556, + "cpuTicks": 55249, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 755326976 + }, + { + "tMs": 341732, + "cpuTicks": 55439, + "rssKiB": 887032, + "peakRssKiB": 887032, + "threads": 34, + "fds": 37, + "writeBytes": 758583296 + }, + { + "tMs": 342914, + "cpuTicks": 55664, + "rssKiB": 887032, + "peakRssKiB": 887032, + "threads": 34, + "fds": 37, + "writeBytes": 762257408 + }, + { + "tMs": 344061, + "cpuTicks": 55851, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 764358656 + }, + { + "tMs": 345234, + "cpuTicks": 55989, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 766308352 + }, + { + "tMs": 346390, + "cpuTicks": 56137, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 768208896 + }, + { + "tMs": 347537, + "cpuTicks": 56292, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 770539520 + }, + { + "tMs": 348726, + "cpuTicks": 56463, + "rssKiB": 886708, + "peakRssKiB": 886708, + "threads": 34, + "fds": 37, + "writeBytes": 773816320 + }, + { + "tMs": 349914, + "cpuTicks": 56683, + "rssKiB": 886708, + "peakRssKiB": 886708, + "threads": 34, + "fds": 37, + "writeBytes": 775749632 + }, + { + "tMs": 351066, + "cpuTicks": 56880, + "rssKiB": 862536, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 777453568 + }, + { + "tMs": 352206, + "cpuTicks": 57025, + "rssKiB": 862536, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 779698176 + }, + { + "tMs": 353359, + "cpuTicks": 57175, + "rssKiB": 862536, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 783454208 + }, + { + "tMs": 354496, + "cpuTicks": 57323, + "rssKiB": 862536, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 785338368 + }, + { + "tMs": 355645, + "cpuTicks": 57469, + "rssKiB": 862536, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 787271680 + }, + { + "tMs": 356856, + "cpuTicks": 57692, + "rssKiB": 887032, + "peakRssKiB": 887032, + "threads": 34, + "fds": 37, + "writeBytes": 790491136 + }, + { + "tMs": 358025, + "cpuTicks": 57907, + "rssKiB": 887032, + "peakRssKiB": 887032, + "threads": 34, + "fds": 37, + "writeBytes": 792477696 + }, + { + "tMs": 359181, + "cpuTicks": 58059, + "rssKiB": 862856, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 794546176 + }, + { + "tMs": 360310, + "cpuTicks": 58214, + "rssKiB": 862856, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 796385280 + }, + { + "tMs": 361453, + "cpuTicks": 58360, + "rssKiB": 862856, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 798740480 + }, + { + "tMs": 362586, + "cpuTicks": 58496, + "rssKiB": 863180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 800677888 + }, + { + "tMs": 363762, + "cpuTicks": 58682, + "rssKiB": 887352, + "peakRssKiB": 887352, + "threads": 34, + "fds": 37, + "writeBytes": 805695488 + }, + { + "tMs": 364945, + "cpuTicks": 58907, + "rssKiB": 887352, + "peakRssKiB": 887352, + "threads": 34, + "fds": 37, + "writeBytes": 807612416 + }, + { + "tMs": 366098, + "cpuTicks": 59115, + "rssKiB": 863180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 809873408 + }, + { + "tMs": 367239, + "cpuTicks": 59261, + "rssKiB": 863180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 811831296 + }, + { + "tMs": 368377, + "cpuTicks": 59424, + "rssKiB": 863180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 813711360 + }, + { + "tMs": 369543, + "cpuTicks": 59562, + "rssKiB": 863180, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 815620096 + }, + { + "tMs": 370727, + "cpuTicks": 59734, + "rssKiB": 887360, + "peakRssKiB": 887360, + "threads": 34, + "fds": 38, + "writeBytes": 818819072 + }, + { + "tMs": 371922, + "cpuTicks": 59959, + "rssKiB": 887360, + "peakRssKiB": 887360, + "threads": 34, + "fds": 38, + "writeBytes": 820699136 + }, + { + "tMs": 373088, + "cpuTicks": 60167, + "rssKiB": 862864, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 824664064 + }, + { + "tMs": 374222, + "cpuTicks": 60320, + "rssKiB": 862864, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 827019264 + }, + { + "tMs": 375366, + "cpuTicks": 60472, + "rssKiB": 862864, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 828887040 + }, + { + "tMs": 376499, + "cpuTicks": 60622, + "rssKiB": 862864, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 830812160 + }, + { + "tMs": 377629, + "cpuTicks": 60782, + "rssKiB": 862864, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 832696320 + }, + { + "tMs": 378847, + "cpuTicks": 61012, + "rssKiB": 886716, + "peakRssKiB": 886716, + "threads": 34, + "fds": 37, + "writeBytes": 835923968 + }, + { + "tMs": 380013, + "cpuTicks": 61254, + "rssKiB": 886716, + "peakRssKiB": 886716, + "threads": 34, + "fds": 37, + "writeBytes": 837857280 + }, + { + "tMs": 381167, + "cpuTicks": 61428, + "rssKiB": 862544, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 840003584 + }, + { + "tMs": 382294, + "cpuTicks": 61585, + "rssKiB": 862544, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 841904128 + }, + { + "tMs": 383436, + "cpuTicks": 61746, + "rssKiB": 862540, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 845570048 + }, + { + "tMs": 384564, + "cpuTicks": 61900, + "rssKiB": 862540, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 847495168 + }, + { + "tMs": 385711, + "cpuTicks": 62073, + "rssKiB": 886712, + "peakRssKiB": 886712, + "threads": 34, + "fds": 38, + "writeBytes": 850747392 + }, + { + "tMs": 386903, + "cpuTicks": 62303, + "rssKiB": 886712, + "peakRssKiB": 886712, + "threads": 34, + "fds": 37, + "writeBytes": 852672512 + }, + { + "tMs": 388044, + "cpuTicks": 62506, + "rssKiB": 886712, + "peakRssKiB": 886712, + "threads": 34, + "fds": 38, + "writeBytes": 854700032 + }, + { + "tMs": 389179, + "cpuTicks": 62647, + "rssKiB": 862860, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 857092096 + }, + { + "tMs": 390321, + "cpuTicks": 62802, + "rssKiB": 862880, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 858914816 + }, + { + "tMs": 391460, + "cpuTicks": 62963, + "rssKiB": 862880, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 860831744 + }, + { + "tMs": 392595, + "cpuTicks": 63123, + "rssKiB": 862880, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 862703616 + }, + { + "tMs": 393789, + "cpuTicks": 63316, + "rssKiB": 887056, + "peakRssKiB": 887056, + "threads": 34, + "fds": 37, + "writeBytes": 867680256 + }, + { + "tMs": 394950, + "cpuTicks": 63530, + "rssKiB": 887056, + "peakRssKiB": 887056, + "threads": 34, + "fds": 37, + "writeBytes": 869662720 + }, + { + "tMs": 396101, + "cpuTicks": 63717, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 871886848 + }, + { + "tMs": 397226, + "cpuTicks": 63868, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 873746432 + }, + { + "tMs": 398352, + "cpuTicks": 64034, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 875655168 + }, + { + "tMs": 399491, + "cpuTicks": 64182, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 877563904 + }, + { + "tMs": 400617, + "cpuTicks": 64332, + "rssKiB": 862580, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 879456256 + }, + { + "tMs": 401819, + "cpuTicks": 64547, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 38, + "writeBytes": 882733056 + }, + { + "tMs": 402972, + "cpuTicks": 64760, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 38, + "writeBytes": 886906880 + }, + { + "tMs": 404125, + "cpuTicks": 64936, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 889139200 + }, + { + "tMs": 405255, + "cpuTicks": 65083, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 891052032 + }, + { + "tMs": 406387, + "cpuTicks": 65236, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 893030400 + }, + { + "tMs": 407531, + "cpuTicks": 65382, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 894881792 + }, + { + "tMs": 408707, + "cpuTicks": 65565, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 37, + "writeBytes": 898105344 + }, + { + "tMs": 409931, + "cpuTicks": 65814, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 37, + "writeBytes": 899985408 + }, + { + "tMs": 411099, + "cpuTicks": 66025, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 902045696 + }, + { + "tMs": 412240, + "cpuTicks": 66162, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 903946240 + }, + { + "tMs": 413360, + "cpuTicks": 66310, + "rssKiB": 862580, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 907624448 + }, + { + "tMs": 414486, + "cpuTicks": 66476, + "rssKiB": 862580, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 909484032 + }, + { + "tMs": 415619, + "cpuTicks": 66636, + "rssKiB": 862580, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 911384576 + }, + { + "tMs": 416819, + "cpuTicks": 66853, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 37, + "writeBytes": 915116032 + }, + { + "tMs": 418005, + "cpuTicks": 67084, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 37, + "writeBytes": 917000192 + }, + { + "tMs": 419159, + "cpuTicks": 67244, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 919146496 + }, + { + "tMs": 420301, + "cpuTicks": 67409, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 921051136 + }, + { + "tMs": 421455, + "cpuTicks": 67553, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 922939392 + }, + { + "tMs": 422590, + "cpuTicks": 67692, + "rssKiB": 862900, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 924827648 + }, + { + "tMs": 423795, + "cpuTicks": 67891, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 37, + "writeBytes": 929820672 + }, + { + "tMs": 424958, + "cpuTicks": 68103, + "rssKiB": 887072, + "peakRssKiB": 887072, + "threads": 34, + "fds": 37, + "writeBytes": 931741696 + }, + { + "tMs": 426119, + "cpuTicks": 68271, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 933986304 + }, + { + "tMs": 427272, + "cpuTicks": 68414, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 935911424 + }, + { + "tMs": 428424, + "cpuTicks": 68564, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 937861120 + }, + { + "tMs": 429585, + "cpuTicks": 68712, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 939794432 + }, + { + "tMs": 430768, + "cpuTicks": 68898, + "rssKiB": 887084, + "peakRssKiB": 887084, + "threads": 34, + "fds": 37, + "writeBytes": 943554560 + }, + { + "tMs": 431956, + "cpuTicks": 69116, + "rssKiB": 887084, + "peakRssKiB": 887084, + "threads": 34, + "fds": 37, + "writeBytes": 945475584 + }, + { + "tMs": 433126, + "cpuTicks": 69318, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 949395456 + }, + { + "tMs": 434281, + "cpuTicks": 69474, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 951271424 + }, + { + "tMs": 435432, + "cpuTicks": 69612, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 953208832 + }, + { + "tMs": 436571, + "cpuTicks": 69773, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 955133952 + }, + { + "tMs": 437726, + "cpuTicks": 69946, + "rssKiB": 886764, + "peakRssKiB": 886764, + "threads": 34, + "fds": 37, + "writeBytes": 958377984 + }, + { + "tMs": 438902, + "cpuTicks": 70154, + "rssKiB": 886764, + "peakRssKiB": 886764, + "threads": 34, + "fds": 37, + "writeBytes": 960307200 + }, + { + "tMs": 440070, + "cpuTicks": 70359, + "rssKiB": 862592, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 962555904 + }, + { + "tMs": 441211, + "cpuTicks": 70507, + "rssKiB": 862596, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 964444160 + }, + { + "tMs": 442353, + "cpuTicks": 70652, + "rssKiB": 862596, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 966397952 + }, + { + "tMs": 443483, + "cpuTicks": 70801, + "rssKiB": 862596, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 970567680 + }, + { + "tMs": 444650, + "cpuTicks": 70952, + "rssKiB": 862596, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 972476416 + }, + { + "tMs": 445851, + "cpuTicks": 71167, + "rssKiB": 886760, + "peakRssKiB": 886760, + "threads": 34, + "fds": 38, + "writeBytes": 975708160 + }, + { + "tMs": 447007, + "cpuTicks": 71370, + "rssKiB": 886760, + "peakRssKiB": 886760, + "threads": 34, + "fds": 38, + "writeBytes": 977641472 + }, + { + "tMs": 448153, + "cpuTicks": 71509, + "rssKiB": 862588, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 979709952 + }, + { + "tMs": 449296, + "cpuTicks": 71648, + "rssKiB": 862912, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 981549056 + }, + { + "tMs": 450453, + "cpuTicks": 71807, + "rssKiB": 863044, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 983367680 + }, + { + "tMs": 451603, + "cpuTicks": 71947, + "rssKiB": 863044, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 985337856 + }, + { + "tMs": 452765, + "cpuTicks": 72123, + "rssKiB": 887216, + "peakRssKiB": 887216, + "threads": 34, + "fds": 37, + "writeBytes": 990322688 + }, + { + "tMs": 453933, + "cpuTicks": 72326, + "rssKiB": 887216, + "peakRssKiB": 887216, + "threads": 34, + "fds": 37, + "writeBytes": 992186368 + }, + { + "tMs": 455093, + "cpuTicks": 72504, + "rssKiB": 863044, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 994480128 + }, + { + "tMs": 456244, + "cpuTicks": 72642, + "rssKiB": 863044, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 996913152 + }, + { + "tMs": 457406, + "cpuTicks": 72786, + "rssKiB": 863044, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 998838272 + }, + { + "tMs": 458557, + "cpuTicks": 72936, + "rssKiB": 863044, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1000779776 + }, + { + "tMs": 459744, + "cpuTicks": 73104, + "rssKiB": 886892, + "peakRssKiB": 886892, + "threads": 34, + "fds": 37, + "writeBytes": 1004044288 + }, + { + "tMs": 460939, + "cpuTicks": 73334, + "rssKiB": 886892, + "peakRssKiB": 886892, + "threads": 34, + "fds": 37, + "writeBytes": 1005912064 + }, + { + "tMs": 462084, + "cpuTicks": 73504, + "rssKiB": 862720, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1008054272 + }, + { + "tMs": 463234, + "cpuTicks": 73661, + "rssKiB": 862720, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1011695616 + }, + { + "tMs": 464403, + "cpuTicks": 73807, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1013592064 + }, + { + "tMs": 465545, + "cpuTicks": 73959, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1015500800 + }, + { + "tMs": 466714, + "cpuTicks": 74116, + "rssKiB": 886620, + "peakRssKiB": 886620, + "threads": 34, + "fds": 37, + "writeBytes": 1018732544 + }, + { + "tMs": 467909, + "cpuTicks": 74333, + "rssKiB": 886620, + "peakRssKiB": 886620, + "threads": 34, + "fds": 37, + "writeBytes": 1020579840 + }, + { + "tMs": 469091, + "cpuTicks": 74522, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1022767104 + }, + { + "tMs": 470249, + "cpuTicks": 74668, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1024667648 + }, + { + "tMs": 471414, + "cpuTicks": 74806, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1026498560 + }, + { + "tMs": 472597, + "cpuTicks": 74950, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1028788224 + }, + { + "tMs": 473797, + "cpuTicks": 75148, + "rssKiB": 886296, + "peakRssKiB": 886296, + "threads": 34, + "fds": 38, + "writeBytes": 1033797632 + }, + { + "tMs": 474963, + "cpuTicks": 75362, + "rssKiB": 886620, + "peakRssKiB": 886620, + "threads": 34, + "fds": 37, + "writeBytes": 1035739136 + }, + { + "tMs": 476133, + "cpuTicks": 75555, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1037922304 + }, + { + "tMs": 477288, + "cpuTicks": 75698, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1039835136 + }, + { + "tMs": 478431, + "cpuTicks": 75847, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1041711104 + }, + { + "tMs": 479560, + "cpuTicks": 76000, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1043582976 + }, + { + "tMs": 480716, + "cpuTicks": 76170, + "rssKiB": 886944, + "peakRssKiB": 886944, + "threads": 34, + "fds": 37, + "writeBytes": 1046781952 + }, + { + "tMs": 481963, + "cpuTicks": 76440, + "rssKiB": 886944, + "peakRssKiB": 886944, + "threads": 34, + "fds": 37, + "writeBytes": 1048682496 + }, + { + "tMs": 483125, + "cpuTicks": 76634, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 1052553216 + }, + { + "tMs": 484247, + "cpuTicks": 76788, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1054457856 + }, + { + "tMs": 485375, + "cpuTicks": 76952, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1056878592 + }, + { + "tMs": 486508, + "cpuTicks": 77100, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1058762752 + }, + { + "tMs": 487641, + "cpuTicks": 77244, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1060667392 + }, + { + "tMs": 488859, + "cpuTicks": 77479, + "rssKiB": 887268, + "peakRssKiB": 887268, + "threads": 34, + "fds": 37, + "writeBytes": 1063878656 + }, + { + "tMs": 490074, + "cpuTicks": 77716, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1066024960 + }, + { + "tMs": 491229, + "cpuTicks": 77860, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1067933696 + }, + { + "tMs": 492417, + "cpuTicks": 77997, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1069887488 + }, + { + "tMs": 493554, + "cpuTicks": 78141, + "rssKiB": 863096, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1073573888 + }, + { + "tMs": 494702, + "cpuTicks": 78297, + "rssKiB": 886944, + "peakRssKiB": 886944, + "threads": 34, + "fds": 37, + "writeBytes": 1076822016 + }, + { + "tMs": 495914, + "cpuTicks": 78541, + "rssKiB": 886944, + "peakRssKiB": 886944, + "threads": 34, + "fds": 37, + "writeBytes": 1078706176 + }, + { + "tMs": 497065, + "cpuTicks": 78730, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1080897536 + }, + { + "tMs": 498206, + "cpuTicks": 78879, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1082851328 + }, + { + "tMs": 499349, + "cpuTicks": 79026, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1085231104 + }, + { + "tMs": 500493, + "cpuTicks": 79179, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1087148032 + }, + { + "tMs": 501641, + "cpuTicks": 79337, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1088995328 + }, + { + "tMs": 502840, + "cpuTicks": 79554, + "rssKiB": 886620, + "peakRssKiB": 886620, + "threads": 34, + "fds": 37, + "writeBytes": 1093976064 + }, + { + "tMs": 504001, + "cpuTicks": 79763, + "rssKiB": 886620, + "peakRssKiB": 886620, + "threads": 34, + "fds": 37, + "writeBytes": 1095946240 + }, + { + "tMs": 505135, + "cpuTicks": 79914, + "rssKiB": 862448, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 1098092544 + }, + { + "tMs": 506273, + "cpuTicks": 80056, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1100021760 + }, + { + "tMs": 507435, + "cpuTicks": 80194, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1101934592 + }, + { + "tMs": 508573, + "cpuTicks": 80335, + "rssKiB": 862772, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1103839232 + }, + { + "tMs": 509724, + "cpuTicks": 80494, + "rssKiB": 886944, + "peakRssKiB": 886944, + "threads": 34, + "fds": 37, + "writeBytes": 1107046400 + }, + { + "tMs": 510933, + "cpuTicks": 80726, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1108926464 + }, + { + "tMs": 512099, + "cpuTicks": 80894, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1111220224 + }, + { + "tMs": 513242, + "cpuTicks": 81036, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1115344896 + }, + { + "tMs": 514390, + "cpuTicks": 81174, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1117265920 + }, + { + "tMs": 515551, + "cpuTicks": 81327, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1119211520 + }, + { + "tMs": 516779, + "cpuTicks": 81530, + "rssKiB": 886628, + "peakRssKiB": 886628, + "threads": 34, + "fds": 37, + "writeBytes": 1122422784 + }, + { + "tMs": 517941, + "cpuTicks": 81739, + "rssKiB": 886628, + "peakRssKiB": 886628, + "threads": 34, + "fds": 37, + "writeBytes": 1124306944 + }, + { + "tMs": 519091, + "cpuTicks": 81917, + "rssKiB": 862680, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1126502400 + }, + { + "tMs": 520242, + "cpuTicks": 82070, + "rssKiB": 862456, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1128386560 + }, + { + "tMs": 521407, + "cpuTicks": 82203, + "rssKiB": 862456, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1130307584 + }, + { + "tMs": 522558, + "cpuTicks": 82346, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1132244992 + }, + { + "tMs": 523705, + "cpuTicks": 82510, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1137213440 + }, + { + "tMs": 524872, + "cpuTicks": 82714, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1139081216 + }, + { + "tMs": 526033, + "cpuTicks": 82930, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1140985856 + }, + { + "tMs": 527219, + "cpuTicks": 83096, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1143611392 + }, + { + "tMs": 528361, + "cpuTicks": 83239, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1145540608 + }, + { + "tMs": 529496, + "cpuTicks": 83401, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1147498496 + }, + { + "tMs": 530630, + "cpuTicks": 83552, + "rssKiB": 862780, + "peakRssKiB": 882824, + "threads": 34, + "fds": 37, + "writeBytes": 1149394944 + }, + { + "tMs": 531842, + "cpuTicks": 83774, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 38, + "writeBytes": 1152528384 + }, + { + "tMs": 533025, + "cpuTicks": 84002, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 38, + "writeBytes": 1156300800 + }, + { + "tMs": 534172, + "cpuTicks": 84163, + "rssKiB": 863100, + "peakRssKiB": 882824, + "threads": 34, + "fds": 38, + "writeBytes": 1158385664 + }, + { + "tMs": 535293, + "cpuTicks": 84314, + "rssKiB": 863424, + "peakRssKiB": 882976, + "threads": 34, + "fds": 37, + "writeBytes": 1160273920 + }, + { + "tMs": 536426, + "cpuTicks": 84475, + "rssKiB": 863424, + "peakRssKiB": 882976, + "threads": 34, + "fds": 37, + "writeBytes": 1162174464 + }, + { + "tMs": 537556, + "cpuTicks": 84632, + "rssKiB": 863424, + "peakRssKiB": 882976, + "threads": 34, + "fds": 37, + "writeBytes": 1164066816 + }, + { + "tMs": 538735, + "cpuTicks": 84808, + "rssKiB": 887596, + "peakRssKiB": 887596, + "threads": 34, + "fds": 37, + "writeBytes": 1167228928 + }, + { + "tMs": 539918, + "cpuTicks": 85030, + "rssKiB": 887596, + "peakRssKiB": 887596, + "threads": 34, + "fds": 37, + "writeBytes": 1169031168 + }, + { + "tMs": 541067, + "cpuTicks": 85244, + "rssKiB": 863424, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1171591168 + }, + { + "tMs": 542213, + "cpuTicks": 85388, + "rssKiB": 863424, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1173540864 + }, + { + "tMs": 543344, + "cpuTicks": 85538, + "rssKiB": 863424, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1177284608 + }, + { + "tMs": 544482, + "cpuTicks": 85698, + "rssKiB": 863424, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1179164672 + }, + { + "tMs": 545610, + "cpuTicks": 85852, + "rssKiB": 863424, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1181102080 + }, + { + "tMs": 546801, + "cpuTicks": 86054, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1184296960 + }, + { + "tMs": 547965, + "cpuTicks": 86280, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1186189312 + }, + { + "tMs": 549120, + "cpuTicks": 86454, + "rssKiB": 862780, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1188397056 + }, + { + "tMs": 550280, + "cpuTicks": 86600, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1190289408 + }, + { + "tMs": 551439, + "cpuTicks": 86737, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1192230912 + }, + { + "tMs": 552585, + "cpuTicks": 86886, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1194143744 + }, + { + "tMs": 553761, + "cpuTicks": 87073, + "rssKiB": 887276, + "peakRssKiB": 887276, + "threads": 34, + "fds": 37, + "writeBytes": 1199136768 + }, + { + "tMs": 554959, + "cpuTicks": 87315, + "rssKiB": 887276, + "peakRssKiB": 887276, + "threads": 34, + "fds": 37, + "writeBytes": 1201573888 + }, + { + "tMs": 556106, + "cpuTicks": 87485, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1203777536 + }, + { + "tMs": 557264, + "cpuTicks": 87629, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1205723136 + }, + { + "tMs": 558409, + "cpuTicks": 87776, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1207676928 + }, + { + "tMs": 559549, + "cpuTicks": 87922, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1209622528 + }, + { + "tMs": 560724, + "cpuTicks": 88092, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1211883520 + }, + { + "tMs": 561945, + "cpuTicks": 88336, + "rssKiB": 886952, + "peakRssKiB": 886952, + "threads": 34, + "fds": 37, + "writeBytes": 1214676992 + }, + { + "tMs": 563098, + "cpuTicks": 88511, + "rssKiB": 862780, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1218613248 + }, + { + "tMs": 564246, + "cpuTicks": 88655, + "rssKiB": 862780, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1220583424 + }, + { + "tMs": 565385, + "cpuTicks": 88799, + "rssKiB": 862780, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1222447104 + }, + { + "tMs": 566516, + "cpuTicks": 88949, + "rssKiB": 862780, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1224376320 + }, + { + "tMs": 567661, + "cpuTicks": 89103, + "rssKiB": 863104, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1226276864 + }, + { + "tMs": 568851, + "cpuTicks": 89314, + "rssKiB": 887276, + "peakRssKiB": 887276, + "threads": 34, + "fds": 37, + "writeBytes": 1230000128 + }, + { + "tMs": 570011, + "cpuTicks": 89523, + "rssKiB": 887284, + "peakRssKiB": 887284, + "threads": 34, + "fds": 37, + "writeBytes": 1231908864 + }, + { + "tMs": 571167, + "cpuTicks": 89678, + "rssKiB": 863112, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1233989632 + }, + { + "tMs": 572303, + "cpuTicks": 89831, + "rssKiB": 863112, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1235898368 + }, + { + "tMs": 573452, + "cpuTicks": 89989, + "rssKiB": 863112, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1239547904 + }, + { + "tMs": 574598, + "cpuTicks": 90132, + "rssKiB": 863112, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1241473024 + }, + { + "tMs": 575766, + "cpuTicks": 90319, + "rssKiB": 887284, + "peakRssKiB": 887284, + "threads": 34, + "fds": 37, + "writeBytes": 1244733440 + }, + { + "tMs": 576945, + "cpuTicks": 90534, + "rssKiB": 887284, + "peakRssKiB": 887284, + "threads": 34, + "fds": 37, + "writeBytes": 1246691328 + }, + { + "tMs": 578093, + "cpuTicks": 90708, + "rssKiB": 862788, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1248813056 + }, + { + "tMs": 579246, + "cpuTicks": 90848, + "rssKiB": 863012, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1250697216 + }, + { + "tMs": 580395, + "cpuTicks": 90989, + "rssKiB": 862916, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1252556800 + }, + { + "tMs": 581538, + "cpuTicks": 91131, + "rssKiB": 862916, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1254572032 + }, + { + "tMs": 582671, + "cpuTicks": 91281, + "rssKiB": 862904, + "peakRssKiB": 883056, + "threads": 34, + "fds": 35, + "writeBytes": 1257664512 + }, + { + "tMs": 583864, + "cpuTicks": 91502, + "rssKiB": 887184, + "peakRssKiB": 887184, + "threads": 34, + "fds": 37, + "writeBytes": 1262034944 + }, + { + "tMs": 585002, + "cpuTicks": 91705, + "rssKiB": 887184, + "peakRssKiB": 887184, + "threads": 34, + "fds": 37, + "writeBytes": 1263939584 + }, + { + "tMs": 586168, + "cpuTicks": 91854, + "rssKiB": 863012, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1266180096 + }, + { + "tMs": 587324, + "cpuTicks": 92008, + "rssKiB": 863012, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1268076544 + }, + { + "tMs": 588459, + "cpuTicks": 92162, + "rssKiB": 863012, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1269964800 + }, + { + "tMs": 589593, + "cpuTicks": 92315, + "rssKiB": 863012, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1271877632 + }, + { + "tMs": 590791, + "cpuTicks": 92516, + "rssKiB": 887184, + "peakRssKiB": 887184, + "threads": 34, + "fds": 37, + "writeBytes": 1274179584 + }, + { + "tMs": 591973, + "cpuTicks": 92734, + "rssKiB": 887184, + "peakRssKiB": 887184, + "threads": 34, + "fds": 37, + "writeBytes": 1276940288 + }, + { + "tMs": 593137, + "cpuTicks": 92939, + "rssKiB": 863012, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1280983040 + }, + { + "tMs": 594276, + "cpuTicks": 93093, + "rssKiB": 862464, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1282887680 + }, + { + "tMs": 595407, + "cpuTicks": 93254, + "rssKiB": 862464, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1285316608 + }, + { + "tMs": 596542, + "cpuTicks": 93404, + "rssKiB": 862688, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1287294976 + }, + { + "tMs": 597674, + "cpuTicks": 93559, + "rssKiB": 867140, + "peakRssKiB": 883056, + "threads": 34, + "fds": 37, + "writeBytes": 1289891840 + }, + { + "tMs": 598865, + "cpuTicks": 93778, + "rssKiB": 886860, + "peakRssKiB": 886860, + "threads": 34, + "fds": 37, + "writeBytes": 1292394496 + } + ], + "db_probe_ms": [ + 10.769, + 8.768, + 7.876, + 8.531, + 8.424, + 8.677, + 7.408, + 8.872, + 7.924, + 8.324, + 8.289, + 9.217, + 7.783, + 8.124, + 8.466, + 7.955, + 7.46, + 7.176, + 7.713, + 8.188, + 7.348, + 7.744, + 10.211, + 10.945, + 7.182, + 7.726, + 9.075, + 10.127, + 8.764, + 8.794, + 8.768, + 9.737, + 8.84, + 7.501, + 10.12, + 9.216, + 8.526, + 7.997, + 8.475, + 7.596, + 8.577, + 8.464, + 8.444, + 7.366, + 8.03, + 9.032, + 10.456, + 10.059, + 8.086, + 16.287, + 9.07, + 10.174, + 7.325, + 8.725, + 10.136, + 7.853, + 7.127, + 8.919, + 11.04, + 7.351, + 7.551, + 7.88, + 7.631, + 9.556, + 7.129, + 8.052, + 7.325, + 8.913, + 7.873, + 8.015, + 7.573, + 9.084, + 7.812, + 7.595, + 7.246, + 7.134, + 8.191, + 15.383, + 7.601, + 7.617, + 10.982, + 9.051, + 7.905, + 7.3, + 10.452, + 9.107, + 8.66, + 7.376, + 9.563, + 11.677, + 7.237, + 11.111, + 7.532, + 10.436, + 7.883, + 7.768, + 7.625, + 7.272, + 8.148, + 7.366, + 7.789, + 7.976, + 9.744, + 8.028, + 9.284, + 8.15, + 8.753, + 14.179, + 8.041, + 8.159, + 7.563, + 10.671, + 7.986, + 7.858, + 7.774, + 30.734, + 8.872, + 9.437, + 7.336, + 12.721, + 8.36, + 7.691, + 7.447, + 8.891, + 8.046, + 9.467, + 8.48, + 19.193, + 10.717, + 8.157, + 8.059, + 7.783, + 7.677, + 8.144, + 10.421, + 8.408, + 7.345, + 8.449, + 13.682, + 7.956, + 7.4, + 8.533, + 11.808, + 8.459, + 7.092, + 7.492, + 12.742, + 8.433, + 7.484, + 7.704, + 11.458, + 7.889, + 7.242, + 8.08, + 7.731, + 7.231, + 7.791, + 7.541, + 7.581, + 9.412, + 7.515, + 15.859, + 7.88, + 7.289, + 8.891, + 12.323, + 8.694, + 7.839, + 8.145, + 9.349, + 8.154, + 7.608, + 9.53, + 10.278, + 8.286, + 7.229, + 7.735, + 11.458, + 7.677, + 7.645, + 7.513, + 13.763, + 8.343, + 7.648, + 7.693, + 7.611, + 9.023, + 8.388, + 14.196, + 9.677, + 10.1, + 7.49, + 11.287, + 8.106, + 9.542, + 7.912, + 11.125, + 8.115, + 7.263, + 8.768, + 9.431, + 7.877, + 8.603, + 7.679, + 9.643, + 7.321, + 7.87, + 9.685, + 8.757, + 7.713, + 7.548, + 7.385, + 9.28, + 7.536, + 7.919, + 7.804, + 9.377, + 8.888, + 7.234, + 8.439, + 8.065, + 8.53, + 8.914, + 8.987, + 8.013, + 9.91, + 9.153, + 9.548, + 7.831, + 8.355, + 8.832, + 8.038, + 7.292, + 7.982, + 8.499, + 8.346, + 7.735, + 7.297, + 8.34, + 14.252, + 7.195, + 8.704, + 7.798, + 9.689, + 7.27, + 7.2, + 14.58, + 13.618, + 7.599, + 7.289, + 8.942, + 9.781, + 7.353, + 7.439, + 8.223, + 8.666, + 7.943, + 7.364, + 10.776, + 7.549, + 9.716, + 8.118, + 7.925, + 8.333, + 7.52, + 9.055, + 8.315, + 7.505, + 7.493, + 9.487, + 10.392, + 7.397, + 7.225, + 8.739, + 9.359, + 7.182, + 7.299, + 11.966, + 13.569, + 7.396, + 8.412, + 8.972, + 7.849, + 9.126, + 7.177, + 8.583, + 7.388, + 8.694, + 7.216, + 8.932, + 7.471, + 8.335, + 8.008, + 7.625, + 7.449, + 7.499, + 9.276, + 8.103, + 7.607, + 7.863, + 8.153, + 7.701, + 7.113, + 7.801, + 9.002, + 9.718, + 7.516, + 7.667, + 11.629, + 17.209, + 7.826, + 7.481, + 9.529, + 8.223, + 9.278, + 7.64, + 7.944, + 7.51, + 9.976, + 7.964, + 11.806, + 7.488, + 8.397, + 8.452, + 8.056, + 7.747, + 7.094, + 8.776, + 8.778, + 19.491, + 7.201, + 13.615, + 7.608, + 7.905, + 9.602, + 14.893, + 10.086, + 7.271, + 7.457, + 9.582, + 8.688, + 7.325, + 7.817, + 8.418, + 8.274, + 9.797, + 7.62, + 8.619, + 7.7, + 10.603, + 7.776, + 11.097, + 7.99, + 7.627, + 8.442, + 8.252, + 8.138, + 8.495, + 10.821, + 9.218, + 7.171, + 7.641, + 12.652, + 8.602, + 7.354, + 7.49, + 11.986, + 8.551, + 7.396, + 7.859, + 10.637, + 7.916, + 7.859, + 7.642, + 9.024, + 8.746, + 8.439, + 7.683, + 7.611, + 7.286, + 8.184, + 7.779, + 8.504, + 7.373, + 10.977, + 8.02, + 8.677, + 7.547, + 7.57, + 11.289, + 10.459, + 7.879, + 7.68, + 17.078, + 8.903, + 8.43, + 7.418, + 9.704, + 16.993, + 7.691, + 8.139, + 7.975, + 8.261, + 7.222, + 7.497, + 8.557, + 7.487, + 7.599, + 8.555, + 9.693, + 7.452, + 8.648, + 7.523, + 7.939, + 7.133, + 8.834, + 13.931, + 8.211, + 7.84, + 7.789, + 14.268, + 8.062, + 7.721, + 7.297, + 13.074, + 9.644, + 7.856, + 7.429, + 13.239, + 10.412, + 7.369, + 7.85, + 8.758, + 8.404, + 8.144, + 7.542, + 17.12, + 8.204, + 11.246, + 7.91, + 8.393, + 7.454, + 8.558, + 13.147, + 8.36, + 7.115, + 9.05, + 10.332, + 8.576, + 7.797, + 7.531, + 9.222, + 11.098, + 7.935, + 7.671, + 16.015, + 8.425, + 7.306, + 7.68, + 9.587, + 7.975, + 7.446, + 7.674, + 9.003, + 8.174, + 7.819, + 7.376, + 8.036, + 9.492, + 8.935, + 8.509, + 9.992, + 7.37, + 9.902, + 11.951, + 8.411, + 7.928, + 8.951, + 15.36, + 9.03, + 7.545, + 8.612, + 11.155, + 7.939, + 7.661, + 7.398, + 8.632, + 7.724, + 8.426, + 7.324, + 8.898, + 8.322, + 7.697, + 7.765, + 8.069, + 9.553, + 8.23, + 7.754, + 7.873, + 8.317, + 8.617, + 10.667, + 7.822, + 7.639, + 9.254, + 9.131, + 8.094, + 7.705, + 7.811, + 12.08, + 8.027, + 9.226, + 7.776, + 12.291, + 8.647, + 8.007, + 7.339, + 8.127, + 8.25, + 7.947, + 7.406, + 7.904, + 8.836, + 7.58, + 7.756, + 15.286, + 9.929, + 7.767, + 9.438, + 7.995, + 7.781, + 8.109, + 9.143, + 7.933, + 7.385, + 10.842, + 10.509, + 8.144, + 7.335, + 9.569, + 11.035, + 7.586, + 7.61, + 7.676, + 9.973, + 10.618, + 8.103, + 8.27, + 11.562, + 8.053, + 8.98, + 7.331, + 9.512, + 8.027, + 8.777, + 7.804, + 8.715, + 7.876, + 8.673, + 8.928, + 10.651, + 7.242, + 8.105, + 12.112, + 12.694, + 7.34, + 7.782, + 12.415, + 9.358, + 7.294, + 7.386, + 10.555, + 7.748, + 7.809, + 7.506, + 12.089, + 8.442, + 7.673, + 8.022, + 7.652, + 9.085, + 7.48, + 7.605, + 7.77, + 8.577, + 7.688, + 11.901, + 8.454, + 11.655, + 12.08, + 14.598, + 7.727, + 7.258, + 7.606, + 13.901, + 7.624, + 7.852, + 7.779, + 13.121, + 8.234, + 7.652, + 7.711, + 9.644, + 10.5, + 7.976, + 7.527, + 8.952, + 8.125, + 7.312, + 7.855, + 8.051, + 7.717, + 10.596, + 11.298, + 7.912, + 7.917, + 8.938, + 10.332, + 8.078, + 7.318, + 7.575, + 11.155, + 8.652, + 7.382, + 7.498, + 9.327, + 7.456, + 8.422, + 7.299, + 9.893, + 8.72, + 7.569, + 8.031, + 8.104, + 14.282, + 7.593, + 7.486, + 7.842, + 8.417, + 7.253, + 7.922, + 7.806, + 8.401, + 8.251, + 8.698, + 7.705, + 7.704, + 8.428, + 9.316, + 7.832, + 8.036, + 7.839, + 9.222, + 9.626, + 7.56, + 7.337, + 9.456, + 7.911, + 6.814, + 7.178, + 10.11, + 8.111, + 10.328, + 7.182, + 7.969, + 12.77, + 8.517, + 7.469, + 9.807, + 8.201, + 7.881, + 15.202, + 7.831, + 7.708, + 8.086, + 9.387, + 7.955, + 8.536, + 11.247, + 8.869, + 8.896, + 7.603, + 7.621, + 11.427, + 7.809, + 7.303, + 7.917, + 9.067, + 11.043, + 7.286, + 7.601, + 8.162, + 8.814, + 7.304, + 7.716, + 7.603, + 7.988, + 8.123, + 7.455, + 8.47, + 7.487, + 8.867, + 17.496, + 7.986, + 7.497, + 10.444, + 10.442, + 8.323, + 7.25, + 7.579, + 10.672, + 7.737, + 7.475, + 7.537, + 10.103, + 8.024, + 8.986, + 7.628, + 8.715, + 8.078, + 7.576, + 7.602, + 8.894, + 9.492, + 7.784, + 7.238, + 8.347, + 7.575, + 10.431, + 10.002, + 9.439, + 7.522, + 8.116, + 9.391, + 13.813, + 8.017, + 8.044, + 13.057, + 7.802, + 7.544, + 7.164, + 12.811, + 7.559, + 8.855, + 7.624, + 10.432, + 9.291, + 7.914, + 7.381, + 8.381, + 8.619, + 7.756, + 7.48, + 7.94, + 9.633, + 7.12, + 10.325, + 8.233, + 8.68, + 9.795, + 10.132, + 7.666, + 7.526, + 8.378, + 13.984, + 7.654, + 8.56, + 7.582, + 14.89, + 8.832, + 7.226, + 7.405, + 8.961, + 9.01, + 7.95, + 7.275, + 8.218, + 8.902, + 7.749, + 7.696, + 11.025, + 9.416, + 8.473, + 14.867, + 10.571, + 7.402, + 9.251, + 9.039, + 8.044, + 7.236, + 8.591, + 9.826, + 8.263, + 7.623, + 7.761, + 8.522, + 7.654, + 7.034, + 7.639, + 8.957, + 7.479, + 8.13, + 7.174, + 9.053, + 7.816, + 9.504, + 7.473, + 7.803, + 8.251, + 10.486, + 7.466, + 10.539, + 8.318, + 7.576, + 9.937, + 15.916, + 7.429, + 8.382, + 11.398, + 7.96, + 8.757, + 8.049, + 10.313, + 8.256, + 7.609, + 9.416, + 9.892, + 7.443, + 8.482, + 7.681, + 13.956, + 7.67, + 7.999, + 8.328, + 7.864, + 10.083, + 8.233, + 7.571, + 7.848, + 8.735, + 8.495, + 14.27, + 8.356, + 7.491, + 8.854, + 9.266, + 12.262, + 7.245, + 9.794, + 8.143, + 9.829, + 7.409, + 9.708, + 14.169, + 7.481, + 7.69, + 7.792, + 10.258, + 7.589, + 8.895, + 7.578, + 9.194, + 8.385, + 7.263, + 7.709, + 7.959, + 8.692, + 7.224, + 8.772, + 7.574, + 7.877, + 7.345, + 8.89, + 7.986, + 7.269, + 8.376, + 8.753, + 8.365, + 8.793, + 7.942, + 9.047, + 8.501, + 7.355, + 7.25, + 8.66, + 7.262, + 8.066, + 7.49, + 11.758, + 7.647, + 7.334, + 7.306, + 8.756, + 9.883, + 7.17, + 7.256, + 15.786, + 9.093, + 9.203, + 15.536, + 8.036, + 7.383, + 8.671, + 9.463, + 8.103, + 7.187, + 8.28, + 8.418, + 8.119, + 7.55, + 7.346, + 12.602, + 8.268, + 7.624, + 7.769, + 10.233, + 7.779, + 7.792, + 7.569, + 7.903, + 8.388, + 7.6, + 7.115, + 8.466, + 8.444, + 8.378, + 8.147, + 8.832, + 8.607, + 10.377, + 13.376, + 12.174, + 9.698, + 9.726, + 9.693, + 7.438, + 8.555, + 8.538, + 12.974, + 7.943, + 7.472, + 9.372, + 11.494, + 9.066, + 7.87, + 7.5, + 15.205, + 8.274, + 7.431, + 7.325, + 7.72, + 8.611, + 7.281, + 7.793, + 8.115, + 8.821, + 7.663, + 13.381, + 8.519, + 10.76, + 9.17, + 8.71, + 8.504, + 7.273, + 7.891, + 11.199, + 11.25, + 7.799, + 7.385, + 15.3, + 7.852, + 7.398, + 7.688, + 16.039, + 7.656, + 8.484, + 7.342, + 14.001, + 7.412, + 7.166, + 7.528, + 8.084, + 9.097, + 7.755, + 10.469, + 13.62, + 10.276, + 7.582, + 9.505, + 8.354, + 8.75, + 8, + 9.559, + 8.229, + 7.367, + 9.611, + 8.495, + 8.408, + 7.372, + 10.598, + 8.758, + 7.867, + 7.691, + 7.579, + 10.232, + 7.835, + 8.161, + 7.525, + 14.79, + 7.574, + 7.672, + 12.509, + 8.133, + 8.149, + 8.86, + 10.999, + 8.645, + 8.53, + 7.54, + 8.811, + 8.355, + 8.772, + 7.858, + 8.564, + 9.634, + 7.227, + 9.164, + 12.291, + 7.421, + 10.246, + 7.386, + 8.248, + 7.165, + 8.246, + 9.063, + 9.528, + 7.201, + 7.564, + 8.719, + 9.412, + 7.487, + 7.454, + 8.51, + 10.421, + 8.169, + 20.793, + 10.917, + 7.735, + 8.467, + 7.282, + 8.446, + 9.278, + 8.69, + 8.836, + 9.547, + 7.769, + 7.179, + 8.32, + 7.715, + 7.191, + 8.235, + 8.043, + 8.956, + 7.421, + 7.565, + 8.262, + 8.083, + 7.744, + 7.502, + 9.277, + 12.702, + 12.797, + 7.641, + 13.233, + 8.41, + 9.511, + 7.426, + 10.291, + 8.089, + 8.246, + 7.775, + 8.791, + 7.362, + 7.548, + 8.367, + 8.02, + 7.767, + 7.328, + 7.686, + 8.164, + 7.61, + 8.302, + 9.172, + 11.411, + 7.918, + 7.367, + 10.437, + 9.18, + 8.249, + 7.805, + 10.531, + 8.457, + 8.784, + 7.548, + 8.566, + 8.727, + 7.771, + 8.296, + 8.575, + 8.233, + 7.56, + 9.807, + 7.774, + 7.924, + 8.074, + 7.758, + 8.277, + 7.273, + 8.135, + 7.608, + 13.784, + 7.933, + 8.045, + 9.43, + 10.626, + 7.748, + 7.42, + 8.951, + 9.169, + 9.276, + 8.923, + 14.475, + 9.711, + 11.359, + 8.592, + 8.335, + 7.825, + 9.265, + 8.326, + 9.054, + 7.418, + 7.119, + 8.139, + 15.761, + 7.234, + 7.765, + 8.789, + 16.2, + 7.134, + 7.502, + 8.076, + 12.106, + 7.617, + 8.012, + 12.197, + 9.979, + 8.509, + 7.318, + 8.405, + 7.83, + 9.649, + 8.025, + 8.789, + 14.699, + 9.692, + 8.848, + 8.538, + 9.163, + 8.625, + 9.197, + 9.319, + 8.033, + 7.296, + 8.69, + 7.871, + 7.622, + 7.332, + 10.12, + 9.715, + 7.212, + 7.494, + 11.564, + 13.48, + 9.358, + 7.159, + 11.688, + 9.628, + 9.166, + 7.607, + 10.701, + 8.249, + 9.211, + 7.38, + 8.71, + 7.448, + 10.219, + 9.18, + 7.654, + 8.494, + 7.537, + 8.224, + 8.371, + 7.482, + 7.114, + 8.036, + 10.56, + 7.499, + 8.817, + 10.471, + 10.31, + 7.534, + 7.526, + 13.808, + 10.618, + 9.115, + 9.607, + 10.335, + 8.317, + 8.662, + 7.256, + 8.561, + 7.766, + 9.548, + 8.341, + 9.283, + 7.237, + 7.772, + 8.229, + 7.673, + 7.89, + 7.609, + 8.285, + 9.042, + 7.593, + 7.206, + 12.727, + 10.892, + 7.509, + 7.606, + 11.912, + 8.488, + 7.691, + 8.033, + 9.085, + 7.29, + 9.209, + 7.617, + 9.456, + 7.692, + 10.648, + 7.896, + 10.598, + 7.258, + 7.977, + 9.481, + 16.037, + 7.409, + 8.268, + 7.51, + 14.8, + 7.87, + 7.453, + 13.08, + 11.31, + 8.078, + 7.901, + 9.126, + 8.855, + 11.313, + 7.848, + 10.443, + 11.068, + 8.26, + 7.224, + 9.232, + 8.2, + 9.565, + 7.506, + 8.323, + 7.269, + 9.34, + 8.992, + 8.889, + 7.379, + 8.886, + 9.607, + 8.838, + 7.33, + 10.059, + 9.205, + 11.984, + 7.708, + 7.155, + 8.101, + 9.505, + 7.761, + 7.583, + 9.186, + 8.581, + 7.91, + 7.369, + 8.726, + 8.138, + 9.314, + 7.692, + 8.635, + 7.383, + 10.712, + 8.541, + 8.779, + 7.503, + 7.32, + 9.208, + 10.491, + 7.62, + 7.08, + 9.35, + 9.697, + 7.875, + 7.681, + 11.535, + 13.341, + 7.361, + 8.59, + 10.134, + 9.919, + 7.864, + 7.267, + 12.198, + 8.547, + 8.957, + 7.251, + 7.802, + 7.568, + 9.416, + 7.755, + 11.205, + 7.895, + 8.021, + 8.273, + 7.563, + 7.517, + 13.087, + 8.036, + 9.106, + 7.596, + 7.301, + 9.976, + 9.36, + 8.424, + 7.94, + 8.211, + 8.663, + 7.738, + 7.559, + 9.247, + 9.456, + 8.105, + 7.531, + 8.499, + 7.355, + 12.124, + 8.262, + 9.624, + 7.722, + 8.144, + 8.395, + 9.442, + 7.449, + 8.03, + 11.687, + 8.225, + 7.366, + 11.583, + 12.109, + 8.718, + 7.405, + 7.178, + 8.759, + 8.566, + 9.152, + 7.979, + 8.551, + 8.173, + 10.775, + 8.504, + 10.183, + 7.648, + 9.022, + 9.872, + 11.136, + 7.753, + 7.717, + 9.479, + 8.021, + 7.546, + 8.218, + 8.629, + 9.127, + 8.415, + 9.237, + 9.298, + 9.508, + 7.255, + 7.256, + 11.147, + 10.994, + 7.179, + 7.518, + 9.405, + 7.935, + 8.408, + 7.459, + 8.504, + 13.096, + 8.334, + 7.436, + 8.936, + 7.616, + 9.417, + 7.804, + 7.836, + 7.853, + 7.255, + 8.206, + 8.18, + 7.185, + 7.421, + 7.582, + 10.831, + 7.629, + 7.802, + 8.293, + 13.752, + 7.966, + 7.732, + 10.203, + 8.076, + 7.748, + 9.289, + 11.902, + 10.171, + 9.335, + 9.09, + 9.037, + 8.219, + 7.49, + 8.207, + 10.038, + 7.694, + 7.455, + 8.386, + 8.222, + 7.198, + 7.283, + 9.729, + 7.777, + 7.446, + 7.908, + 8.483, + 8.589, + 7.367, + 7.342, + 25.442, + 11.318, + 8.862, + 7.396, + 16.39, + 9.033, + 9.632, + 7.361, + 9.61, + 8.437, + 8.635, + 11.126, + 12.276, + 7.701, + 8.816, + 11.529, + 9.121, + 7.448, + 7.493, + 8.397, + 7.767, + 7.553, + 7.368, + 9.392, + 12.08, + 8.12, + 9.823, + 7.97, + 16.084, + 7.311, + 7.803, + 9.455, + 8.604, + 9.27, + 7.81, + 8.188, + 8.857, + 8.393, + 7.61, + 8.349, + 7.472, + 8.083, + 7.443, + 7.727, + 7.579, + 7.854, + 7.831, + 12.861, + 7.763, + 8.171, + 9.014, + 9.893, + 8.073, + 8.44, + 19.033, + 10.967, + 7.837, + 7.702, + 12.368, + 12.77, + 7.389, + 7.363, + 9.537, + 10.484, + 9.725, + 7.659, + 8.184, + 7.468, + 9.425, + 7.476, + 8.156, + 7.499, + 9.796, + 8.16, + 7.794, + 7.641, + 9.116, + 9.691, + 8.6, + 7.216, + 8.13, + 16.786, + 13.527, + 7.24, + 7.289, + 22.765, + 8.325, + 7.327, + 7.295, + 13.267, + 11.95, + 8.476, + 7.481, + 9.415, + 8.458, + 8.173, + 7.242, + 8.153, + 7.882, + 7.922, + 7.374, + 7.849, + 7.624, + 9.156, + 7.469, + 9.323, + 7.905, + 8.052, + 13.995, + 8.037, + 8.168, + 10.137, + 13.164, + 16.343, + 8.702, + 7.402, + 14.274, + 8.428, + 8.289, + 7.553, + 10.093, + 7.786, + 7.763, + 8.003, + 12.713, + 8.296, + 8.972, + 7.696, + 9.509, + 9.151, + 10.19, + 8.767, + 9.691, + 7.393, + 7.807, + 20.5, + 15.114, + 7.366, + 8.495, + 11.585, + 9.829, + 8.126, + 7.62, + 12.564, + 8.77, + 7.711, + 8.048, + 9.962, + 8.531, + 7.281, + 7.678, + 10.258, + 9.404, + 7.772, + 7.28, + 8.116, + 8.84, + 8.361, + 13.014, + 14.349, + 8.216, + 11.814, + 11.207, + 8.194, + 7.465, + 10.949, + 11.085, + 7.862, + 8.467, + 7.199, + 13.113, + 7.936, + 7.502, + 7.24, + 11.062, + 8.423, + 7.868, + 7.394, + 13.003, + 8.475, + 7.807, + 8.795, + 7.979, + 9, + 8.216, + 13.962, + 8.432, + 7.944, + 8.29, + 9.238, + 9.13, + 7.925, + 8.029, + 11.979, + 11.59, + 8.18, + 7.591, + 11.121, + 7.452, + 8.162, + 7.504, + 9.458, + 7.975, + 8.007, + 7.82, + 12.404, + 8.604, + 9.692, + 7.438, + 12.333, + 8.625, + 9.689, + 14.721, + 8.31, + 7.191, + 9.938, + 8.821, + 14.393, + 7.271, + 8.349, + 14.222, + 10.281, + 7.835, + 7.589, + 8.897, + 7.654, + 7.56, + 7.813, + 7.888, + 7.754, + 7.458, + 7.189, + 8.048, + 8.683, + 7.26, + 7.237, + 8.155, + 8.892, + 10.557, + 11.545, + 8.093, + 7.818, + 8.261, + 10.05, + 9.32, + 8.103, + 9.692, + 10.155, + 7.722, + 8.067, + 7.91, + 8.702, + 8.798, + 7.568, + 7.716, + 8.739, + 7.488, + 7.853, + 7.52, + 8.938, + 8.145, + 7.471, + 7.163, + 8.296, + 8.12, + 7.478, + 8.544, + 8.511, + 9.351, + 8.325, + 11.475, + 7.998, + 7.404, + 8.752, + 9.55, + 12.908, + 7.653, + 8.766, + 13.331, + 8.101, + 7.536, + 7.495, + 9.81, + 7.595, + 8.866, + 8.403, + 9.649, + 8.497, + 7.63, + 7.436, + 7.646, + 8.15, + 7.867, + 7.405, + 7.891, + 7.939, + 8.118, + 7.892, + 16.366, + 7.315, + 7.795, + 9.832, + 15.11, + 7.612, + 7.229, + 14.852, + 8.991, + 7.795, + 7.38, + 8.311, + 7.452, + 8.067, + 7.594, + 9.133, + 8.863, + 7.746, + 7.572, + 8.33, + 8.945, + 7.557, + 7.66, + 7.865, + 7.516, + 11.712, + 7.638, + 9.23, + 7.863, + 9.057, + 9.297, + 7.805, + 7.182, + 8.303, + 10.157, + 7.766, + 7.518, + 7.73, + 12.084, + 7.934, + 7.538, + 8.612, + 10.664, + 7.883, + 8.535, + 7.555, + 8.264, + 8.579, + 11.37, + 10.158, + 7.84, + 8.08, + 7.581, + 7.492, + 9.494, + 7.989, + 10.586, + 9.513, + 7.572, + 8.973, + 7.932, + 9.607, + 14.517, + 7.594, + 8.549, + 8.991, + 8.837, + 8.065, + 7.201, + 8.938, + 8.883, + 7.891, + 7.604, + 10.481, + 9.862, + 8.719, + 7.692, + 9.281, + 7.793, + 8.323, + 7.656, + 7.986, + 8.223, + 7.749, + 11.572, + 7.941, + 8.387, + 8.173, + 22.667, + 7.901, + 8.692, + 7.642, + 12.773, + 7.867, + 7.22, + 7.784, + 16.146, + 8.574, + 7.754, + 7.513, + 8.639, + 7.912, + 9.687, + 7.051, + 9.871, + 7.808, + 8.285, + 7.503, + 8.185, + 7.564, + 10.498, + 9.545, + 8.048, + 7.454, + 8.67, + 11.245, + 8.036, + 7.685, + 7.747, + 10.612, + 8.833, + 7.501, + 7.555, + 11.948, + 9.971, + 7.376, + 7.978, + 8.55, + 8.121, + 10.366, + 8.645, + 8.621, + 8.02, + 7.711, + 7.937, + 8.435, + 7.413, + 9.469, + 7.858, + 10.092, + 7.372, + 8.624, + 17.99, + 9.819, + 7.309, + 7.653, + 12.83, + 8.145, + 7.465, + 7.55, + 9.642, + 8.579, + 7.719, + 7.737, + 8.254, + 8.842, + 7.593, + 7.899, + 8.319, + 8.901, + 9.598, + 13.24, + 7.745, + 7.567, + 8.347, + 7.769, + 7.609, + 7.233, + 9.423, + 26.069, + 7.697, + 7.399, + 7.489, + 12.783, + 10.652, + 7.736, + 7.49, + 12.798, + 8.197, + 8.583, + 7.548, + 9.004, + 8.105, + 8.335, + 7.463, + 7.402, + 8.893, + 7.718, + 7.766, + 8.491, + 8.13, + 9.038, + 7.103, + 7.954, + 7.766, + 7.684, + 10.257, + 14.344, + 7.582, + 8.056, + 10.264, + 9.552, + 8.42, + 7.935, + 10.164, + 7.748, + 7.592, + 7.52, + 26.591, + 7.753, + 7.435, + 7.67, + 8.03, + 7.385, + 7.496, + 8.659, + 8.235, + 7.307, + 8.832, + 7.897, + 9.107, + 7.519, + 8.092, + 10.08, + 7.788, + 7.383, + 7.751, + 8.928, + 9.704, + 7.582, + 7.703, + 14.262, + 8.002, + 7.437, + 7.466, + 9.113, + 7.904, + 7.717, + 7.13, + 8.847, + 8.041, + 8.312, + 8.625, + 8.63, + 7.682, + 7.932, + 7.6, + 7.869, + 7.191, + 10.12, + 12.226, + 7.787, + 7.881, + 9.059, + 14.077, + 13.728, + 8.2, + 8.813, + 14.208, + 8.67, + 7.715, + 7.64, + 10.132, + 7.517, + 7.303, + 7.278, + 8.273, + 7.911, + 7.345, + 7.598, + 8.811, + 9.956, + 7.477, + 8.081, + 11.836, + 7.555, + 8.49, + 7.496, + 7.803, + 7.994, + 8.821, + 10.761, + 7.789, + 7.246, + 7.124, + 14.657, + 7.865, + 7.77, + 7.744, + 15.516, + 20.442, + 8.185, + 7.325, + 8.525, + 8.122, + 9.045, + 8.591, + 11.469, + 8.043, + 8.901, + 7.429, + 8.415, + 7.32, + 8.976, + 7.615, + 8.086, + 7.547, + 8.185, + 16.079, + 15.983, + 7.34, + 9.165, + 13.318, + 7.925, + 8.14, + 7.701, + 9.859, + 8.025, + 7.5, + 8.512, + 12.081, + 8.051, + 7.398, + 7.415, + 9.461, + 7.758, + 8.449, + 7.829, + 7.875, + 8.388, + 9.299, + 7.672, + 7.611, + 7.813, + 10.529, + 12.4, + 8.731, + 7.284, + 7.49, + 10.449, + 7.874, + 7.103, + 9.102, + 14.291, + 9.295, + 9.643, + 7.24, + 10.841, + 8.842, + 7.411, + 7.732, + 8.566, + 9.817, + 7.555, + 7.414, + 7.727, + 9.252, + 7.568, + 7.436, + 7.88, + 7.541, + 8.184, + 7.267, + 7.975, + 7.495, + 8.414, + 17.79, + 8.484, + 7.216, + 7.433, + 10.734, + 9.88, + 8.681, + 7.718, + 10.226, + 7.856, + 7.859, + 7.651, + 11.553, + 10.627, + 8.003, + 7.403, + 9.046, + 9.655, + 8.516, + 7.663, + 8.34, + 7.926, + 8.288, + 8.116, + 8.226, + 7.279, + 8.622, + 9.852, + 8.017, + 7.773, + 8.089, + 11.328, + 7.92, + 7.589, + 7.294, + 11.349, + 12.04, + 7.355, + 7.813, + 10.129, + 10.108, + 9.094, + 8.079, + 8.485, + 8.152, + 9.266, + 7.619, + 10.636, + 9.821, + 8.689, + 7.787, + 8.264, + 7.626, + 9.142, + 7.473, + 8.318, + 7.937, + 8.093, + 14.316, + 8.418, + 7.372, + 7.787, + 13.028, + 8.38, + 10.251, + 7.616, + 10.918, + 8.477, + 7.452, + 8.508, + 8.255, + 8, + 7.495, + 7.707, + 7.589, + 7.579, + 8.245, + 8.285, + 8.76, + 7.295, + 9.217, + 7.78, + 8.531, + 7.503, + 8.698, + 17.397, + 9.048, + 7.473, + 10.024, + 10.691, + 8.257, + 7.421, + 7.882, + 13, + 8.189, + 8.027, + 7.427, + 11.049, + 8.336, + 7.996, + 7.245, + 8.511, + 7.726, + 10.07, + 7.504, + 8.14, + 9.181, + 7.791, + 8.42, + 8.635, + 7.413, + 8.094, + 8.413, + 11.552, + 7.366, + 11.407, + 9.43, + 10.126, + 7.254, + 7.682, + 10.366, + 15.696, + 9.288, + 7.823, + 9.513, + 8.21, + 7.214, + 7.458, + 7.973, + 9.865, + 7.228, + 8.04, + 8.935, + 8.827, + 8.525, + 7.356, + 8.111, + 7.288, + 10.143, + 7.682, + 7.509, + 7.691, + 10.171, + 10.591, + 8.448, + 7.585, + 7.439, + 15.91, + 8.148, + 7.22, + 7.208, + 9.957, + 11.003, + 7.622, + 9.029, + 10.559, + 8.176, + 7.69, + 7.859, + 8.633, + 8.102, + 11.041, + 7.913, + 16.282, + 9.621, + 8.717, + 8.009, + 13.976, + 7.805, + 8.052, + 10.919, + 9.68, + 7.689, + 8.343, + 12.541, + 7.946, + 7.547, + 7.397, + 10.838, + 8.412, + 7.282, + 7.548, + 10.575, + 7.686, + 9.846, + 7.829, + 8.007, + 9.827, + 7.716, + 7.652, + 10.975 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135512_947142bf-e75b-481e-bf5d-93a7103e42e5.mp4", + "tMs": -27532 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135512_cd84a0b9-8cd6-44dc-8cd2-3defec173c55.mp4", + "tMs": -27531 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135512_4fe58628-178b-4144-8d73-1c4c3858e015.mp4", + "tMs": -27526 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135512_ad6b56c7-cdab-4cba-9226-ba80ff3a826e.mp4", + "tMs": -27520 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135525_2c37acac-db54-4ebc-824d-cf0e3112b747.mp4", + "tMs": -17321 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135525_67174d6a-398e-4ab5-acd7-7505cd371315.mp4", + "tMs": -17317 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135525_d22c4ea2-a3a0-4cfb-b951-386364396c67.mp4", + "tMs": -17308 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135525_08938b2e-3284-47eb-a85a-882968b68434.mp4", + "tMs": -17307 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135535_1ef50dc4-8f0a-4090-af92-8f8937b6c814.mp4", + "tMs": -7322 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135535_8cd515c3-c4ed-4e3a-a5ea-ce86dc3057af.mp4", + "tMs": -7316 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135535_45a225a9-cece-45c2-81af-5af460712153.mp4", + "tMs": -7309 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135535_7bbf9b9e-2220-4531-8564-280baf2e6f4a.mp4", + "tMs": -7304 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135545_41f63fa0-c1f6-406b-8835-7696287b334e.mp4", + "tMs": 2678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135545_686bcd1d-6531-49f5-bbb8-faa4348b052c.mp4", + "tMs": 2683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135545_57c8f173-3432-4b9e-8006-242eb8587a8e.mp4", + "tMs": 2687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135545_8ef2e426-42b1-4080-b408-7b3f5616be31.mp4", + "tMs": 2692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135555_ef703ec2-6cd7-4624-877c-876d46518acc.mp4", + "tMs": 12678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135555_fc0aba6d-e858-445b-b362-dc3409f4d769.mp4", + "tMs": 12683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135555_128c9c81-f7c7-4891-a0dc-b9f10b42a4c6.mp4", + "tMs": 12687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135555_4f20027e-b412-4cbe-84db-9225a7514686.mp4", + "tMs": 12696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135605_750712b7-6b02-4e71-b697-2c2bba98e4e7.mp4", + "tMs": 22678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135605_1d6b0de8-7daf-4652-b8d8-d3ac669f9f1a.mp4", + "tMs": 22683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135605_1b684309-a5eb-471c-868f-f45cc2264793.mp4", + "tMs": 22691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135605_ddca5ce0-c5ee-47ad-b2d0-c76ac3c3d5b6.mp4", + "tMs": 22693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135615_b6bf3734-5e61-48de-85a5-adf9d2c671f9.mp4", + "tMs": 32677 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135615_f1514def-28fa-4890-9e62-f27c44979f5c.mp4", + "tMs": 32689 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135615_93759d51-1977-4e9b-867c-d3b2ff89798b.mp4", + "tMs": 32690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135615_014b96cb-e5a4-47f7-87ce-c03445cb9228.mp4", + "tMs": 32704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135625_f105b525-e524-431b-b0da-31666e9dbb21.mp4", + "tMs": 42677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135625_c9df426f-b22a-4440-8bfe-7a866cb41d84.mp4", + "tMs": 42684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135625_64d9cba6-4d5e-4010-bcad-3faf9e4abb3d.mp4", + "tMs": 42692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135625_33089a8e-3305-41d9-b12e-b5407458b3a5.mp4", + "tMs": 42696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135635_641eed67-eb01-41e6-8690-21aba825b9ca.mp4", + "tMs": 52679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135635_95bd8a05-0962-435a-8f32-2197a4deb96d.mp4", + "tMs": 52685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135635_bbb90bc1-3590-4710-bbec-eb694f29e724.mp4", + "tMs": 52693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135635_0c96246a-97d5-41dc-bd39-82bf8502b85d.mp4", + "tMs": 52695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135645_6234e774-83dc-45e4-8856-ac53afb9fe43.mp4", + "tMs": 62679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135645_fb5d54f7-e859-48f7-96b8-dbfc887c95c3.mp4", + "tMs": 62683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135645_137e20f5-0f37-4b82-82a8-85b58da8f625.mp4", + "tMs": 62686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135645_3518bb85-99a5-42a6-83de-713df44e56db.mp4", + "tMs": 62696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135655_355f6d12-75d9-4a2c-beb8-dcbda0f84a98.mp4", + "tMs": 72681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135655_ca5d1d68-76ba-4f59-9814-9d1b1fc0d48b.mp4", + "tMs": 72683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135655_c877ed41-8607-4250-81f8-1c95b1c723e6.mp4", + "tMs": 72689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135655_1a78ee44-d270-4f5e-9c6e-c8beba811167.mp4", + "tMs": 72697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135705_cb9e7a90-d39d-48ce-ae21-63cb1105a44a.mp4", + "tMs": 82682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135705_dcc9c8ae-9c8e-4864-9eef-cd0f5c1045a9.mp4", + "tMs": 82683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135705_e695cb46-d80e-43be-95c7-74d5970c8677.mp4", + "tMs": 82691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135705_a4fc2a24-ec50-449e-88f8-7a43c3248e5c.mp4", + "tMs": 82693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135715_1f40e83b-1b3a-4316-86d3-867fc9160750.mp4", + "tMs": 92677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135715_2c0976f9-de80-47cb-b185-d28b70b794b4.mp4", + "tMs": 92684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135715_d4c4cd0b-385c-4dde-b4da-6450dfe56ae9.mp4", + "tMs": 92690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135715_f855267d-d17d-4a94-ab9d-694700de7f94.mp4", + "tMs": 92701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135725_7b138399-a0fd-40f3-9966-c47b05db3cea.mp4", + "tMs": 102683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135725_c9acca51-3117-4c1d-9cbd-b072bddd8d8c.mp4", + "tMs": 102685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135725_49f9f74b-cd8f-47a2-adbd-2fa8acf4707a.mp4", + "tMs": 102692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135725_fbe45a3d-28ce-46f5-857d-ef65e254725e.mp4", + "tMs": 102695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135735_f22f6fa5-2e92-4200-a0c0-64fc16f7fa00.mp4", + "tMs": 112678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135735_d381551d-21f8-4bb1-8879-be5753d44b05.mp4", + "tMs": 112682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135735_2005007d-aff7-4f44-8254-537ffe5553be.mp4", + "tMs": 112686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135735_12ef180b-6c6f-4e7a-8ede-310a41de8212.mp4", + "tMs": 112696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135745_eb14e973-721f-4c61-bf1f-4a591993a1cb.mp4", + "tMs": 122677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135745_f3dd38a2-7f72-4beb-b407-5ed1f5741096.mp4", + "tMs": 122683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135745_50845850-340e-4d78-ae88-c9c107e2b4e0.mp4", + "tMs": 122686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135745_3c2765b6-cd37-48e4-95fd-531b90a560d6.mp4", + "tMs": 122697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135755_831ee636-a236-4b40-89b4-31701d494266.mp4", + "tMs": 132680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135755_c40cb46a-ee7a-4453-a68c-fd11e90f0ac6.mp4", + "tMs": 132700 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135755_0e84aa3a-630e-4062-bc52-abdf072a558a.mp4", + "tMs": 132700 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135755_007c54c2-aff0-4651-84ae-076884283d33.mp4", + "tMs": 132707 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135805_8df16502-b5bf-4d35-8d3d-a299a8452461.mp4", + "tMs": 142682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135805_6c9ab6d8-fa43-48d1-8ae5-7eb79d625130.mp4", + "tMs": 142682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135805_5aade48d-8090-4556-9e79-440f0db4cfff.mp4", + "tMs": 142687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135805_b426e4c0-acd5-42a6-b351-9bedcc364fc6.mp4", + "tMs": 142694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135815_f20218ef-f778-405d-891b-90fe14fffb7e.mp4", + "tMs": 152679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135815_4497aab1-9fd2-499b-91bf-617630affcc5.mp4", + "tMs": 152683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135815_19bbac6c-cd53-4423-8b24-66f6cca02d7e.mp4", + "tMs": 152690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135815_b3245465-1681-401e-b7ae-753a4154dbc2.mp4", + "tMs": 152696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135825_c03d4db5-28f1-4a3b-a493-f06cbf82bf73.mp4", + "tMs": 162677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135825_671f881e-17f0-4016-b3de-95691e1b1891.mp4", + "tMs": 162685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135825_9f513668-cec6-4e0c-8465-6652103759b9.mp4", + "tMs": 162687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135825_19fda768-07c3-46ab-a8e7-8311995fe35e.mp4", + "tMs": 162692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135835_233100ec-761c-4ebb-a9f3-1f9ba93653a0.mp4", + "tMs": 172678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135835_11d3156f-876a-46f4-98a1-d85b0bcf1ea7.mp4", + "tMs": 172683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135835_3a1b2256-fe74-4f71-80c5-9188247ccf05.mp4", + "tMs": 172690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135835_fbf72ef9-ae23-43e8-9e02-b5c859edd79e.mp4", + "tMs": 172697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135845_ddd9002c-a760-4ae2-857f-474e2eecdbb1.mp4", + "tMs": 182679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135845_2c9b0481-924b-4edc-b71f-44d019e177dd.mp4", + "tMs": 182683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135845_33a0640e-5c4f-44e6-ae9b-ae2a214a21ce.mp4", + "tMs": 182689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135845_a69a8389-a05e-4310-be70-1b8f62395179.mp4", + "tMs": 182693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135855_357e68ee-64db-456c-b70c-81b189d5c0c7.mp4", + "tMs": 192678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135855_99339ae4-a176-40a3-8884-4d3a74ee2f4f.mp4", + "tMs": 192684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135855_ebb57ec1-d507-4928-b924-f3585b022fb5.mp4", + "tMs": 192688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135855_fe7d2192-34c6-4c08-93c9-f963beda2c6d.mp4", + "tMs": 192694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135905_ac48a4bb-ba8c-4a59-86b0-196b6322c40c.mp4", + "tMs": 202684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135905_4f5f3c96-3e37-43e6-a85b-a45f13e07189.mp4", + "tMs": 202684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135905_b23b7f1e-848b-4198-9963-19363f2c65ab.mp4", + "tMs": 202688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135905_fbb83160-aee6-4a50-a792-dcd572f3e917.mp4", + "tMs": 202699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135915_c4bc1f45-94c3-4c5f-872b-1f3c696fb648.mp4", + "tMs": 212678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135915_b0f70132-f164-4861-af95-f7901a8119cf.mp4", + "tMs": 212683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135915_253490d3-c7b8-4128-a2b4-fb792f2516b8.mp4", + "tMs": 212688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135915_8a426b3a-3964-4f7b-8c83-361173547038.mp4", + "tMs": 212697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135925_ba784725-19c4-49ff-b51c-e65bb019a2f7.mp4", + "tMs": 222678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135925_01777ead-26dd-40e7-957c-135262d19e0c.mp4", + "tMs": 222684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135925_0234cb11-f8f5-4570-9136-c28b90f38de8.mp4", + "tMs": 222686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135925_c8948ebb-bebc-455a-b643-01df3fa4602e.mp4", + "tMs": 222696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135935_330c2444-fabe-4762-874e-0eae4e118367.mp4", + "tMs": 232677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135935_1fad50a5-d77a-4da0-b58f-5e59fab0cd5d.mp4", + "tMs": 232684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135935_02fd8425-5a80-4c85-a584-81c5df737801.mp4", + "tMs": 232692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135935_50eb8bd4-32f5-4f46-b7f9-fe97e040ab45.mp4", + "tMs": 232695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135945_2159815a-41fb-46a3-998d-2e40488782d6.mp4", + "tMs": 242679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135945_b0f9c928-2d24-46dc-984a-0ded730ce7d6.mp4", + "tMs": 242692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135945_a03b19c0-2a64-45c9-95d2-be1ac96f0e7a.mp4", + "tMs": 242696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135945_2d67bb13-e459-4b58-842e-8ba2e0b69cdf.mp4", + "tMs": 242699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_135955_f218ed1d-fb2e-4468-ba80-8e3e14966c6c.mp4", + "tMs": 252680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_135955_ac54c60b-859d-4203-8e39-1c19b4b68ea6.mp4", + "tMs": 252682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_135955_c4843937-71b6-4738-bcdf-e99142f87731.mp4", + "tMs": 252687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_135955_1823b1f6-59d8-4968-86bc-03091a7cc355.mp4", + "tMs": 252695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140005_d54eebdc-09df-46c1-9578-be30dd6350ac.mp4", + "tMs": 262682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140005_f47248bc-93f6-4ac1-bdd1-546473a8dce6.mp4", + "tMs": 262683 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140005_21f15b8d-4a6c-432c-b9b2-20cef4bd197c.mp4", + "tMs": 262699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140005_f2dd70a8-1cd7-4770-9e63-a8c196ccdd20.mp4", + "tMs": 262700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140015_d17520ac-7cc8-43a5-a31d-75ec86175c4a.mp4", + "tMs": 272679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140015_9785286a-b38b-4b11-9676-caa6abf259e9.mp4", + "tMs": 272684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140015_5e7d96b2-7705-4d2d-8a60-f1f5a9148fb7.mp4", + "tMs": 272691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140015_ea21d738-6477-44dd-9b50-3301ea03db63.mp4", + "tMs": 272701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140025_48cc0e4d-9ee7-4a32-ac28-4dcffc052865.mp4", + "tMs": 282679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140025_67d14954-fcc9-4057-bdcd-af108dc880e3.mp4", + "tMs": 282686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140025_d02a8484-396b-428e-85ec-c9487b2c68b6.mp4", + "tMs": 282693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140025_7377f608-bfcc-416c-8c2f-883010e70ea4.mp4", + "tMs": 282697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140035_481cedc8-5c59-4f59-a5ff-35cadbcda13b.mp4", + "tMs": 292682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140035_45d8b10c-7e89-48c5-8b1b-2b033ba14b51.mp4", + "tMs": 292684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140035_3a52be19-b4d3-45a5-af5b-567053567a1d.mp4", + "tMs": 292691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140035_3dae9611-b35a-4ec6-bd74-52fe02f87748.mp4", + "tMs": 292699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140045_12cc794a-b3b2-4813-bd64-47583413193b.mp4", + "tMs": 302678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140045_57bc509a-7e9d-4ed5-8900-ebebda5304ba.mp4", + "tMs": 302687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140045_79836897-f269-4d77-9149-bdc2f2c25b34.mp4", + "tMs": 302697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140045_906ce849-8ed9-41e6-b6b8-3c22b9867862.mp4", + "tMs": 302698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140055_f67f2cde-540d-422f-9e63-4909e6652797.mp4", + "tMs": 312679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140055_66d525c9-1727-48b7-a7ba-67e561ca7484.mp4", + "tMs": 312688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140055_a529a5fa-a8a2-40cf-99bc-ffbeef024c8d.mp4", + "tMs": 312693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140055_cbce3cc7-cbe6-4ddd-b028-757c1d96b3cf.mp4", + "tMs": 312698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140105_0fe5020d-9d8c-43b5-bf62-04ab178f89de.mp4", + "tMs": 322681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140105_02953b19-2c9e-47ba-b4ec-1d547504aa34.mp4", + "tMs": 322687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140105_7b622b14-2a8c-4b02-907a-e6fcc7e60e70.mp4", + "tMs": 322687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140105_627d4967-1fcb-48a5-aadf-d93df845b390.mp4", + "tMs": 322692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140115_dbddba3d-0d17-486b-8ba6-c37f34948647.mp4", + "tMs": 332680 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140115_43afc65c-11ff-41a5-8bbf-957030d1720f.mp4", + "tMs": 332701 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140115_fa4166f8-aced-407d-a5dc-f9af557e848b.mp4", + "tMs": 332701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140115_8976b0dd-e7e8-48e0-9e56-f60a2d79faba.mp4", + "tMs": 332701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140125_d1018d27-b493-47dd-b7cc-8fb4a9564196.mp4", + "tMs": 342678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140125_94fe2328-8149-4a02-a6a4-50767439ec2d.mp4", + "tMs": 342685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140125_4ff138ef-507e-44d7-a74c-a36eacc35be6.mp4", + "tMs": 342688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140125_e881b206-af38-4f3d-84bb-144bcb6484e7.mp4", + "tMs": 342693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140135_9bf7a8d5-2e54-4ee1-86b6-61478bc0cdfe.mp4", + "tMs": 352678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140135_0487996d-f6ea-432a-ad54-60e08373ea71.mp4", + "tMs": 352682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140135_aa0c1930-8be1-456d-bc56-7bedc0774bd3.mp4", + "tMs": 352687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140135_dc9722b0-9483-4b2f-b888-9cef6e90ecbb.mp4", + "tMs": 352695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140145_1db59ea9-7a24-4e93-a3a1-edc3687788d4.mp4", + "tMs": 362679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140145_4ceecda3-b187-4b4b-a2f8-d916be5fd7bb.mp4", + "tMs": 362681 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140145_441261ca-a18b-4fd5-9cf7-a54f552149c6.mp4", + "tMs": 362688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140145_5e34fde4-45ea-4ed3-a50e-f8f90acf4ae9.mp4", + "tMs": 362694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140155_435f71f1-cba2-41e3-a143-dac13a9c6346.mp4", + "tMs": 372683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140155_5fc089c4-391a-4cf9-8ed8-98adf704cd1a.mp4", + "tMs": 372685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140155_4b43e5c2-bdac-4d3e-9e70-b70e4dd357ee.mp4", + "tMs": 372692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140155_dfc61ef7-da8c-4332-bb2e-aeda00f153be.mp4", + "tMs": 372695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140205_47829fce-014e-4321-8053-801196dd2580.mp4", + "tMs": 382679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140205_a9d9f1f9-b2f3-4eca-a860-f96a301399d5.mp4", + "tMs": 382683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140205_e36b8db7-fa07-4315-9541-3ea2079ad815.mp4", + "tMs": 382689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140205_4e17cbaf-e5fe-4e05-8542-5b82c8859206.mp4", + "tMs": 382712 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140215_e3e98b23-760d-436d-be32-72e21bb87cf6.mp4", + "tMs": 392679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140215_04e92429-dd15-4182-8d54-f6b5f653f803.mp4", + "tMs": 392684 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140215_6dab3b8f-dfed-46bd-bc3d-418dcdb30ad5.mp4", + "tMs": 392699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140215_c8f1c244-c485-4f99-8280-a1a8c93ce6fa.mp4", + "tMs": 392711 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140225_dee850c5-a8a5-4f8a-b573-96fce1c331d8.mp4", + "tMs": 402678 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140225_a7231e2b-763d-4653-a559-969821140639.mp4", + "tMs": 402696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140225_2e5be6ff-1f53-4246-b3fe-7d196dd2e6e0.mp4", + "tMs": 402696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140225_b6ef4b54-db1f-4dd7-8e54-639582cd99f9.mp4", + "tMs": 402700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140235_af1ef33b-06b9-40e7-890f-ed9b10d07882.mp4", + "tMs": 412679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140235_1f02d2e3-87c5-4ffb-b1d5-1359db659ccc.mp4", + "tMs": 412693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140235_086fe104-0369-4a0a-88d9-79656ea178bf.mp4", + "tMs": 412693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140235_f9c59834-5dfc-43ca-a1b3-78ce8acea9a5.mp4", + "tMs": 412695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140245_603b81f4-e80b-4511-865c-546e4522ce4d.mp4", + "tMs": 422683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140245_09855ab9-0d4d-4fde-8e94-47ed6d87a869.mp4", + "tMs": 422685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140245_782c3892-cebd-48cb-8e90-b816e295a4b3.mp4", + "tMs": 422691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140245_e505ea13-e787-4a94-a3f1-e106d6e04617.mp4", + "tMs": 422693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140255_a4a078b2-0eb4-452f-befe-7c75666ea452.mp4", + "tMs": 432679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140255_5829e532-530f-4b79-914f-ad0bc6a3c6fb.mp4", + "tMs": 432684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140255_2bf24577-db5e-4357-af6e-47a7dc0d202a.mp4", + "tMs": 432696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140255_dfd194b3-0cce-4e17-80fb-4d98b60201ba.mp4", + "tMs": 432707 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140305_89dcd263-cca8-4dfa-bf39-f5498c079bf6.mp4", + "tMs": 442679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140305_3911c4b6-5aee-42de-8b19-d9ee9862ba45.mp4", + "tMs": 442688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140305_be4e0a37-96b8-43e4-9d95-60dec3af1ecc.mp4", + "tMs": 442691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140305_b8447df2-5002-4844-9710-d1974ff99343.mp4", + "tMs": 442704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140315_7deee28d-8ffd-4db8-a6ea-9ca6a1ba1a7d.mp4", + "tMs": 452678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140315_e1c08762-ff87-4524-985a-9ae826185193.mp4", + "tMs": 452685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140315_58e742f6-9d2c-480d-ace0-773dabaf0ca4.mp4", + "tMs": 452690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140315_b875b4da-f86f-4e95-b03e-063085e3918d.mp4", + "tMs": 452696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140325_7ef3626a-cb65-441b-84a4-0e770714dc93.mp4", + "tMs": 462679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140325_9362483d-10aa-4f35-8bc7-d52f562fcad8.mp4", + "tMs": 462683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140325_e00165cd-05d8-478f-8729-f1d2dbbfb6df.mp4", + "tMs": 462689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140325_73b9b8ec-af72-46da-a045-a6053c89171b.mp4", + "tMs": 462701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140335_e9f1df62-9087-4c28-90f8-1664f49c524a.mp4", + "tMs": 472679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140335_b64ad5cf-7b9f-416e-aaea-a2ded4347c29.mp4", + "tMs": 472683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140335_e63b1e96-7568-46c9-93dd-ba8d2e01f375.mp4", + "tMs": 472687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140335_492170b7-2bf1-4074-a9d5-175223dee1d4.mp4", + "tMs": 472697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140345_b08860f8-f19e-4f36-9110-8638fffed5a9.mp4", + "tMs": 482680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140345_acf97109-2634-4cdd-8fcc-602cbbe56014.mp4", + "tMs": 482681 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140345_007fc837-d3ae-4ad1-bdb7-df7c53eaf17d.mp4", + "tMs": 482687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140345_c3156180-2528-4a43-a380-ec02a80fd484.mp4", + "tMs": 482693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140355_360f331c-12f7-4233-b355-de7e0f84bd34.mp4", + "tMs": 492677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140355_fb3cb620-0f72-43c9-92a9-802b8ee595f6.mp4", + "tMs": 492685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140355_ef02e00a-b550-499e-965d-c6943a139f22.mp4", + "tMs": 492687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140355_1c1f00a4-50ee-42b2-8afa-19bb18ab0861.mp4", + "tMs": 492695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140405_8baf808e-a1e3-463a-996e-e5f5939d287d.mp4", + "tMs": 502678 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140405_f0e3e1a3-f560-4b4a-a791-77c6c808492c.mp4", + "tMs": 502694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140405_a344db5d-2b57-402a-a27e-256219a075b8.mp4", + "tMs": 502694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140405_1a2bb634-56d1-415a-9846-1d48999c9579.mp4", + "tMs": 502698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140415_7009a60f-dcc5-4d43-aa60-4938ed272b19.mp4", + "tMs": 512680 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140415_ea7ec5d4-428f-43c7-a90b-4519996a39ac.mp4", + "tMs": 512700 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140415_c773885a-143c-43b8-8637-2b3c82accf13.mp4", + "tMs": 512701 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140415_3d79095c-8b56-4eac-81ad-853250197197.mp4", + "tMs": 512701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140425_7364390a-fccd-4030-8de6-64bf649d11fe.mp4", + "tMs": 522679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140425_65e9a3b8-aa20-4375-9aca-d4b0451e7cf1.mp4", + "tMs": 522683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140425_ec93db84-58cf-4a69-832e-6ac0dd69c672.mp4", + "tMs": 522690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140425_217e3f17-b8cf-44ae-8024-3616738a4d80.mp4", + "tMs": 522693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140435_2f92db6d-31ff-41bd-a90b-2981a516baad.mp4", + "tMs": 532679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140435_dbd41872-6e93-4d5d-8a75-1306d792340b.mp4", + "tMs": 532682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140435_5dbdc2d3-b996-41a0-a544-431138fdc640.mp4", + "tMs": 532689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140435_ce98ab6d-bd02-4527-ab7f-29f2ec16e318.mp4", + "tMs": 532693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140445_f4510624-5490-4ca3-9356-281a92c90b25.mp4", + "tMs": 542679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140445_22f68114-ff64-48ea-954d-a12ee0dce44c.mp4", + "tMs": 542684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140445_fe7fffca-0612-44d9-a651-1f547a01b3f6.mp4", + "tMs": 542694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140445_bef40249-9960-4a61-bbc4-a31b78ba6ea6.mp4", + "tMs": 542694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140455_9c6f276c-e129-4a1c-9361-2bf06953ffe1.mp4", + "tMs": 552681 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140455_f4f6a6b5-afb8-42ce-bb39-5b494af3e9e8.mp4", + "tMs": 552686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140455_37433777-bd6f-4608-9994-63bf808f8508.mp4", + "tMs": 552686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140455_8b085f31-ec84-4465-b1a3-3fc7a55eefb3.mp4", + "tMs": 552694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140505_336b8c8b-1485-43fd-8055-b43029ccaeae.mp4", + "tMs": 562679 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140505_435b39ca-45f6-430f-ae39-684d04cc642d.mp4", + "tMs": 562680 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140505_f7637213-e8a6-4d49-a96c-0e162018835d.mp4", + "tMs": 562684 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140505_a43b67d8-ed5f-4571-90d9-6ef6fb72ce2a.mp4", + "tMs": 562692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140515_aaa30416-2bd5-49a1-b75b-f62c25a0c332.mp4", + "tMs": 572678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140515_92a68bdc-b362-4184-836d-3efeb27dc28c.mp4", + "tMs": 572683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140515_084406ae-627f-4d3b-bcf4-1c697fa8105b.mp4", + "tMs": 572692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140515_2f1cad38-cea5-49fb-b0b2-79b457d4ac7f.mp4", + "tMs": 572695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140525_654c8910-93ff-46fb-b7cf-83aa46c8084d.mp4", + "tMs": 582683 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140525_77541e83-b957-4631-bb49-384a37f8a157.mp4", + "tMs": 582683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140525_51ecd958-67fe-4a3e-a641-420d6f6b38eb.mp4", + "tMs": 582687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140525_f98ec198-79ec-4601-b75b-f399ba94800c.mp4", + "tMs": 582695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140535_7b3f8170-71a1-4f86-83c9-f78283707607.mp4", + "tMs": 592678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140535_380e68ee-07ee-4927-9ac8-7a07ee8405fc.mp4", + "tMs": 592684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140535_76e97dd5-d584-49b0-aad6-f2adacf819cf.mp4", + "tMs": 592690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140535_55045c63-0348-4618-88c7-f507d3dc4bdc.mp4", + "tMs": 592696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140545_d9fc4def-0458-452d-bd7a-7f376177629e.mp4", + "tMs": 602679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140545_4967d8a8-adbd-4412-be5f-8d8d075a568f.mp4", + "tMs": 602683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140545_b71979dc-1d33-423e-b2b6-da2a0c3b1b85.mp4", + "tMs": 602689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140545_3944ba16-c074-4bcb-8fa3-81e416d41db6.mp4", + "tMs": 602697 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 0, + "assets": { + "by_state_reason": {}, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 0, + "ready_asset_bytes_max": 0, + "detections_total": 2529, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2529 + } + }, + "finished_at": "2026-11-08T19:05:51.038Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-disabled-run2.json b/tests/performance/observations/results/2026-11-08-x2-disabled-run2.json new file mode 100644 index 000000000..e346b95ae --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-disabled-run2.json @@ -0,0 +1,8680 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "disabled", + "status": "completed", + "started_at": "2026-11-08T19:27:38.477Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 2.75, + 3.15, + 4.43 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "405055d8456e5a351ce4dc41ae15057c8885b2ab150071a9a27c201cf6e8e4d9", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": {}, + "config_extra": null, + "system_settings": {} + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-bammbY", + "detector": { + "inferences_in_window": 2408, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 2408, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 0, + "dropped": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 0, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 0, + "high_water_items": 0, + "high_water_bytes": 0, + "camera_high_water_items": 0, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 33554432, + "camera_bytes": 33554432, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 0, + "inline": 0 + }, + "discard_pending_bytes_max": 0, + "backlog_items_max": 0, + "backlog_bytes_max": 0, + "drop_ring_depth_max": 0, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0, + "queue_allocated_end": 0, + "worker_running_end": 0, + "retained_bytes_by_camera_end": {}, + "budget_bytes_by_camera_end": {}, + "history_observations_end": 0, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 0, + "sum_seconds": 0, + "mean_seconds": null, + "max_seconds": 0, + "p50_upper_seconds": "+Inf", + "p99_upper_seconds": "+Inf", + "p999_upper_seconds": "+Inf", + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 0 + }, + { + "le": 0.00001, + "count": 0 + }, + { + "le": 0.00005, + "count": 0 + }, + { + "le": 0.0001, + "count": 0 + }, + { + "le": 0.00025, + "count": 0 + }, + { + "le": 0.0005, + "count": 0 + }, + { + "le": 0.001, + "count": 0 + }, + { + "le": 0.01, + "count": 0 + }, + { + "le": "+Inf", + "count": 0 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "window_unix": { + "start": 1794166097, + "end": 1794166699 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9973.947, + "p50": 10000.148, + "p95": 10009.659, + "p99": 10020.136, + "max": 10024.894, + "mean": 10000.076 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -26.053, + "p50": 0.148, + "p95": 9.659, + "p99": 20.136, + "max": 24.894, + "mean": 0.076 + }, + "db_probe_ms": { + "count": 2319, + "min": 6.803, + "p50": 8.184, + "p95": 12.744, + "p99": 16.59, + "max": 24.48, + "mean": 8.828 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9973.947, + "p50": 10000.463, + "p95": 10011.982, + "p99": 10022.388, + "max": 10024.894, + "mean": 10000.004 + }, + "Load1": { + "count": 60, + "min": 9978.472, + "p50": 9999.8, + "p95": 10004.902, + "p99": 10014.101, + "max": 10024.437, + "mean": 10000.005 + }, + "Load2": { + "count": 60, + "min": 9982.502, + "p50": 10000.084, + "p95": 10012.638, + "p99": 10016.828, + "max": 10016.916, + "mean": 10000.2 + }, + "Load3": { + "count": 60, + "min": 9977.643, + "p50": 10000.202, + "p95": 10009.659, + "p99": 10017.223, + "max": 10018.563, + "mean": 10000.096 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032217280, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.2, + "mean": 15.101 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 599.852, + "cpu_percent_one_core": { + "count": 520, + "min": 115.161, + "p50": 139.932, + "p95": 193.649, + "p99": 205.19, + "max": 217.306, + "mean": 150.089 + }, + "cpu_percent_one_core_avg": 150.452, + "rss_kib": { + "count": 521, + "min": 825748, + "p50": 829996, + "p95": 854364, + "p99": 854808, + "max": 862876, + "mean": 836888.23 + }, + "rss_high_water_kib": 872948, + "disk_write_bytes": 1233571840, + "disk_write_bytes_per_sec": 2056461.872, + "threads_max": 34, + "fds_max": 37 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.131, + "processes": 5 + }, + "storage_bytes_end": 1086895166, + "loadavg_at_end": [ + 2.79, + 3.02, + 3.72 + ] + }, + "window": { + "start": "2026-11-08T19:28:17.658Z", + "end": "2026-11-08T19:38:18.808Z", + "seconds": 601.15 + }, + "samples": { + "proc": [ + { + "tMs": 16, + "cpuTicks": 4503, + "rssKiB": 848612, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 60559360 + }, + { + "tMs": 1184, + "cpuTicks": 4650, + "rssKiB": 825748, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 62586880 + }, + { + "tMs": 2356, + "cpuTicks": 4813, + "rssKiB": 826108, + "peakRssKiB": 866344, + "threads": 34, + "fds": 37, + "writeBytes": 64450560 + }, + { + "tMs": 3487, + "cpuTicks": 4987, + "rssKiB": 826352, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 68030464 + }, + { + "tMs": 4632, + "cpuTicks": 5146, + "rssKiB": 826512, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 69877760 + }, + { + "tMs": 5821, + "cpuTicks": 5354, + "rssKiB": 851096, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 73072640 + }, + { + "tMs": 6983, + "cpuTicks": 5569, + "rssKiB": 851156, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 74907648 + }, + { + "tMs": 8146, + "cpuTicks": 5758, + "rssKiB": 827064, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 77168640 + }, + { + "tMs": 9288, + "cpuTicks": 5916, + "rssKiB": 827432, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 79056896 + }, + { + "tMs": 10417, + "cpuTicks": 6066, + "rssKiB": 827780, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 80900096 + }, + { + "tMs": 11562, + "cpuTicks": 6212, + "rssKiB": 827788, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 82776064 + }, + { + "tMs": 12706, + "cpuTicks": 6366, + "rssKiB": 851988, + "peakRssKiB": 868812, + "threads": 34, + "fds": 37, + "writeBytes": 87728128 + }, + { + "tMs": 13906, + "cpuTicks": 6595, + "rssKiB": 851992, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 89571328 + }, + { + "tMs": 15061, + "cpuTicks": 6785, + "rssKiB": 828120, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 91668480 + }, + { + "tMs": 16197, + "cpuTicks": 6942, + "rssKiB": 828144, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 93536256 + }, + { + "tMs": 17328, + "cpuTicks": 7098, + "rssKiB": 828156, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 95383552 + }, + { + "tMs": 18449, + "cpuTicks": 7245, + "rssKiB": 828164, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 97230848 + }, + { + "tMs": 19567, + "cpuTicks": 7400, + "rssKiB": 828168, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 99115008 + }, + { + "tMs": 20698, + "cpuTicks": 7553, + "rssKiB": 862876, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 101367808 + }, + { + "tMs": 21930, + "cpuTicks": 7800, + "rssKiB": 852072, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 104054784 + }, + { + "tMs": 23071, + "cpuTicks": 7976, + "rssKiB": 827900, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 108077056 + }, + { + "tMs": 24215, + "cpuTicks": 8115, + "rssKiB": 827900, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 109998080 + }, + { + "tMs": 25350, + "cpuTicks": 8266, + "rssKiB": 827920, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 111886336 + }, + { + "tMs": 26488, + "cpuTicks": 8397, + "rssKiB": 827920, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 113750016 + }, + { + "tMs": 27621, + "cpuTicks": 8540, + "rssKiB": 827920, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 115621888 + }, + { + "tMs": 28796, + "cpuTicks": 8734, + "rssKiB": 852420, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 118759424 + }, + { + "tMs": 29932, + "cpuTicks": 8933, + "rssKiB": 853144, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 120569856 + }, + { + "tMs": 31097, + "cpuTicks": 9106, + "rssKiB": 829008, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 122695680 + }, + { + "tMs": 32244, + "cpuTicks": 9253, + "rssKiB": 829008, + "peakRssKiB": 868812, + "threads": 34, + "fds": 36, + "writeBytes": 124628992 + }, + { + "tMs": 33395, + "cpuTicks": 9396, + "rssKiB": 829332, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 128266240 + }, + { + "tMs": 34531, + "cpuTicks": 9551, + "rssKiB": 829332, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 130117632 + }, + { + "tMs": 35683, + "cpuTicks": 9705, + "rssKiB": 849048, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 133033984 + }, + { + "tMs": 36865, + "cpuTicks": 9921, + "rssKiB": 853512, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 135217152 + }, + { + "tMs": 38031, + "cpuTicks": 10121, + "rssKiB": 829340, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 137297920 + }, + { + "tMs": 39178, + "cpuTicks": 10279, + "rssKiB": 829340, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 139165696 + }, + { + "tMs": 40325, + "cpuTicks": 10426, + "rssKiB": 829380, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 141291520 + }, + { + "tMs": 41475, + "cpuTicks": 10559, + "rssKiB": 829380, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 143200256 + }, + { + "tMs": 42616, + "cpuTicks": 10707, + "rssKiB": 829380, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 145084416 + }, + { + "tMs": 43814, + "cpuTicks": 10907, + "rssKiB": 853552, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 150007808 + }, + { + "tMs": 45013, + "cpuTicks": 11130, + "rssKiB": 853552, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 151887872 + }, + { + "tMs": 46154, + "cpuTicks": 11285, + "rssKiB": 829412, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 154005504 + }, + { + "tMs": 47298, + "cpuTicks": 11437, + "rssKiB": 829412, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 155910144 + }, + { + "tMs": 48446, + "cpuTicks": 11583, + "rssKiB": 829412, + "peakRssKiB": 870032, + "threads": 34, + "fds": 36, + "writeBytes": 157773824 + }, + { + "tMs": 49585, + "cpuTicks": 11723, + "rssKiB": 829412, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 159674368 + }, + { + "tMs": 50745, + "cpuTicks": 11907, + "rssKiB": 853636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 161972224 + }, + { + "tMs": 51931, + "cpuTicks": 12143, + "rssKiB": 853640, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 164667392 + }, + { + "tMs": 53084, + "cpuTicks": 12352, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 168509440 + }, + { + "tMs": 54211, + "cpuTicks": 12498, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 170393600 + }, + { + "tMs": 55324, + "cpuTicks": 12656, + "rssKiB": 829144, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 172544000 + }, + { + "tMs": 56449, + "cpuTicks": 12806, + "rssKiB": 829144, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 174391296 + }, + { + "tMs": 57572, + "cpuTicks": 12957, + "rssKiB": 829144, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 176226304 + }, + { + "tMs": 58737, + "cpuTicks": 13135, + "rssKiB": 853640, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 179380224 + }, + { + "tMs": 59912, + "cpuTicks": 13359, + "rssKiB": 853640, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 181194752 + }, + { + "tMs": 61077, + "cpuTicks": 13590, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 183255040 + }, + { + "tMs": 62205, + "cpuTicks": 13746, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 185143296 + }, + { + "tMs": 63344, + "cpuTicks": 13916, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 188764160 + }, + { + "tMs": 64470, + "cpuTicks": 14072, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 190599168 + }, + { + "tMs": 65591, + "cpuTicks": 14219, + "rssKiB": 829488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 192491520 + }, + { + "tMs": 66728, + "cpuTicks": 14390, + "rssKiB": 853660, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 195690496 + }, + { + "tMs": 67908, + "cpuTicks": 14616, + "rssKiB": 853660, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 197578752 + }, + { + "tMs": 69080, + "cpuTicks": 14829, + "rssKiB": 829488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 199720960 + }, + { + "tMs": 70227, + "cpuTicks": 14972, + "rssKiB": 829168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 201609216 + }, + { + "tMs": 71374, + "cpuTicks": 15120, + "rssKiB": 829168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 203743232 + }, + { + "tMs": 72526, + "cpuTicks": 15273, + "rssKiB": 829168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 205639680 + }, + { + "tMs": 73667, + "cpuTicks": 15427, + "rssKiB": 829168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 209276928 + }, + { + "tMs": 74875, + "cpuTicks": 15651, + "rssKiB": 853340, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 212484096 + }, + { + "tMs": 76044, + "cpuTicks": 15891, + "rssKiB": 853356, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 214425600 + }, + { + "tMs": 77178, + "cpuTicks": 16044, + "rssKiB": 829184, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 216477696 + }, + { + "tMs": 78293, + "cpuTicks": 16197, + "rssKiB": 829184, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 218329088 + }, + { + "tMs": 79431, + "cpuTicks": 16350, + "rssKiB": 829184, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 220192768 + }, + { + "tMs": 80552, + "cpuTicks": 16506, + "rssKiB": 829200, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 222064640 + }, + { + "tMs": 81670, + "cpuTicks": 16652, + "rssKiB": 829200, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 223850496 + }, + { + "tMs": 82862, + "cpuTicks": 16877, + "rssKiB": 853048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 228757504 + }, + { + "tMs": 84011, + "cpuTicks": 17084, + "rssKiB": 853048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 230690816 + }, + { + "tMs": 85146, + "cpuTicks": 17243, + "rssKiB": 828876, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 232726528 + }, + { + "tMs": 86271, + "cpuTicks": 17387, + "rssKiB": 828884, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 234610688 + }, + { + "tMs": 87420, + "cpuTicks": 17550, + "rssKiB": 828884, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 236752896 + }, + { + "tMs": 88588, + "cpuTicks": 17697, + "rssKiB": 829204, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 238628864 + }, + { + "tMs": 89756, + "cpuTicks": 17891, + "rssKiB": 853376, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 241774592 + }, + { + "tMs": 90916, + "cpuTicks": 18100, + "rssKiB": 853412, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 243683328 + }, + { + "tMs": 92073, + "cpuTicks": 18288, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 245829632 + }, + { + "tMs": 93218, + "cpuTicks": 18434, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 249446400 + }, + { + "tMs": 94353, + "cpuTicks": 18587, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 251330560 + }, + { + "tMs": 95488, + "cpuTicks": 18760, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 253251584 + }, + { + "tMs": 96623, + "cpuTicks": 18899, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 255111168 + }, + { + "tMs": 97817, + "cpuTicks": 19113, + "rssKiB": 853416, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 258281472 + }, + { + "tMs": 98992, + "cpuTicks": 19341, + "rssKiB": 853316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 260165632 + }, + { + "tMs": 100135, + "cpuTicks": 19523, + "rssKiB": 829372, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 262225920 + }, + { + "tMs": 101283, + "cpuTicks": 19674, + "rssKiB": 829380, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 264118272 + }, + { + "tMs": 102420, + "cpuTicks": 19831, + "rssKiB": 829156, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 266031104 + }, + { + "tMs": 103576, + "cpuTicks": 19982, + "rssKiB": 829156, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 269991936 + }, + { + "tMs": 104765, + "cpuTicks": 20160, + "rssKiB": 853328, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 273199104 + }, + { + "tMs": 105934, + "cpuTicks": 20376, + "rssKiB": 853340, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 275075072 + }, + { + "tMs": 107096, + "cpuTicks": 20555, + "rssKiB": 829168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 277192704 + }, + { + "tMs": 108230, + "cpuTicks": 20706, + "rssKiB": 829168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 279085056 + }, + { + "tMs": 109363, + "cpuTicks": 20858, + "rssKiB": 828940, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 280969216 + }, + { + "tMs": 110508, + "cpuTicks": 21000, + "rssKiB": 828944, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 282857472 + }, + { + "tMs": 111657, + "cpuTicks": 21154, + "rssKiB": 828944, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 284651520 + }, + { + "tMs": 112843, + "cpuTicks": 21370, + "rssKiB": 853344, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 289546240 + }, + { + "tMs": 114005, + "cpuTicks": 21586, + "rssKiB": 853344, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 291438592 + }, + { + "tMs": 115154, + "cpuTicks": 21756, + "rssKiB": 829172, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 293560320 + }, + { + "tMs": 116301, + "cpuTicks": 21903, + "rssKiB": 829172, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 295456768 + }, + { + "tMs": 117444, + "cpuTicks": 22053, + "rssKiB": 829492, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 297345024 + }, + { + "tMs": 118596, + "cpuTicks": 22202, + "rssKiB": 829720, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 299503616 + }, + { + "tMs": 119771, + "cpuTicks": 22386, + "rssKiB": 853664, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 302620672 + }, + { + "tMs": 120928, + "cpuTicks": 22591, + "rssKiB": 853684, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 304467968 + }, + { + "tMs": 122079, + "cpuTicks": 22783, + "rssKiB": 829512, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 306569216 + }, + { + "tMs": 123218, + "cpuTicks": 22932, + "rssKiB": 829512, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 310173696 + }, + { + "tMs": 124342, + "cpuTicks": 23082, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 312045568 + }, + { + "tMs": 125467, + "cpuTicks": 23229, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 313958400 + }, + { + "tMs": 126588, + "cpuTicks": 23371, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 315822080 + }, + { + "tMs": 127744, + "cpuTicks": 23548, + "rssKiB": 854008, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 319012864 + }, + { + "tMs": 128922, + "cpuTicks": 23779, + "rssKiB": 854008, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 320921600 + }, + { + "tMs": 130071, + "cpuTicks": 23986, + "rssKiB": 854008, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 322830336 + }, + { + "tMs": 131226, + "cpuTicks": 24159, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 324964352 + }, + { + "tMs": 132369, + "cpuTicks": 24308, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 326942720 + }, + { + "tMs": 133509, + "cpuTicks": 24475, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 330924032 + }, + { + "tMs": 134645, + "cpuTicks": 24617, + "rssKiB": 829836, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 332783616 + }, + { + "tMs": 135845, + "cpuTicks": 24833, + "rssKiB": 853692, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 336023552 + }, + { + "tMs": 136995, + "cpuTicks": 25043, + "rssKiB": 853692, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 337895424 + }, + { + "tMs": 138141, + "cpuTicks": 25196, + "rssKiB": 829520, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 339972096 + }, + { + "tMs": 139289, + "cpuTicks": 25366, + "rssKiB": 829196, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 341856256 + }, + { + "tMs": 140437, + "cpuTicks": 25503, + "rssKiB": 829196, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 343748608 + }, + { + "tMs": 141570, + "cpuTicks": 25650, + "rssKiB": 829196, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 345550848 + }, + { + "tMs": 142717, + "cpuTicks": 25810, + "rssKiB": 853368, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 350498816 + }, + { + "tMs": 143892, + "cpuTicks": 26027, + "rssKiB": 853368, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 352399360 + }, + { + "tMs": 145056, + "cpuTicks": 26241, + "rssKiB": 829196, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 354623488 + }, + { + "tMs": 146195, + "cpuTicks": 26383, + "rssKiB": 829192, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 356564992 + }, + { + "tMs": 147341, + "cpuTicks": 26522, + "rssKiB": 829192, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 358793216 + }, + { + "tMs": 148484, + "cpuTicks": 26656, + "rssKiB": 829192, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 360652800 + }, + { + "tMs": 149625, + "cpuTicks": 26810, + "rssKiB": 829192, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 362549248 + }, + { + "tMs": 150821, + "cpuTicks": 27023, + "rssKiB": 853400, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 365735936 + }, + { + "tMs": 151994, + "cpuTicks": 27241, + "rssKiB": 853400, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 367628288 + }, + { + "tMs": 153142, + "cpuTicks": 27409, + "rssKiB": 829548, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 371585024 + }, + { + "tMs": 154267, + "cpuTicks": 27561, + "rssKiB": 829548, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 373477376 + }, + { + "tMs": 155391, + "cpuTicks": 27720, + "rssKiB": 829560, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 375447552 + }, + { + "tMs": 156519, + "cpuTicks": 27864, + "rssKiB": 829560, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 377311232 + }, + { + "tMs": 157657, + "cpuTicks": 28012, + "rssKiB": 829560, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 379187200 + }, + { + "tMs": 158846, + "cpuTicks": 28227, + "rssKiB": 853732, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 382377984 + }, + { + "tMs": 160004, + "cpuTicks": 28427, + "rssKiB": 853732, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 384217088 + }, + { + "tMs": 161133, + "cpuTicks": 28586, + "rssKiB": 829560, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 386342912 + }, + { + "tMs": 162265, + "cpuTicks": 28717, + "rssKiB": 829560, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 388558848 + }, + { + "tMs": 163412, + "cpuTicks": 28865, + "rssKiB": 829560, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 392228864 + }, + { + "tMs": 164559, + "cpuTicks": 29018, + "rssKiB": 829240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 394125312 + }, + { + "tMs": 165730, + "cpuTicks": 29183, + "rssKiB": 853408, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 397377536 + }, + { + "tMs": 166903, + "cpuTicks": 29394, + "rssKiB": 853404, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 399261696 + }, + { + "tMs": 168061, + "cpuTicks": 29592, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 401354752 + }, + { + "tMs": 169196, + "cpuTicks": 29740, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 403234816 + }, + { + "tMs": 170345, + "cpuTicks": 29888, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 405131264 + }, + { + "tMs": 171480, + "cpuTicks": 30044, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 406913024 + }, + { + "tMs": 172614, + "cpuTicks": 30204, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 408780800 + }, + { + "tMs": 173777, + "cpuTicks": 30397, + "rssKiB": 853404, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 413773824 + }, + { + "tMs": 174934, + "cpuTicks": 30616, + "rssKiB": 853404, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 415670272 + }, + { + "tMs": 176088, + "cpuTicks": 30811, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 417824768 + }, + { + "tMs": 177234, + "cpuTicks": 30954, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 420073472 + }, + { + "tMs": 178357, + "cpuTicks": 31117, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 421900288 + }, + { + "tMs": 179489, + "cpuTicks": 31265, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 423739392 + }, + { + "tMs": 180625, + "cpuTicks": 31421, + "rssKiB": 829232, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 425594880 + }, + { + "tMs": 181836, + "cpuTicks": 31654, + "rssKiB": 853316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 428740608 + }, + { + "tMs": 182997, + "cpuTicks": 31867, + "rssKiB": 853636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 432484352 + }, + { + "tMs": 184137, + "cpuTicks": 32039, + "rssKiB": 829464, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 434585600 + }, + { + "tMs": 185267, + "cpuTicks": 32186, + "rssKiB": 829464, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 436469760 + }, + { + "tMs": 186406, + "cpuTicks": 32346, + "rssKiB": 829464, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 438390784 + }, + { + "tMs": 187585, + "cpuTicks": 32494, + "rssKiB": 829464, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 440303616 + }, + { + "tMs": 188731, + "cpuTicks": 32661, + "rssKiB": 853636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 443531264 + }, + { + "tMs": 189898, + "cpuTicks": 32885, + "rssKiB": 853636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 445444096 + }, + { + "tMs": 191059, + "cpuTicks": 33095, + "rssKiB": 829476, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 447631360 + }, + { + "tMs": 192180, + "cpuTicks": 33246, + "rssKiB": 829476, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 449937408 + }, + { + "tMs": 193329, + "cpuTicks": 33404, + "rssKiB": 829476, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 453550080 + }, + { + "tMs": 194455, + "cpuTicks": 33559, + "rssKiB": 829028, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 455454720 + }, + { + "tMs": 195602, + "cpuTicks": 33721, + "rssKiB": 829256, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 457355264 + }, + { + "tMs": 196797, + "cpuTicks": 33936, + "rssKiB": 853428, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 460546048 + }, + { + "tMs": 197958, + "cpuTicks": 34145, + "rssKiB": 853428, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 462462976 + }, + { + "tMs": 199119, + "cpuTicks": 34314, + "rssKiB": 829256, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 464580608 + }, + { + "tMs": 200253, + "cpuTicks": 34457, + "rssKiB": 829032, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 466440192 + }, + { + "tMs": 201397, + "cpuTicks": 34601, + "rssKiB": 829256, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 468234240 + }, + { + "tMs": 202515, + "cpuTicks": 34752, + "rssKiB": 829032, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 470126592 + }, + { + "tMs": 203633, + "cpuTicks": 34901, + "rssKiB": 828932, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 473772032 + }, + { + "tMs": 204827, + "cpuTicks": 35111, + "rssKiB": 853104, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 476983296 + }, + { + "tMs": 205990, + "cpuTicks": 35326, + "rssKiB": 853104, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 478916608 + }, + { + "tMs": 207131, + "cpuTicks": 35490, + "rssKiB": 828932, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 481357824 + }, + { + "tMs": 208264, + "cpuTicks": 35638, + "rssKiB": 828932, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 483225600 + }, + { + "tMs": 209393, + "cpuTicks": 35793, + "rssKiB": 829256, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 485068800 + }, + { + "tMs": 210531, + "cpuTicks": 35952, + "rssKiB": 829268, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 486993920 + }, + { + "tMs": 211674, + "cpuTicks": 36099, + "rssKiB": 829484, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 489529344 + }, + { + "tMs": 212869, + "cpuTicks": 36326, + "rssKiB": 853764, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 493809664 + }, + { + "tMs": 214028, + "cpuTicks": 36525, + "rssKiB": 853764, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 495706112 + }, + { + "tMs": 215154, + "cpuTicks": 36687, + "rssKiB": 829596, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 497881088 + }, + { + "tMs": 216284, + "cpuTicks": 36836, + "rssKiB": 829612, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 499761152 + }, + { + "tMs": 217406, + "cpuTicks": 36990, + "rssKiB": 829612, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 501600256 + }, + { + "tMs": 218533, + "cpuTicks": 37139, + "rssKiB": 829612, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 503508992 + }, + { + "tMs": 219658, + "cpuTicks": 37287, + "rssKiB": 829612, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 505409536 + }, + { + "tMs": 220824, + "cpuTicks": 37489, + "rssKiB": 853784, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 508604416 + }, + { + "tMs": 222030, + "cpuTicks": 37717, + "rssKiB": 829612, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 511270912 + }, + { + "tMs": 223179, + "cpuTicks": 37856, + "rssKiB": 829384, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 514928640 + }, + { + "tMs": 224323, + "cpuTicks": 38007, + "rssKiB": 829384, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 516763648 + }, + { + "tMs": 225478, + "cpuTicks": 38168, + "rssKiB": 829384, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 518737920 + }, + { + "tMs": 226611, + "cpuTicks": 38321, + "rssKiB": 829384, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 520654848 + }, + { + "tMs": 227821, + "cpuTicks": 38534, + "rssKiB": 853556, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 523882496 + }, + { + "tMs": 228987, + "cpuTicks": 38764, + "rssKiB": 853556, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 525885440 + }, + { + "tMs": 230131, + "cpuTicks": 38917, + "rssKiB": 829708, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 528011264 + }, + { + "tMs": 231276, + "cpuTicks": 39074, + "rssKiB": 829708, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 529883136 + }, + { + "tMs": 232411, + "cpuTicks": 39233, + "rssKiB": 829708, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 531795968 + }, + { + "tMs": 233535, + "cpuTicks": 39388, + "rssKiB": 829708, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 535470080 + }, + { + "tMs": 234663, + "cpuTicks": 39539, + "rssKiB": 829708, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 537821184 + }, + { + "tMs": 235868, + "cpuTicks": 39763, + "rssKiB": 853888, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 541020160 + }, + { + "tMs": 237028, + "cpuTicks": 39974, + "rssKiB": 853888, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 542892032 + }, + { + "tMs": 238172, + "cpuTicks": 40138, + "rssKiB": 830040, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 545005568 + }, + { + "tMs": 239315, + "cpuTicks": 40287, + "rssKiB": 830040, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 546852864 + }, + { + "tMs": 240446, + "cpuTicks": 40436, + "rssKiB": 830048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 548708352 + }, + { + "tMs": 241578, + "cpuTicks": 40574, + "rssKiB": 830048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 550580224 + }, + { + "tMs": 242758, + "cpuTicks": 40772, + "rssKiB": 854220, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 555528192 + }, + { + "tMs": 243938, + "cpuTicks": 40994, + "rssKiB": 854220, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 557367296 + }, + { + "tMs": 245091, + "cpuTicks": 41188, + "rssKiB": 830048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 559513600 + }, + { + "tMs": 246253, + "cpuTicks": 41325, + "rssKiB": 830048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 561455104 + }, + { + "tMs": 247393, + "cpuTicks": 41486, + "rssKiB": 830048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 563302400 + }, + { + "tMs": 248532, + "cpuTicks": 41626, + "rssKiB": 830048, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 565207040 + }, + { + "tMs": 249667, + "cpuTicks": 41774, + "rssKiB": 829724, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 567103488 + }, + { + "tMs": 250858, + "cpuTicks": 41992, + "rssKiB": 853896, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 570716160 + }, + { + "tMs": 252001, + "cpuTicks": 42191, + "rssKiB": 853896, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 572612608 + }, + { + "tMs": 253131, + "cpuTicks": 42348, + "rssKiB": 829724, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 576561152 + }, + { + "tMs": 254257, + "cpuTicks": 42502, + "rssKiB": 829724, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 578457600 + }, + { + "tMs": 255382, + "cpuTicks": 42642, + "rssKiB": 829720, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 580358144 + }, + { + "tMs": 256515, + "cpuTicks": 42778, + "rssKiB": 829720, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 582266880 + }, + { + "tMs": 257650, + "cpuTicks": 42919, + "rssKiB": 829720, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 584192000 + }, + { + "tMs": 258845, + "cpuTicks": 43132, + "rssKiB": 853896, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 587366400 + }, + { + "tMs": 259996, + "cpuTicks": 43323, + "rssKiB": 853880, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 589283328 + }, + { + "tMs": 261156, + "cpuTicks": 43505, + "rssKiB": 829708, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 591396864 + }, + { + "tMs": 262314, + "cpuTicks": 43654, + "rssKiB": 829692, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 593293312 + }, + { + "tMs": 263451, + "cpuTicks": 43802, + "rssKiB": 829360, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 597442560 + }, + { + "tMs": 264583, + "cpuTicks": 43948, + "rssKiB": 829360, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 599379968 + }, + { + "tMs": 265771, + "cpuTicks": 44145, + "rssKiB": 853872, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 602599424 + }, + { + "tMs": 266933, + "cpuTicks": 44374, + "rssKiB": 853872, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 604459008 + }, + { + "tMs": 268086, + "cpuTicks": 44588, + "rssKiB": 829700, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 606605312 + }, + { + "tMs": 269212, + "cpuTicks": 44751, + "rssKiB": 829700, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 608460800 + }, + { + "tMs": 270331, + "cpuTicks": 44901, + "rssKiB": 829848, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 610361344 + }, + { + "tMs": 271457, + "cpuTicks": 45055, + "rssKiB": 829848, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 612274176 + }, + { + "tMs": 272571, + "cpuTicks": 45219, + "rssKiB": 829848, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 614174720 + }, + { + "tMs": 273737, + "cpuTicks": 45396, + "rssKiB": 854020, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 619155456 + }, + { + "tMs": 274939, + "cpuTicks": 45651, + "rssKiB": 854020, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 621068288 + }, + { + "tMs": 276096, + "cpuTicks": 45830, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 623194112 + }, + { + "tMs": 277245, + "cpuTicks": 45988, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 625520640 + }, + { + "tMs": 278384, + "cpuTicks": 46142, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 627408896 + }, + { + "tMs": 279539, + "cpuTicks": 46301, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 629297152 + }, + { + "tMs": 280674, + "cpuTicks": 46466, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 631906304 + }, + { + "tMs": 281900, + "cpuTicks": 46709, + "rssKiB": 853740, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 634449920 + }, + { + "tMs": 283077, + "cpuTicks": 46915, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 638402560 + }, + { + "tMs": 284276, + "cpuTicks": 47071, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 640286720 + }, + { + "tMs": 285454, + "cpuTicks": 47234, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 642195456 + }, + { + "tMs": 286600, + "cpuTicks": 47379, + "rssKiB": 829564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 644120576 + }, + { + "tMs": 287766, + "cpuTicks": 47566, + "rssKiB": 853416, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 647405568 + }, + { + "tMs": 288936, + "cpuTicks": 47780, + "rssKiB": 853640, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 649306112 + }, + { + "tMs": 290104, + "cpuTicks": 47987, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 651567104 + }, + { + "tMs": 291230, + "cpuTicks": 48135, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 653381632 + }, + { + "tMs": 292346, + "cpuTicks": 48283, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 655695872 + }, + { + "tMs": 293476, + "cpuTicks": 48443, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 659390464 + }, + { + "tMs": 294594, + "cpuTicks": 48594, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 661262336 + }, + { + "tMs": 295773, + "cpuTicks": 48794, + "rssKiB": 853416, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 664535040 + }, + { + "tMs": 296915, + "cpuTicks": 48995, + "rssKiB": 853416, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 666419200 + }, + { + "tMs": 298075, + "cpuTicks": 49198, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 668626944 + }, + { + "tMs": 299204, + "cpuTicks": 49357, + "rssKiB": 829244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 670457856 + }, + { + "tMs": 300326, + "cpuTicks": 49509, + "rssKiB": 829468, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 672313344 + }, + { + "tMs": 301456, + "cpuTicks": 49661, + "rssKiB": 829252, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 674197504 + }, + { + "tMs": 302573, + "cpuTicks": 49803, + "rssKiB": 829572, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 676081664 + }, + { + "tMs": 303728, + "cpuTicks": 49977, + "rssKiB": 853744, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 681050112 + }, + { + "tMs": 304961, + "cpuTicks": 50243, + "rssKiB": 853744, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 682987520 + }, + { + "tMs": 306143, + "cpuTicks": 50434, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 685789184 + }, + { + "tMs": 307292, + "cpuTicks": 50586, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 687636480 + }, + { + "tMs": 308420, + "cpuTicks": 50744, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 689520640 + }, + { + "tMs": 309552, + "cpuTicks": 50891, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 691445760 + }, + { + "tMs": 310674, + "cpuTicks": 51049, + "rssKiB": 829600, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 693645312 + }, + { + "tMs": 311888, + "cpuTicks": 51297, + "rssKiB": 853772, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 696557568 + }, + { + "tMs": 313038, + "cpuTicks": 51504, + "rssKiB": 829600, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 700452864 + }, + { + "tMs": 314168, + "cpuTicks": 51651, + "rssKiB": 829600, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 702324736 + }, + { + "tMs": 315300, + "cpuTicks": 51790, + "rssKiB": 829600, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 704221184 + }, + { + "tMs": 316428, + "cpuTicks": 51945, + "rssKiB": 829604, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 706187264 + }, + { + "tMs": 317565, + "cpuTicks": 52106, + "rssKiB": 829604, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 708050944 + }, + { + "tMs": 318721, + "cpuTicks": 52278, + "rssKiB": 853776, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 711237632 + }, + { + "tMs": 319931, + "cpuTicks": 52534, + "rssKiB": 853776, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 713138176 + }, + { + "tMs": 321083, + "cpuTicks": 52745, + "rssKiB": 829604, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 715538432 + }, + { + "tMs": 322213, + "cpuTicks": 52900, + "rssKiB": 829604, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 717578240 + }, + { + "tMs": 323337, + "cpuTicks": 53056, + "rssKiB": 829604, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 721235968 + }, + { + "tMs": 324469, + "cpuTicks": 53212, + "rssKiB": 829280, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 723165184 + }, + { + "tMs": 325599, + "cpuTicks": 53403, + "rssKiB": 829280, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 725082112 + }, + { + "tMs": 326798, + "cpuTicks": 53603, + "rssKiB": 853776, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 728293376 + }, + { + "tMs": 327994, + "cpuTicks": 53863, + "rssKiB": 853776, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 730189824 + }, + { + "tMs": 329147, + "cpuTicks": 54021, + "rssKiB": 829604, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 732372992 + }, + { + "tMs": 330284, + "cpuTicks": 54174, + "rssKiB": 829676, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 734257152 + }, + { + "tMs": 331434, + "cpuTicks": 54322, + "rssKiB": 829680, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 736141312 + }, + { + "tMs": 332574, + "cpuTicks": 54473, + "rssKiB": 829680, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 737992704 + }, + { + "tMs": 333754, + "cpuTicks": 54653, + "rssKiB": 854176, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 743419904 + }, + { + "tMs": 334932, + "cpuTicks": 54865, + "rssKiB": 854176, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 745320448 + }, + { + "tMs": 336090, + "cpuTicks": 55054, + "rssKiB": 830012, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 747524096 + }, + { + "tMs": 337219, + "cpuTicks": 55205, + "rssKiB": 830012, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 749391872 + }, + { + "tMs": 338371, + "cpuTicks": 55349, + "rssKiB": 830012, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 751267840 + }, + { + "tMs": 339530, + "cpuTicks": 55483, + "rssKiB": 830012, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 753152000 + }, + { + "tMs": 340679, + "cpuTicks": 55627, + "rssKiB": 833348, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 756092928 + }, + { + "tMs": 341856, + "cpuTicks": 55842, + "rssKiB": 854184, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 758312960 + }, + { + "tMs": 343020, + "cpuTicks": 56058, + "rssKiB": 854184, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 762134528 + }, + { + "tMs": 344175, + "cpuTicks": 56209, + "rssKiB": 829648, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 764116992 + }, + { + "tMs": 345330, + "cpuTicks": 56363, + "rssKiB": 829648, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 766074880 + }, + { + "tMs": 346463, + "cpuTicks": 56506, + "rssKiB": 829648, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 767991808 + }, + { + "tMs": 347614, + "cpuTicks": 56654, + "rssKiB": 829648, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 769863680 + }, + { + "tMs": 348792, + "cpuTicks": 56845, + "rssKiB": 853764, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 773525504 + }, + { + "tMs": 349984, + "cpuTicks": 57083, + "rssKiB": 853988, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 775516160 + }, + { + "tMs": 351129, + "cpuTicks": 57244, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 777474048 + }, + { + "tMs": 352246, + "cpuTicks": 57388, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 779399168 + }, + { + "tMs": 353389, + "cpuTicks": 57547, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 783077376 + }, + { + "tMs": 354522, + "cpuTicks": 57704, + "rssKiB": 829592, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 784969728 + }, + { + "tMs": 355641, + "cpuTicks": 57840, + "rssKiB": 829916, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 786878464 + }, + { + "tMs": 356838, + "cpuTicks": 58058, + "rssKiB": 854088, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 790073344 + }, + { + "tMs": 357997, + "cpuTicks": 58278, + "rssKiB": 854088, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 791916544 + }, + { + "tMs": 359133, + "cpuTicks": 58431, + "rssKiB": 829916, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 794034176 + }, + { + "tMs": 360270, + "cpuTicks": 58576, + "rssKiB": 829916, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 795906048 + }, + { + "tMs": 361418, + "cpuTicks": 58728, + "rssKiB": 829916, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 797782016 + }, + { + "tMs": 362574, + "cpuTicks": 58883, + "rssKiB": 829916, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 799752192 + }, + { + "tMs": 363758, + "cpuTicks": 59064, + "rssKiB": 854088, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 805212160 + }, + { + "tMs": 364921, + "cpuTicks": 59280, + "rssKiB": 854088, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 807071744 + }, + { + "tMs": 366074, + "cpuTicks": 59453, + "rssKiB": 829576, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 809365504 + }, + { + "tMs": 367215, + "cpuTicks": 59588, + "rssKiB": 829576, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 811241472 + }, + { + "tMs": 368356, + "cpuTicks": 59726, + "rssKiB": 829576, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 813137920 + }, + { + "tMs": 369496, + "cpuTicks": 59876, + "rssKiB": 829576, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 815050752 + }, + { + "tMs": 370650, + "cpuTicks": 60034, + "rssKiB": 829540, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 816943104 + }, + { + "tMs": 371842, + "cpuTicks": 60248, + "rssKiB": 853712, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 820207616 + }, + { + "tMs": 373007, + "cpuTicks": 60460, + "rssKiB": 853712, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 823853056 + }, + { + "tMs": 374151, + "cpuTicks": 60649, + "rssKiB": 829540, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 825999360 + }, + { + "tMs": 375278, + "cpuTicks": 60790, + "rssKiB": 829540, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 828362752 + }, + { + "tMs": 376416, + "cpuTicks": 60935, + "rssKiB": 829544, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 830324736 + }, + { + "tMs": 377574, + "cpuTicks": 61079, + "rssKiB": 829544, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 832217088 + }, + { + "tMs": 378740, + "cpuTicks": 61247, + "rssKiB": 853716, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 835452928 + }, + { + "tMs": 379912, + "cpuTicks": 61461, + "rssKiB": 853716, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 837349376 + }, + { + "tMs": 381074, + "cpuTicks": 61646, + "rssKiB": 829076, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 839327744 + }, + { + "tMs": 382199, + "cpuTicks": 61794, + "rssKiB": 829076, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 841383936 + }, + { + "tMs": 383350, + "cpuTicks": 61944, + "rssKiB": 829076, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 845074432 + }, + { + "tMs": 384493, + "cpuTicks": 62090, + "rssKiB": 829400, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 846987264 + }, + { + "tMs": 385640, + "cpuTicks": 62252, + "rssKiB": 829400, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 848924672 + }, + { + "tMs": 386836, + "cpuTicks": 62467, + "rssKiB": 853892, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 852135936 + }, + { + "tMs": 388003, + "cpuTicks": 62702, + "rssKiB": 853892, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 854020096 + }, + { + "tMs": 389159, + "cpuTicks": 62881, + "rssKiB": 829720, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 856145920 + }, + { + "tMs": 390298, + "cpuTicks": 63028, + "rssKiB": 829744, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 858480640 + }, + { + "tMs": 391458, + "cpuTicks": 63172, + "rssKiB": 829744, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 860377088 + }, + { + "tMs": 392609, + "cpuTicks": 63334, + "rssKiB": 829744, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 862248960 + }, + { + "tMs": 393837, + "cpuTicks": 63551, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 867229696 + }, + { + "tMs": 394998, + "cpuTicks": 63760, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 869289984 + }, + { + "tMs": 396159, + "cpuTicks": 63924, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 871403520 + }, + { + "tMs": 397294, + "cpuTicks": 64071, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 873271296 + }, + { + "tMs": 398437, + "cpuTicks": 64223, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 875163648 + }, + { + "tMs": 399581, + "cpuTicks": 64375, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 877088768 + }, + { + "tMs": 400792, + "cpuTicks": 64573, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 880320512 + }, + { + "tMs": 401940, + "cpuTicks": 64778, + "rssKiB": 854564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 882253824 + }, + { + "tMs": 403096, + "cpuTicks": 64960, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 886710272 + }, + { + "tMs": 404244, + "cpuTicks": 65105, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 888602624 + }, + { + "tMs": 405396, + "cpuTicks": 65250, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 890527744 + }, + { + "tMs": 406535, + "cpuTicks": 65398, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 892411904 + }, + { + "tMs": 407664, + "cpuTicks": 65548, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 894320640 + }, + { + "tMs": 408873, + "cpuTicks": 65782, + "rssKiB": 854564, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 897515520 + }, + { + "tMs": 410039, + "cpuTicks": 66000, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 899657728 + }, + { + "tMs": 411186, + "cpuTicks": 66150, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 901541888 + }, + { + "tMs": 412323, + "cpuTicks": 66302, + "rssKiB": 830392, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 903434240 + }, + { + "tMs": 413461, + "cpuTicks": 66442, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 907096064 + }, + { + "tMs": 414600, + "cpuTicks": 66590, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 909004800 + }, + { + "tMs": 415812, + "cpuTicks": 66795, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 912265216 + }, + { + "tMs": 416996, + "cpuTicks": 67015, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 914112512 + }, + { + "tMs": 418153, + "cpuTicks": 67184, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 916750336 + }, + { + "tMs": 419300, + "cpuTicks": 67328, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 918577152 + }, + { + "tMs": 420455, + "cpuTicks": 67484, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 920428544 + }, + { + "tMs": 421628, + "cpuTicks": 67646, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 922370048 + }, + { + "tMs": 422819, + "cpuTicks": 67849, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 927350784 + }, + { + "tMs": 424004, + "cpuTicks": 68082, + "rssKiB": 854240, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 929304576 + }, + { + "tMs": 425168, + "cpuTicks": 68261, + "rssKiB": 830068, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 931586048 + }, + { + "tMs": 426315, + "cpuTicks": 68406, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 933560320 + }, + { + "tMs": 427463, + "cpuTicks": 68551, + "rssKiB": 829748, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 935473152 + }, + { + "tMs": 428611, + "cpuTicks": 68693, + "rssKiB": 829748, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 937496576 + }, + { + "tMs": 429805, + "cpuTicks": 68892, + "rssKiB": 853920, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 940716032 + }, + { + "tMs": 430968, + "cpuTicks": 69104, + "rssKiB": 853920, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 942624768 + }, + { + "tMs": 432134, + "cpuTicks": 69284, + "rssKiB": 829748, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 945307648 + }, + { + "tMs": 433285, + "cpuTicks": 69436, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 948981760 + }, + { + "tMs": 434423, + "cpuTicks": 69575, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 950845440 + }, + { + "tMs": 435552, + "cpuTicks": 69714, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 952778752 + }, + { + "tMs": 436682, + "cpuTicks": 69858, + "rssKiB": 848216, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 955703296 + }, + { + "tMs": 437898, + "cpuTicks": 70105, + "rssKiB": 854244, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 957947904 + }, + { + "tMs": 439080, + "cpuTicks": 70314, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 960110592 + }, + { + "tMs": 440218, + "cpuTicks": 70462, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 961986560 + }, + { + "tMs": 441336, + "cpuTicks": 70614, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 963858432 + }, + { + "tMs": 442480, + "cpuTicks": 70767, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 965726208 + }, + { + "tMs": 443636, + "cpuTicks": 70919, + "rssKiB": 830072, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 969433088 + }, + { + "tMs": 444839, + "cpuTicks": 71130, + "rssKiB": 853920, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 973127680 + }, + { + "tMs": 446009, + "cpuTicks": 71349, + "rssKiB": 853920, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 975069184 + }, + { + "tMs": 447157, + "cpuTicks": 71521, + "rssKiB": 829652, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 977223680 + }, + { + "tMs": 448287, + "cpuTicks": 71675, + "rssKiB": 829976, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 979079168 + }, + { + "tMs": 449436, + "cpuTicks": 71827, + "rssKiB": 829976, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 980901888 + }, + { + "tMs": 450585, + "cpuTicks": 71985, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 982790144 + }, + { + "tMs": 451776, + "cpuTicks": 72174, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 986013696 + }, + { + "tMs": 452949, + "cpuTicks": 72389, + "rssKiB": 854808, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 989696000 + }, + { + "tMs": 454112, + "cpuTicks": 72569, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 991887360 + }, + { + "tMs": 455261, + "cpuTicks": 72720, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 993902592 + }, + { + "tMs": 456398, + "cpuTicks": 72879, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 995819520 + }, + { + "tMs": 457540, + "cpuTicks": 73037, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 997715968 + }, + { + "tMs": 458676, + "cpuTicks": 73202, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1000726528 + }, + { + "tMs": 459879, + "cpuTicks": 73434, + "rssKiB": 854808, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1003315200 + }, + { + "tMs": 461033, + "cpuTicks": 73645, + "rssKiB": 854808, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1005314048 + }, + { + "tMs": 462174, + "cpuTicks": 73810, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1007456256 + }, + { + "tMs": 463306, + "cpuTicks": 73969, + "rssKiB": 830636, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1011085312 + }, + { + "tMs": 464437, + "cpuTicks": 74119, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1013006336 + }, + { + "tMs": 465587, + "cpuTicks": 74270, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1014939648 + }, + { + "tMs": 466738, + "cpuTicks": 74448, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1018195968 + }, + { + "tMs": 467914, + "cpuTicks": 74676, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1020133376 + }, + { + "tMs": 469085, + "cpuTicks": 74899, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1022435328 + }, + { + "tMs": 470223, + "cpuTicks": 75061, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1024364544 + }, + { + "tMs": 471342, + "cpuTicks": 75216, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1026166784 + }, + { + "tMs": 472483, + "cpuTicks": 75377, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1028489216 + }, + { + "tMs": 473611, + "cpuTicks": 75537, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1032163328 + }, + { + "tMs": 474807, + "cpuTicks": 75752, + "rssKiB": 854164, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1035341824 + }, + { + "tMs": 476009, + "cpuTicks": 75999, + "rssKiB": 854164, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1037279232 + }, + { + "tMs": 477152, + "cpuTicks": 76176, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1039446016 + }, + { + "tMs": 478270, + "cpuTicks": 76333, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1041256448 + }, + { + "tMs": 479390, + "cpuTicks": 76496, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1043124224 + }, + { + "tMs": 480522, + "cpuTicks": 76649, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1044975616 + }, + { + "tMs": 481648, + "cpuTicks": 76811, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1046851584 + }, + { + "tMs": 482857, + "cpuTicks": 77039, + "rssKiB": 854164, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1051820032 + }, + { + "tMs": 484015, + "cpuTicks": 77259, + "rssKiB": 839144, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1053728768 + }, + { + "tMs": 485162, + "cpuTicks": 77441, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1055903744 + }, + { + "tMs": 486296, + "cpuTicks": 77586, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 1058250752 + }, + { + "tMs": 487460, + "cpuTicks": 77733, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1060114432 + }, + { + "tMs": 488591, + "cpuTicks": 77874, + "rssKiB": 829992, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1062002688 + }, + { + "tMs": 489791, + "cpuTicks": 78074, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1065234432 + }, + { + "tMs": 490959, + "cpuTicks": 78293, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1067134976 + }, + { + "tMs": 492115, + "cpuTicks": 78482, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1069281280 + }, + { + "tMs": 493268, + "cpuTicks": 78628, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1072979968 + }, + { + "tMs": 494400, + "cpuTicks": 78772, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1074880512 + }, + { + "tMs": 495539, + "cpuTicks": 78920, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1076781056 + }, + { + "tMs": 496709, + "cpuTicks": 79093, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1080070144 + }, + { + "tMs": 497923, + "cpuTicks": 79334, + "rssKiB": 854488, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1081958400 + }, + { + "tMs": 499092, + "cpuTicks": 79517, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1084170240 + }, + { + "tMs": 500243, + "cpuTicks": 79659, + "rssKiB": 830316, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1086599168 + }, + { + "tMs": 501383, + "cpuTicks": 79806, + "rssKiB": 829996, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1088466944 + }, + { + "tMs": 502511, + "cpuTicks": 79954, + "rssKiB": 829996, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1090347008 + }, + { + "tMs": 503642, + "cpuTicks": 80096, + "rssKiB": 829996, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1094033408 + }, + { + "tMs": 504816, + "cpuTicks": 80297, + "rssKiB": 854168, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1097277440 + }, + { + "tMs": 505992, + "cpuTicks": 80515, + "rssKiB": 853844, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 1099218944 + }, + { + "tMs": 507147, + "cpuTicks": 80683, + "rssKiB": 829996, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 1101426688 + }, + { + "tMs": 508282, + "cpuTicks": 80825, + "rssKiB": 829996, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 1103286272 + }, + { + "tMs": 509423, + "cpuTicks": 80984, + "rssKiB": 829996, + "peakRssKiB": 872924, + "threads": 34, + "fds": 37, + "writeBytes": 1105149952 + }, + { + "tMs": 510572, + "cpuTicks": 81126, + "rssKiB": 830136, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1107005440 + }, + { + "tMs": 511729, + "cpuTicks": 81300, + "rssKiB": 854308, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1110245376 + }, + { + "tMs": 512923, + "cpuTicks": 81530, + "rssKiB": 854308, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1113849856 + }, + { + "tMs": 514105, + "cpuTicks": 81742, + "rssKiB": 830460, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1116585984 + }, + { + "tMs": 515275, + "cpuTicks": 81886, + "rssKiB": 830460, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1118519296 + }, + { + "tMs": 516422, + "cpuTicks": 82034, + "rssKiB": 830460, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1120428032 + }, + { + "tMs": 517563, + "cpuTicks": 82189, + "rssKiB": 830460, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1122287616 + }, + { + "tMs": 518731, + "cpuTicks": 82363, + "rssKiB": 854632, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1125498880 + }, + { + "tMs": 519919, + "cpuTicks": 82588, + "rssKiB": 854952, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1127448576 + }, + { + "tMs": 521084, + "cpuTicks": 82786, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1129648128 + }, + { + "tMs": 522240, + "cpuTicks": 82941, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1131581440 + }, + { + "tMs": 523396, + "cpuTicks": 83082, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1135222784 + }, + { + "tMs": 524565, + "cpuTicks": 83226, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1137188864 + }, + { + "tMs": 525761, + "cpuTicks": 83411, + "rssKiB": 854952, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1140436992 + }, + { + "tMs": 526949, + "cpuTicks": 83641, + "rssKiB": 854952, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1142403072 + }, + { + "tMs": 528098, + "cpuTicks": 83836, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1145135104 + }, + { + "tMs": 529242, + "cpuTicks": 83982, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1147072512 + }, + { + "tMs": 530382, + "cpuTicks": 84131, + "rssKiB": 830780, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1149030400 + }, + { + "tMs": 531507, + "cpuTicks": 84286, + "rssKiB": 830456, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1150820352 + }, + { + "tMs": 532645, + "cpuTicks": 84422, + "rssKiB": 830456, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1152733184 + }, + { + "tMs": 533841, + "cpuTicks": 84634, + "rssKiB": 854628, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1157726208 + }, + { + "tMs": 535005, + "cpuTicks": 84840, + "rssKiB": 854628, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1159827456 + }, + { + "tMs": 536157, + "cpuTicks": 84987, + "rssKiB": 830136, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1161900032 + }, + { + "tMs": 537305, + "cpuTicks": 85133, + "rssKiB": 830136, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1163821056 + }, + { + "tMs": 538446, + "cpuTicks": 85287, + "rssKiB": 830460, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1165684736 + }, + { + "tMs": 539580, + "cpuTicks": 85420, + "rssKiB": 830460, + "peakRssKiB": 872924, + "threads": 34, + "fds": 36, + "writeBytes": 1167564800 + }, + { + "tMs": 540744, + "cpuTicks": 85583, + "rssKiB": 854632, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1170800640 + }, + { + "tMs": 541949, + "cpuTicks": 85823, + "rssKiB": 854632, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1173176320 + }, + { + "tMs": 543110, + "cpuTicks": 85989, + "rssKiB": 830460, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1177096192 + }, + { + "tMs": 544257, + "cpuTicks": 86141, + "rssKiB": 830460, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1178951680 + }, + { + "tMs": 545409, + "cpuTicks": 86290, + "rssKiB": 830460, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1180934144 + }, + { + "tMs": 546548, + "cpuTicks": 86453, + "rssKiB": 830460, + "peakRssKiB": 872948, + "threads": 34, + "fds": 37, + "writeBytes": 1182806016 + }, + { + "tMs": 547670, + "cpuTicks": 86596, + "rssKiB": 830460, + "peakRssKiB": 872948, + "threads": 34, + "fds": 37, + "writeBytes": 1184690176 + }, + { + "tMs": 548883, + "cpuTicks": 86843, + "rssKiB": 854040, + "peakRssKiB": 872948, + "threads": 34, + "fds": 37, + "writeBytes": 1187872768 + }, + { + "tMs": 550051, + "cpuTicks": 87067, + "rssKiB": 854364, + "peakRssKiB": 872948, + "threads": 34, + "fds": 37, + "writeBytes": 1189797888 + }, + { + "tMs": 551211, + "cpuTicks": 87221, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1191940096 + }, + { + "tMs": 552366, + "cpuTicks": 87373, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1193836544 + }, + { + "tMs": 553513, + "cpuTicks": 87530, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1197514752 + }, + { + "tMs": 554639, + "cpuTicks": 87687, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1199366144 + }, + { + "tMs": 555834, + "cpuTicks": 87917, + "rssKiB": 854364, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1203068928 + }, + { + "tMs": 556992, + "cpuTicks": 88137, + "rssKiB": 854364, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1205026816 + }, + { + "tMs": 558155, + "cpuTicks": 88320, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1207205888 + }, + { + "tMs": 559289, + "cpuTicks": 88465, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1209139200 + }, + { + "tMs": 560433, + "cpuTicks": 88617, + "rssKiB": 830192, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1211011072 + }, + { + "tMs": 561578, + "cpuTicks": 88773, + "rssKiB": 829588, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1212846080 + }, + { + "tMs": 562763, + "cpuTicks": 88982, + "rssKiB": 853760, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1217830912 + }, + { + "tMs": 563949, + "cpuTicks": 89210, + "rssKiB": 853760, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1219735552 + }, + { + "tMs": 565111, + "cpuTicks": 89421, + "rssKiB": 829812, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1221939200 + }, + { + "tMs": 566247, + "cpuTicks": 89592, + "rssKiB": 829592, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1223847936 + }, + { + "tMs": 567375, + "cpuTicks": 89754, + "rssKiB": 829912, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1225748480 + }, + { + "tMs": 568509, + "cpuTicks": 89913, + "rssKiB": 829912, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1227653120 + }, + { + "tMs": 569639, + "cpuTicks": 90059, + "rssKiB": 829912, + "peakRssKiB": 872948, + "threads": 34, + "fds": 37, + "writeBytes": 1230028800 + }, + { + "tMs": 570839, + "cpuTicks": 90286, + "rssKiB": 854096, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1233190912 + }, + { + "tMs": 571996, + "cpuTicks": 90499, + "rssKiB": 854420, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1235099648 + }, + { + "tMs": 573155, + "cpuTicks": 90680, + "rssKiB": 830248, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1239097344 + }, + { + "tMs": 574301, + "cpuTicks": 90829, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1241022464 + }, + { + "tMs": 575432, + "cpuTicks": 90989, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1242984448 + }, + { + "tMs": 576576, + "cpuTicks": 91149, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1244958720 + }, + { + "tMs": 577726, + "cpuTicks": 91320, + "rssKiB": 854744, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1248178176 + }, + { + "tMs": 578909, + "cpuTicks": 91543, + "rssKiB": 854744, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1250107392 + }, + { + "tMs": 580086, + "cpuTicks": 91764, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1252315136 + }, + { + "tMs": 581236, + "cpuTicks": 91909, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1254301696 + }, + { + "tMs": 582396, + "cpuTicks": 92053, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1256804352 + }, + { + "tMs": 583568, + "cpuTicks": 92198, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1260568576 + }, + { + "tMs": 584783, + "cpuTicks": 92395, + "rssKiB": 854096, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1263775744 + }, + { + "tMs": 585976, + "cpuTicks": 92628, + "rssKiB": 854096, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1265741824 + }, + { + "tMs": 587149, + "cpuTicks": 92815, + "rssKiB": 829924, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1268019200 + }, + { + "tMs": 588287, + "cpuTicks": 92963, + "rssKiB": 829924, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1269936128 + }, + { + "tMs": 589439, + "cpuTicks": 93129, + "rssKiB": 829604, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1271889920 + }, + { + "tMs": 590584, + "cpuTicks": 93287, + "rssKiB": 829604, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1273774080 + }, + { + "tMs": 591767, + "cpuTicks": 93472, + "rssKiB": 853776, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1276915712 + }, + { + "tMs": 592958, + "cpuTicks": 93715, + "rssKiB": 853776, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1280638976 + }, + { + "tMs": 594117, + "cpuTicks": 93909, + "rssKiB": 829924, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1283379200 + }, + { + "tMs": 595243, + "cpuTicks": 94068, + "rssKiB": 829924, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1285251072 + }, + { + "tMs": 596374, + "cpuTicks": 94218, + "rssKiB": 829924, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1287159808 + }, + { + "tMs": 597515, + "cpuTicks": 94357, + "rssKiB": 830248, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1289105408 + }, + { + "tMs": 598658, + "cpuTicks": 94511, + "rssKiB": 830572, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1290973184 + }, + { + "tMs": 599868, + "cpuTicks": 94752, + "rssKiB": 854744, + "peakRssKiB": 872948, + "threads": 34, + "fds": 36, + "writeBytes": 1294131200 + } + ], + "db_probe_ms": [ + 9.375, + 8.32, + 8.244, + 11.28, + 8.307, + 9.908, + 8.976, + 13.21, + 8.153, + 7.7, + 9.044, + 10.095, + 8.21, + 9.75, + 9.429, + 8.402, + 7.795, + 7.992, + 10.331, + 9.853, + 10.511, + 7.869, + 8.996, + 8.986, + 7.481, + 7.531, + 15.515, + 8.291, + 8.255, + 7.947, + 9.296, + 8.216, + 8.171, + 7.373, + 8.701, + 7.798, + 9.408, + 8.236, + 7.562, + 7.254, + 7.381, + 8.359, + 8.336, + 7.25, + 8.03, + 8.35, + 8.624, + 7.992, + 8.375, + 10.973, + 9.013, + 7.672, + 7.169, + 10.184, + 8.245, + 8.759, + 7.433, + 8.117, + 9.727, + 8.196, + 7.446, + 10.037, + 8.158, + 9.276, + 7.701, + 8.091, + 7.839, + 7.653, + 7.59, + 13.045, + 8.059, + 7.825, + 8.484, + 8.205, + 7.852, + 7.522, + 10.49, + 9.866, + 7.722, + 7.38, + 15.738, + 9.045, + 7.297, + 7.897, + 8.92, + 11.22, + 8.776, + 8.411, + 8.835, + 8.067, + 8.461, + 7.116, + 8.144, + 7.393, + 9.632, + 7.497, + 8.035, + 7.374, + 7.765, + 8.689, + 10.886, + 7.83, + 7.321, + 8.171, + 7.949, + 8.287, + 7.195, + 11.706, + 8.863, + 7.606, + 7.569, + 9.419, + 8.717, + 8.363, + 7.527, + 8.581, + 8.709, + 8.515, + 7.33, + 8.678, + 8.697, + 8.739, + 7.514, + 8.797, + 7.321, + 9.617, + 8.176, + 9.066, + 7.849, + 7.46, + 8.83, + 8.067, + 7.371, + 7.57, + 9.65, + 9, + 7.724, + 8.054, + 11.381, + 13.16, + 7.3, + 8.093, + 9.038, + 8.121, + 10.582, + 7.177, + 9.357, + 7.501, + 8.652, + 7.307, + 8.02, + 7.364, + 8.426, + 7.487, + 8.43, + 7.217, + 8.711, + 7.826, + 8.586, + 7.571, + 7.206, + 8.007, + 9.44, + 7.375, + 8.579, + 16.901, + 14.023, + 8.117, + 7.535, + 10.116, + 11.933, + 8.064, + 7.549, + 13.028, + 9.294, + 7.574, + 7.353, + 10.806, + 7.409, + 8.168, + 7.648, + 9.187, + 7.428, + 8.248, + 8.061, + 7.728, + 7.409, + 7.33, + 7.886, + 9.664, + 7.823, + 7.154, + 10.015, + 8.659, + 7.569, + 8.044, + 10.62, + 10.519, + 7.483, + 8.808, + 12.667, + 8.55, + 7.631, + 8.373, + 13.598, + 7.954, + 8.206, + 7.486, + 9.617, + 7.707, + 7.699, + 7.378, + 12.251, + 7.288, + 9.143, + 7.488, + 8.006, + 8.008, + 7.055, + 9.219, + 7.637, + 7.816, + 7.159, + 19.04, + 8.164, + 7.586, + 7.348, + 8.63, + 10.196, + 7.774, + 8.733, + 9.947, + 8.964, + 8.08, + 7.461, + 9.177, + 8.084, + 8.192, + 7.52, + 8.66, + 9.726, + 9.026, + 7.888, + 18.746, + 7.55, + 7.347, + 7.873, + 8.953, + 7.519, + 7.557, + 13.19, + 7.794, + 7.88, + 7.515, + 11.342, + 9.1, + 7.532, + 7.253, + 10.348, + 8.666, + 8.095, + 8.074, + 8.121, + 14.657, + 8.478, + 7.689, + 8.154, + 8.125, + 9.109, + 8.351, + 9.545, + 7.126, + 8.511, + 8.731, + 8.113, + 7.414, + 7.751, + 11.222, + 8.493, + 7.453, + 8.443, + 14.031, + 8.579, + 7.552, + 7.507, + 13.126, + 8.112, + 7.478, + 7.779, + 10.274, + 9.382, + 7.612, + 7.487, + 10.234, + 10.563, + 7.783, + 7.903, + 7.764, + 7.933, + 8.984, + 8.465, + 8.823, + 7.188, + 8.696, + 7.397, + 11.132, + 7.388, + 8.543, + 13.699, + 15.21, + 7.427, + 8.122, + 8.98, + 7.878, + 7.465, + 7.328, + 12.093, + 8.008, + 7.495, + 8.574, + 9.229, + 7.838, + 7.7, + 7.38, + 7.911, + 7.639, + 8.118, + 7.246, + 7.785, + 7.422, + 11.126, + 7.341, + 12.187, + 7.372, + 8.668, + 20.375, + 11.559, + 7.269, + 10.295, + 15.291, + 8.734, + 7.779, + 7.672, + 13.055, + 7.963, + 7.725, + 8.17, + 9.645, + 7.684, + 8.296, + 8.146, + 8.124, + 8.884, + 7.397, + 7.303, + 8.012, + 8.454, + 9.338, + 7.439, + 8.876, + 7.896, + 8.814, + 11.475, + 7.763, + 8.032, + 8.538, + 13.228, + 8.117, + 7.416, + 9.34, + 10.579, + 7.863, + 9.372, + 7.675, + 14.404, + 7.784, + 7.102, + 7.856, + 15.24, + 10.246, + 7.512, + 7.567, + 13.955, + 8.274, + 8.19, + 8.065, + 8.206, + 14.999, + 10.445, + 9.838, + 7.816, + 7.314, + 8.597, + 12.06, + 8.11, + 7.216, + 10.07, + 13.483, + 7.77, + 7.13, + 7.399, + 9.511, + 7.999, + 7.345, + 7.229, + 9.035, + 8.041, + 7.119, + 7.158, + 9.079, + 8.662, + 7.538, + 7.282, + 7.558, + 8.142, + 8.097, + 8.219, + 8.131, + 8.091, + 8.709, + 10.53, + 8.586, + 7.944, + 9.857, + 12.363, + 8.092, + 7.492, + 8.265, + 16.645, + 8.291, + 7.222, + 7.589, + 9.993, + 8.137, + 8.424, + 7.677, + 10.053, + 8.289, + 8.233, + 7.846, + 8.227, + 10.134, + 7.659, + 7.653, + 9.937, + 7.868, + 8.388, + 9.726, + 8.153, + 10.372, + 9.266, + 12.665, + 8.895, + 9.066, + 8.018, + 11.398, + 7.579, + 7.283, + 7.75, + 10.306, + 7.541, + 7.314, + 8.184, + 9.839, + 7.956, + 7.413, + 7.238, + 9.937, + 9.004, + 7.511, + 7.481, + 14.903, + 7.677, + 9.4, + 14.939, + 9.657, + 9.026, + 8.743, + 12.021, + 8.635, + 7.591, + 8.321, + 8.232, + 7.954, + 7.439, + 7.832, + 12.041, + 7.569, + 7.931, + 8.29, + 13.61, + 7.739, + 7.567, + 7.291, + 11.141, + 8.443, + 7.722, + 7.345, + 8.152, + 9.579, + 8.359, + 14.567, + 7.641, + 9.711, + 8.76, + 15.138, + 8.369, + 8.995, + 9.059, + 11.985, + 8.215, + 7.473, + 8.694, + 12.106, + 7.909, + 7.232, + 7.417, + 8.037, + 7.405, + 7.184, + 7.498, + 9.181, + 7.278, + 10.254, + 7.647, + 9.694, + 8.092, + 8.051, + 8.273, + 8.415, + 9.484, + 7.329, + 8.412, + 8.295, + 7.682, + 8.058, + 10.378, + 10.449, + 7.19, + 8.178, + 8.266, + 7.683, + 7.607, + 7.959, + 8.393, + 8.884, + 7.517, + 7.972, + 10.285, + 7.783, + 8.257, + 7.466, + 10.484, + 7.641, + 7.516, + 8.059, + 8.043, + 8.145, + 7.676, + 11.268, + 7.615, + 8.16, + 7.166, + 10.796, + 10.778, + 8.822, + 7.886, + 8.387, + 7.988, + 7.229, + 8.188, + 9.805, + 7.525, + 7.744, + 10.516, + 9.27, + 8.116, + 7.708, + 7.655, + 13.135, + 7.533, + 9.105, + 8.077, + 9.432, + 8.109, + 7.36, + 8.113, + 9.963, + 8.346, + 8.226, + 12.816, + 10.735, + 8.597, + 10.925, + 8.922, + 8.66, + 10.442, + 8.391, + 8.772, + 13.516, + 7.217, + 7.492, + 9.801, + 7.202, + 7.198, + 8.303, + 9.244, + 7.204, + 7.334, + 6.998, + 14.58, + 7.277, + 7.266, + 7.275, + 9.566, + 7.511, + 8.706, + 8.867, + 7.953, + 8.12, + 7.515, + 8.889, + 8.633, + 7.84, + 8.052, + 7.835, + 7.045, + 9.399, + 9.126, + 12.074, + 7.391, + 9.043, + 7.967, + 7.741, + 7.697, + 7.265, + 9.033, + 8.215, + 7.703, + 8.407, + 7.526, + 9.891, + 7.469, + 7.339, + 7.555, + 8.113, + 9.103, + 7.689, + 10.641, + 7.887, + 11, + 7.287, + 11.084, + 7.925, + 7.947, + 7.366, + 10.689, + 8.251, + 7.513, + 8.626, + 10.434, + 7.724, + 7.89, + 9.35, + 11.446, + 7.901, + 9.699, + 7.683, + 10.791, + 7.74, + 7.514, + 7.288, + 9.67, + 8.39, + 9.343, + 10.747, + 8.022, + 8.588, + 7.361, + 9.506, + 8.223, + 9.204, + 7.207, + 7.982, + 7.754, + 8.032, + 8.005, + 9.646, + 7.69, + 7.209, + 7.923, + 8.731, + 7.796, + 7.22, + 11.014, + 8.254, + 8.075, + 7.917, + 7.364, + 8.848, + 7.448, + 7.653, + 7.546, + 13.558, + 8.037, + 9.247, + 10.445, + 9.595, + 8.909, + 8.221, + 9.058, + 7.935, + 8.605, + 7.615, + 9.018, + 7.684, + 8.494, + 8.565, + 7.973, + 8.181, + 7.467, + 12.742, + 7.892, + 7.34, + 7.15, + 7.157, + 7.928, + 7.159, + 7.089, + 7.466, + 8.292, + 7.184, + 7.837, + 7.555, + 9.709, + 9.727, + 8.397, + 9.054, + 8.468, + 7.747, + 7.629, + 9.669, + 7.964, + 8.406, + 11.293, + 13.539, + 7.859, + 7.355, + 8.416, + 8.902, + 7.606, + 7.645, + 9.946, + 8.911, + 7.89, + 6.803, + 8.181, + 10.241, + 8.585, + 8.116, + 7.738, + 8.365, + 7.677, + 7.858, + 9.032, + 7.708, + 8.347, + 8.014, + 12.64, + 8.394, + 7.716, + 8.094, + 9.068, + 10.98, + 8.118, + 8.787, + 8.949, + 7.739, + 7.271, + 8.031, + 7.746, + 7.698, + 7.38, + 8.664, + 10.327, + 7.535, + 7.104, + 7.617, + 9.134, + 8.973, + 7.554, + 7.261, + 9.855, + 7.633, + 7.669, + 10.122, + 8.203, + 8.37, + 7.89, + 8.75, + 9.442, + 7.868, + 8.096, + 8.762, + 12.032, + 9.176, + 9.113, + 9.708, + 7.855, + 8.69, + 8.428, + 11.148, + 7.792, + 7.444, + 9.878, + 9.595, + 7.577, + 7.533, + 7.204, + 10.429, + 7.673, + 8.917, + 9.193, + 10.484, + 7.818, + 8.318, + 9.401, + 7.678, + 8.522, + 7.13, + 12.203, + 10.85, + 9.442, + 7.182, + 10.224, + 7.803, + 11.27, + 8.467, + 10.638, + 7.564, + 7.258, + 9.246, + 7.803, + 7.28, + 7.615, + 8.968, + 11.217, + 7.453, + 7.542, + 7.707, + 12.396, + 9.1, + 9.124, + 9.784, + 10.708, + 7.695, + 7.674, + 12.142, + 8.188, + 9.465, + 8.679, + 9.265, + 8.142, + 9.292, + 7.73, + 8.524, + 7.445, + 7.402, + 8.082, + 11.861, + 7.294, + 7.347, + 8.028, + 9.177, + 8.45, + 7.589, + 8.427, + 9.197, + 7.481, + 7.217, + 11.44, + 9.462, + 7.971, + 7.285, + 8.852, + 7.828, + 7.806, + 8.327, + 9.213, + 7.485, + 8.485, + 10.957, + 14.387, + 7.68, + 8.408, + 7.985, + 10.461, + 7.268, + 9.359, + 12.115, + 11.159, + 7.46, + 7.465, + 7.643, + 9.22, + 7.655, + 8.552, + 7.728, + 15.799, + 7.59, + 7.225, + 11.852, + 10.202, + 7.418, + 9.443, + 13.092, + 8.285, + 8.035, + 7.322, + 10.533, + 7.473, + 9.034, + 7.364, + 7.944, + 7.301, + 9.421, + 9.278, + 8.317, + 7.463, + 7.204, + 8.119, + 9.026, + 7.311, + 7.547, + 9.795, + 15.911, + 12.151, + 8.312, + 12.362, + 10.428, + 7.305, + 7.808, + 9.524, + 8.364, + 9.163, + 7.542, + 9.005, + 10.786, + 8.564, + 7.438, + 8.53, + 8.276, + 7.953, + 8.275, + 8.666, + 8.144, + 7.5, + 8.181, + 8.155, + 7.749, + 7.557, + 7.852, + 7.883, + 7.485, + 7.736, + 9.645, + 11.538, + 9.999, + 7.857, + 9.231, + 8.37, + 7.633, + 7.609, + 8.88, + 8.111, + 8.239, + 8.909, + 9.664, + 7.526, + 8.528, + 8.099, + 10.712, + 7.951, + 9.388, + 8.794, + 7.929, + 7.917, + 8.443, + 7.346, + 7.866, + 8.152, + 7.719, + 10.533, + 9.503, + 8.039, + 7.146, + 15.434, + 15.63, + 10.258, + 7.388, + 8.039, + 8.305, + 7.492, + 9.278, + 8.596, + 10.195, + 8.58, + 8.292, + 8.103, + 10.526, + 9.079, + 7.521, + 8.395, + 7.52, + 8.832, + 7.52, + 9.087, + 7.199, + 9.079, + 8.067, + 8.162, + 7.164, + 7.379, + 17.972, + 8.334, + 7.429, + 7.231, + 9.708, + 10.862, + 7.139, + 8.109, + 13.307, + 10.124, + 7.643, + 8.596, + 9.167, + 7.894, + 8.122, + 7.869, + 8.449, + 7.704, + 8.704, + 9.308, + 9.21, + 7.485, + 9.253, + 7.637, + 7.578, + 7.494, + 7.484, + 19.791, + 8.615, + 7.337, + 8.455, + 11.138, + 9.415, + 7.155, + 7.588, + 12.322, + 10.675, + 7.42, + 7.366, + 7.81, + 9.645, + 8.436, + 7.615, + 9.934, + 8.552, + 8.108, + 7.732, + 8.524, + 7.241, + 8.095, + 7.593, + 7.604, + 7.238, + 8.778, + 9.436, + 11.658, + 7.588, + 8.219, + 11.063, + 9.003, + 7.384, + 7.6, + 10.09, + 10.203, + 7.319, + 7.199, + 9.033, + 8.598, + 7.505, + 8.729, + 9.136, + 7.517, + 8.721, + 7.565, + 7.67, + 7.559, + 10.075, + 7.572, + 8.344, + 7.641, + 7.429, + 8.879, + 8.516, + 9.072, + 7.342, + 12.699, + 8.754, + 7.388, + 7.321, + 11.195, + 9.95, + 7.681, + 8.281, + 9.413, + 8.212, + 8.184, + 7.155, + 8.376, + 7.321, + 8.507, + 8.151, + 9.151, + 7.174, + 8.603, + 7.302, + 7.518, + 7.386, + 9.873, + 7.943, + 9.031, + 7.032, + 7.467, + 11.412, + 8.591, + 7.274, + 8.317, + 21.315, + 7.964, + 9.595, + 7.394, + 8.197, + 10.159, + 7.672, + 10.621, + 8.827, + 9.395, + 7.583, + 8.384, + 7.826, + 8.249, + 8.045, + 7.39, + 9.145, + 7.773, + 7.415, + 7.645, + 7.762, + 7.942, + 10.423, + 7.179, + 11.528, + 9.37, + 8.555, + 10.94, + 8.227, + 7.45, + 8.472, + 16.981, + 8.421, + 7.24, + 10.194, + 14.462, + 8.201, + 7.187, + 8.036, + 8.861, + 8.726, + 10.475, + 7.526, + 7.936, + 10.531, + 9.179, + 7.391, + 7.719, + 8.869, + 12.27, + 9.79, + 7.725, + 8.534, + 7.393, + 14.636, + 9.193, + 7.758, + 7.538, + 17.152, + 16.023, + 7.2, + 13.615, + 15.305, + 11.203, + 7.167, + 7.376, + 8.366, + 9.481, + 8.22, + 7.082, + 9.05, + 10.457, + 9.197, + 7.563, + 8.006, + 7.222, + 10.099, + 12.331, + 8.86, + 8.761, + 9.119, + 7.984, + 8.511, + 7.187, + 8.699, + 16.934, + 12.013, + 7.439, + 8.335, + 14.227, + 7.504, + 7.385, + 7.412, + 9.978, + 9.292, + 8.327, + 7.423, + 8.935, + 8.483, + 7.962, + 7.192, + 9.108, + 8.434, + 8.58, + 10.058, + 7.927, + 8.133, + 9.722, + 11.365, + 7.635, + 7.144, + 9.267, + 13.091, + 8.556, + 7.37, + 8.214, + 17.138, + 11.013, + 7.673, + 7.87, + 9.454, + 8.512, + 7.314, + 7.843, + 14.87, + 8.343, + 7.433, + 7.463, + 8.11, + 8.447, + 7.658, + 9.017, + 8.007, + 8.196, + 8.659, + 13.152, + 8.718, + 7.452, + 8.863, + 8.837, + 8.873, + 7.578, + 7.765, + 8.989, + 7.956, + 7.642, + 8.398, + 10.377, + 7.696, + 13.359, + 8.779, + 9.544, + 8.208, + 7.343, + 8.089, + 7.78, + 10.028, + 7.183, + 7.698, + 8.13, + 7.312, + 8.296, + 9.848, + 7.825, + 7.562, + 8.221, + 10.502, + 8.281, + 7.607, + 9.103, + 8.655, + 7.579, + 7.324, + 7.85, + 10.056, + 7.48, + 7.109, + 7.411, + 8.59, + 11.531, + 7.372, + 7.915, + 7.88, + 8.682, + 8.616, + 7.434, + 8.825, + 7.742, + 7.47, + 8.134, + 7.782, + 7.479, + 8.584, + 9.202, + 8.567, + 7.298, + 8.914, + 8.525, + 7.895, + 8.377, + 7.847, + 10.421, + 8.374, + 7.601, + 7.354, + 9.41, + 7.564, + 7.57, + 7.978, + 10.752, + 7.977, + 7.423, + 9.757, + 8.133, + 8.663, + 8.511, + 7.793, + 8.062, + 11.1, + 8.299, + 10.092, + 8.555, + 8.199, + 10.016, + 12.176, + 7.562, + 7.681, + 8.334, + 12.545, + 7.914, + 8.344, + 8.088, + 12.09, + 7.839, + 7.161, + 7.313, + 10.314, + 9.891, + 7.465, + 8.007, + 13.935, + 7.798, + 7.312, + 8.182, + 7.669, + 8.041, + 7.57, + 8.192, + 7.883, + 8.142, + 7.642, + 17.151, + 8.235, + 7.848, + 8.165, + 10.709, + 7.604, + 7.724, + 7.912, + 9.358, + 7.59, + 7.314, + 8.649, + 10.988, + 7.949, + 7.906, + 7.194, + 9.037, + 9.628, + 7.499, + 7.095, + 7.873, + 9.325, + 7.576, + 7.264, + 8.641, + 7.525, + 8.787, + 11.296, + 7.704, + 7.444, + 7.853, + 8.885, + 8.283, + 7.531, + 8.445, + 9.442, + 8.552, + 7.744, + 7.834, + 10.347, + 10.921, + 7.381, + 7.611, + 12.418, + 7.65, + 8.214, + 7.406, + 7.749, + 7.987, + 8.32, + 7.215, + 9.424, + 9.36, + 7.38, + 7.372, + 7.48, + 9.664, + 7.875, + 9.403, + 7.601, + 7.594, + 9.269, + 8.24, + 11.579, + 7.178, + 9.983, + 13.685, + 7.529, + 7.104, + 7.343, + 9.107, + 7.766, + 7.175, + 7.796, + 9.261, + 7.934, + 7.424, + 7.236, + 9.76, + 8.562, + 7.653, + 12.593, + 8.543, + 9.732, + 8.846, + 13.285, + 8.063, + 7.897, + 9.041, + 14.731, + 9.315, + 7.241, + 8.792, + 9.165, + 7.872, + 7.727, + 8.493, + 8.959, + 9.9, + 9.138, + 7.861, + 9.974, + 7.432, + 7.804, + 7.762, + 10.843, + 7.627, + 8.214, + 7.441, + 9.081, + 7.662, + 10.26, + 10.451, + 8.206, + 7.503, + 9.187, + 9.087, + 9.037, + 7.904, + 8.12, + 8.787, + 7.736, + 7.422, + 8.19, + 14.209, + 8.693, + 7.589, + 8.464, + 11.188, + 8.642, + 9.305, + 7.558, + 9.13, + 8.16, + 7.67, + 7.227, + 8.266, + 8.319, + 7.34, + 7.312, + 8.125, + 8.231, + 11.296, + 9.045, + 8.595, + 7.341, + 8.773, + 11.151, + 8.927, + 7.089, + 8.119, + 9.81, + 8.401, + 7.786, + 7.842, + 14.312, + 7.414, + 8.379, + 7.813, + 8.197, + 7.508, + 8.261, + 7.729, + 7.856, + 8.542, + 8.426, + 7.904, + 9.21, + 7.186, + 8.32, + 9.729, + 8.313, + 7.393, + 9.121, + 9.255, + 8.822, + 7.372, + 9.46, + 10.228, + 12.474, + 7.761, + 7.394, + 14.221, + 7.765, + 7.494, + 7.583, + 8.323, + 8.834, + 9.793, + 7.104, + 8.259, + 8.566, + 7.432, + 7.574, + 7.571, + 7.397, + 7.728, + 7.928, + 8.144, + 7.686, + 8.994, + 11.34, + 8.524, + 7.812, + 9.751, + 10.844, + 14.378, + 7.364, + 8.104, + 10.965, + 7.759, + 7.233, + 7.463, + 9.172, + 7.284, + 7.703, + 7.534, + 10.373, + 8.119, + 7.233, + 8.096, + 8.301, + 8.125, + 7.772, + 7.748, + 8.044, + 8.655, + 8.899, + 8.536, + 8.087, + 7.548, + 9.313, + 10.661, + 8.882, + 7.817, + 8.112, + 19.503, + 9.421, + 7.468, + 7.485, + 9.647, + 9.979, + 8.361, + 8.744, + 13.428, + 8.309, + 7.791, + 7.519, + 8.049, + 7.516, + 7.643, + 7.688, + 7.506, + 8.39, + 9.403, + 8.554, + 8.075, + 7.188, + 7.724, + 9.575, + 7.814, + 7.385, + 7.648, + 12.854, + 8.72, + 7.368, + 7.491, + 11.185, + 9.514, + 7.597, + 7.573, + 9.838, + 8.049, + 7.695, + 7.727, + 9.24, + 8.153, + 7.996, + 7.511, + 7.677, + 7.516, + 9.662, + 8.115, + 9.32, + 7.322, + 7.944, + 15.749, + 7.957, + 7.383, + 7.421, + 13.837, + 8.601, + 7.525, + 7.575, + 14.597, + 8.775, + 8.845, + 7.293, + 9.927, + 12.935, + 7.4, + 7.329, + 8.859, + 10.54, + 8.056, + 9.349, + 7.981, + 9.116, + 8.32, + 7.781, + 10.229, + 7.299, + 9.162, + 10.93, + 15.156, + 7.248, + 9.993, + 12.389, + 8.164, + 7.667, + 9.224, + 12.357, + 8.594, + 7.417, + 7.597, + 9.528, + 7.638, + 7.313, + 8.086, + 11.943, + 7.579, + 8.021, + 8.176, + 8.147, + 9.434, + 7.501, + 7.665, + 8.202, + 7.184, + 8.897, + 11.034, + 7.542, + 7.342, + 8.589, + 10.271, + 8.239, + 7.426, + 7.546, + 11.524, + 16.34, + 8.01, + 7.836, + 13.21, + 7.553, + 7.307, + 7.274, + 8.387, + 8.001, + 8.285, + 7.791, + 17.265, + 7.722, + 7.452, + 7.224, + 8.125, + 9.024, + 8.236, + 7.361, + 13.423, + 7.997, + 10.626, + 12.614, + 8.052, + 7.557, + 8.041, + 13.365, + 9.309, + 8.564, + 10.801, + 17.82, + 8.105, + 7.687, + 7.522, + 8.511, + 8.354, + 7.273, + 7.408, + 7.831, + 8.023, + 8.174, + 7.419, + 8.138, + 11.235, + 7.481, + 7.328, + 8.052, + 7.725, + 9.048, + 22.109, + 9.758, + 7.791, + 9.266, + 9.414, + 12.541, + 7.616, + 11.776, + 8.669, + 7.421, + 7.444, + 7.773, + 10.253, + 7.529, + 7.998, + 7.228, + 8.281, + 7.846, + 7.784, + 7.468, + 8.618, + 8.503, + 8.262, + 7.568, + 9.284, + 9.004, + 9.038, + 18.429, + 10.084, + 7.748, + 8.623, + 8.542, + 7.734, + 8.43, + 8.312, + 13.258, + 7.58, + 7.472, + 15.488, + 10.316, + 7.695, + 7.272, + 8.05, + 9, + 8.213, + 7.55, + 7.588, + 8.279, + 8.151, + 8.551, + 7.944, + 7.594, + 9.716, + 7.832, + 10.841, + 8.56, + 10.435, + 7.742, + 8.555, + 8.004, + 7.344, + 8.605, + 13.48, + 9.239, + 7.37, + 8.187, + 11.092, + 7.469, + 7.617, + 8.998, + 15.657, + 7.32, + 9.665, + 8.559, + 14.796, + 8.173, + 9.122, + 7.576, + 9.889, + 7.478, + 8.583, + 15.76, + 8.456, + 10.665, + 7.389, + 9.673, + 9.861, + 8.12, + 7.897, + 9.303, + 8.396, + 7.779, + 9.833, + 11.306, + 7.888, + 7.704, + 8.588, + 7.565, + 7.234, + 7.605, + 7.912, + 8.698, + 7.307, + 7.164, + 7.625, + 13.538, + 7.308, + 7.516, + 11.159, + 12.755, + 11.246, + 7.665, + 9.289, + 8.205, + 10.287, + 10.387, + 11.406, + 8.132, + 7.742, + 9.119, + 8.172, + 7.57, + 7.372, + 8.448, + 8.425, + 7.213, + 7.58, + 7.974, + 9.169, + 8.08, + 7.931, + 7.535, + 10.026, + 7.344, + 8.584, + 7.404, + 8.523, + 11.107, + 7.544, + 12.502, + 8.015, + 7.827, + 7.231, + 8.316, + 7.435, + 9.372, + 8.623, + 9.6, + 9, + 7.515, + 7.772, + 8.409, + 8.032, + 7.593, + 10.145, + 8.848, + 7.391, + 7.014, + 7.964, + 8.929, + 7.206, + 7.129, + 7.258, + 10.615, + 7.272, + 7.443, + 8.188, + 18.441, + 8.474, + 7.412, + 8.086, + 7.609, + 7.803, + 8.111, + 8.497, + 7.91, + 7.694, + 9.978, + 8.056, + 8.769, + 7.545, + 8.537, + 8.118, + 8.078, + 7.527, + 8.114, + 8.079, + 7.791, + 7.386, + 7.706, + 8.14, + 7.576, + 8.842, + 8.519, + 12.624, + 7.968, + 7.891, + 13.19, + 7.955, + 8.48, + 7.486, + 12.112, + 7.841, + 7.642, + 10.308, + 9.293, + 8.924, + 7.184, + 9.549, + 8.253, + 7.884, + 7.493, + 8.479, + 8.847, + 7.796, + 7.233, + 7.163, + 8.447, + 7.801, + 8.588, + 10.44, + 13.129, + 8.036, + 7.776, + 8.913, + 14.116, + 9.986, + 7.469, + 9.018, + 8.086, + 8.707, + 11.93, + 10.863, + 7.747, + 8.172, + 8.651, + 10.405, + 8.077, + 7.216, + 7.348, + 8.91, + 7.72, + 8.904, + 9.345, + 8.851, + 7.921, + 7.879, + 10.127, + 8.164, + 8.507, + 7.75, + 11.054, + 8.111, + 7.604, + 7.71, + 9.51, + 7.959, + 7.212, + 8.173, + 9.685, + 9.315, + 7.461, + 8.446, + 9.102, + 8.261, + 8.081, + 8.462, + 8.148, + 7.998, + 7.414, + 8.67, + 12.527, + 7.345, + 7.512, + 7.423, + 8.294, + 8.005, + 7.563, + 9.665, + 8.976, + 10.349, + 9.333, + 9.732, + 9.14, + 8.431, + 7.691, + 8.451, + 10.632, + 7.363, + 8.883, + 11.052, + 8.832, + 7.432, + 9.062, + 8.99, + 7.795, + 7.487, + 7.629, + 13.503, + 7.085, + 7.636, + 8.011, + 10.37, + 7.486, + 7.939, + 7.234, + 7.635, + 8.166, + 9.217, + 7.591, + 9.178, + 8.401, + 7.994, + 11.729, + 7.857, + 7.305, + 8.207, + 10.295, + 8.212, + 7.361, + 7.98, + 10.291, + 23.277, + 8.18, + 7.264, + 12.605, + 7.314, + 7.404, + 7.327, + 10.641, + 8.096, + 8.02, + 7.308, + 10.783, + 8.287, + 9.26, + 7.757, + 9.775, + 8.162, + 8.85, + 12.053, + 12.067, + 7.393, + 10.16, + 14.895, + 10.595, + 7.458, + 9.338, + 13.705, + 7.85, + 7.526, + 10.764, + 16.059, + 8.194, + 7.54, + 7.5, + 12.372, + 7.594, + 7.819, + 7.413, + 12.337, + 7.445, + 7.57, + 7.894, + 15.391, + 8.355, + 7.489, + 15.082, + 10.644, + 9.624, + 7.291, + 8.708, + 12.109, + 7.385, + 9.723, + 8.597, + 8.593, + 7.408, + 8.181, + 10.417, + 7.328, + 7.86, + 9.219, + 11.962, + 9.754, + 9.26, + 7.234, + 9.333, + 7.655, + 9.258, + 7.607, + 7.912, + 8.137, + 7.377, + 21.22, + 9.537, + 10.129, + 8.187, + 8.27, + 7.619, + 10.461, + 8.22, + 10.298, + 8.798, + 7.433, + 8.581, + 14.75, + 8.273, + 7.109, + 7.742, + 10.526, + 7.573, + 7.226, + 8.457, + 11.621, + 7.685, + 7.735, + 8.054, + 13.362, + 8.253, + 7.241, + 11.324, + 8.806, + 8.79, + 8.429, + 10.511, + 13.234, + 9.848, + 8.854, + 8.246, + 10.697, + 7.553, + 8.9, + 13.297, + 7.417, + 7.564, + 10.119, + 8.488, + 7.754, + 7.767, + 9.788, + 15.192, + 7.841, + 7.61, + 7.618, + 9.687, + 7.683, + 7.522, + 12.047, + 10.162, + 8.106, + 7.341, + 8.91, + 8.757, + 9.806, + 8.364, + 9.04, + 7.655, + 8.563, + 7.962, + 8.164, + 8.299, + 7.665, + 10.986, + 9.15, + 7.977, + 7.527, + 10.64, + 9.113, + 7.919, + 7.69, + 7.621, + 7.728, + 8.086, + 9.231, + 10.358, + 9.916, + 7.311, + 7.785, + 10.652, + 10.178, + 9.769, + 8.149, + 16.809, + 7.874, + 8.654, + 9.669, + 9.797, + 7.939, + 7.493, + 10.714, + 10.374, + 7.664, + 8.168, + 8.494, + 7.941, + 7.393, + 7.198, + 7.468, + 9.911, + 9.976, + 8.714, + 24.48, + 20.845, + 9.394, + 7.786, + 12.619, + 11.64, + 9.33, + 7.566, + 13.01, + 7.962, + 9.496, + 7.944, + 9.727, + 8.594, + 7.322, + 9.803, + 8.286, + 7.432, + 7.865, + 8.55, + 11.213, + 7.327, + 7.201, + 7.576, + 10.294, + 7.549, + 7.344, + 11.299, + 8.03 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142747_943f49e4-3b33-4bb9-812c-52342de6a73e.mp4", + "tMs": -27527 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142747_69d705cc-fbc7-4e96-ad6f-1144b0e9e220.mp4", + "tMs": -27526 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142747_71596698-7468-4672-8723-1f7dbb89f53d.mp4", + "tMs": -27521 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142747_f29822a3-9ae2-4e38-bb1d-3ba0f677dc12.mp4", + "tMs": -27515 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142800_57faf231-2f55-48d7-9b49-e3c63445fee8.mp4", + "tMs": -17317 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142800_e03ea528-402c-4ca8-a336-5fb671940aea.mp4", + "tMs": -17311 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142800_09361389-845f-4860-a07e-5f7ae4e10e35.mp4", + "tMs": -17304 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142800_a9f47325-fcd2-4eea-a3d5-fb6a5aa9ce70.mp4", + "tMs": -17298 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142810_457f5138-7a9b-42c6-a8fa-2df2b7339c03.mp4", + "tMs": -7318 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142810_54566ea8-15af-4064-a09f-560614a44e37.mp4", + "tMs": -7313 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142810_6898b0ad-5a87-413f-a98f-79f83d3376ff.mp4", + "tMs": -7308 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142810_af8ae053-4767-4112-a12b-8f5f325a1e14.mp4", + "tMs": -7298 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142820_0e4baf73-79e3-4dfd-9338-0406ec94d19d.mp4", + "tMs": 2683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142820_bf70e643-3f47-463d-b6d9-9e2bd7367516.mp4", + "tMs": 2691 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142820_e4da86bd-f5a1-4ba2-8e2b-a4248d0554ca.mp4", + "tMs": 2692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142820_65656871-43da-451b-9489-edd04d2c7199.mp4", + "tMs": 2718 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142830_e4ab717c-761e-4da7-bca4-7428eb439d5e.mp4", + "tMs": 12683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142830_5112793d-cd86-4688-aa89-d71d3397559a.mp4", + "tMs": 12688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142830_14beb8ed-ec1a-4d91-a5c6-1cd58c725f94.mp4", + "tMs": 12692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142830_e481bb84-48bb-4d02-889a-c51bd38d3f75.mp4", + "tMs": 12698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142840_8c46649c-dbac-4999-96b1-34d016d9c706.mp4", + "tMs": 22684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142840_f3eb3922-9db8-4cb0-8475-a17974e0df13.mp4", + "tMs": 22689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142840_18b98333-2565-4b92-acf6-70fad6bc6ffc.mp4", + "tMs": 22694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142840_0e35b4c9-3bbe-4474-8ca2-c4e1756c3061.mp4", + "tMs": 22701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142850_e5cbec8d-3e8c-4ca4-a0b7-3fb64e0c2020.mp4", + "tMs": 32686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142850_72cabfab-0f03-4b86-951f-f9d625954bea.mp4", + "tMs": 32686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142850_4c06aa4d-5a54-4dcb-ad12-45f35afde82e.mp4", + "tMs": 32689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142850_0c57fbb1-4cb0-4c4e-b8d1-cdac582f09e2.mp4", + "tMs": 32695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142900_ed663155-5f1b-426d-9101-fff1fe320270.mp4", + "tMs": 42682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142900_bb4c49a3-3258-48a7-be84-3fd773690528.mp4", + "tMs": 42689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142900_ea2d3a63-a703-49ae-8b72-85ff878251d6.mp4", + "tMs": 42696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142900_8cb1c094-8bc0-4ae5-80be-65444a4f87bd.mp4", + "tMs": 42699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142910_1ef4f4a3-f97a-4264-9bf5-7df9be974a72.mp4", + "tMs": 52682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142910_80fa2f67-2735-43a6-b6aa-3edc2977daa7.mp4", + "tMs": 52689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142910_941f4410-6877-4d9c-a621-cf6d4558d721.mp4", + "tMs": 52694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142910_c3b40445-910d-44f8-b2ba-cf4f0da3ac8e.mp4", + "tMs": 52702 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142920_d66ad2c4-adcc-48a5-9103-8de209c135a1.mp4", + "tMs": 62683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142920_2959ba92-4d28-44e8-9ad4-e56b473a75fb.mp4", + "tMs": 62688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142920_bfc6639e-7dfa-495b-96e4-ff229a79181a.mp4", + "tMs": 62697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142920_ae29cce3-2df0-446d-b451-251186ed9917.mp4", + "tMs": 62701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142930_08020752-6839-4010-8e6c-61ac84e55bf6.mp4", + "tMs": 72683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142930_eb300c9a-bfb0-4437-9447-11c89e7aa722.mp4", + "tMs": 72687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142930_090c00f6-7436-4132-b594-a47ed9bec253.mp4", + "tMs": 72691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142930_1d86f8a9-40f0-461c-ad96-3d46ee4efa68.mp4", + "tMs": 72696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142940_a5036845-a791-4d17-abd9-43e35fd54ede.mp4", + "tMs": 82686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142940_5ce6ff4c-81dc-4b5e-8d03-fd76d7022dc9.mp4", + "tMs": 82689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142940_50ae0b49-ddf6-4d9f-9c3c-95853b9639ed.mp4", + "tMs": 82695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142940_c6d85cd9-3301-41f5-afd0-f09227d291dd.mp4", + "tMs": 82698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142950_8b3b5063-8233-4a30-b295-e9953a0b9c07.mp4", + "tMs": 92684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142950_06a38e0a-6d4e-4725-9ee1-c28c4c1ee2b3.mp4", + "tMs": 92688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142950_ceeab029-3d8f-4ab1-acd3-f3248bf5c165.mp4", + "tMs": 92693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142950_ffbb1591-753a-43f9-8c8d-9b641ef5f366.mp4", + "tMs": 92701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143000_651d9317-8c2b-4e8c-aaba-6cef61f10a3a.mp4", + "tMs": 102689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143000_6e1d9b6b-c4de-4f8e-984a-8a250872ff62.mp4", + "tMs": 102705 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143000_69e3cb6e-c126-45fa-8bf1-1704490cb7d6.mp4", + "tMs": 102705 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143000_aa5cbd35-b1dc-44c1-8359-6d39c658a8be.mp4", + "tMs": 102705 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143010_13028d95-4a28-480c-a53e-9f26a5ad2a63.mp4", + "tMs": 112682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143010_c6ffae38-4fed-418e-a4ed-6836b1f5c2b4.mp4", + "tMs": 112687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143010_16078ce6-555c-4515-a517-2f69f3e2c791.mp4", + "tMs": 112692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143010_ea54f63e-6427-4f79-997f-5b7e0c062db1.mp4", + "tMs": 112698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143020_333e2c9e-3ad1-44be-90e1-87e3086666aa.mp4", + "tMs": 122683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143020_9b39bb02-79ae-4452-bd9d-349fa3f30a1b.mp4", + "tMs": 122689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143020_0deeedd5-7431-4e68-9f14-ef17c2969924.mp4", + "tMs": 122695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143020_156fb873-2f42-4a63-a884-f2759f623708.mp4", + "tMs": 122704 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143030_67fbdd1c-3d1e-4e56-b9b0-73cfe9dfd2e1.mp4", + "tMs": 132692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143030_3dc6f919-7a5a-4f16-87dd-35c93972229b.mp4", + "tMs": 132694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143030_a973035c-4afc-4864-b5ac-43b5f92d0a82.mp4", + "tMs": 132709 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143030_34389552-dc78-4c3b-af06-9765b6820038.mp4", + "tMs": 132712 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143040_ca2e7667-9a90-4019-9d9d-9117c5e2ada3.mp4", + "tMs": 142682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143040_e7824d28-6686-4e7a-bbad-6d93dd120587.mp4", + "tMs": 142688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143040_221b0cd9-378b-474a-ad04-bae0a603f9a2.mp4", + "tMs": 142695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143040_712802e8-9a69-44a5-aee5-9a3304b13e8d.mp4", + "tMs": 142708 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143050_f7d9f05e-0501-4392-8684-e7815c54011b.mp4", + "tMs": 152683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143050_78381adf-6f0f-40c2-ab00-400d60c5badf.mp4", + "tMs": 152686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143050_7efed9ed-43d6-4908-b25c-0de4766bff16.mp4", + "tMs": 152693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143050_67add2d5-6bfa-43d7-a75d-3431e56a16b1.mp4", + "tMs": 152697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143100_e89b2ee5-dc6d-4268-b955-4ec9bdd87d8a.mp4", + "tMs": 162687 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143100_f88d6256-8698-4f2a-8903-2bf673573eef.mp4", + "tMs": 162687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143100_bba6ec4c-eeae-4c56-9d6b-bf09a5dbdaf6.mp4", + "tMs": 162696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143100_732ca75c-d77b-4aa6-ab13-bde651c34dc6.mp4", + "tMs": 162697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143110_3c77fd42-7660-4ad6-b71e-d45ac79913a8.mp4", + "tMs": 172683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143110_a71c4c30-4301-4c26-b16f-d12c604f0870.mp4", + "tMs": 172687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143110_3f14ac54-a91e-4d57-8813-a55fbd70edb5.mp4", + "tMs": 172696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143110_aecd8216-1c1b-4d16-98e9-3d5668e3d987.mp4", + "tMs": 172697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143120_523b61d2-0859-4109-b260-5468056e35e7.mp4", + "tMs": 182683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143120_d9dbf87f-595e-462f-91a3-11aac47ec44b.mp4", + "tMs": 182685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143120_01642ed3-42e8-4635-8179-03d2f2eb03dc.mp4", + "tMs": 182691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143120_6fb19c2b-d8d2-4e3f-9610-01a5e55e14a7.mp4", + "tMs": 182697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143130_3aa384ce-1591-4207-836c-0156e481f0dc.mp4", + "tMs": 192682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143130_c395c1d2-91b8-42e2-ae82-105c97637432.mp4", + "tMs": 192685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143130_d3b91748-35a6-4078-b21a-477950901c87.mp4", + "tMs": 192691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143130_9bfcdfec-17c4-4e24-89c4-15d2b1cfd9cc.mp4", + "tMs": 192696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143140_ea485b15-811d-45c2-a99c-8789bd13a475.mp4", + "tMs": 202683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143140_2a89ab48-bc5a-4acc-848d-9e43d9c53772.mp4", + "tMs": 202690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143140_1a73f881-f760-421d-95b6-59f4a0a5ae2d.mp4", + "tMs": 202697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143140_3cffa99c-5741-4242-b78f-3d4ba446d10e.mp4", + "tMs": 202704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143150_ee8aae8d-8924-4db9-a2b4-02e2096a2aa9.mp4", + "tMs": 212685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143150_beb5d6bf-420a-4f29-b2b9-fca6dde676d6.mp4", + "tMs": 212689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143150_400bf5af-b817-4d9b-98b1-82ae85b24d71.mp4", + "tMs": 212695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143150_5e0afcdd-3302-4835-a46c-7bf6ad88e97b.mp4", + "tMs": 212698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143200_fb254ad8-3e81-40a6-a1c1-e8bba134787d.mp4", + "tMs": 222683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143200_4e41f515-2431-477d-8513-54ea3ee2c036.mp4", + "tMs": 222689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143200_d4ec30f3-a642-479d-bc65-8f579a58e544.mp4", + "tMs": 222694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143200_dbb28ba0-9976-40cb-a911-10f754eb1fd5.mp4", + "tMs": 222699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143210_0b583d12-e02f-4e48-bef3-1f78b0088972.mp4", + "tMs": 232682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143210_2e0b394e-5505-4362-80a0-66f69d460900.mp4", + "tMs": 232686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143210_43c95d4c-4a0d-4db6-a1bd-c58cc5904ad6.mp4", + "tMs": 232693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143210_8c3edb4e-65a5-4427-895c-ed98da51cf8f.mp4", + "tMs": 232699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143220_eae67ca1-60d1-4a6d-8b66-a53b4c5f4220.mp4", + "tMs": 242682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143220_f05b4fd1-a9ac-4b0e-9732-617d9d08ab28.mp4", + "tMs": 242689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143220_b407c0a4-6e70-4050-87cb-e6cc9b3eeade.mp4", + "tMs": 242693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143220_eb52500c-0e73-4bc5-801e-45faee4e9503.mp4", + "tMs": 242701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143230_99337baa-124b-4226-99e1-a91521eb404f.mp4", + "tMs": 252683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143230_f50b4a05-6006-4b6d-9f6c-a3ee814e60fa.mp4", + "tMs": 252688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143230_5c9e27a6-7de1-4baa-9c11-fee7e549bd5a.mp4", + "tMs": 252692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143230_b2d0b379-5229-4a2c-9032-ce9a2923ff88.mp4", + "tMs": 252700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143240_59e3170c-0b14-497c-bdf3-6d6ddc52b62e.mp4", + "tMs": 262686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143240_b915300d-3e2a-4a7c-acee-b61522d562a4.mp4", + "tMs": 262689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143240_7e17ac52-6743-4198-a88d-b7e364e63eb7.mp4", + "tMs": 262694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143240_840bae0b-2f26-4b6c-8f9a-a65a480eadc9.mp4", + "tMs": 262702 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143250_86c192fa-fb49-4fa4-92b3-26a80a189e4e.mp4", + "tMs": 272683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143250_3eab3fbf-18dc-4a92-9b86-20b21e88aeb0.mp4", + "tMs": 272694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143250_e7a23e67-b7fd-45df-bf7a-e1b2623dc9c5.mp4", + "tMs": 272694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143250_82a74d52-f266-4994-9a25-a0924c748597.mp4", + "tMs": 272700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143300_f32989f0-5681-4e60-aceb-e9a53ae45ce9.mp4", + "tMs": 282683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143300_ca807fcd-a9d5-40a3-98e0-c50fa1594cfc.mp4", + "tMs": 282694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143300_089ac2a5-0ed7-4b55-a955-3d2261481f03.mp4", + "tMs": 282698 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143300_ffea0d97-2edb-403d-afdd-e34171c080bb.mp4", + "tMs": 282702 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143310_3bd2272d-08c2-42f5-97f6-452714fba595.mp4", + "tMs": 292692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143310_06d4f8a3-ff57-4dc4-932d-bd5a51f8a0a5.mp4", + "tMs": 292699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143310_f454358c-1e21-4418-a091-9e1a7ad1ffee.mp4", + "tMs": 292703 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143310_b4d9f6c1-d5a0-410d-bfbd-9a54e9013453.mp4", + "tMs": 292721 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143320_e5412b76-3ff9-4ae1-bf43-83e98c24f5a4.mp4", + "tMs": 302682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143320_c50cdd75-ae6d-4ef9-9bdb-c72b2f5876d4.mp4", + "tMs": 302688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143320_3beacc5d-95a1-494f-96dd-430d693b010a.mp4", + "tMs": 302694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143320_8b0268dd-d290-445b-904f-be8adfe7b86c.mp4", + "tMs": 302698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143330_ed01290f-437d-4d51-9194-82a1d771e8cf.mp4", + "tMs": 312684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143330_fbbccb55-d4b0-4dfb-9fff-1f1240341a71.mp4", + "tMs": 312687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143330_ee1dad0b-22df-4c83-87b2-6d29a3e95ae2.mp4", + "tMs": 312693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143330_5e80a4de-70ad-4a74-ae72-8e84805a601d.mp4", + "tMs": 312699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143340_963cfd46-466a-4c9b-baf1-8c3badfccc94.mp4", + "tMs": 322682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143340_8fb1a9e8-e874-4373-a260-c9865c4e5799.mp4", + "tMs": 322687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143340_d5aad2d5-f98c-429e-9dc5-9fc9f82a94f5.mp4", + "tMs": 322694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143340_b48fe184-594d-4dba-898e-ae95667525d3.mp4", + "tMs": 322707 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143350_897ed8ef-d776-4b7b-a347-708117c0c01a.mp4", + "tMs": 332681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143350_902cda6c-0d14-4d3e-9a9f-6593dfb273d8.mp4", + "tMs": 332686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143350_6b9d83f3-b94b-4b71-811a-50df3c238eba.mp4", + "tMs": 332692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143350_a79bf5d6-1ee3-4991-b20b-fd9eefc1ce60.mp4", + "tMs": 332697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143400_97d46759-45ff-474d-8c20-24f179be7601.mp4", + "tMs": 342682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143400_49e089bc-e163-44e3-84b3-e9189de1eaca.mp4", + "tMs": 342685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143400_e72eff8a-9d50-419d-a864-89fbcfa4d447.mp4", + "tMs": 342690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143400_643d03b8-8efd-4338-b25d-fefc60b53811.mp4", + "tMs": 342697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143410_8051df66-c2bd-4156-ac53-fd9a82eb6359.mp4", + "tMs": 352684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143410_ed370d4d-dec4-4ec8-8e22-b99f48a3eee9.mp4", + "tMs": 352686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143410_80c133f0-d5dc-4819-8bd5-0c6f4a313948.mp4", + "tMs": 352693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143410_20fdc0c4-d21a-429e-a711-635ea08af07d.mp4", + "tMs": 352699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143420_2836d22b-a41f-44c2-86f6-91b56119429f.mp4", + "tMs": 362709 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143420_2c845def-833d-4518-ba86-d46839e0871b.mp4", + "tMs": 362710 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143420_93c1f456-5a61-474f-b7bd-e60ecdc86f9d.mp4", + "tMs": 362711 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143420_ae623f6c-f651-4ee1-bea1-8b8b20e4826e.mp4", + "tMs": 362711 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143430_d890c368-c074-45c2-9745-9d97a30ebf66.mp4", + "tMs": 372683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143430_1324c15f-dd79-4a8c-a0c4-45f5767dbda1.mp4", + "tMs": 372689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143430_3e03982c-281c-41e0-8081-4f0870dbb110.mp4", + "tMs": 372696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143430_c488314b-0966-491b-a53a-c4bcc3cef14c.mp4", + "tMs": 372703 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143440_c390a596-0548-4f35-95b2-519f7c2313dd.mp4", + "tMs": 382683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143440_16d6adc9-f65b-4501-ad94-1ffe063417cf.mp4", + "tMs": 382689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143440_8a37736f-995a-4173-ac89-7be96faac992.mp4", + "tMs": 382701 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143440_cee8b0ff-4bb1-46bb-88b9-3c415d033220.mp4", + "tMs": 382703 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143450_ddee1ad9-a82c-4192-8e00-617c50fd9cca.mp4", + "tMs": 392683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143450_60f2f57e-729e-47ef-b13b-9a0a607f65a6.mp4", + "tMs": 392689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143450_0aadc7d8-ce2d-41ff-801a-9859da2c8ed4.mp4", + "tMs": 392695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143450_1c1a5c1d-8d6f-4483-b90d-4dcbd5194536.mp4", + "tMs": 392700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143500_7c287c93-00f4-4401-9ee8-846a37762546.mp4", + "tMs": 402684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143500_aab18e7c-dc6f-4718-a56a-d91c458a1a09.mp4", + "tMs": 402690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143500_0b6c2008-d2ca-4a29-a64a-c6f8c0847dad.mp4", + "tMs": 402694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143500_fad972ff-969f-42de-9a26-84ed4ba83e18.mp4", + "tMs": 402702 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143510_66f655b5-758e-4687-96de-983528245707.mp4", + "tMs": 412682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143510_68b58fb6-6908-4855-b385-b7b33d5ac351.mp4", + "tMs": 412690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143510_48822629-1983-4e50-9df1-b97f979cbee3.mp4", + "tMs": 412693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143510_aa82bfa4-358f-49eb-b2c9-35ac664ba7eb.mp4", + "tMs": 412700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143520_a88e2964-9d31-40c4-a3c5-08239f1bac65.mp4", + "tMs": 422683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143520_51a6d94e-80aa-4296-9311-5142b5d4f318.mp4", + "tMs": 422694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143520_b0135ed7-1313-4348-aad0-e2521eae822c.mp4", + "tMs": 422694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143520_1b38130d-f021-48cf-ae93-abe74dd28841.mp4", + "tMs": 422701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143530_8edb4f5f-e4f5-40b4-aaed-ca3716de8704.mp4", + "tMs": 432680 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143530_237a493c-697a-4b6b-a839-deae0987c696.mp4", + "tMs": 432689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143530_f23c8676-5b76-4b9d-940f-8a44e15ba1a4.mp4", + "tMs": 432699 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143530_ffd47d5f-3a9a-42ec-a337-8db7616c3377.mp4", + "tMs": 432701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143540_24ddf5c3-a4b9-43cd-8d96-a483f75b497f.mp4", + "tMs": 442682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143540_efef6efb-5081-4137-8e66-28ad6a60c715.mp4", + "tMs": 442686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143540_4a470abd-b793-4d27-83ea-bf0626145f3f.mp4", + "tMs": 442692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143540_281e1017-f262-4243-a96d-0729183d0020.mp4", + "tMs": 442697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143550_6c94713b-979c-4e93-a445-cb713c9e75f1.mp4", + "tMs": 452682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143550_fd6357a3-68bc-481b-b6ca-923b2f93bd67.mp4", + "tMs": 452688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143550_9c703215-18aa-41ec-a922-ed38c08b28b7.mp4", + "tMs": 452693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143550_5ed4b9a7-744a-47c6-834b-b7caeb0b7f36.mp4", + "tMs": 452699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143600_ef4d7e39-56bd-42fa-876d-9f0e81cf6beb.mp4", + "tMs": 462685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143600_7703c3fa-da63-4c2f-a361-f196aa3eefa6.mp4", + "tMs": 462688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143600_4c2f71f9-afd4-45b7-89e4-2f0aeb301ff8.mp4", + "tMs": 462696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143600_2b33cf08-6ed8-4a0d-a042-f6827dbb9463.mp4", + "tMs": 462701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143610_86d61346-d887-4aad-a05e-2ffaf1194a5b.mp4", + "tMs": 472680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143610_731c8f7e-2aa3-4a62-8fbe-2756a6de4a3b.mp4", + "tMs": 472690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143610_d780763e-e71d-445d-ac60-b4db06dd9496.mp4", + "tMs": 472698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143610_91345f1e-2c70-4a98-9c89-27e13a93bd83.mp4", + "tMs": 472698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143620_af81d2a4-a5ae-42e9-82af-ccb25aa8f962.mp4", + "tMs": 482682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143620_79239b56-b8f7-49e6-bb10-525be6ce8c09.mp4", + "tMs": 482689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143620_5242a719-1077-4b3d-ad35-d0ffdab99e3f.mp4", + "tMs": 482699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143620_7a2c2f35-17d6-4915-b916-cc4416e02f76.mp4", + "tMs": 482700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143630_b2b582d3-9ba0-40fe-90bb-52c85a68a756.mp4", + "tMs": 492682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143630_8b95b5d8-1a92-41f9-80c4-636277c3c949.mp4", + "tMs": 492687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143630_2a2b1f9e-db90-43d0-a0d3-01050651f917.mp4", + "tMs": 492696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143630_fe16b33a-a5b2-47d3-ab21-e100261ae2a7.mp4", + "tMs": 492704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143640_d03b178d-9a20-45df-a129-c9daa2123ab7.mp4", + "tMs": 502682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143640_bd6e2456-8f63-43d1-a3be-0494c66191b6.mp4", + "tMs": 502687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143640_e98cc182-d0ce-425a-8586-76d9d3f7ffe4.mp4", + "tMs": 502692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143640_850b5e47-94d8-4cbc-986b-efece7cc5f21.mp4", + "tMs": 502699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143650_f6d7db03-b79c-484a-95b5-ad64cc70703a.mp4", + "tMs": 512682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143650_a97e63e1-6c97-4968-ab04-0f65ef2648c8.mp4", + "tMs": 512687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143650_54554381-6510-4111-8ae5-46e7d4ae1a3e.mp4", + "tMs": 512692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143650_21a3a61f-d37b-48e5-b5f3-068b535d5324.mp4", + "tMs": 512696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143700_7ec5e752-f1a8-4a1e-9285-f5b719cafbf8.mp4", + "tMs": 522684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143700_24c106bf-f603-4ba4-ada9-3b01ec943eb4.mp4", + "tMs": 522686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143700_85faf425-e836-4898-9c26-77ba04cba336.mp4", + "tMs": 522695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143700_40f71451-ad98-4581-876b-5017edd75581.mp4", + "tMs": 522699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143710_6f08f72a-3828-4b7e-bcad-8b1779af51e0.mp4", + "tMs": 532681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143710_fb10592c-6499-40ce-bf26-c83f29947aa2.mp4", + "tMs": 532687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143710_94cd484d-586f-457a-9b1d-6fa508d3a31a.mp4", + "tMs": 532694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143710_3a907a3d-281e-44de-b2bc-99e16585de97.mp4", + "tMs": 532697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143720_5269f97c-53fe-4160-b0cf-a8861fdbf7c1.mp4", + "tMs": 542683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143720_e67a9e17-96a1-481d-8c88-b5c9fe6dbe8b.mp4", + "tMs": 542689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143720_a84df6e3-ac5e-43b2-96ba-4e72f768e31e.mp4", + "tMs": 542694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143720_2268e57b-fb90-499b-b47e-a35a35677724.mp4", + "tMs": 542701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143730_de041e10-ac76-4724-b955-736bf291c685.mp4", + "tMs": 552683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143730_6de546a0-914a-403f-88dc-ac0545cb8e82.mp4", + "tMs": 552687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143730_d989a4ae-c479-45be-827b-49bed99627fa.mp4", + "tMs": 552692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143730_1f6a9416-08eb-429e-bcb2-5e742aca9fdc.mp4", + "tMs": 552701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143740_285ba303-1b06-4842-9966-33ab3d609f42.mp4", + "tMs": 562682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143740_cfb3beba-91b5-4bc0-8827-1d0c329a499c.mp4", + "tMs": 562690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143740_170d8d41-8337-4055-8de2-017bb946ffa8.mp4", + "tMs": 562694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143740_5d25abd5-6ce2-4c1e-8a12-9f1865e67be6.mp4", + "tMs": 562709 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143750_b51e478e-71c2-4a1e-9376-b5d532071a9f.mp4", + "tMs": 572687 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143750_ea7db908-81c2-42d5-a818-830c26d72d25.mp4", + "tMs": 572689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143750_af007852-8b4f-4f34-a1b3-a4e027a0fa69.mp4", + "tMs": 572700 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143750_0462b24b-2b53-407a-91ad-40d3fed59765.mp4", + "tMs": 572719 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143800_68f5b492-5895-40d9-bb43-fc32186b9f5f.mp4", + "tMs": 582681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143800_63bfb109-6c17-4ae0-8553-b61cf6acb973.mp4", + "tMs": 582686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143800_0106c008-3274-41ed-b2a4-5ed36a1e3e65.mp4", + "tMs": 582691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143800_84362137-bf90-4ee7-909f-7e2c7882c1c9.mp4", + "tMs": 582700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143810_23eb9f46-c0ca-41b9-b555-30b51de56c2d.mp4", + "tMs": 592683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143810_b3499cca-c470-49dc-bd41-04406ea8d9df.mp4", + "tMs": 592687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143810_322c3715-0912-4145-ac19-1edb8c1b894d.mp4", + "tMs": 592704 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143810_f026d381-107b-4ad6-ae7d-39e497d825c4.mp4", + "tMs": 592708 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143820_221fc272-5c17-4897-95dc-b477144dbf79.mp4", + "tMs": 602681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143820_2bcba666-06bb-4423-b8e8-23e7b789d002.mp4", + "tMs": 602688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143820_bb532cf1-d7f2-42d9-8f86-ebc7651cf0cf.mp4", + "tMs": 602692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143820_562493e8-ae30-41d8-acb1-b30a860cff2e.mp4", + "tMs": 602703 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 0, + "assets": { + "by_state_reason": {}, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 0, + "ready_asset_bytes_max": 0, + "detections_total": 2528, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2528 + } + }, + "finished_at": "2026-11-08T19:38:25.949Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-disabled-run3.json b/tests/performance/observations/results/2026-11-08-x2-disabled-run3.json new file mode 100644 index 000000000..157a6c5da --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-disabled-run3.json @@ -0,0 +1,8670 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "disabled", + "status": "completed", + "started_at": "2026-11-08T19:39:11.193Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 3.21, + 3.1, + 3.71 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "405055d8456e5a351ce4dc41ae15057c8885b2ab150071a9a27c201cf6e8e4d9", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": {}, + "config_extra": null, + "system_settings": {} + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-nWAku5", + "detector": { + "inferences_in_window": 2408, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 2408, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 0, + "dropped": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 0, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 0, + "high_water_items": 0, + "high_water_bytes": 0, + "camera_high_water_items": 0, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 33554432, + "camera_bytes": 33554432, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 0, + "inline": 0 + }, + "discard_pending_bytes_max": 0, + "backlog_items_max": 0, + "backlog_bytes_max": 0, + "drop_ring_depth_max": 0, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0, + "queue_allocated_end": 0, + "worker_running_end": 0, + "retained_bytes_by_camera_end": {}, + "budget_bytes_by_camera_end": {}, + "history_observations_end": 0, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 0, + "sum_seconds": 0, + "mean_seconds": null, + "max_seconds": 0, + "p50_upper_seconds": "+Inf", + "p99_upper_seconds": "+Inf", + "p999_upper_seconds": "+Inf", + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 0 + }, + { + "le": 0.00001, + "count": 0 + }, + { + "le": 0.00005, + "count": 0 + }, + { + "le": 0.0001, + "count": 0 + }, + { + "le": 0.00025, + "count": 0 + }, + { + "le": 0.0005, + "count": 0 + }, + { + "le": 0.001, + "count": 0 + }, + { + "le": 0.01, + "count": 0 + }, + { + "le": "+Inf", + "count": 0 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "window_unix": { + "start": 1794166790, + "end": 1794167392 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9969.527, + "p50": 9999.949, + "p95": 10016.068, + "p99": 10022.559, + "max": 10035.628, + "mean": 10000.151 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -30.473, + "p50": -0.051, + "p95": 16.068, + "p99": 22.559, + "max": 35.628, + "mean": 0.151 + }, + "db_probe_ms": { + "count": 2318, + "min": 7.042, + "p50": 8.13, + "p95": 13.401, + "p99": 18.628, + "max": 38.949, + "mean": 8.898 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9975.363, + "p50": 10001.634, + "p95": 10016.068, + "p99": 10018.678, + "max": 10018.798, + "mean": 10000.405 + }, + "Load1": { + "count": 60, + "min": 9969.527, + "p50": 9999.342, + "p95": 10016.074, + "p99": 10026.012, + "max": 10035.628, + "mean": 9999.915 + }, + "Load2": { + "count": 60, + "min": 9975.501, + "p50": 9999.879, + "p95": 10019.991, + "p99": 10025.022, + "max": 10028.198, + "mean": 10000.196 + }, + "Load3": { + "count": 60, + "min": 9977.879, + "p50": 10000.249, + "p95": 10009.636, + "p99": 10016.255, + "max": 10019.812, + "mean": 10000.087 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032217280, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.1, + "mean": 15.1 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 599.02, + "cpu_percent_one_core": { + "count": 519, + "min": 117.225, + "p50": 145.669, + "p95": 198.316, + "p99": 205.255, + "max": 210.014, + "mean": 155.145 + }, + "cpu_percent_one_core_avg": 155.534, + "rss_kib": { + "count": 520, + "min": 768892, + "p50": 773878, + "p95": 870949.6, + "p99": 872089.72, + "max": 873260, + "mean": 800991.331 + }, + "rss_high_water_kib": 873260, + "disk_write_bytes": 1232535552, + "disk_write_bytes_per_sec": 2057587.986, + "threads_max": 34, + "fds_max": 38 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.088, + "processes": 5 + }, + "storage_bytes_end": 1086895166, + "loadavg_at_end": [ + 7.79, + 7.43, + 5.76 + ] + }, + "window": { + "start": "2026-11-08T19:39:50.398Z", + "end": "2026-11-08T19:49:51.578Z", + "seconds": 601.18 + }, + "samples": { + "proc": [ + { + "tMs": 10, + "cpuTicks": 5076, + "rssKiB": 864280, + "peakRssKiB": 864280, + "threads": 34, + "fds": 36, + "writeBytes": 60706816 + }, + { + "tMs": 1153, + "cpuTicks": 5248, + "rssKiB": 768892, + "peakRssKiB": 860740, + "threads": 34, + "fds": 36, + "writeBytes": 62763008 + }, + { + "tMs": 2318, + "cpuTicks": 5389, + "rssKiB": 769280, + "peakRssKiB": 861528, + "threads": 34, + "fds": 36, + "writeBytes": 64598016 + }, + { + "tMs": 3470, + "cpuTicks": 5548, + "rssKiB": 769832, + "peakRssKiB": 861816, + "threads": 34, + "fds": 36, + "writeBytes": 68206592 + }, + { + "tMs": 4617, + "cpuTicks": 5703, + "rssKiB": 769984, + "peakRssKiB": 862192, + "threads": 34, + "fds": 36, + "writeBytes": 70082560 + }, + { + "tMs": 5811, + "cpuTicks": 5909, + "rssKiB": 867108, + "peakRssKiB": 867108, + "threads": 34, + "fds": 36, + "writeBytes": 73273344 + }, + { + "tMs": 6950, + "cpuTicks": 6122, + "rssKiB": 867192, + "peakRssKiB": 867192, + "threads": 34, + "fds": 36, + "writeBytes": 75132928 + }, + { + "tMs": 8100, + "cpuTicks": 6317, + "rssKiB": 769244, + "peakRssKiB": 862908, + "threads": 34, + "fds": 36, + "writeBytes": 77398016 + }, + { + "tMs": 9245, + "cpuTicks": 6469, + "rssKiB": 769196, + "peakRssKiB": 862908, + "threads": 34, + "fds": 36, + "writeBytes": 79245312 + }, + { + "tMs": 10390, + "cpuTicks": 6621, + "rssKiB": 769864, + "peakRssKiB": 862908, + "threads": 34, + "fds": 36, + "writeBytes": 81121280 + }, + { + "tMs": 11520, + "cpuTicks": 6781, + "rssKiB": 769880, + "peakRssKiB": 862908, + "threads": 34, + "fds": 36, + "writeBytes": 83009536 + }, + { + "tMs": 12649, + "cpuTicks": 6937, + "rssKiB": 769896, + "peakRssKiB": 862908, + "threads": 34, + "fds": 36, + "writeBytes": 84873216 + }, + { + "tMs": 13875, + "cpuTicks": 7184, + "rssKiB": 867232, + "peakRssKiB": 867232, + "threads": 34, + "fds": 36, + "writeBytes": 89780224 + }, + { + "tMs": 15031, + "cpuTicks": 7404, + "rssKiB": 843088, + "peakRssKiB": 862908, + "threads": 34, + "fds": 36, + "writeBytes": 91721728 + }, + { + "tMs": 16183, + "cpuTicks": 7569, + "rssKiB": 770876, + "peakRssKiB": 863104, + "threads": 34, + "fds": 36, + "writeBytes": 93761536 + }, + { + "tMs": 17307, + "cpuTicks": 7719, + "rssKiB": 770884, + "peakRssKiB": 863656, + "threads": 34, + "fds": 36, + "writeBytes": 95604736 + }, + { + "tMs": 18443, + "cpuTicks": 7873, + "rssKiB": 770896, + "peakRssKiB": 863656, + "threads": 34, + "fds": 36, + "writeBytes": 97476608 + }, + { + "tMs": 19569, + "cpuTicks": 8038, + "rssKiB": 770572, + "peakRssKiB": 863656, + "threads": 34, + "fds": 36, + "writeBytes": 99340288 + }, + { + "tMs": 20771, + "cpuTicks": 8225, + "rssKiB": 867352, + "peakRssKiB": 867352, + "threads": 34, + "fds": 36, + "writeBytes": 101597184 + }, + { + "tMs": 21946, + "cpuTicks": 8458, + "rssKiB": 867352, + "peakRssKiB": 867352, + "threads": 34, + "fds": 36, + "writeBytes": 104308736 + }, + { + "tMs": 23080, + "cpuTicks": 8643, + "rssKiB": 770660, + "peakRssKiB": 863892, + "threads": 34, + "fds": 36, + "writeBytes": 108347392 + }, + { + "tMs": 24217, + "cpuTicks": 8816, + "rssKiB": 769368, + "peakRssKiB": 863892, + "threads": 34, + "fds": 36, + "writeBytes": 110227456 + }, + { + "tMs": 25335, + "cpuTicks": 8957, + "rssKiB": 769064, + "peakRssKiB": 863892, + "threads": 34, + "fds": 36, + "writeBytes": 112099328 + }, + { + "tMs": 26461, + "cpuTicks": 9112, + "rssKiB": 770260, + "peakRssKiB": 863892, + "threads": 34, + "fds": 36, + "writeBytes": 113987584 + }, + { + "tMs": 27581, + "cpuTicks": 9261, + "rssKiB": 770904, + "peakRssKiB": 863892, + "threads": 34, + "fds": 36, + "writeBytes": 115838976 + }, + { + "tMs": 28752, + "cpuTicks": 9438, + "rssKiB": 867916, + "peakRssKiB": 867916, + "threads": 34, + "fds": 36, + "writeBytes": 118988800 + }, + { + "tMs": 29933, + "cpuTicks": 9669, + "rssKiB": 868640, + "peakRssKiB": 868640, + "threads": 34, + "fds": 36, + "writeBytes": 120786944 + }, + { + "tMs": 31105, + "cpuTicks": 9872, + "rssKiB": 771992, + "peakRssKiB": 863892, + "threads": 34, + "fds": 37, + "writeBytes": 122974208 + }, + { + "tMs": 32240, + "cpuTicks": 10042, + "rssKiB": 772540, + "peakRssKiB": 864044, + "threads": 34, + "fds": 37, + "writeBytes": 124846080 + }, + { + "tMs": 33371, + "cpuTicks": 10205, + "rssKiB": 773092, + "peakRssKiB": 864848, + "threads": 34, + "fds": 37, + "writeBytes": 128462848 + }, + { + "tMs": 34490, + "cpuTicks": 10354, + "rssKiB": 772864, + "peakRssKiB": 864848, + "threads": 34, + "fds": 36, + "writeBytes": 130310144 + }, + { + "tMs": 35604, + "cpuTicks": 10525, + "rssKiB": 772880, + "peakRssKiB": 864848, + "threads": 34, + "fds": 36, + "writeBytes": 132251648 + }, + { + "tMs": 36765, + "cpuTicks": 10719, + "rssKiB": 869892, + "peakRssKiB": 869892, + "threads": 34, + "fds": 36, + "writeBytes": 135458816 + }, + { + "tMs": 37949, + "cpuTicks": 10955, + "rssKiB": 868792, + "peakRssKiB": 868792, + "threads": 34, + "fds": 36, + "writeBytes": 137310208 + }, + { + "tMs": 39103, + "cpuTicks": 11156, + "rssKiB": 772104, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 139665408 + }, + { + "tMs": 40243, + "cpuTicks": 11312, + "rssKiB": 772208, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 141594624 + }, + { + "tMs": 41363, + "cpuTicks": 11468, + "rssKiB": 772212, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 143474688 + }, + { + "tMs": 42488, + "cpuTicks": 11633, + "rssKiB": 772212, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 145383424 + }, + { + "tMs": 43632, + "cpuTicks": 11797, + "rssKiB": 772212, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 148992000 + }, + { + "tMs": 44848, + "cpuTicks": 12022, + "rssKiB": 868576, + "peakRssKiB": 868576, + "threads": 34, + "fds": 36, + "writeBytes": 152145920 + }, + { + "tMs": 46017, + "cpuTicks": 12249, + "rssKiB": 844416, + "peakRssKiB": 865328, + "threads": 34, + "fds": 37, + "writeBytes": 154112000 + }, + { + "tMs": 47167, + "cpuTicks": 12405, + "rssKiB": 771900, + "peakRssKiB": 865328, + "threads": 34, + "fds": 38, + "writeBytes": 156164096 + }, + { + "tMs": 48308, + "cpuTicks": 12562, + "rssKiB": 771576, + "peakRssKiB": 865328, + "threads": 34, + "fds": 37, + "writeBytes": 158023680 + }, + { + "tMs": 49463, + "cpuTicks": 12713, + "rssKiB": 771576, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 159891456 + }, + { + "tMs": 50609, + "cpuTicks": 12881, + "rssKiB": 771636, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 161746944 + }, + { + "tMs": 51828, + "cpuTicks": 13097, + "rssKiB": 868004, + "peakRssKiB": 868004, + "threads": 34, + "fds": 36, + "writeBytes": 164859904 + }, + { + "tMs": 52979, + "cpuTicks": 13319, + "rssKiB": 868004, + "peakRssKiB": 868004, + "threads": 34, + "fds": 36, + "writeBytes": 168452096 + }, + { + "tMs": 54127, + "cpuTicks": 13512, + "rssKiB": 770996, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 170856448 + }, + { + "tMs": 55262, + "cpuTicks": 13674, + "rssKiB": 771228, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 172756992 + }, + { + "tMs": 56377, + "cpuTicks": 13823, + "rssKiB": 771000, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 174620672 + }, + { + "tMs": 57517, + "cpuTicks": 13975, + "rssKiB": 771324, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 176488448 + }, + { + "tMs": 58653, + "cpuTicks": 14137, + "rssKiB": 771640, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 178352128 + }, + { + "tMs": 59828, + "cpuTicks": 14347, + "rssKiB": 868320, + "peakRssKiB": 868320, + "threads": 34, + "fds": 36, + "writeBytes": 181473280 + }, + { + "tMs": 60990, + "cpuTicks": 14578, + "rssKiB": 868320, + "peakRssKiB": 868320, + "threads": 34, + "fds": 36, + "writeBytes": 183316480 + }, + { + "tMs": 62150, + "cpuTicks": 14804, + "rssKiB": 772280, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 185397248 + }, + { + "tMs": 63268, + "cpuTicks": 14973, + "rssKiB": 772280, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 188989440 + }, + { + "tMs": 64378, + "cpuTicks": 15132, + "rssKiB": 772508, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 190849024 + }, + { + "tMs": 65488, + "cpuTicks": 15296, + "rssKiB": 772052, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 192753664 + }, + { + "tMs": 66611, + "cpuTicks": 15456, + "rssKiB": 771732, + "peakRssKiB": 865328, + "threads": 34, + "fds": 37, + "writeBytes": 194584576 + }, + { + "tMs": 67783, + "cpuTicks": 15655, + "rssKiB": 868420, + "peakRssKiB": 868420, + "threads": 34, + "fds": 36, + "writeBytes": 197812224 + }, + { + "tMs": 68969, + "cpuTicks": 15904, + "rssKiB": 868096, + "peakRssKiB": 868096, + "threads": 34, + "fds": 36, + "writeBytes": 199708672 + }, + { + "tMs": 70106, + "cpuTicks": 16106, + "rssKiB": 771732, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 202063872 + }, + { + "tMs": 71248, + "cpuTicks": 16263, + "rssKiB": 771736, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 203976704 + }, + { + "tMs": 72390, + "cpuTicks": 16434, + "rssKiB": 771372, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 205852672 + }, + { + "tMs": 73528, + "cpuTicks": 16602, + "rssKiB": 770724, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 209448960 + }, + { + "tMs": 74656, + "cpuTicks": 16772, + "rssKiB": 770724, + "peakRssKiB": 865328, + "threads": 34, + "fds": 37, + "writeBytes": 211308544 + }, + { + "tMs": 75868, + "cpuTicks": 17010, + "rssKiB": 867416, + "peakRssKiB": 867416, + "threads": 34, + "fds": 36, + "writeBytes": 214511616 + }, + { + "tMs": 77032, + "cpuTicks": 17248, + "rssKiB": 867416, + "peakRssKiB": 867416, + "threads": 34, + "fds": 36, + "writeBytes": 216391680 + }, + { + "tMs": 78178, + "cpuTicks": 17430, + "rssKiB": 770728, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 218456064 + }, + { + "tMs": 79310, + "cpuTicks": 17591, + "rssKiB": 770396, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 220323840 + }, + { + "tMs": 80458, + "cpuTicks": 17763, + "rssKiB": 770400, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 222216192 + }, + { + "tMs": 81585, + "cpuTicks": 17920, + "rssKiB": 770528, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 224006144 + }, + { + "tMs": 82747, + "cpuTicks": 18130, + "rssKiB": 866760, + "peakRssKiB": 866760, + "threads": 34, + "fds": 36, + "writeBytes": 228978688 + }, + { + "tMs": 83946, + "cpuTicks": 18374, + "rssKiB": 867084, + "peakRssKiB": 867084, + "threads": 34, + "fds": 36, + "writeBytes": 230850560 + }, + { + "tMs": 85109, + "cpuTicks": 18564, + "rssKiB": 771044, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 233226240 + }, + { + "tMs": 86267, + "cpuTicks": 18707, + "rssKiB": 771044, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 235159552 + }, + { + "tMs": 87406, + "cpuTicks": 18878, + "rssKiB": 771160, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 237056000 + }, + { + "tMs": 88520, + "cpuTicks": 19038, + "rssKiB": 771808, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 238891008 + }, + { + "tMs": 89648, + "cpuTicks": 19201, + "rssKiB": 772128, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 240734208 + }, + { + "tMs": 90862, + "cpuTicks": 19436, + "rssKiB": 868844, + "peakRssKiB": 868844, + "threads": 34, + "fds": 36, + "writeBytes": 243929088 + }, + { + "tMs": 92055, + "cpuTicks": 19679, + "rssKiB": 844676, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 245800960 + }, + { + "tMs": 93198, + "cpuTicks": 19854, + "rssKiB": 772160, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 249626624 + }, + { + "tMs": 94317, + "cpuTicks": 20009, + "rssKiB": 772484, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 251478016 + }, + { + "tMs": 95441, + "cpuTicks": 20171, + "rssKiB": 772496, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 253394944 + }, + { + "tMs": 96561, + "cpuTicks": 20333, + "rssKiB": 772400, + "peakRssKiB": 865328, + "threads": 34, + "fds": 36, + "writeBytes": 255246336 + }, + { + "tMs": 97720, + "cpuTicks": 20503, + "rssKiB": 869412, + "peakRssKiB": 869412, + "threads": 34, + "fds": 36, + "writeBytes": 258408448 + }, + { + "tMs": 98902, + "cpuTicks": 20727, + "rssKiB": 869084, + "peakRssKiB": 869084, + "threads": 34, + "fds": 36, + "writeBytes": 260304896 + }, + { + "tMs": 100064, + "cpuTicks": 20951, + "rssKiB": 772400, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 262402048 + }, + { + "tMs": 101184, + "cpuTicks": 21108, + "rssKiB": 772400, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 264327168 + }, + { + "tMs": 102307, + "cpuTicks": 21268, + "rssKiB": 772724, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 266612736 + }, + { + "tMs": 103423, + "cpuTicks": 21423, + "rssKiB": 772724, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 270241792 + }, + { + "tMs": 104543, + "cpuTicks": 21582, + "rssKiB": 772724, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 272072704 + }, + { + "tMs": 105664, + "cpuTicks": 21738, + "rssKiB": 772400, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 273969152 + }, + { + "tMs": 106911, + "cpuTicks": 21997, + "rssKiB": 868764, + "peakRssKiB": 868764, + "threads": 34, + "fds": 36, + "writeBytes": 277159936 + }, + { + "tMs": 108077, + "cpuTicks": 22215, + "rssKiB": 772076, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 279236608 + }, + { + "tMs": 109202, + "cpuTicks": 22370, + "rssKiB": 772076, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 281133056 + }, + { + "tMs": 110329, + "cpuTicks": 22529, + "rssKiB": 771548, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 283037696 + }, + { + "tMs": 111469, + "cpuTicks": 22695, + "rssKiB": 771548, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 284827648 + }, + { + "tMs": 112598, + "cpuTicks": 22841, + "rssKiB": 771224, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 286650368 + }, + { + "tMs": 113773, + "cpuTicks": 23048, + "rssKiB": 867264, + "peakRssKiB": 867264, + "threads": 34, + "fds": 36, + "writeBytes": 291618816 + }, + { + "tMs": 114925, + "cpuTicks": 23266, + "rssKiB": 867264, + "peakRssKiB": 867264, + "threads": 34, + "fds": 36, + "writeBytes": 293457920 + }, + { + "tMs": 116104, + "cpuTicks": 23483, + "rssKiB": 770944, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 295608320 + }, + { + "tMs": 117231, + "cpuTicks": 23644, + "rssKiB": 770944, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 297775104 + }, + { + "tMs": 118383, + "cpuTicks": 23801, + "rssKiB": 771268, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 299614208 + }, + { + "tMs": 119536, + "cpuTicks": 23964, + "rssKiB": 771268, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 301441024 + }, + { + "tMs": 120685, + "cpuTicks": 24129, + "rssKiB": 790588, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 304648192 + }, + { + "tMs": 121905, + "cpuTicks": 24376, + "rssKiB": 867308, + "peakRssKiB": 867308, + "threads": 34, + "fds": 36, + "writeBytes": 306495488 + }, + { + "tMs": 123047, + "cpuTicks": 24580, + "rssKiB": 819612, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 310272000 + }, + { + "tMs": 124190, + "cpuTicks": 24755, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 312287232 + }, + { + "tMs": 125329, + "cpuTicks": 24905, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 314228736 + }, + { + "tMs": 126460, + "cpuTicks": 25063, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 316076032 + }, + { + "tMs": 127571, + "cpuTicks": 25220, + "rssKiB": 771916, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 317947904 + }, + { + "tMs": 128697, + "cpuTicks": 25378, + "rssKiB": 868604, + "peakRssKiB": 868604, + "threads": 34, + "fds": 36, + "writeBytes": 321126400 + }, + { + "tMs": 129917, + "cpuTicks": 25622, + "rssKiB": 868604, + "peakRssKiB": 868604, + "threads": 34, + "fds": 36, + "writeBytes": 323039232 + }, + { + "tMs": 131085, + "cpuTicks": 25831, + "rssKiB": 771916, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 325177344 + }, + { + "tMs": 132280, + "cpuTicks": 25971, + "rssKiB": 771916, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 327069696 + }, + { + "tMs": 133437, + "cpuTicks": 26115, + "rssKiB": 772240, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 331014144 + }, + { + "tMs": 134578, + "cpuTicks": 26262, + "rssKiB": 771916, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 332910592 + }, + { + "tMs": 135757, + "cpuTicks": 26437, + "rssKiB": 868604, + "peakRssKiB": 868604, + "threads": 34, + "fds": 37, + "writeBytes": 336134144 + }, + { + "tMs": 136930, + "cpuTicks": 26648, + "rssKiB": 868604, + "peakRssKiB": 868604, + "threads": 34, + "fds": 37, + "writeBytes": 338030592 + }, + { + "tMs": 138089, + "cpuTicks": 26834, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 37, + "writeBytes": 340238336 + }, + { + "tMs": 139242, + "cpuTicks": 26976, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 37, + "writeBytes": 342106112 + }, + { + "tMs": 140395, + "cpuTicks": 27125, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 344023040 + }, + { + "tMs": 141532, + "cpuTicks": 27266, + "rssKiB": 771592, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 345821184 + }, + { + "tMs": 142692, + "cpuTicks": 27425, + "rssKiB": 861988, + "peakRssKiB": 865420, + "threads": 34, + "fds": 33, + "writeBytes": 349478912 + }, + { + "tMs": 143879, + "cpuTicks": 27645, + "rssKiB": 868408, + "peakRssKiB": 868408, + "threads": 34, + "fds": 36, + "writeBytes": 352677888 + }, + { + "tMs": 145039, + "cpuTicks": 27853, + "rssKiB": 838076, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 354762752 + }, + { + "tMs": 146184, + "cpuTicks": 28004, + "rssKiB": 772692, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 357273600 + }, + { + "tMs": 147322, + "cpuTicks": 28167, + "rssKiB": 772692, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 359133184 + }, + { + "tMs": 148473, + "cpuTicks": 28307, + "rssKiB": 772692, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 360984576 + }, + { + "tMs": 149630, + "cpuTicks": 28457, + "rssKiB": 773016, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 362868736 + }, + { + "tMs": 150850, + "cpuTicks": 28685, + "rssKiB": 869080, + "peakRssKiB": 869080, + "threads": 34, + "fds": 36, + "writeBytes": 366075904 + }, + { + "tMs": 152037, + "cpuTicks": 28916, + "rssKiB": 869728, + "peakRssKiB": 869728, + "threads": 34, + "fds": 36, + "writeBytes": 367935488 + }, + { + "tMs": 153190, + "cpuTicks": 29092, + "rssKiB": 773040, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 371757056 + }, + { + "tMs": 154335, + "cpuTicks": 29241, + "rssKiB": 773040, + "peakRssKiB": 865420, + "threads": 34, + "fds": 36, + "writeBytes": 373653504 + }, + { + "tMs": 155464, + "cpuTicks": 29395, + "rssKiB": 773040, + "peakRssKiB": 865524, + "threads": 34, + "fds": 37, + "writeBytes": 375562240 + }, + { + "tMs": 156594, + "cpuTicks": 29538, + "rssKiB": 772940, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 377413632 + }, + { + "tMs": 157773, + "cpuTicks": 29730, + "rssKiB": 869500, + "peakRssKiB": 869500, + "threads": 34, + "fds": 36, + "writeBytes": 380616704 + }, + { + "tMs": 158941, + "cpuTicks": 29948, + "rssKiB": 869500, + "peakRssKiB": 869500, + "threads": 34, + "fds": 36, + "writeBytes": 382500864 + }, + { + "tMs": 160096, + "cpuTicks": 30157, + "rssKiB": 772488, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 384679936 + }, + { + "tMs": 161236, + "cpuTicks": 30303, + "rssKiB": 772488, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 386928640 + }, + { + "tMs": 162365, + "cpuTicks": 30467, + "rssKiB": 772488, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 388841472 + }, + { + "tMs": 163504, + "cpuTicks": 30622, + "rssKiB": 772488, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 392474624 + }, + { + "tMs": 164642, + "cpuTicks": 30785, + "rssKiB": 772488, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 394383360 + }, + { + "tMs": 165841, + "cpuTicks": 31010, + "rssKiB": 869176, + "peakRssKiB": 869176, + "threads": 34, + "fds": 36, + "writeBytes": 397602816 + }, + { + "tMs": 167002, + "cpuTicks": 31233, + "rssKiB": 845004, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 399564800 + }, + { + "tMs": 168150, + "cpuTicks": 31399, + "rssKiB": 771840, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 401629184 + }, + { + "tMs": 169279, + "cpuTicks": 31544, + "rssKiB": 771840, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 403566592 + }, + { + "tMs": 170415, + "cpuTicks": 31693, + "rssKiB": 771516, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 405438464 + }, + { + "tMs": 171553, + "cpuTicks": 31855, + "rssKiB": 771516, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 407273472 + }, + { + "tMs": 172677, + "cpuTicks": 32003, + "rssKiB": 771516, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 409169920 + }, + { + "tMs": 173881, + "cpuTicks": 32234, + "rssKiB": 868108, + "peakRssKiB": 868108, + "threads": 34, + "fds": 36, + "writeBytes": 414195712 + }, + { + "tMs": 175043, + "cpuTicks": 32462, + "rssKiB": 868756, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 416137216 + }, + { + "tMs": 176231, + "cpuTicks": 32639, + "rssKiB": 772088, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 418729984 + }, + { + "tMs": 177372, + "cpuTicks": 32781, + "rssKiB": 772728, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 420642816 + }, + { + "tMs": 178523, + "cpuTicks": 32932, + "rssKiB": 772728, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 422510592 + }, + { + "tMs": 179663, + "cpuTicks": 33072, + "rssKiB": 773052, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 424345600 + }, + { + "tMs": 180894, + "cpuTicks": 33312, + "rssKiB": 869740, + "peakRssKiB": 869740, + "threads": 34, + "fds": 36, + "writeBytes": 427520000 + }, + { + "tMs": 182035, + "cpuTicks": 33504, + "rssKiB": 773052, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 429662208 + }, + { + "tMs": 183186, + "cpuTicks": 33655, + "rssKiB": 773376, + "peakRssKiB": 865556, + "threads": 34, + "fds": 36, + "writeBytes": 433283072 + }, + { + "tMs": 184335, + "cpuTicks": 33803, + "rssKiB": 774024, + "peakRssKiB": 865648, + "threads": 34, + "fds": 36, + "writeBytes": 435154944 + }, + { + "tMs": 185492, + "cpuTicks": 33952, + "rssKiB": 774024, + "peakRssKiB": 865668, + "threads": 34, + "fds": 36, + "writeBytes": 437055488 + }, + { + "tMs": 186630, + "cpuTicks": 34094, + "rssKiB": 774024, + "peakRssKiB": 865668, + "threads": 34, + "fds": 36, + "writeBytes": 438935552 + }, + { + "tMs": 187800, + "cpuTicks": 34286, + "rssKiB": 871036, + "peakRssKiB": 871036, + "threads": 34, + "fds": 37, + "writeBytes": 442134528 + }, + { + "tMs": 188976, + "cpuTicks": 34514, + "rssKiB": 871036, + "peakRssKiB": 871036, + "threads": 34, + "fds": 36, + "writeBytes": 444035072 + }, + { + "tMs": 190127, + "cpuTicks": 34701, + "rssKiB": 774024, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 446504960 + }, + { + "tMs": 191264, + "cpuTicks": 34853, + "rssKiB": 774024, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 448409600 + }, + { + "tMs": 192403, + "cpuTicks": 35015, + "rssKiB": 774024, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 450297856 + }, + { + "tMs": 193539, + "cpuTicks": 35172, + "rssKiB": 773476, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 453963776 + }, + { + "tMs": 194688, + "cpuTicks": 35330, + "rssKiB": 823432, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 457158656 + }, + { + "tMs": 195910, + "cpuTicks": 35581, + "rssKiB": 868548, + "peakRssKiB": 868548, + "threads": 34, + "fds": 36, + "writeBytes": 459079680 + }, + { + "tMs": 197070, + "cpuTicks": 35804, + "rssKiB": 771632, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 461164544 + }, + { + "tMs": 198209, + "cpuTicks": 35957, + "rssKiB": 771308, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 463040512 + }, + { + "tMs": 199338, + "cpuTicks": 36127, + "rssKiB": 770664, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 464941056 + }, + { + "tMs": 200471, + "cpuTicks": 36286, + "rssKiB": 770664, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 466808832 + }, + { + "tMs": 201599, + "cpuTicks": 36448, + "rssKiB": 770664, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 468619264 + }, + { + "tMs": 202770, + "cpuTicks": 36643, + "rssKiB": 867352, + "peakRssKiB": 867352, + "threads": 34, + "fds": 36, + "writeBytes": 473600000 + }, + { + "tMs": 203936, + "cpuTicks": 36864, + "rssKiB": 867352, + "peakRssKiB": 867352, + "threads": 34, + "fds": 36, + "writeBytes": 475475968 + }, + { + "tMs": 205099, + "cpuTicks": 37064, + "rssKiB": 770988, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 478011392 + }, + { + "tMs": 206243, + "cpuTicks": 37208, + "rssKiB": 770988, + "peakRssKiB": 866216, + "threads": 34, + "fds": 37, + "writeBytes": 479948800 + }, + { + "tMs": 207384, + "cpuTicks": 37363, + "rssKiB": 771312, + "peakRssKiB": 866216, + "threads": 34, + "fds": 37, + "writeBytes": 481845248 + }, + { + "tMs": 208515, + "cpuTicks": 37509, + "rssKiB": 771312, + "peakRssKiB": 866216, + "threads": 34, + "fds": 37, + "writeBytes": 483708928 + }, + { + "tMs": 209642, + "cpuTicks": 37658, + "rssKiB": 771960, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 485593088 + }, + { + "tMs": 210815, + "cpuTicks": 37861, + "rssKiB": 868660, + "peakRssKiB": 868660, + "threads": 34, + "fds": 36, + "writeBytes": 488804352 + }, + { + "tMs": 211981, + "cpuTicks": 38083, + "rssKiB": 868884, + "peakRssKiB": 868884, + "threads": 34, + "fds": 36, + "writeBytes": 490745856 + }, + { + "tMs": 213125, + "cpuTicks": 38262, + "rssKiB": 772840, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 494657536 + }, + { + "tMs": 214282, + "cpuTicks": 38416, + "rssKiB": 773168, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 496574464 + }, + { + "tMs": 215429, + "cpuTicks": 38565, + "rssKiB": 773168, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 498524160 + }, + { + "tMs": 216555, + "cpuTicks": 38713, + "rssKiB": 773168, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 500383744 + }, + { + "tMs": 217744, + "cpuTicks": 38910, + "rssKiB": 869856, + "peakRssKiB": 869856, + "threads": 34, + "fds": 36, + "writeBytes": 503545856 + }, + { + "tMs": 218949, + "cpuTicks": 39148, + "rssKiB": 869532, + "peakRssKiB": 869532, + "threads": 34, + "fds": 36, + "writeBytes": 505466880 + }, + { + "tMs": 220102, + "cpuTicks": 39347, + "rssKiB": 772844, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 508022784 + }, + { + "tMs": 221230, + "cpuTicks": 39509, + "rssKiB": 772844, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 509960192 + }, + { + "tMs": 222361, + "cpuTicks": 39678, + "rssKiB": 773168, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 511873024 + }, + { + "tMs": 223483, + "cpuTicks": 39840, + "rssKiB": 773168, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 515551232 + }, + { + "tMs": 224605, + "cpuTicks": 40004, + "rssKiB": 772620, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 517390336 + }, + { + "tMs": 225780, + "cpuTicks": 40205, + "rssKiB": 868996, + "peakRssKiB": 868996, + "threads": 34, + "fds": 36, + "writeBytes": 520622080 + }, + { + "tMs": 226960, + "cpuTicks": 40449, + "rssKiB": 868996, + "peakRssKiB": 868996, + "threads": 34, + "fds": 36, + "writeBytes": 522584064 + }, + { + "tMs": 228122, + "cpuTicks": 40670, + "rssKiB": 772308, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 524718080 + }, + { + "tMs": 229255, + "cpuTicks": 40831, + "rssKiB": 772308, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 526622720 + }, + { + "tMs": 230379, + "cpuTicks": 41003, + "rssKiB": 772628, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 528551936 + }, + { + "tMs": 231513, + "cpuTicks": 41171, + "rssKiB": 772304, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 530386944 + }, + { + "tMs": 232642, + "cpuTicks": 41343, + "rssKiB": 772304, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 532283392 + }, + { + "tMs": 233875, + "cpuTicks": 41590, + "rssKiB": 868992, + "peakRssKiB": 868992, + "threads": 34, + "fds": 36, + "writeBytes": 537686016 + }, + { + "tMs": 235026, + "cpuTicks": 41817, + "rssKiB": 868896, + "peakRssKiB": 868896, + "threads": 34, + "fds": 36, + "writeBytes": 539623424 + }, + { + "tMs": 236153, + "cpuTicks": 42000, + "rssKiB": 772532, + "peakRssKiB": 866216, + "threads": 34, + "fds": 37, + "writeBytes": 541798400 + }, + { + "tMs": 237281, + "cpuTicks": 42172, + "rssKiB": 772852, + "peakRssKiB": 866216, + "threads": 34, + "fds": 37, + "writeBytes": 543703040 + }, + { + "tMs": 238415, + "cpuTicks": 42333, + "rssKiB": 773176, + "peakRssKiB": 866216, + "threads": 34, + "fds": 37, + "writeBytes": 545570816 + }, + { + "tMs": 239540, + "cpuTicks": 42502, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 547418112 + }, + { + "tMs": 240666, + "cpuTicks": 42674, + "rssKiB": 773176, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 549318656 + }, + { + "tMs": 241865, + "cpuTicks": 42915, + "rssKiB": 869868, + "peakRssKiB": 869868, + "threads": 34, + "fds": 36, + "writeBytes": 552493056 + }, + { + "tMs": 243016, + "cpuTicks": 43154, + "rssKiB": 870188, + "peakRssKiB": 870188, + "threads": 34, + "fds": 36, + "writeBytes": 556134400 + }, + { + "tMs": 244180, + "cpuTicks": 43317, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 558292992 + }, + { + "tMs": 245311, + "cpuTicks": 43476, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 560173056 + }, + { + "tMs": 246442, + "cpuTicks": 43638, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 562053120 + }, + { + "tMs": 247579, + "cpuTicks": 43792, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 564346880 + }, + { + "tMs": 248747, + "cpuTicks": 43989, + "rssKiB": 869864, + "peakRssKiB": 869864, + "threads": 34, + "fds": 36, + "writeBytes": 567558144 + }, + { + "tMs": 249925, + "cpuTicks": 44216, + "rssKiB": 870188, + "peakRssKiB": 870188, + "threads": 34, + "fds": 36, + "writeBytes": 569425920 + }, + { + "tMs": 251078, + "cpuTicks": 44427, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 571527168 + }, + { + "tMs": 252259, + "cpuTicks": 44580, + "rssKiB": 774148, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 573476864 + }, + { + "tMs": 253399, + "cpuTicks": 44741, + "rssKiB": 773824, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 577134592 + }, + { + "tMs": 254560, + "cpuTicks": 44903, + "rssKiB": 773824, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 579067904 + }, + { + "tMs": 255748, + "cpuTicks": 45093, + "rssKiB": 870512, + "peakRssKiB": 870512, + "threads": 34, + "fds": 36, + "writeBytes": 582328320 + }, + { + "tMs": 256914, + "cpuTicks": 45310, + "rssKiB": 870512, + "peakRssKiB": 870512, + "threads": 34, + "fds": 36, + "writeBytes": 584200192 + }, + { + "tMs": 258077, + "cpuTicks": 45488, + "rssKiB": 773824, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 586334208 + }, + { + "tMs": 259219, + "cpuTicks": 45647, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 588292096 + }, + { + "tMs": 260367, + "cpuTicks": 45807, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 590176256 + }, + { + "tMs": 261504, + "cpuTicks": 45947, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 592015360 + }, + { + "tMs": 262661, + "cpuTicks": 46106, + "rssKiB": 773500, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 594341888 + }, + { + "tMs": 263849, + "cpuTicks": 46322, + "rssKiB": 869184, + "peakRssKiB": 869184, + "threads": 34, + "fds": 36, + "writeBytes": 599314432 + }, + { + "tMs": 264978, + "cpuTicks": 46525, + "rssKiB": 869184, + "peakRssKiB": 869184, + "threads": 34, + "fds": 36, + "writeBytes": 601206784 + }, + { + "tMs": 266124, + "cpuTicks": 46707, + "rssKiB": 772820, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 603320320 + }, + { + "tMs": 267269, + "cpuTicks": 46880, + "rssKiB": 773144, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 605216768 + }, + { + "tMs": 268416, + "cpuTicks": 47028, + "rssKiB": 773468, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 607059968 + }, + { + "tMs": 269556, + "cpuTicks": 47183, + "rssKiB": 773468, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 608919552 + }, + { + "tMs": 270740, + "cpuTicks": 47369, + "rssKiB": 870304, + "peakRssKiB": 870304, + "threads": 34, + "fds": 36, + "writeBytes": 612139008 + }, + { + "tMs": 271925, + "cpuTicks": 47595, + "rssKiB": 869692, + "peakRssKiB": 869692, + "threads": 34, + "fds": 36, + "writeBytes": 614006784 + }, + { + "tMs": 273086, + "cpuTicks": 47821, + "rssKiB": 773644, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 617975808 + }, + { + "tMs": 274220, + "cpuTicks": 47983, + "rssKiB": 774292, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 619851776 + }, + { + "tMs": 275363, + "cpuTicks": 48153, + "rssKiB": 774292, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 621772800 + }, + { + "tMs": 276488, + "cpuTicks": 48317, + "rssKiB": 774292, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 624136192 + }, + { + "tMs": 277622, + "cpuTicks": 48492, + "rssKiB": 774616, + "peakRssKiB": 866216, + "threads": 34, + "fds": 36, + "writeBytes": 625987584 + }, + { + "tMs": 278858, + "cpuTicks": 48718, + "rssKiB": 870980, + "peakRssKiB": 870980, + "threads": 34, + "fds": 36, + "writeBytes": 629223424 + }, + { + "tMs": 280007, + "cpuTicks": 48931, + "rssKiB": 870980, + "peakRssKiB": 870980, + "threads": 34, + "fds": 36, + "writeBytes": 631111680 + }, + { + "tMs": 281169, + "cpuTicks": 49090, + "rssKiB": 774292, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 633327616 + }, + { + "tMs": 282302, + "cpuTicks": 49246, + "rssKiB": 774292, + "peakRssKiB": 866344, + "threads": 34, + "fds": 36, + "writeBytes": 635228160 + }, + { + "tMs": 283462, + "cpuTicks": 49400, + "rssKiB": 774292, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 638889984 + }, + { + "tMs": 284602, + "cpuTicks": 49569, + "rssKiB": 774292, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 640778240 + }, + { + "tMs": 285778, + "cpuTicks": 49760, + "rssKiB": 870332, + "peakRssKiB": 870332, + "threads": 34, + "fds": 36, + "writeBytes": 643985408 + }, + { + "tMs": 286962, + "cpuTicks": 49988, + "rssKiB": 870332, + "peakRssKiB": 870332, + "threads": 34, + "fds": 36, + "writeBytes": 645894144 + }, + { + "tMs": 288122, + "cpuTicks": 50168, + "rssKiB": 773316, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 648011776 + }, + { + "tMs": 289259, + "cpuTicks": 50342, + "rssKiB": 773316, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 649949184 + }, + { + "tMs": 290400, + "cpuTicks": 50492, + "rssKiB": 772992, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 651841536 + }, + { + "tMs": 291551, + "cpuTicks": 50652, + "rssKiB": 772992, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 654168064 + }, + { + "tMs": 292685, + "cpuTicks": 50812, + "rssKiB": 789724, + "peakRssKiB": 866488, + "threads": 34, + "fds": 32, + "writeBytes": 657358848 + }, + { + "tMs": 293886, + "cpuTicks": 51043, + "rssKiB": 869684, + "peakRssKiB": 869684, + "threads": 34, + "fds": 36, + "writeBytes": 661020672 + }, + { + "tMs": 295052, + "cpuTicks": 51263, + "rssKiB": 772576, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 663199744 + }, + { + "tMs": 296215, + "cpuTicks": 51418, + "rssKiB": 772900, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 665137152 + }, + { + "tMs": 297366, + "cpuTicks": 51573, + "rssKiB": 773220, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 667054080 + }, + { + "tMs": 298519, + "cpuTicks": 51724, + "rssKiB": 773448, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 668971008 + }, + { + "tMs": 299680, + "cpuTicks": 51875, + "rssKiB": 773440, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 671780864 + }, + { + "tMs": 300892, + "cpuTicks": 52112, + "rssKiB": 869596, + "peakRssKiB": 869596, + "threads": 34, + "fds": 36, + "writeBytes": 674021376 + }, + { + "tMs": 302046, + "cpuTicks": 52322, + "rssKiB": 773232, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 676212736 + }, + { + "tMs": 303196, + "cpuTicks": 52467, + "rssKiB": 773556, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 680361984 + }, + { + "tMs": 304341, + "cpuTicks": 52624, + "rssKiB": 773880, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 682348544 + }, + { + "tMs": 305482, + "cpuTicks": 52771, + "rssKiB": 773880, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 684314624 + }, + { + "tMs": 306625, + "cpuTicks": 52929, + "rssKiB": 773880, + "peakRssKiB": 866488, + "threads": 34, + "fds": 37, + "writeBytes": 686223360 + }, + { + "tMs": 307846, + "cpuTicks": 53152, + "rssKiB": 870564, + "peakRssKiB": 870564, + "threads": 34, + "fds": 36, + "writeBytes": 689434624 + }, + { + "tMs": 309011, + "cpuTicks": 53374, + "rssKiB": 870240, + "peakRssKiB": 870240, + "threads": 34, + "fds": 36, + "writeBytes": 691359744 + }, + { + "tMs": 310161, + "cpuTicks": 53571, + "rssKiB": 773876, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 693587968 + }, + { + "tMs": 311320, + "cpuTicks": 53734, + "rssKiB": 773872, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 695578624 + }, + { + "tMs": 312495, + "cpuTicks": 53887, + "rssKiB": 773872, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 697544704 + }, + { + "tMs": 313631, + "cpuTicks": 54030, + "rssKiB": 773644, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 701227008 + }, + { + "tMs": 314877, + "cpuTicks": 54265, + "rssKiB": 870008, + "peakRssKiB": 870008, + "threads": 34, + "fds": 36, + "writeBytes": 704425984 + }, + { + "tMs": 316033, + "cpuTicks": 54488, + "rssKiB": 870040, + "peakRssKiB": 870040, + "threads": 34, + "fds": 36, + "writeBytes": 706355200 + }, + { + "tMs": 317162, + "cpuTicks": 54660, + "rssKiB": 773352, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 709042176 + }, + { + "tMs": 318293, + "cpuTicks": 54822, + "rssKiB": 773352, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 710975488 + }, + { + "tMs": 319427, + "cpuTicks": 54982, + "rssKiB": 773352, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 712904704 + }, + { + "tMs": 320559, + "cpuTicks": 55127, + "rssKiB": 773352, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 714776576 + }, + { + "tMs": 321701, + "cpuTicks": 55301, + "rssKiB": 869708, + "peakRssKiB": 869708, + "threads": 34, + "fds": 36, + "writeBytes": 717864960 + }, + { + "tMs": 322896, + "cpuTicks": 55539, + "rssKiB": 869708, + "peakRssKiB": 869708, + "threads": 34, + "fds": 36, + "writeBytes": 721489920 + }, + { + "tMs": 324086, + "cpuTicks": 55774, + "rssKiB": 806040, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 723501056 + }, + { + "tMs": 325261, + "cpuTicks": 55934, + "rssKiB": 772372, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 725504000 + }, + { + "tMs": 326389, + "cpuTicks": 56094, + "rssKiB": 772696, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 727408640 + }, + { + "tMs": 327532, + "cpuTicks": 56236, + "rssKiB": 773020, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 729292800 + }, + { + "tMs": 328667, + "cpuTicks": 56404, + "rssKiB": 773020, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 731123712 + }, + { + "tMs": 329909, + "cpuTicks": 56657, + "rssKiB": 869784, + "peakRssKiB": 869784, + "threads": 34, + "fds": 36, + "writeBytes": 734273536 + }, + { + "tMs": 331080, + "cpuTicks": 56878, + "rssKiB": 773096, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 736432128 + }, + { + "tMs": 332234, + "cpuTicks": 57039, + "rssKiB": 773092, + "peakRssKiB": 866488, + "threads": 34, + "fds": 37, + "writeBytes": 738729984 + }, + { + "tMs": 333366, + "cpuTicks": 57195, + "rssKiB": 773412, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 742383616 + }, + { + "tMs": 334490, + "cpuTicks": 57355, + "rssKiB": 774060, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 744308736 + }, + { + "tMs": 335644, + "cpuTicks": 57504, + "rssKiB": 774060, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 746233856 + }, + { + "tMs": 336842, + "cpuTicks": 57717, + "rssKiB": 870748, + "peakRssKiB": 870748, + "threads": 34, + "fds": 36, + "writeBytes": 749420544 + }, + { + "tMs": 338007, + "cpuTicks": 57942, + "rssKiB": 845928, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 751407104 + }, + { + "tMs": 339148, + "cpuTicks": 58115, + "rssKiB": 773412, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 753516544 + }, + { + "tMs": 340287, + "cpuTicks": 58287, + "rssKiB": 773736, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 755474432 + }, + { + "tMs": 341413, + "cpuTicks": 58432, + "rssKiB": 773736, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 757415936 + }, + { + "tMs": 342545, + "cpuTicks": 58600, + "rssKiB": 774060, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 759332864 + }, + { + "tMs": 343674, + "cpuTicks": 58765, + "rssKiB": 774060, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 763011072 + }, + { + "tMs": 344878, + "cpuTicks": 58996, + "rssKiB": 870100, + "peakRssKiB": 870100, + "threads": 34, + "fds": 36, + "writeBytes": 766242816 + }, + { + "tMs": 346031, + "cpuTicks": 59227, + "rssKiB": 845928, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 768675840 + }, + { + "tMs": 347188, + "cpuTicks": 59391, + "rssKiB": 773412, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 770809856 + }, + { + "tMs": 348334, + "cpuTicks": 59557, + "rssKiB": 773412, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 772698112 + }, + { + "tMs": 349477, + "cpuTicks": 59696, + "rssKiB": 773412, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 774586368 + }, + { + "tMs": 350615, + "cpuTicks": 59855, + "rssKiB": 773412, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 776495104 + }, + { + "tMs": 351819, + "cpuTicks": 60060, + "rssKiB": 869776, + "peakRssKiB": 869776, + "threads": 34, + "fds": 37, + "writeBytes": 779649024 + }, + { + "tMs": 352985, + "cpuTicks": 60291, + "rssKiB": 869452, + "peakRssKiB": 869452, + "threads": 34, + "fds": 37, + "writeBytes": 783314944 + }, + { + "tMs": 354149, + "cpuTicks": 60479, + "rssKiB": 772764, + "peakRssKiB": 866488, + "threads": 34, + "fds": 37, + "writeBytes": 785485824 + }, + { + "tMs": 355292, + "cpuTicks": 60627, + "rssKiB": 772444, + "peakRssKiB": 866488, + "threads": 34, + "fds": 37, + "writeBytes": 787386368 + }, + { + "tMs": 356435, + "cpuTicks": 60776, + "rssKiB": 772764, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 789295104 + }, + { + "tMs": 357577, + "cpuTicks": 60934, + "rssKiB": 772992, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 791138304 + }, + { + "tMs": 358742, + "cpuTicks": 61109, + "rssKiB": 870652, + "peakRssKiB": 870652, + "threads": 34, + "fds": 36, + "writeBytes": 794329088 + }, + { + "tMs": 359938, + "cpuTicks": 61352, + "rssKiB": 870652, + "peakRssKiB": 870652, + "threads": 34, + "fds": 36, + "writeBytes": 796119040 + }, + { + "tMs": 361101, + "cpuTicks": 61560, + "rssKiB": 773964, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 798703616 + }, + { + "tMs": 362252, + "cpuTicks": 61713, + "rssKiB": 773964, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 800583680 + }, + { + "tMs": 363400, + "cpuTicks": 61866, + "rssKiB": 773964, + "peakRssKiB": 866488, + "threads": 34, + "fds": 36, + "writeBytes": 804233216 + }, + { + "tMs": 364550, + "cpuTicks": 62016, + "rssKiB": 773964, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 806178816 + }, + { + "tMs": 365689, + "cpuTicks": 62173, + "rssKiB": 834780, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 809426944 + }, + { + "tMs": 366888, + "cpuTicks": 62396, + "rssKiB": 870656, + "peakRssKiB": 870656, + "threads": 34, + "fds": 36, + "writeBytes": 811302912 + }, + { + "tMs": 368049, + "cpuTicks": 62605, + "rssKiB": 846160, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 813244416 + }, + { + "tMs": 369201, + "cpuTicks": 62759, + "rssKiB": 772568, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 815411200 + }, + { + "tMs": 370335, + "cpuTicks": 62919, + "rssKiB": 773216, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 817311744 + }, + { + "tMs": 371465, + "cpuTicks": 63077, + "rssKiB": 773216, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 819212288 + }, + { + "tMs": 372620, + "cpuTicks": 63233, + "rssKiB": 773536, + "peakRssKiB": 866552, + "threads": 34, + "fds": 37, + "writeBytes": 821190656 + }, + { + "tMs": 373833, + "cpuTicks": 63453, + "rssKiB": 870548, + "peakRssKiB": 870548, + "threads": 34, + "fds": 36, + "writeBytes": 826642432 + }, + { + "tMs": 375016, + "cpuTicks": 63687, + "rssKiB": 870548, + "peakRssKiB": 870548, + "threads": 34, + "fds": 36, + "writeBytes": 828547072 + }, + { + "tMs": 376169, + "cpuTicks": 63886, + "rssKiB": 773860, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 830738432 + }, + { + "tMs": 377288, + "cpuTicks": 64058, + "rssKiB": 773860, + "peakRssKiB": 866552, + "threads": 34, + "fds": 37, + "writeBytes": 832667648 + }, + { + "tMs": 378446, + "cpuTicks": 64223, + "rssKiB": 773860, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 834600960 + }, + { + "tMs": 379581, + "cpuTicks": 64381, + "rssKiB": 773860, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 836632576 + }, + { + "tMs": 380748, + "cpuTicks": 64570, + "rssKiB": 869580, + "peakRssKiB": 869580, + "threads": 34, + "fds": 36, + "writeBytes": 838934528 + }, + { + "tMs": 381926, + "cpuTicks": 64797, + "rssKiB": 869580, + "peakRssKiB": 869580, + "threads": 34, + "fds": 36, + "writeBytes": 841687040 + }, + { + "tMs": 383084, + "cpuTicks": 65016, + "rssKiB": 772516, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 845635584 + }, + { + "tMs": 384215, + "cpuTicks": 65173, + "rssKiB": 772516, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 847589376 + }, + { + "tMs": 385340, + "cpuTicks": 65318, + "rssKiB": 772836, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 849489920 + }, + { + "tMs": 386468, + "cpuTicks": 65474, + "rssKiB": 773160, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 851427328 + }, + { + "tMs": 387586, + "cpuTicks": 65642, + "rssKiB": 773160, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 853794816 + }, + { + "tMs": 388783, + "cpuTicks": 65845, + "rssKiB": 870172, + "peakRssKiB": 870172, + "threads": 34, + "fds": 36, + "writeBytes": 857010176 + }, + { + "tMs": 389951, + "cpuTicks": 66065, + "rssKiB": 870196, + "peakRssKiB": 870196, + "threads": 34, + "fds": 37, + "writeBytes": 858894336 + }, + { + "tMs": 391092, + "cpuTicks": 66280, + "rssKiB": 773508, + "peakRssKiB": 866552, + "threads": 34, + "fds": 37, + "writeBytes": 861036544 + }, + { + "tMs": 392281, + "cpuTicks": 66448, + "rssKiB": 773508, + "peakRssKiB": 866552, + "threads": 34, + "fds": 37, + "writeBytes": 862928896 + }, + { + "tMs": 393415, + "cpuTicks": 66602, + "rssKiB": 774156, + "peakRssKiB": 866552, + "threads": 34, + "fds": 37, + "writeBytes": 866562048 + }, + { + "tMs": 394553, + "cpuTicks": 66751, + "rssKiB": 774480, + "peakRssKiB": 866552, + "threads": 34, + "fds": 37, + "writeBytes": 868470784 + }, + { + "tMs": 395675, + "cpuTicks": 66915, + "rssKiB": 774480, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 870371328 + }, + { + "tMs": 396865, + "cpuTicks": 67139, + "rssKiB": 871168, + "peakRssKiB": 871168, + "threads": 34, + "fds": 36, + "writeBytes": 873578496 + }, + { + "tMs": 398030, + "cpuTicks": 67359, + "rssKiB": 846996, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 875573248 + }, + { + "tMs": 399172, + "cpuTicks": 67517, + "rssKiB": 774152, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 877641728 + }, + { + "tMs": 400308, + "cpuTicks": 67674, + "rssKiB": 774476, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 879501312 + }, + { + "tMs": 401454, + "cpuTicks": 67836, + "rssKiB": 774476, + "peakRssKiB": 866552, + "threads": 34, + "fds": 36, + "writeBytes": 881455104 + }, + { + "tMs": 402596, + "cpuTicks": 67985, + "rssKiB": 774800, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 883830784 + }, + { + "tMs": 403795, + "cpuTicks": 68192, + "rssKiB": 871488, + "peakRssKiB": 871488, + "threads": 34, + "fds": 36, + "writeBytes": 888848384 + }, + { + "tMs": 404968, + "cpuTicks": 68405, + "rssKiB": 871164, + "peakRssKiB": 871164, + "threads": 34, + "fds": 36, + "writeBytes": 890728448 + }, + { + "tMs": 406120, + "cpuTicks": 68583, + "rssKiB": 774480, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 892862464 + }, + { + "tMs": 407261, + "cpuTicks": 68745, + "rssKiB": 773888, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 894746624 + }, + { + "tMs": 408406, + "cpuTicks": 68911, + "rssKiB": 773888, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 896667648 + }, + { + "tMs": 409542, + "cpuTicks": 69067, + "rssKiB": 773240, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 898600960 + }, + { + "tMs": 410723, + "cpuTicks": 69243, + "rssKiB": 869928, + "peakRssKiB": 869928, + "threads": 34, + "fds": 36, + "writeBytes": 900878336 + }, + { + "tMs": 411929, + "cpuTicks": 69486, + "rssKiB": 869928, + "peakRssKiB": 869928, + "threads": 34, + "fds": 36, + "writeBytes": 903675904 + }, + { + "tMs": 413091, + "cpuTicks": 69707, + "rssKiB": 772916, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 907599872 + }, + { + "tMs": 414212, + "cpuTicks": 69866, + "rssKiB": 772916, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 909500416 + }, + { + "tMs": 415342, + "cpuTicks": 70023, + "rssKiB": 772268, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 911863808 + }, + { + "tMs": 416471, + "cpuTicks": 70190, + "rssKiB": 772916, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 913821696 + }, + { + "tMs": 417596, + "cpuTicks": 70363, + "rssKiB": 773240, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 915738624 + }, + { + "tMs": 418789, + "cpuTicks": 70569, + "rssKiB": 870896, + "peakRssKiB": 870896, + "threads": 34, + "fds": 36, + "writeBytes": 919007232 + }, + { + "tMs": 419975, + "cpuTicks": 70790, + "rssKiB": 870896, + "peakRssKiB": 870896, + "threads": 34, + "fds": 36, + "writeBytes": 920866816 + }, + { + "tMs": 421126, + "cpuTicks": 70976, + "rssKiB": 774208, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 923074560 + }, + { + "tMs": 422255, + "cpuTicks": 71144, + "rssKiB": 773884, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 924983296 + }, + { + "tMs": 423384, + "cpuTicks": 71304, + "rssKiB": 773884, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 928677888 + }, + { + "tMs": 424513, + "cpuTicks": 71463, + "rssKiB": 773884, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 930639872 + }, + { + "tMs": 425650, + "cpuTicks": 71608, + "rssKiB": 773884, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 932585472 + }, + { + "tMs": 426838, + "cpuTicks": 71827, + "rssKiB": 870572, + "peakRssKiB": 870572, + "threads": 34, + "fds": 36, + "writeBytes": 935788544 + }, + { + "tMs": 428000, + "cpuTicks": 72052, + "rssKiB": 846400, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 937758720 + }, + { + "tMs": 429154, + "cpuTicks": 72217, + "rssKiB": 773564, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 940425216 + }, + { + "tMs": 430306, + "cpuTicks": 72389, + "rssKiB": 773368, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 942268416 + }, + { + "tMs": 431453, + "cpuTicks": 72558, + "rssKiB": 773144, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 944173056 + }, + { + "tMs": 432593, + "cpuTicks": 72735, + "rssKiB": 773784, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 946094080 + }, + { + "tMs": 433818, + "cpuTicks": 72953, + "rssKiB": 870152, + "peakRssKiB": 870152, + "threads": 34, + "fds": 36, + "writeBytes": 951021568 + }, + { + "tMs": 435032, + "cpuTicks": 73202, + "rssKiB": 821808, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 953073664 + }, + { + "tMs": 436182, + "cpuTicks": 73351, + "rssKiB": 773464, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 955125760 + }, + { + "tMs": 437328, + "cpuTicks": 73512, + "rssKiB": 773464, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 957014016 + }, + { + "tMs": 438460, + "cpuTicks": 73664, + "rssKiB": 773464, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 958935040 + }, + { + "tMs": 439592, + "cpuTicks": 73822, + "rssKiB": 773464, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 960827392 + }, + { + "tMs": 440775, + "cpuTicks": 74015, + "rssKiB": 869832, + "peakRssKiB": 869832, + "threads": 34, + "fds": 36, + "writeBytes": 963092480 + }, + { + "tMs": 441946, + "cpuTicks": 74235, + "rssKiB": 869516, + "peakRssKiB": 869516, + "threads": 34, + "fds": 36, + "writeBytes": 965881856 + }, + { + "tMs": 443100, + "cpuTicks": 74420, + "rssKiB": 772140, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 970297344 + }, + { + "tMs": 444237, + "cpuTicks": 74567, + "rssKiB": 772140, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 972218368 + }, + { + "tMs": 445372, + "cpuTicks": 74717, + "rssKiB": 772140, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 974082048 + }, + { + "tMs": 446497, + "cpuTicks": 74870, + "rssKiB": 773132, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 975990784 + }, + { + "tMs": 447630, + "cpuTicks": 75027, + "rssKiB": 773456, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 977883136 + }, + { + "tMs": 448830, + "cpuTicks": 75240, + "rssKiB": 871112, + "peakRssKiB": 871112, + "threads": 34, + "fds": 36, + "writeBytes": 981082112 + }, + { + "tMs": 449973, + "cpuTicks": 75450, + "rssKiB": 871128, + "peakRssKiB": 871128, + "threads": 34, + "fds": 36, + "writeBytes": 982929408 + }, + { + "tMs": 451129, + "cpuTicks": 75640, + "rssKiB": 774440, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 985120768 + }, + { + "tMs": 452259, + "cpuTicks": 75797, + "rssKiB": 774440, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 987025408 + }, + { + "tMs": 453381, + "cpuTicks": 75946, + "rssKiB": 775080, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 990732288 + }, + { + "tMs": 454532, + "cpuTicks": 76101, + "rssKiB": 775404, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 992743424 + }, + { + "tMs": 455653, + "cpuTicks": 76254, + "rssKiB": 775404, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 994734080 + }, + { + "tMs": 456876, + "cpuTicks": 76508, + "rssKiB": 872092, + "peakRssKiB": 872092, + "threads": 34, + "fds": 36, + "writeBytes": 998481920 + }, + { + "tMs": 458040, + "cpuTicks": 76739, + "rssKiB": 871768, + "peakRssKiB": 871768, + "threads": 34, + "fds": 36, + "writeBytes": 1000427520 + }, + { + "tMs": 459197, + "cpuTicks": 76917, + "rssKiB": 775404, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 1002627072 + }, + { + "tMs": 460329, + "cpuTicks": 77055, + "rssKiB": 775080, + "peakRssKiB": 866952, + "threads": 34, + "fds": 36, + "writeBytes": 1004560384 + }, + { + "tMs": 461456, + "cpuTicks": 77201, + "rssKiB": 775080, + "peakRssKiB": 866956, + "threads": 34, + "fds": 36, + "writeBytes": 1006514176 + }, + { + "tMs": 462575, + "cpuTicks": 77345, + "rssKiB": 775080, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1008422912 + }, + { + "tMs": 463732, + "cpuTicks": 77517, + "rssKiB": 871448, + "peakRssKiB": 871448, + "threads": 34, + "fds": 36, + "writeBytes": 1013424128 + }, + { + "tMs": 464918, + "cpuTicks": 77743, + "rssKiB": 871124, + "peakRssKiB": 871124, + "threads": 34, + "fds": 36, + "writeBytes": 1015283712 + }, + { + "tMs": 466074, + "cpuTicks": 77928, + "rssKiB": 774440, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1017487360 + }, + { + "tMs": 467237, + "cpuTicks": 78101, + "rssKiB": 774440, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1019461632 + }, + { + "tMs": 468393, + "cpuTicks": 78253, + "rssKiB": 774440, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1021378560 + }, + { + "tMs": 469527, + "cpuTicks": 78395, + "rssKiB": 774440, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1023279104 + }, + { + "tMs": 470680, + "cpuTicks": 78538, + "rssKiB": 773972, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1025912832 + }, + { + "tMs": 471883, + "cpuTicks": 78769, + "rssKiB": 870480, + "peakRssKiB": 870480, + "threads": 34, + "fds": 36, + "writeBytes": 1028780032 + }, + { + "tMs": 473028, + "cpuTicks": 78990, + "rssKiB": 822136, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1032523776 + }, + { + "tMs": 474178, + "cpuTicks": 79136, + "rssKiB": 773468, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1034575872 + }, + { + "tMs": 475329, + "cpuTicks": 79282, + "rssKiB": 773148, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1036480512 + }, + { + "tMs": 476477, + "cpuTicks": 79425, + "rssKiB": 773148, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1038385152 + }, + { + "tMs": 477630, + "cpuTicks": 79575, + "rssKiB": 772504, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1040244736 + }, + { + "tMs": 478818, + "cpuTicks": 79773, + "rssKiB": 869836, + "peakRssKiB": 869836, + "threads": 34, + "fds": 36, + "writeBytes": 1043410944 + }, + { + "tMs": 479971, + "cpuTicks": 79978, + "rssKiB": 869192, + "peakRssKiB": 869192, + "threads": 34, + "fds": 36, + "writeBytes": 1045188608 + }, + { + "tMs": 481113, + "cpuTicks": 80142, + "rssKiB": 772504, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1047355392 + }, + { + "tMs": 482251, + "cpuTicks": 80296, + "rssKiB": 772828, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1049210880 + }, + { + "tMs": 483400, + "cpuTicks": 80434, + "rssKiB": 773148, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1052880896 + }, + { + "tMs": 484536, + "cpuTicks": 80574, + "rssKiB": 773468, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1055244288 + }, + { + "tMs": 485682, + "cpuTicks": 80724, + "rssKiB": 773684, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1058455552 + }, + { + "tMs": 486911, + "cpuTicks": 80970, + "rssKiB": 870480, + "peakRssKiB": 870480, + "threads": 34, + "fds": 36, + "writeBytes": 1060360192 + }, + { + "tMs": 488080, + "cpuTicks": 81171, + "rssKiB": 773792, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1062555648 + }, + { + "tMs": 489221, + "cpuTicks": 81313, + "rssKiB": 773792, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1064513536 + }, + { + "tMs": 490358, + "cpuTicks": 81454, + "rssKiB": 774440, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1066401792 + }, + { + "tMs": 491503, + "cpuTicks": 81612, + "rssKiB": 774440, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1068351488 + }, + { + "tMs": 492629, + "cpuTicks": 81770, + "rssKiB": 775088, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1070260224 + }, + { + "tMs": 493847, + "cpuTicks": 81992, + "rssKiB": 871452, + "peakRssKiB": 871452, + "threads": 34, + "fds": 36, + "writeBytes": 1075253248 + }, + { + "tMs": 495008, + "cpuTicks": 82207, + "rssKiB": 871452, + "peakRssKiB": 871452, + "threads": 34, + "fds": 36, + "writeBytes": 1077198848 + }, + { + "tMs": 496174, + "cpuTicks": 82376, + "rssKiB": 774764, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1079521280 + }, + { + "tMs": 497321, + "cpuTicks": 82533, + "rssKiB": 774988, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1081933824 + }, + { + "tMs": 498457, + "cpuTicks": 82679, + "rssKiB": 774808, + "peakRssKiB": 867164, + "threads": 34, + "fds": 36, + "writeBytes": 1083854848 + }, + { + "tMs": 499598, + "cpuTicks": 82832, + "rssKiB": 774584, + "peakRssKiB": 867168, + "threads": 34, + "fds": 36, + "writeBytes": 1085784064 + }, + { + "tMs": 500805, + "cpuTicks": 83043, + "rssKiB": 870948, + "peakRssKiB": 870948, + "threads": 34, + "fds": 36, + "writeBytes": 1088094208 + }, + { + "tMs": 501974, + "cpuTicks": 83257, + "rssKiB": 870948, + "peakRssKiB": 870948, + "threads": 34, + "fds": 36, + "writeBytes": 1090871296 + }, + { + "tMs": 503170, + "cpuTicks": 83433, + "rssKiB": 773612, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1094754304 + }, + { + "tMs": 504310, + "cpuTicks": 83587, + "rssKiB": 773936, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1096630272 + }, + { + "tMs": 505459, + "cpuTicks": 83732, + "rssKiB": 773936, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1098506240 + }, + { + "tMs": 506604, + "cpuTicks": 83886, + "rssKiB": 774260, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1100460032 + }, + { + "tMs": 507821, + "cpuTicks": 84097, + "rssKiB": 871596, + "peakRssKiB": 871596, + "threads": 34, + "fds": 36, + "writeBytes": 1103704064 + }, + { + "tMs": 508971, + "cpuTicks": 84311, + "rssKiB": 871596, + "peakRssKiB": 871596, + "threads": 34, + "fds": 36, + "writeBytes": 1105580032 + }, + { + "tMs": 510131, + "cpuTicks": 84506, + "rssKiB": 774924, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1107664896 + }, + { + "tMs": 511275, + "cpuTicks": 84656, + "rssKiB": 774932, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1109520384 + }, + { + "tMs": 512412, + "cpuTicks": 84804, + "rssKiB": 774932, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1111994368 + }, + { + "tMs": 513537, + "cpuTicks": 84948, + "rssKiB": 774608, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1115656192 + }, + { + "tMs": 514659, + "cpuTicks": 85111, + "rssKiB": 774932, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1117577216 + }, + { + "tMs": 515855, + "cpuTicks": 85330, + "rssKiB": 871620, + "peakRssKiB": 871620, + "threads": 34, + "fds": 36, + "writeBytes": 1120808960 + }, + { + "tMs": 517028, + "cpuTicks": 85554, + "rssKiB": 847448, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1122783232 + }, + { + "tMs": 518163, + "cpuTicks": 85719, + "rssKiB": 774284, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1124876288 + }, + { + "tMs": 519303, + "cpuTicks": 85877, + "rssKiB": 773960, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1126797312 + }, + { + "tMs": 520433, + "cpuTicks": 86031, + "rssKiB": 774608, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1128685568 + }, + { + "tMs": 521555, + "cpuTicks": 86183, + "rssKiB": 774612, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1130651648 + }, + { + "tMs": 522681, + "cpuTicks": 86345, + "rssKiB": 775044, + "peakRssKiB": 867708, + "threads": 34, + "fds": 34, + "writeBytes": 1133813760 + }, + { + "tMs": 523883, + "cpuTicks": 86582, + "rssKiB": 870980, + "peakRssKiB": 870980, + "threads": 34, + "fds": 36, + "writeBytes": 1137500160 + }, + { + "tMs": 525056, + "cpuTicks": 86802, + "rssKiB": 837044, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1140035584 + }, + { + "tMs": 526195, + "cpuTicks": 86970, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1142235136 + }, + { + "tMs": 527322, + "cpuTicks": 87120, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1144193024 + }, + { + "tMs": 528470, + "cpuTicks": 87275, + "rssKiB": 774192, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1146130432 + }, + { + "tMs": 529609, + "cpuTicks": 87423, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1148116992 + }, + { + "tMs": 530800, + "cpuTicks": 87619, + "rssKiB": 869364, + "peakRssKiB": 869364, + "threads": 34, + "fds": 36, + "writeBytes": 1150435328 + }, + { + "tMs": 531978, + "cpuTicks": 87839, + "rssKiB": 869040, + "peakRssKiB": 869040, + "threads": 34, + "fds": 37, + "writeBytes": 1153196032 + }, + { + "tMs": 533128, + "cpuTicks": 88026, + "rssKiB": 772352, + "peakRssKiB": 867708, + "threads": 34, + "fds": 37, + "writeBytes": 1157234688 + }, + { + "tMs": 534263, + "cpuTicks": 88188, + "rssKiB": 772252, + "peakRssKiB": 867708, + "threads": 34, + "fds": 37, + "writeBytes": 1159196672 + }, + { + "tMs": 535383, + "cpuTicks": 88340, + "rssKiB": 772028, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1161162752 + }, + { + "tMs": 536507, + "cpuTicks": 88491, + "rssKiB": 772032, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1163051008 + }, + { + "tMs": 537643, + "cpuTicks": 88654, + "rssKiB": 772352, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1164890112 + }, + { + "tMs": 538858, + "cpuTicks": 88892, + "rssKiB": 870012, + "peakRssKiB": 870012, + "threads": 34, + "fds": 36, + "writeBytes": 1168584704 + }, + { + "tMs": 540034, + "cpuTicks": 89121, + "rssKiB": 845840, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1170472960 + }, + { + "tMs": 541175, + "cpuTicks": 89294, + "rssKiB": 773324, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1172541440 + }, + { + "tMs": 542295, + "cpuTicks": 89452, + "rssKiB": 773324, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1174405120 + }, + { + "tMs": 543417, + "cpuTicks": 89620, + "rssKiB": 773324, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1178079232 + }, + { + "tMs": 544544, + "cpuTicks": 89775, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1179967488 + }, + { + "tMs": 545671, + "cpuTicks": 89943, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1181863936 + }, + { + "tMs": 546904, + "cpuTicks": 90190, + "rssKiB": 870656, + "peakRssKiB": 870656, + "threads": 34, + "fds": 36, + "writeBytes": 1185067008 + }, + { + "tMs": 548067, + "cpuTicks": 90405, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1187155968 + }, + { + "tMs": 549218, + "cpuTicks": 90546, + "rssKiB": 773320, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1189064704 + }, + { + "tMs": 550360, + "cpuTicks": 90701, + "rssKiB": 773644, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1190928384 + }, + { + "tMs": 551483, + "cpuTicks": 90858, + "rssKiB": 773644, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1192886272 + }, + { + "tMs": 552620, + "cpuTicks": 91011, + "rssKiB": 773968, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1194799104 + }, + { + "tMs": 553847, + "cpuTicks": 91237, + "rssKiB": 870656, + "peakRssKiB": 870656, + "threads": 34, + "fds": 36, + "writeBytes": 1200259072 + }, + { + "tMs": 555007, + "cpuTicks": 91449, + "rssKiB": 845836, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1202225152 + }, + { + "tMs": 556154, + "cpuTicks": 91614, + "rssKiB": 773320, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1204367360 + }, + { + "tMs": 557317, + "cpuTicks": 91766, + "rssKiB": 773320, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1206247424 + }, + { + "tMs": 558473, + "cpuTicks": 91923, + "rssKiB": 773320, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1208123392 + }, + { + "tMs": 559622, + "cpuTicks": 92070, + "rssKiB": 773000, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1210114048 + }, + { + "tMs": 560851, + "cpuTicks": 92307, + "rssKiB": 869364, + "peakRssKiB": 869364, + "threads": 34, + "fds": 36, + "writeBytes": 1212493824 + }, + { + "tMs": 562024, + "cpuTicks": 92536, + "rssKiB": 869364, + "peakRssKiB": 869364, + "threads": 34, + "fds": 37, + "writeBytes": 1215168512 + }, + { + "tMs": 563175, + "cpuTicks": 92704, + "rssKiB": 772352, + "peakRssKiB": 867708, + "threads": 34, + "fds": 37, + "writeBytes": 1219137536 + }, + { + "tMs": 564312, + "cpuTicks": 92856, + "rssKiB": 772676, + "peakRssKiB": 867708, + "threads": 34, + "fds": 37, + "writeBytes": 1221132288 + }, + { + "tMs": 565442, + "cpuTicks": 93010, + "rssKiB": 772676, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1223016448 + }, + { + "tMs": 566575, + "cpuTicks": 93164, + "rssKiB": 773000, + "peakRssKiB": 867708, + "threads": 34, + "fds": 36, + "writeBytes": 1225453568 + }, + { + "tMs": 567753, + "cpuTicks": 93353, + "rssKiB": 871944, + "peakRssKiB": 871944, + "threads": 34, + "fds": 36, + "writeBytes": 1228668928 + }, + { + "tMs": 568950, + "cpuTicks": 93583, + "rssKiB": 872592, + "peakRssKiB": 872592, + "threads": 34, + "fds": 36, + "writeBytes": 1230610432 + }, + { + "tMs": 570102, + "cpuTicks": 93772, + "rssKiB": 775924, + "peakRssKiB": 867724, + "threads": 34, + "fds": 36, + "writeBytes": 1232756736 + }, + { + "tMs": 571264, + "cpuTicks": 93924, + "rssKiB": 775924, + "peakRssKiB": 868284, + "threads": 34, + "fds": 36, + "writeBytes": 1234698240 + }, + { + "tMs": 572394, + "cpuTicks": 94082, + "rssKiB": 775924, + "peakRssKiB": 868284, + "threads": 34, + "fds": 36, + "writeBytes": 1236680704 + }, + { + "tMs": 573533, + "cpuTicks": 94234, + "rssKiB": 776248, + "peakRssKiB": 868284, + "threads": 34, + "fds": 37, + "writeBytes": 1240330240 + }, + { + "tMs": 574654, + "cpuTicks": 94384, + "rssKiB": 776572, + "peakRssKiB": 868284, + "threads": 34, + "fds": 36, + "writeBytes": 1242259456 + }, + { + "tMs": 575862, + "cpuTicks": 94603, + "rssKiB": 873260, + "peakRssKiB": 873260, + "threads": 34, + "fds": 37, + "writeBytes": 1245528064 + }, + { + "tMs": 577030, + "cpuTicks": 94826, + "rssKiB": 873260, + "peakRssKiB": 873260, + "threads": 34, + "fds": 37, + "writeBytes": 1247432704 + }, + { + "tMs": 578162, + "cpuTicks": 94989, + "rssKiB": 776572, + "peakRssKiB": 868756, + "threads": 34, + "fds": 37, + "writeBytes": 1249648640 + }, + { + "tMs": 579307, + "cpuTicks": 95145, + "rssKiB": 776572, + "peakRssKiB": 868756, + "threads": 34, + "fds": 37, + "writeBytes": 1252114432 + }, + { + "tMs": 580458, + "cpuTicks": 95299, + "rssKiB": 776036, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1253994496 + }, + { + "tMs": 581600, + "cpuTicks": 95453, + "rssKiB": 776040, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1255919616 + }, + { + "tMs": 582787, + "cpuTicks": 95650, + "rssKiB": 872728, + "peakRssKiB": 872728, + "threads": 34, + "fds": 36, + "writeBytes": 1260974080 + }, + { + "tMs": 583954, + "cpuTicks": 95885, + "rssKiB": 872404, + "peakRssKiB": 872404, + "threads": 34, + "fds": 36, + "writeBytes": 1262895104 + }, + { + "tMs": 585112, + "cpuTicks": 96077, + "rssKiB": 775392, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1265090560 + }, + { + "tMs": 586275, + "cpuTicks": 96222, + "rssKiB": 775392, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1267089408 + }, + { + "tMs": 587421, + "cpuTicks": 96374, + "rssKiB": 775392, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1268965376 + }, + { + "tMs": 588566, + "cpuTicks": 96519, + "rssKiB": 775392, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1270882304 + }, + { + "tMs": 589749, + "cpuTicks": 96706, + "rssKiB": 870792, + "peakRssKiB": 870792, + "threads": 34, + "fds": 36, + "writeBytes": 1274097664 + }, + { + "tMs": 590938, + "cpuTicks": 96941, + "rssKiB": 870792, + "peakRssKiB": 870792, + "threads": 34, + "fds": 36, + "writeBytes": 1275297792 + }, + { + "tMs": 592099, + "cpuTicks": 97151, + "rssKiB": 774104, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1278087168 + }, + { + "tMs": 593236, + "cpuTicks": 97309, + "rssKiB": 774104, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1282334720 + }, + { + "tMs": 594370, + "cpuTicks": 97458, + "rssKiB": 774104, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1284227072 + }, + { + "tMs": 595505, + "cpuTicks": 97617, + "rssKiB": 774424, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1286127616 + }, + { + "tMs": 596655, + "cpuTicks": 97785, + "rssKiB": 774424, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1288065024 + }, + { + "tMs": 597882, + "cpuTicks": 98027, + "rssKiB": 872080, + "peakRssKiB": 872080, + "threads": 34, + "fds": 37, + "writeBytes": 1291259904 + }, + { + "tMs": 599030, + "cpuTicks": 98244, + "rssKiB": 848232, + "peakRssKiB": 868756, + "threads": 34, + "fds": 36, + "writeBytes": 1293242368 + } + ], + "db_probe_ms": [ + 10.554, + 8.873, + 7.724, + 8.703, + 7.902, + 10.07, + 7.91, + 8.018, + 7.886, + 7.37, + 8.857, + 8.92, + 7.944, + 7.299, + 8.142, + 9.224, + 7.914, + 7.433, + 7.619, + 10.769, + 7.738, + 7.751, + 13.812, + 8.441, + 7.35, + 7.463, + 10.605, + 10.76, + 8.099, + 7.903, + 10.281, + 8.301, + 8.654, + 7.254, + 9.186, + 7.582, + 8.059, + 8.136, + 8.086, + 7.334, + 8.119, + 9.15, + 16.809, + 7.579, + 7.311, + 7.888, + 7.906, + 7.624, + 7.438, + 14.593, + 10.518, + 7.251, + 7.595, + 12.558, + 8.902, + 7.473, + 7.313, + 8.732, + 8.879, + 8.689, + 7.503, + 11.232, + 14.168, + 8.523, + 7.431, + 10.432, + 7.247, + 9.815, + 7.685, + 7.872, + 9.944, + 8.318, + 9.628, + 14.965, + 7.228, + 7.822, + 13.251, + 9.033, + 7.787, + 8.657, + 9.937, + 9.593, + 7.398, + 7.579, + 9.254, + 8.436, + 7.487, + 7.573, + 13.219, + 7.507, + 7.501, + 8.08, + 9.507, + 9.478, + 7.912, + 7.812, + 7.581, + 7.401, + 10.903, + 9.238, + 14.17, + 7.192, + 10.28, + 10.885, + 7.848, + 7.146, + 10.135, + 15.026, + 8.946, + 7.463, + 7.543, + 11.432, + 10.257, + 7.354, + 7.441, + 9.753, + 21.442, + 7.984, + 7.311, + 9.641, + 7.6, + 8.991, + 7.494, + 9.559, + 7.704, + 8.845, + 8.315, + 9.298, + 7.385, + 7.819, + 10.003, + 8.19, + 8.12, + 7.557, + 17.131, + 11.492, + 8.079, + 7.23, + 12.889, + 13.498, + 7.338, + 11.095, + 10.698, + 9.119, + 7.483, + 7.722, + 9.098, + 10.321, + 7.725, + 7.593, + 7.904, + 7.916, + 7.92, + 7.42, + 8.177, + 7.328, + 7.678, + 7.384, + 14.847, + 7.78, + 8.633, + 11.645, + 12.495, + 7.436, + 7.79, + 12.75, + 8.173, + 7.342, + 8.17, + 19.302, + 8.7, + 8.223, + 7.184, + 9.344, + 14.883, + 7.439, + 7.49, + 9.725, + 8.083, + 8.074, + 8.055, + 7.769, + 7.809, + 9.917, + 10.303, + 7.943, + 8.014, + 9.137, + 13.392, + 9.275, + 7.833, + 8.06, + 15.823, + 9.157, + 7.632, + 7.515, + 12.769, + 7.985, + 7.287, + 7.463, + 7.774, + 8.881, + 8.486, + 9.074, + 9.654, + 8.127, + 7.622, + 7.279, + 7.779, + 7.808, + 8.373, + 10.172, + 7.886, + 7.429, + 7.793, + 9.759, + 8.455, + 8.086, + 7.429, + 11.743, + 8.304, + 7.926, + 8.56, + 13.461, + 8.151, + 7.565, + 7.632, + 9.289, + 8.173, + 7.645, + 9.652, + 10.204, + 10.909, + 7.78, + 7.903, + 9.851, + 9.87, + 7.873, + 7.953, + 12.884, + 7.896, + 8.684, + 22.157, + 8.115, + 7.471, + 8.207, + 13.363, + 8.007, + 7.647, + 8.308, + 16.355, + 8.545, + 8.217, + 10.865, + 9.273, + 7.58, + 8.198, + 9.11, + 10.006, + 7.683, + 7.744, + 7.325, + 8.864, + 7.982, + 7.998, + 7.987, + 7.742, + 8.659, + 7.46, + 16.682, + 17.873, + 7.902, + 8.132, + 10.84, + 13.222, + 8.642, + 9.504, + 9.004, + 11.108, + 7.713, + 8.01, + 10.151, + 7.419, + 7.479, + 10.704, + 11.901, + 9.767, + 7.803, + 7.626, + 10.513, + 7.197, + 8.455, + 13.548, + 8.993, + 9.423, + 7.692, + 15.707, + 13.794, + 8.166, + 7.907, + 9.788, + 8.639, + 9.64, + 7.744, + 8.444, + 7.826, + 8.126, + 9.134, + 8.587, + 8.465, + 7.55, + 7.57, + 9.367, + 8.09, + 7.252, + 8.158, + 11.315, + 7.227, + 8.997, + 24.661, + 9.141, + 8.951, + 7.736, + 15.491, + 10.464, + 8.5, + 8.823, + 12.446, + 9.496, + 8.311, + 7.438, + 7.854, + 8.371, + 9.195, + 8.101, + 7.876, + 8.173, + 7.351, + 8.446, + 7.698, + 7.802, + 8.387, + 9.021, + 10.235, + 7.7, + 7.242, + 21.761, + 9.135, + 8.972, + 8.243, + 13.849, + 9.452, + 7.685, + 7.915, + 10.401, + 7.913, + 8.387, + 7.374, + 9.694, + 9.228, + 7.977, + 7.633, + 8.429, + 7.389, + 9.31, + 8.474, + 8.642, + 7.637, + 7.371, + 8.771, + 7.944, + 7.367, + 7.596, + 13.092, + 13.412, + 7.49, + 7.612, + 10.471, + 12.384, + 7.61, + 7.379, + 9.581, + 16.105, + 7.623, + 7.683, + 11.318, + 7.492, + 7.256, + 7.545, + 9.247, + 7.594, + 9.213, + 7.255, + 8.403, + 7.527, + 7.147, + 8.472, + 9.858, + 7.861, + 7.808, + 10.631, + 8.303, + 7.405, + 7.707, + 16.907, + 8.897, + 7.272, + 7.803, + 12.734, + 10.458, + 7.317, + 8.314, + 8.648, + 15.203, + 7.517, + 7.209, + 7.919, + 7.833, + 8.258, + 8.103, + 8.396, + 8.311, + 8.741, + 7.794, + 7.755, + 7.479, + 8.037, + 8.641, + 8.513, + 7.527, + 7.821, + 20.481, + 8.215, + 7.144, + 7.537, + 13.033, + 8.374, + 7.63, + 7.631, + 9.437, + 9.153, + 7.994, + 8.02, + 9.247, + 10.083, + 7.64, + 7.346, + 8.219, + 9.777, + 8.574, + 7.359, + 8.146, + 7.357, + 9.296, + 7.534, + 7.622, + 7.723, + 9.094, + 14.036, + 8.623, + 7.733, + 7.737, + 11.257, + 8.597, + 7.38, + 7.492, + 11.033, + 9.775, + 7.302, + 7.235, + 7.862, + 8.437, + 7.496, + 7.251, + 9.175, + 7.907, + 8.889, + 7.357, + 8.912, + 8.824, + 8.945, + 7.475, + 8.064, + 7.112, + 7.098, + 8.851, + 9.944, + 7.429, + 7.513, + 19.952, + 9.817, + 7.785, + 7.204, + 15.818, + 8.217, + 8.114, + 7.437, + 11.286, + 9.843, + 8.284, + 7.432, + 10.284, + 12.531, + 10.276, + 7.491, + 7.876, + 7.88, + 7.855, + 7.678, + 7.854, + 7.599, + 8.017, + 9.463, + 9.504, + 8.25, + 8.73, + 16.794, + 8.527, + 7.468, + 8.34, + 9.607, + 8.604, + 16.307, + 8.249, + 8.772, + 8.516, + 7.822, + 8.548, + 7.838, + 8.012, + 7.353, + 7.28, + 8.39, + 7.911, + 8.729, + 7.719, + 7.998, + 7.121, + 8.678, + 7.604, + 8.106, + 7.361, + 7.641, + 9.508, + 7.692, + 7.292, + 7.522, + 20.474, + 8.045, + 7.54, + 7.76, + 10.377, + 8.429, + 7.159, + 7.618, + 8.342, + 9.336, + 7.406, + 7.401, + 13.846, + 7.855, + 7.304, + 7.969, + 7.855, + 9.622, + 8.779, + 7.695, + 7.658, + 7.187, + 8.641, + 9.449, + 8.244, + 7.482, + 7.411, + 11.596, + 10.359, + 7.244, + 8.13, + 30.788, + 9.466, + 7.329, + 7.2, + 10.708, + 9.054, + 9.252, + 7.318, + 10.697, + 10.315, + 7.449, + 8.119, + 9.26, + 7.633, + 8.354, + 7.511, + 8.364, + 8.998, + 7.718, + 8.486, + 7.948, + 7.857, + 8.217, + 9.716, + 8.137, + 7.3, + 7.828, + 10.787, + 8.149, + 7.098, + 8.124, + 9.056, + 9.985, + 7.194, + 7.578, + 8.012, + 8.171, + 7.326, + 7.152, + 8.315, + 7.223, + 7.169, + 7.691, + 11.123, + 13.094, + 8.212, + 7.866, + 7.886, + 7.521, + 7.733, + 17.479, + 7.778, + 7.618, + 7.556, + 12.337, + 8.446, + 7.348, + 7.601, + 16.091, + 8.123, + 7.188, + 7.229, + 8.557, + 9.598, + 7.418, + 7.12, + 7.994, + 7.686, + 8.04, + 7.148, + 8.779, + 7.53, + 8.148, + 7.496, + 7.743, + 7.522, + 8.267, + 8.777, + 9.746, + 7.591, + 8.155, + 11.002, + 7.714, + 7.27, + 7.442, + 11.036, + 8.837, + 7.336, + 7.581, + 10.28, + 8.244, + 7.845, + 7.3, + 8.228, + 9.472, + 7.718, + 7.667, + 8.033, + 9.948, + 7.341, + 7.468, + 7.953, + 7.47, + 7.922, + 7.881, + 9.979, + 7.476, + 9.435, + 21.169, + 8.193, + 8.091, + 7.467, + 12.417, + 8.573, + 7.787, + 7.2, + 14.308, + 8.315, + 7.443, + 7.586, + 8.253, + 7.539, + 7.239, + 7.416, + 7.944, + 9.078, + 8.689, + 8.367, + 8.883, + 7.431, + 7.867, + 7.671, + 8.583, + 7.587, + 10.306, + 9.013, + 8.205, + 7.391, + 7.758, + 15.865, + 11.079, + 9.859, + 7.871, + 8.623, + 11.178, + 8.432, + 7.522, + 12.372, + 10.151, + 7.292, + 7.639, + 8.014, + 9.875, + 10.799, + 7.609, + 7.878, + 8.41, + 7.282, + 7.484, + 8.691, + 7.613, + 9.219, + 9.2, + 8.515, + 10.077, + 9.199, + 13.399, + 8.47, + 7.71, + 7.246, + 13.212, + 8.178, + 7.226, + 7.43, + 13.28, + 9.359, + 7.668, + 8.18, + 9.756, + 8.371, + 7.301, + 7.263, + 7.773, + 8.973, + 8.165, + 7.361, + 7.865, + 7.145, + 9.374, + 7.261, + 7.981, + 7.456, + 7.954, + 13.344, + 7.818, + 7.138, + 8.491, + 10.978, + 7.921, + 8.036, + 7.54, + 15.976, + 8.126, + 7.931, + 7.193, + 13.481, + 9.366, + 7.668, + 9.356, + 9.575, + 9.574, + 7.157, + 7.08, + 8.239, + 8.301, + 10.188, + 7.304, + 7.617, + 7.393, + 8.033, + 9.69, + 8.345, + 8.566, + 9.272, + 14.791, + 8.598, + 8.539, + 7.309, + 11.299, + 7.594, + 7.563, + 7.047, + 12.553, + 10.401, + 7.371, + 7.787, + 8.883, + 8.229, + 7.843, + 8.972, + 8.311, + 8.947, + 7.514, + 8.815, + 9.017, + 7.679, + 7.742, + 8.435, + 7.863, + 7.688, + 7.562, + 14.203, + 8.035, + 8.349, + 8.211, + 12.899, + 11.153, + 7.315, + 7.762, + 16.641, + 8.669, + 7.535, + 8.86, + 8.437, + 8.001, + 7.274, + 7.512, + 13.494, + 7.953, + 7.233, + 7.24, + 7.835, + 8.315, + 8.736, + 8.372, + 7.427, + 7.3, + 8.756, + 19.591, + 8.367, + 7.841, + 8.786, + 10.09, + 8.541, + 7.466, + 10.748, + 16.113, + 10.967, + 7.853, + 7.533, + 10.468, + 7.552, + 7.71, + 7.157, + 15.106, + 8.622, + 7.744, + 7.658, + 8.234, + 8.487, + 9.101, + 7.531, + 7.997, + 7.985, + 8.527, + 9.542, + 10.639, + 7.322, + 8.429, + 12.44, + 8.952, + 7.285, + 7.758, + 10.923, + 8.733, + 7.369, + 10.014, + 14.524, + 8.591, + 7.413, + 8.18, + 19.295, + 7.338, + 7.451, + 7.053, + 8.261, + 8.59, + 7.657, + 7.147, + 8.076, + 9.161, + 12.989, + 12.992, + 8.471, + 10.114, + 7.929, + 11.796, + 8.63, + 9.216, + 8.422, + 9.287, + 8.285, + 7.589, + 9.208, + 9.052, + 7.68, + 11.68, + 7.648, + 8.734, + 7.621, + 7.323, + 7.171, + 9.526, + 7.307, + 7.586, + 7.457, + 9.473, + 7.992, + 8.449, + 29.144, + 9.303, + 8.518, + 8.037, + 10.423, + 7.634, + 11.195, + 7.933, + 9.628, + 8.42, + 7.397, + 7.514, + 11.258, + 7.258, + 7.34, + 8.475, + 7.784, + 7.16, + 7.279, + 7.888, + 7.947, + 7.201, + 7.255, + 7.894, + 9.767, + 7.876, + 7.324, + 9.264, + 8.05, + 8.381, + 7.645, + 9.431, + 12.24, + 10.053, + 7.547, + 9.276, + 7.743, + 13.877, + 8.474, + 12.569, + 8.453, + 8.018, + 10.568, + 8.086, + 7.479, + 7.301, + 9.315, + 10.91, + 7.394, + 7.236, + 7.382, + 9.649, + 7.615, + 7.292, + 8.001, + 9.074, + 7.909, + 7.159, + 8.988, + 7.815, + 8.128, + 7.483, + 9.408, + 8.892, + 9.044, + 7.709, + 9.509, + 7.658, + 13.517, + 7.638, + 7.638, + 7.483, + 7.526, + 9.558, + 8.746, + 7.217, + 7.502, + 8.728, + 10.277, + 7.571, + 7.448, + 7.518, + 10.702, + 8.96, + 7.233, + 12.441, + 8.389, + 8.042, + 7.562, + 9.772, + 9.664, + 8.734, + 7.8, + 9.526, + 7.917, + 7.185, + 8.944, + 9.683, + 7.622, + 7.21, + 8.65, + 7.835, + 7.73, + 7.218, + 7.652, + 8.921, + 7.55, + 7.879, + 7.24, + 15.241, + 8.077, + 7.66, + 7.935, + 8.886, + 8.441, + 7.149, + 13.923, + 7.513, + 8.654, + 7.411, + 10.054, + 7.723, + 8.127, + 7.682, + 12.366, + 8.926, + 8.002, + 8.027, + 11.461, + 8.969, + 7.262, + 8.678, + 8.895, + 7.811, + 10.497, + 9.973, + 8.681, + 9.68, + 7.509, + 7.708, + 8.686, + 8.526, + 7.459, + 17.432, + 8.429, + 7.641, + 7.674, + 9.322, + 8.515, + 8.812, + 7.245, + 13.241, + 7.847, + 7.844, + 9.608, + 11.095, + 7.441, + 7.683, + 7.965, + 8.559, + 8.512, + 7.292, + 9.622, + 8.976, + 7.556, + 7.557, + 7.875, + 8.944, + 7.95, + 7.276, + 10.539, + 8.552, + 9.398, + 7.394, + 9.214, + 12.749, + 7.926, + 7.602, + 9.812, + 8.71, + 7.573, + 8.063, + 8.518, + 8.424, + 7.24, + 7.089, + 12.947, + 7.395, + 11.537, + 10.312, + 8.732, + 7.723, + 7.285, + 9.387, + 11.059, + 7.723, + 7.716, + 8.027, + 8.408, + 8.121, + 10.925, + 13.442, + 7.948, + 8.063, + 7.304, + 9.032, + 7.782, + 7.441, + 8.248, + 8.958, + 8.121, + 7.229, + 8.964, + 9.11, + 7.622, + 7.677, + 9.631, + 13.061, + 7.44, + 7.621, + 7.852, + 9.017, + 7.69, + 7.793, + 7.196, + 10.499, + 8.923, + 8.452, + 14.546, + 10.475, + 9.045, + 7.832, + 13.146, + 8.335, + 7.462, + 8.828, + 9.714, + 9.187, + 7.271, + 8.151, + 9.297, + 8.16, + 7.048, + 9.068, + 13.523, + 7.425, + 7.714, + 8.034, + 10.065, + 7.63, + 7.355, + 7.245, + 8.112, + 8.82, + 7.575, + 8.066, + 8.504, + 8.538, + 7.521, + 8.874, + 8.637, + 7.709, + 7.586, + 10.538, + 8.54, + 7.459, + 10.037, + 9.58, + 7.922, + 7.537, + 8.423, + 12.716, + 7.829, + 9.052, + 7.042, + 12.596, + 7.654, + 12.369, + 7.477, + 9.322, + 8.587, + 7.131, + 9.16, + 7.924, + 8.726, + 7.432, + 10.27, + 8.347, + 11.796, + 8.2, + 13.167, + 8.885, + 7.478, + 9.028, + 10.159, + 7.764, + 9.179, + 8.332, + 8.879, + 8.486, + 7.695, + 7.153, + 8.144, + 7.603, + 7.357, + 8.298, + 14.564, + 7.366, + 7.402, + 8.722, + 9.21, + 7.913, + 7.67, + 10.25, + 7.814, + 7.216, + 8.077, + 10.232, + 8.274, + 7.312, + 8.797, + 8.312, + 8.541, + 7.234, + 8.631, + 9.78, + 7.659, + 7.738, + 7.749, + 13.03, + 7.242, + 7.169, + 7.952, + 12.143, + 7.832, + 7.517, + 7.56, + 9.12, + 8.491, + 7.665, + 9.802, + 12.856, + 8.031, + 7.843, + 9.349, + 7.733, + 7.6, + 8.801, + 14.265, + 7.9, + 7.843, + 12.645, + 9.699, + 8.02, + 7.741, + 10.17, + 9.811, + 7.587, + 7.163, + 7.722, + 9.023, + 7.269, + 7.727, + 7.491, + 8.654, + 7.799, + 7.422, + 7.676, + 8.607, + 7.838, + 7.651, + 11.197, + 8.587, + 10.326, + 9.025, + 13.842, + 8.791, + 7.611, + 8.323, + 8.537, + 8.112, + 7.351, + 8.119, + 8.934, + 9.076, + 7.378, + 9.862, + 14.398, + 7.443, + 7.459, + 7.301, + 8.973, + 7.421, + 7.222, + 7.449, + 10.125, + 8.895, + 16.406, + 12.219, + 7.964, + 8.082, + 7.239, + 12.44, + 13.674, + 8.5, + 11.027, + 15.319, + 8.102, + 7.565, + 8.772, + 8.69, + 7.547, + 7.65, + 10.148, + 8.061, + 8.56, + 7.633, + 9.077, + 8.277, + 7.447, + 7.531, + 7.41, + 9.465, + 8.084, + 7.094, + 14.924, + 9.653, + 8.516, + 7.374, + 8.639, + 7.824, + 9.382, + 9.216, + 8.61, + 8.537, + 8.831, + 8.099, + 8.543, + 7.525, + 7.486, + 9.916, + 9.375, + 11.646, + 7.469, + 7.681, + 8.475, + 7.305, + 7.962, + 7.309, + 11.355, + 7.188, + 7.331, + 9.613, + 10.148, + 8.151, + 12.84, + 8.809, + 8.292, + 9.629, + 7.927, + 8.654, + 7.59, + 8.532, + 9.299, + 9.681, + 7.488, + 7.376, + 8.902, + 9.491, + 7.211, + 7.756, + 8.13, + 8.933, + 7.316, + 7.182, + 7.735, + 9.642, + 7.637, + 7.321, + 9.815, + 10.151, + 9.682, + 9.187, + 8.756, + 7.974, + 10.799, + 7.144, + 8.494, + 7.838, + 8.429, + 9.772, + 9.37, + 7.672, + 7.766, + 8.561, + 9.545, + 7.826, + 7.63, + 7.845, + 8.91, + 8.172, + 7.432, + 8.464, + 10.482, + 8.283, + 7.176, + 9.253, + 9.699, + 7.478, + 7.796, + 8.311, + 7.668, + 8.186, + 9.711, + 14.071, + 9.958, + 9.438, + 7.949, + 11.641, + 12.022, + 7.861, + 9.942, + 7.943, + 7.612, + 7.328, + 9.767, + 8.595, + 7.717, + 7.355, + 7.912, + 11.972, + 7.571, + 7.214, + 7.345, + 8.417, + 7.757, + 7.604, + 8.307, + 8.382, + 8.81, + 7.663, + 15.797, + 7.888, + 8.634, + 7.667, + 10.726, + 8.025, + 8.041, + 8.904, + 8.554, + 7.701, + 7.363, + 8.868, + 9.541, + 7.616, + 7.422, + 8.942, + 7.924, + 8.161, + 7.493, + 8.964, + 9.693, + 7.33, + 7.224, + 7.388, + 7.937, + 9.204, + 7.348, + 9.566, + 13.021, + 8.054, + 7.664, + 38.949, + 8.169, + 7.87, + 7.792, + 10.007, + 10.192, + 8.045, + 7.774, + 10.333, + 7.483, + 7.322, + 9.043, + 11.598, + 7.392, + 7.403, + 8.386, + 8.653, + 7.246, + 7.324, + 7.663, + 11.635, + 7.641, + 7.808, + 20.373, + 11.871, + 7.643, + 7.544, + 10.034, + 8.254, + 9.672, + 9.898, + 8.322, + 7.883, + 8.044, + 8.193, + 13.524, + 7.799, + 8.033, + 8.207, + 7.844, + 7.858, + 8.383, + 10.159, + 8.016, + 7.421, + 7.318, + 10.177, + 9.128, + 7.496, + 7.277, + 13.313, + 8.618, + 7.675, + 7.225, + 9.255, + 8.859, + 8.249, + 7.351, + 10.395, + 7.624, + 9.347, + 7.151, + 13.774, + 8, + 8.498, + 8.121, + 9.493, + 7.52, + 7.338, + 8.035, + 8.599, + 7.474, + 7.565, + 9.463, + 9.533, + 8.184, + 7.524, + 13.175, + 8.103, + 7.461, + 7.553, + 9.393, + 9.45, + 7.575, + 7.235, + 13.651, + 8.015, + 8.204, + 7.336, + 13.046, + 8.077, + 8.904, + 7.711, + 8.55, + 7.629, + 7.119, + 8.541, + 11.416, + 7.632, + 10.725, + 8.338, + 10.07, + 7.478, + 7.648, + 7.719, + 8.954, + 7.469, + 7.223, + 13.7, + 9.54, + 10.051, + 7.115, + 9.425, + 10.242, + 8.326, + 7.996, + 9.973, + 8.369, + 9.166, + 9.852, + 12.49, + 8.29, + 9.609, + 9.082, + 8.944, + 7.672, + 7.971, + 8.389, + 7.517, + 8.38, + 9.251, + 9.698, + 8.978, + 8.95, + 7.763, + 14.168, + 12.39, + 7.499, + 8.129, + 10.137, + 8.645, + 7.2, + 7.47, + 8.381, + 9.563, + 7.388, + 8.238, + 8.973, + 8.043, + 9.444, + 7.206, + 9.804, + 8.051, + 8.601, + 8.898, + 8.63, + 9.749, + 8.616, + 8.248, + 8.871, + 8.075, + 7.77, + 34.286, + 8.645, + 7.678, + 7.659, + 14.178, + 11.846, + 7.829, + 7.364, + 7.972, + 11.011, + 7.686, + 7.356, + 10.183, + 7.393, + 8.537, + 8.374, + 8.887, + 7.581, + 8.407, + 9.225, + 7.752, + 8.335, + 7.37, + 10.234, + 7.763, + 7.695, + 7.676, + 15.834, + 8.077, + 8.051, + 7.482, + 9.176, + 9.114, + 7.99, + 7.741, + 8.538, + 9.151, + 7.767, + 7.679, + 9.263, + 7.792, + 8.376, + 7.424, + 8.217, + 7.921, + 7.229, + 7.282, + 7.953, + 7.718, + 7.231, + 7.969, + 7.449, + 7.547, + 7.295, + 9.315, + 8.107, + 8.332, + 8.46, + 18.667, + 8.596, + 7.64, + 7.198, + 8.654, + 8.309, + 7.235, + 7.276, + 17.365, + 8.18, + 8.021, + 7.371, + 9.921, + 7.495, + 7.624, + 14.4, + 10.49, + 7.813, + 8.049, + 9.451, + 9.209, + 7.626, + 8.238, + 9.343, + 8.109, + 7.944, + 8.178, + 13.68, + 7.924, + 7.783, + 7.604, + 9.468, + 7.569, + 7.826, + 8.06, + 10.033, + 8.665, + 7.361, + 7.561, + 10.965, + 8.9, + 9.264, + 7.874, + 9.201, + 7.594, + 9.994, + 8.227, + 7.932, + 7.119, + 8.932, + 8.272, + 8.016, + 8.46, + 7.893, + 15.539, + 8.154, + 7.567, + 7.365, + 10.889, + 8.126, + 7.532, + 7.475, + 8.113, + 10.638, + 7.258, + 7.671, + 8.945, + 7.912, + 7.185, + 7.2, + 8.004, + 7.775, + 7.451, + 7.69, + 8.131, + 7.781, + 11.081, + 7.909, + 7.804, + 7.765, + 10.608, + 7.909, + 7.92, + 8.868, + 7.953, + 15.293, + 8.868, + 7.354, + 7.805, + 10.156, + 9.293, + 7.492, + 7.867, + 10.075, + 8.481, + 7.482, + 7.625, + 8.836, + 7.525, + 7.792, + 7.56, + 8.304, + 7.589, + 8.159, + 7.663, + 8.127, + 7.459, + 8.737, + 8.24, + 8.259, + 7.413, + 7.649, + 13.209, + 8.057, + 8.959, + 8.938, + 8.33, + 8.596, + 8.423, + 7.468, + 8.241, + 9.27, + 8.353, + 7.804, + 8.427, + 9.165, + 7.822, + 7.618, + 8.24, + 7.422, + 8.968, + 7.886, + 8.747, + 7.39, + 7.755, + 8.956, + 9.489, + 7.596, + 7.524, + 9.44, + 7.915, + 7.605, + 8.278, + 17.311, + 8.311, + 7.205, + 7.652, + 10.617, + 7.793, + 7.497, + 7.656, + 8.986, + 9.441, + 8.008, + 7.466, + 10.22, + 7.456, + 8.545, + 7.888, + 7.961, + 7.286, + 7.924, + 7.263, + 7.907, + 7.941, + 7.542, + 8.398, + 8.157, + 8.12, + 7.893, + 14.549, + 9.347, + 7.2, + 7.347, + 12.531, + 8.752, + 7.336, + 7.245, + 8.829, + 10.714, + 7.404, + 7.233, + 8.58, + 8.208, + 9.265, + 7.298, + 8.145, + 7.513, + 8.695, + 7.378, + 7.946, + 8.13, + 8.445, + 7.802, + 7.462, + 7.435, + 7.203, + 9.279, + 10.273, + 7.615, + 7.676, + 11.758, + 8.367, + 7.201, + 7.735, + 10.538, + 7.824, + 7.62, + 7.56, + 10.35, + 11.459, + 11.069, + 7.157, + 11.53, + 7.494, + 8.25, + 7.377, + 8.29, + 7.579, + 7.342, + 8.418, + 7.853, + 7.436, + 7.295, + 8.054, + 8.304, + 7.839, + 8.876, + 18.883, + 8.688, + 7.329, + 7.974, + 10.705, + 9.608, + 7.545, + 7.199, + 12.782, + 8.104, + 8.114, + 7.729, + 18.628, + 7.806, + 8.68, + 8.152, + 7.952, + 8.017, + 9.449, + 8.268, + 8.75, + 7.62, + 8.84, + 9.128, + 9.371, + 7.83, + 7.341, + 11.803, + 7.95, + 7.63, + 10.361, + 9.164, + 15.759, + 8.263, + 7.539, + 10.561, + 11.636, + 7.314, + 7.617, + 11.979, + 7.396, + 8.145, + 8.194, + 8.412, + 7.561, + 10.04, + 8.238, + 8.75, + 7.143, + 9.969, + 8.792, + 8.608, + 7.651, + 8.076, + 10.189, + 8.039, + 7.561, + 7.705, + 14.117, + 8.963, + 7.406, + 7.634, + 12.246, + 9.848, + 8.245, + 7.66, + 10.23, + 8.889, + 7.388, + 7.798, + 8.656, + 8.281, + 7.687, + 7.548, + 9.085, + 7.645, + 9.483, + 7.796, + 7.569, + 7.195, + 7.742, + 8.078, + 7.851, + 7.119, + 7.38, + 14.677, + 10.519, + 7.699, + 7.462, + 12.56, + 10.775, + 7.265, + 8.369, + 10.618, + 15.656, + 7.532, + 7.591, + 11.083, + 9.451, + 8.427, + 7.311, + 9.374, + 8.23, + 8.255, + 7.232, + 7.854, + 9.362, + 8.341, + 7.834, + 7.66, + 7.093, + 8.136, + 17.086, + 8.12, + 7.397, + 7.466, + 11.112, + 8.19, + 7.464, + 8.476, + 15.665, + 8.459, + 7.676, + 8.901, + 11.438, + 9.127, + 7.57, + 7.731, + 7.85, + 8.173, + 7.816, + 7.988, + 7.95, + 7.337, + 8.327, + 8.469, + 9.24, + 7.26, + 8.974, + 20.291, + 9.043, + 7.731, + 9.203, + 9.693, + 8.049, + 8.032, + 7.34, + 25.783, + 8.058, + 7.56, + 7.331, + 10.972, + 7.456, + 7.305, + 7.51, + 9.254, + 10.469, + 7.318, + 7.354, + 8.11, + 8.843, + 7.64, + 7.704, + 7.74, + 7.723, + 8.81, + 8.701, + 10.372, + 7.791, + 9.067, + 10.014, + 7.968, + 8.63, + 8.341, + 13.375, + 9.148, + 7.974, + 7.48, + 12.62, + 7.721, + 7.535, + 7.404, + 7.528, + 8.205, + 7.865, + 9.096, + 8.063, + 8.622, + 8.084, + 7.952, + 8.386, + 8.95, + 9.438, + 9.082, + 7.793, + 7.593, + 8.164, + 15.205, + 9.613, + 7.256, + 8.14, + 9.003, + 8.543, + 8.088, + 7.611, + 9.051, + 12.628, + 8.571, + 7.524, + 8.908, + 19.079, + 8.077, + 7.767, + 7.947, + 9.334, + 7.5, + 9.2, + 10.299, + 7.46, + 7.893, + 7.542, + 7.901, + 14.317, + 8.077, + 15.213, + 8.243, + 10.653, + 9.018, + 14.658, + 8.053, + 7.714, + 8.998, + 9.913, + 9.117, + 7.557, + 7.268, + 10.306, + 9.816, + 7.578, + 7.473, + 8.363, + 9.396, + 7.612, + 7.513, + 11.372, + 9.109, + 7.561, + 8.096, + 8.624, + 7.815, + 8.624, + 9.854, + 13.99, + 7.186, + 9.677, + 14.193, + 7.694, + 7.524, + 9.159, + 11.087, + 7.609, + 8.856, + 7.59, + 9.47, + 7.584, + 7.725, + 7.313, + 8.613, + 19.267, + 7.28, + 7.234, + 9.616, + 9.791, + 7.46, + 7.661, + 7.719, + 7.98, + 7.415, + 20.137, + 8.326, + 10.588, + 9.58, + 11.691, + 7.965, + 7.782, + 7.901, + 10.003, + 8.276, + 8.132, + 7.214, + 11.516, + 7.307, + 7.447, + 8.958, + 9.241, + 7.768, + 7.867, + 7.487, + 7.945, + 8.008, + 7.678, + 7.152, + 8.096, + 10.542, + 7.919, + 14, + 8.247, + 7.496, + 8.303, + 9.055, + 7.649, + 7.262, + 7.798, + 8.783, + 7.74, + 7.147, + 7.183, + 11.224, + 8.256, + 7.294, + 8.487, + 10.9, + 7.805, + 7.572, + 7.395, + 8.155, + 7.343, + 7.539, + 8.199, + 8.945, + 9.859, + 8.034, + 12.434, + 9.958, + 7.862, + 11.797, + 13.765, + 8.224, + 7.656, + 9.445, + 18.626, + 8.736, + 8.409, + 7.962, + 15.278, + 8.728, + 7.275, + 7.104, + 14.953, + 7.354, + 7.647, + 7.252, + 12.128, + 7.895, + 7.735, + 7.237, + 7.74, + 7.775, + 7.977, + 12.934, + 8.465 + ], + "segment_events": [ + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143920_6757546a-8041-4de1-af5c-6d9678000cc9.mp4", + "tMs": -27522 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143920_b4a165cd-7246-4346-9ad2-f94a526fe5e1.mp4", + "tMs": -27522 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143920_7afa6dc8-92fd-4cb3-a00e-58244de0b801.mp4", + "tMs": -27522 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143920_c6b2272b-74b8-45bd-903b-0858af9419b0.mp4", + "tMs": -27520 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143933_3d6dd10a-7125-44fb-99f7-0cb319238ada.mp4", + "tMs": -17309 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143933_cb0d50c4-d3dd-44c2-9ef9-6650652c0367.mp4", + "tMs": -17298 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143933_10110e05-fb23-45ea-a07b-a21f7f04548e.mp4", + "tMs": -17298 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143933_175597fc-d4b7-4fbb-8036-003830ff16ed.mp4", + "tMs": -17298 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143943_62501e4c-0261-4a1e-b7fd-739572b35905.mp4", + "tMs": -7313 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143943_f6774dea-1d01-40f4-b099-06eb3db03dc9.mp4", + "tMs": -7306 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143943_47b6ccfa-0af1-4f44-956c-a7dcf756b41b.mp4", + "tMs": -7306 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143943_b1b850e4-6f01-42cf-b650-56ca57bfee2e.mp4", + "tMs": -7295 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_143953_5a0742b9-0c1b-4650-b157-29bf2607b317.mp4", + "tMs": 2687 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_143953_41b3466a-3d1d-467d-befa-3e31209c7e45.mp4", + "tMs": 2694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_143953_432f166a-a32a-4c6a-a922-c88db4f97089.mp4", + "tMs": 2694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_143953_b2d5b8b5-d5d6-46be-afd3-c8eb56cf29cf.mp4", + "tMs": 2695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144003_2da2a0cb-4f48-46a9-a69e-b4c6a0ebf850.mp4", + "tMs": 12701 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144003_6b997a67-22cf-4cd4-a6b4-2b6adf35a9c3.mp4", + "tMs": 12701 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144003_45a52025-5e0b-42bd-af92-5ee3de837d0b.mp4", + "tMs": 12701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144003_6f3c207b-3b84-4be7-ad0f-9844dbc4aa4b.mp4", + "tMs": 12702 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144013_d99a008b-bb0a-46f9-9c0d-a1a430da8560.mp4", + "tMs": 22694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144013_6307af60-7056-480f-805a-9add54e7d09f.mp4", + "tMs": 22699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144013_f060c2df-321e-434c-aa80-1b4b420f2924.mp4", + "tMs": 22699 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144013_8b15301a-3e65-4215-94e1-ff8088ea7f53.mp4", + "tMs": 22699 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144023_91de8afb-72e3-4469-97d3-ba012fb1d170.mp4", + "tMs": 32690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144023_e2233f8c-8b2f-4050-b0e7-361ed81f8828.mp4", + "tMs": 32698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144023_bfc309df-bd51-48b7-bed9-1c57ca961a3b.mp4", + "tMs": 32698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144023_7f607484-e12a-495f-a787-70fd64c88db2.mp4", + "tMs": 32699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144033_e7d76b66-23b6-43b0-a6cb-d697b8c0d321.mp4", + "tMs": 42687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144033_c7c93495-6b44-4bff-96d2-2efeeed26a10.mp4", + "tMs": 42698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144033_2008585f-5c8e-4039-a59a-35c2b0194d7c.mp4", + "tMs": 42704 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144033_28eae8a0-6d9d-47e8-bac8-63565f255cbd.mp4", + "tMs": 42706 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144043_b62ec44b-a938-45ea-bf5e-7dced7edb066.mp4", + "tMs": 52688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144043_e14536ab-f360-4575-ad77-b61fc4e8156c.mp4", + "tMs": 52702 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144043_1efe7e55-d9b5-4ed2-be1e-4ee1ae643ab3.mp4", + "tMs": 52705 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144043_b08c6a9d-7023-4a51-8c31-cd4425a02617.mp4", + "tMs": 52708 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144053_4ad73459-1fed-4d25-97e8-4c5d62b796fb.mp4", + "tMs": 62688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144053_f3560901-85b2-4b15-8aee-88ff325cf630.mp4", + "tMs": 62694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144053_3a7f1557-617a-4808-abe3-d5dbf4ba41fe.mp4", + "tMs": 62694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144053_4b79ea53-1c5e-4fb1-961f-ac269af18006.mp4", + "tMs": 62695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144103_7b42881f-9e81-4e8b-b65b-425136a2bc4a.mp4", + "tMs": 72690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144103_01faf942-cf0a-4bf1-9165-a4b5b7819d70.mp4", + "tMs": 72693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144103_38a38db3-12c5-4c6b-880f-b10b9885676e.mp4", + "tMs": 72694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144103_6af29f41-1bfe-485d-b05e-cd14aa46b868.mp4", + "tMs": 72696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144113_b03506b4-279b-472a-b15c-44d6b2b91aee.mp4", + "tMs": 82691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144113_d3239680-9f09-47f4-9d79-1d5cf14dd552.mp4", + "tMs": 82703 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144113_e2997f8d-7eb4-44ad-af06-1e93b754079f.mp4", + "tMs": 82706 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144113_ab8299d4-524d-4eba-9fc0-37e3ac102dde.mp4", + "tMs": 82715 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144123_d50b4f38-7c33-4c5e-b5c9-9e914a6d4f7e.mp4", + "tMs": 92688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144123_f20bc9fb-d196-4fd6-946f-94d264736fe5.mp4", + "tMs": 92688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144123_ac1d559b-d128-4f83-9a2f-0f486c13d751.mp4", + "tMs": 92688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144123_8e19a8a6-4f26-4308-822f-50ce32d472b6.mp4", + "tMs": 92691 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144133_c47f3249-7915-42e9-b173-a929a7d13346.mp4", + "tMs": 102688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144133_00d9a7f6-d5e4-482f-a672-ff13f45d2afd.mp4", + "tMs": 102695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144133_30601e53-8ab6-47ef-99ea-6033cf5a1abd.mp4", + "tMs": 102695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144133_cfa3a2b3-5d74-4c16-a08b-c78a22ec0f08.mp4", + "tMs": 102696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144143_d59e9c58-242e-4aae-ab0e-649f1f76b2d1.mp4", + "tMs": 112690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144143_c9f66d40-b1d5-480b-bb6a-04d7bcf05411.mp4", + "tMs": 112696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144143_f0270652-f272-4660-91e9-f6cc86472db0.mp4", + "tMs": 112696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144143_14735176-94cc-4cf5-b687-fd38269e5cb9.mp4", + "tMs": 112696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144153_49f3c08c-668d-47a6-bb67-c07146244f0c.mp4", + "tMs": 122688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144153_c6b3b6b5-2775-41c4-87ba-4ed078bdd6cf.mp4", + "tMs": 122692 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144153_ed84516c-a711-4ebc-b055-6de7045991eb.mp4", + "tMs": 122692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144153_0566325d-62d3-4246-8cd1-753bb5a738d2.mp4", + "tMs": 122693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144203_bb88d50e-34a9-4c13-bc00-07280337c99e.mp4", + "tMs": 132703 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144203_9dc7fb1a-b270-48d0-be73-242ade4524ac.mp4", + "tMs": 132704 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144203_274ddee8-2d2d-4d22-9c21-8c9fc2c0eadd.mp4", + "tMs": 132705 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144203_a88f2b55-4261-4ddc-a96c-8870b32b3ee8.mp4", + "tMs": 132705 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144213_10751f7a-bf5b-4c70-8fd7-828cdaf93647.mp4", + "tMs": 142688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144213_db4c0e26-66f9-4e06-bd61-eacdec0e8ae0.mp4", + "tMs": 142694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144213_c3de38e3-64aa-47eb-9574-d571d1923640.mp4", + "tMs": 142696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144213_7175a31a-a484-4266-b708-99277196e034.mp4", + "tMs": 142696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144223_4c245a9b-177c-4702-8b4f-cc2f64322af2.mp4", + "tMs": 152690 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144223_7e05dd3f-bc09-4be6-bd59-d746fb50b627.mp4", + "tMs": 152695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144223_cb041470-fcf8-4c73-837f-1540f3cf5d26.mp4", + "tMs": 152696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144223_3f653e49-2e0d-4f03-9d03-1160ff73add0.mp4", + "tMs": 152699 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144233_ac7a182e-779f-4a99-80c1-c52350dc1460.mp4", + "tMs": 162687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144233_bdcd8399-f8e9-4c7f-b2cd-3afe01086d1d.mp4", + "tMs": 162687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144233_37379b17-a0ef-4139-83ae-3f831d294fad.mp4", + "tMs": 162694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144233_d99ba237-5121-4c23-85cf-8f6dbc231b36.mp4", + "tMs": 162694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144243_ce669299-f1ed-4369-99f2-53460514fe9f.mp4", + "tMs": 172688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144243_963cd2f7-0bbf-48c5-8d7b-d740e4f810f3.mp4", + "tMs": 172696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144243_2afc59d5-91ff-4055-a6a7-e7852e012b7a.mp4", + "tMs": 172699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144243_3df0806f-3ad0-4b47-a28e-38e929e9f69a.mp4", + "tMs": 172699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144253_71bfe117-85ed-4273-89eb-f54a2c7a115c.mp4", + "tMs": 182686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144253_e19df260-353d-4f35-8c58-8932e27a292b.mp4", + "tMs": 182694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144253_b9279387-c9ae-4805-9a86-3e014cf190b7.mp4", + "tMs": 182694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144253_7ed2e7c1-791e-4a2a-81df-7d479d2d20a1.mp4", + "tMs": 182696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144303_6b91efe9-76f3-4fdd-9c33-27d25af0f2b6.mp4", + "tMs": 192688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144303_fa0eace1-0bfc-4650-bb44-9680b70b9de7.mp4", + "tMs": 192694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144303_2ecbeb3f-c173-4044-9177-7a0fb46c60bb.mp4", + "tMs": 192696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144303_8b000211-1592-48e1-a0c2-dd9abadd8c0d.mp4", + "tMs": 192697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144313_ee492e88-2b68-45be-9ce0-82d02eeeebfc.mp4", + "tMs": 202689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144313_563f1fef-cc8f-4ec7-a393-f2de85275025.mp4", + "tMs": 202697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144313_db6aa58e-820a-4441-8635-58562ebf34d2.mp4", + "tMs": 202698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144313_1037821f-0728-46dc-a62e-ade202141f3a.mp4", + "tMs": 202699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144323_92d93881-bd83-424a-a4fc-9f1f0892b0b1.mp4", + "tMs": 212688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144323_5cd9bcb3-ae69-446c-900b-a92c076bde1d.mp4", + "tMs": 212692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144323_e639c2c1-d71c-47c6-929d-bfee17523506.mp4", + "tMs": 212693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144323_5c207627-5f7e-4c17-931f-18e9a2f2ed56.mp4", + "tMs": 212694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144333_5227a8ab-7a9f-43d5-8747-adc2ca7b3903.mp4", + "tMs": 222688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144333_c44f71b0-66d0-4bdc-b4eb-47d5070e1dc2.mp4", + "tMs": 222694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144333_84175c68-641f-47d8-8394-3f79cdab4519.mp4", + "tMs": 222695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144333_70e6a66c-6379-49a4-b435-daab4e971a81.mp4", + "tMs": 222698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144343_c8daf068-7d32-49ab-b1cf-f9e6fee41241.mp4", + "tMs": 232692 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144343_f39bc19b-8d99-40d7-bacb-f808b58d122e.mp4", + "tMs": 232697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144343_0234bdf9-6146-49ac-b2f3-866985d0f5ec.mp4", + "tMs": 232698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144343_0b436ba2-4520-43b8-b936-a2025b5bfb7d.mp4", + "tMs": 232708 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144353_f287d57a-8bf9-4594-9483-86149077da8a.mp4", + "tMs": 242687 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144353_65d11b13-eada-4331-8699-29546239708a.mp4", + "tMs": 242697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144353_aa2a62c4-72e8-4ca8-8a93-fa5fccd5a54f.mp4", + "tMs": 242700 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144353_a606753c-a452-4988-942a-f9ebccb5808a.mp4", + "tMs": 242700 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144403_ef8e161e-06fe-47c3-95f1-4f67ebfea152.mp4", + "tMs": 252689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144403_581e74b7-c88e-40af-8355-10a2072b91a4.mp4", + "tMs": 252696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144403_62b60568-05d2-45ea-b048-d6f7bf3cf54f.mp4", + "tMs": 252697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144403_572b8328-4f49-4929-b137-130125c0490d.mp4", + "tMs": 252697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144413_7d721d6c-0234-4fd7-b9f6-5b88dcebb7f6.mp4", + "tMs": 262689 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144413_1cb5e48a-98f8-467e-92cc-aa2d5fde40b2.mp4", + "tMs": 262696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144413_df7c575a-f651-43a4-ad61-25b75c4968fc.mp4", + "tMs": 262699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144413_3e3ee4ab-c48b-4fe7-bf66-cb99e922754f.mp4", + "tMs": 262712 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144423_a49bc3d3-5a9c-429d-882d-85789c2444ac.mp4", + "tMs": 272688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144423_1f0e974e-0cb0-4485-962d-45c667a0f4ab.mp4", + "tMs": 272700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144423_a5770b71-51db-412b-8a60-7926500a238d.mp4", + "tMs": 272702 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144423_dca15047-9ae9-47e8-91f2-c48d02c442d6.mp4", + "tMs": 272702 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144433_461a2f57-affa-467a-b194-e5c950a755dc.mp4", + "tMs": 282687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144433_84582f61-6214-41ab-b1ac-2773eb4cbd2e.mp4", + "tMs": 282692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144433_ffa28312-859c-414e-aad1-220976b4b556.mp4", + "tMs": 282693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144433_9b6623f7-d7e0-4859-8da4-be43ad8448ee.mp4", + "tMs": 282707 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144443_9c2f786c-297a-4b39-b9d9-8b451439f114.mp4", + "tMs": 292688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144443_3cdab5c0-552b-4ff0-b899-b87a745bfb8e.mp4", + "tMs": 292693 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144443_e69016b9-c48e-4375-9ca0-b5b4d173d7de.mp4", + "tMs": 292694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144443_1b49d73e-0193-48e1-b3f9-da64772049da.mp4", + "tMs": 292695 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144453_574cc3ff-f1d1-4a3a-a312-9d6985cf7f74.mp4", + "tMs": 302687 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144453_f19b6548-1871-4a99-8164-cd9c160ca997.mp4", + "tMs": 302693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144453_937183fe-83b1-4d38-a7d4-5df21fe058ff.mp4", + "tMs": 302694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144453_a53045a4-11df-411b-8aec-7d03b77e4d8c.mp4", + "tMs": 302695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144503_414f05bd-fa8e-4f3b-9928-778350aa0bf7.mp4", + "tMs": 312688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144503_465c5185-a53e-4918-a7e4-ced479ac375e.mp4", + "tMs": 312697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144503_9b4a6604-c8d7-49f9-a699-349f69329a62.mp4", + "tMs": 312698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144503_04aef691-fb03-4da5-8245-b24e112f356b.mp4", + "tMs": 312698 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144513_00725f61-1298-4c6f-aec1-a3d4bb5e2d51.mp4", + "tMs": 322689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144513_bc2f98e7-d410-4286-85c6-9635e8323183.mp4", + "tMs": 322694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144513_1dbf5581-f744-4168-a358-6e06ea286e94.mp4", + "tMs": 322694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144513_ff32d599-c330-47f2-ad92-4d9dfecdd82b.mp4", + "tMs": 322695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144523_a7728ddb-9192-4a10-a01d-5da7d4619a64.mp4", + "tMs": 332694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144523_f0cc937c-628f-4efd-8c8b-7a3307eeeda8.mp4", + "tMs": 332695 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144523_0050e785-172c-416f-8fbf-576bf1d3665e.mp4", + "tMs": 332696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144523_e0430cae-6d00-49aa-92c2-f0b14042635f.mp4", + "tMs": 332697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144533_0435ac90-5d0d-4d01-bc98-93001616bbf6.mp4", + "tMs": 342690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144533_1a2271da-5213-4d54-8cdb-aef84c253636.mp4", + "tMs": 342701 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144533_43441496-797c-4242-84b0-f86626e141f5.mp4", + "tMs": 342704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144533_aab86f28-bd54-48ba-adcc-afd0d363f034.mp4", + "tMs": 342711 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144543_7bccacf8-e323-49f4-b181-7492dae86b05.mp4", + "tMs": 352687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144543_f6decf07-4b88-45bc-8874-535b481f8774.mp4", + "tMs": 352692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144543_fafea7f1-e276-4982-b3e5-a8a40b29dfda.mp4", + "tMs": 352693 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144543_0a82a3e7-d554-452c-b55e-bec79b27545a.mp4", + "tMs": 352725 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144553_ffe07654-e16d-485e-9a3e-fe833ad28c28.mp4", + "tMs": 362689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144553_2c8cbc56-91e7-47f4-90dc-1f24d47e3455.mp4", + "tMs": 362695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144553_b78d17f1-41e1-4c03-9858-ba4a77b70df8.mp4", + "tMs": 362695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144553_581966dc-f921-4691-a154-206a386221df.mp4", + "tMs": 362697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144603_6894b91e-6a71-455d-8e03-e3634bd9288f.mp4", + "tMs": 372687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144603_e32c3ab6-9579-44ed-bcc2-1a4919c0eb78.mp4", + "tMs": 372694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144603_37a21d23-4b57-49ae-b66c-02c7ea36a64c.mp4", + "tMs": 372697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144603_176a2662-d19d-45a1-96a6-103c6de17988.mp4", + "tMs": 372697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144613_7c14bf7b-73c2-4d5c-8c74-d724dc8508e8.mp4", + "tMs": 382687 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144613_0be0f58a-3ccb-4e7c-b3b8-d95da80a14bc.mp4", + "tMs": 382694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144613_eb5b8672-9bdd-49f3-89e5-b891d4ad9439.mp4", + "tMs": 382695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144613_24e2b7c4-fb63-45e2-a4e6-8c9f56e026a7.mp4", + "tMs": 382701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144623_4cacfcc0-cbe2-4e14-a5c8-bbc5e16b3a5b.mp4", + "tMs": 392690 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144623_96b39d1c-6551-490f-ac5d-da15fb4cd22c.mp4", + "tMs": 392696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144623_5caa7fa2-21e3-4ea7-8735-39f189880df9.mp4", + "tMs": 392700 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144623_f5bdaac0-d437-478e-87bc-b5ecbc9fc053.mp4", + "tMs": 392700 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144633_d6028c0e-5b31-4171-ac3b-6683e9803f02.mp4", + "tMs": 402691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144633_5eca6cf4-7420-4f9d-8d34-2853a2bf4de0.mp4", + "tMs": 402694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144633_910c185e-6157-4cf8-89a7-eea5253d8e7e.mp4", + "tMs": 402694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144633_c93d1fa8-27f4-42ec-89e6-11d8733014fa.mp4", + "tMs": 402695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144643_96b44a99-f031-4dd6-80f8-b76ee88122f9.mp4", + "tMs": 412690 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144643_73ea0ec5-52a4-4692-bcf5-f66274b453b3.mp4", + "tMs": 412707 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144643_cd2c5cf5-4f28-4c2e-ac23-d79315dcbcfe.mp4", + "tMs": 412709 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144643_5055fa60-dfde-42d8-bf72-2fa31939a7ac.mp4", + "tMs": 412713 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144653_71127e3a-52a0-4aa0-87c5-98bc4e05545e.mp4", + "tMs": 422689 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144653_3f9f06a0-4815-4227-9206-f58f7c1da4eb.mp4", + "tMs": 422705 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144653_ba09a491-9994-4289-affb-c8a51a676e3a.mp4", + "tMs": 422705 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144653_3871c86d-1b81-472e-a9a9-88b61df8acd3.mp4", + "tMs": 422708 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144703_54afd976-7599-4386-8681-2e04f2d50f75.mp4", + "tMs": 432688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144703_92672f04-1f2f-447d-b495-36fba5fe3d1f.mp4", + "tMs": 432697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144703_0f3c4ee1-530d-46e2-abdb-11f233304bd1.mp4", + "tMs": 432700 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144703_b6e5ce59-c259-433e-8f65-26ece6fd18d6.mp4", + "tMs": 432702 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144713_bc32745c-d6ed-4678-8764-fb164f93575a.mp4", + "tMs": 442688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144713_266c00c2-533a-4ab4-8cb6-9885d64603bd.mp4", + "tMs": 442698 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144713_3af99325-fae8-45a1-a5bc-c1b6f2c7758d.mp4", + "tMs": 442716 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144713_a73bfa17-2983-4e93-90e5-a93f84d67e89.mp4", + "tMs": 442719 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144723_fbe21b1a-eacf-4864-9e85-c9668d102384.mp4", + "tMs": 452693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144723_4246d419-d31d-4896-9b7c-d67fd335bfba.mp4", + "tMs": 452693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144723_81f8f843-d6ce-475c-9c0f-3df85e981f2e.mp4", + "tMs": 452694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144723_800c0b7d-eb03-40b4-9fd1-cc87e6916129.mp4", + "tMs": 452694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144733_621507a0-a0fb-4440-971f-77a2b1d41531.mp4", + "tMs": 462687 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144733_3d9f53af-81b8-4a2e-be5d-3b3ac65cef07.mp4", + "tMs": 462697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144733_74626289-2ca8-438d-911d-9737f91abc73.mp4", + "tMs": 462699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144733_48dfc1b6-5042-4761-ba44-851e926547a0.mp4", + "tMs": 462706 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144743_60f77aa4-4669-4ada-83ab-b4c792f8d681.mp4", + "tMs": 472688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144743_dee912a7-049f-4d7d-a155-67ad5e736201.mp4", + "tMs": 472694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144743_3a76c518-8708-44b0-8d87-464591b962fb.mp4", + "tMs": 472694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144743_089af68b-fbf0-4be6-bf6c-d4975eeaca5a.mp4", + "tMs": 472694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144753_c1481ad6-089e-4d6d-bd7b-e2ab8214ef9e.mp4", + "tMs": 482690 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144753_3c6a59ed-b4dd-4069-9359-9d9cd20b6bea.mp4", + "tMs": 482693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144753_0f86bd48-5304-4200-b4dd-1e91bb86dc7e.mp4", + "tMs": 482693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144753_35458313-f69b-4ac0-add0-dc7e2c6fb2f2.mp4", + "tMs": 482693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144803_c450f8d3-a3c6-4e78-a61d-b28f62009fc1.mp4", + "tMs": 492687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144803_a1e00e55-b25a-4209-8b27-5edef4371a2a.mp4", + "tMs": 492692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144803_1d8d6d70-ce9c-465c-86f5-f01f34a59e19.mp4", + "tMs": 492692 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144803_66cb90b2-836a-4590-bb0a-3070f5556719.mp4", + "tMs": 492693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144813_d1cf8866-f6d0-44fa-b379-da6795faad1e.mp4", + "tMs": 502688 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144813_f0a5cfdc-15dd-4096-85e3-62d7078fcddb.mp4", + "tMs": 502695 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144813_6e0c1ce7-4e04-4948-944a-e327dd3cf4f1.mp4", + "tMs": 502699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144813_5996fa02-d221-40ad-ae11-e6f1e29819d0.mp4", + "tMs": 502699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144823_94ff4b07-3fe0-455e-908c-b5f5d9203009.mp4", + "tMs": 512687 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144823_80f4829b-90b1-4a27-90cd-9a8214dd1120.mp4", + "tMs": 512692 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144823_16278509-89c2-4990-95cb-aaf130a754aa.mp4", + "tMs": 512694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144823_4994c748-0f98-48c7-90b9-43ec428f3582.mp4", + "tMs": 512694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144833_6c166d54-652c-4c92-a157-ab176eb7db6e.mp4", + "tMs": 522689 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144833_935e4899-5174-4085-b785-7efb26203df5.mp4", + "tMs": 522696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144833_041cbfa3-5a69-4bad-bd30-04518336e335.mp4", + "tMs": 522696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144833_fd2433bd-aac6-4b49-b853-603f90df45ce.mp4", + "tMs": 522697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144843_23942980-e5bf-47b5-983b-21e3fdef7418.mp4", + "tMs": 532688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144843_0f5fb98c-76da-4b45-bc21-50bc545b3108.mp4", + "tMs": 532688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144843_3d32dcf1-d402-4bfc-a8b7-9009e6a5f073.mp4", + "tMs": 532694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144843_aa9294e5-3cdd-4ae5-991c-030f190e4891.mp4", + "tMs": 532697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144853_5a4c5a83-688f-4dc8-8d1c-17d8e95b62c1.mp4", + "tMs": 542690 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144853_04594871-705d-4b8f-ab0d-36ebc28b67cb.mp4", + "tMs": 542704 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144853_50e2d65f-e7d5-4d89-a8d4-842627a5caa3.mp4", + "tMs": 542704 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144853_743ee107-aff1-41d3-b7c1-982e9c959434.mp4", + "tMs": 542704 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144903_f21a9de6-fdca-4746-82e0-d25ebe3abfac.mp4", + "tMs": 552697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144903_c9f8540d-7d1e-4123-ab8f-45fd75970b16.mp4", + "tMs": 552697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144903_e8f2bdff-3807-47bd-aaec-b5042d78fb68.mp4", + "tMs": 552714 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144903_00d68f4d-2f15-4338-ae91-da86a8234e01.mp4", + "tMs": 552714 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144913_895c9734-eeae-4e95-80c7-8774bc32946a.mp4", + "tMs": 562689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144913_e02c8e4b-79ce-40ec-affa-eca3199d6429.mp4", + "tMs": 562695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144913_5d632bed-b110-4df1-b174-a7d76485edd5.mp4", + "tMs": 562695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144913_ef7f2e4d-4af4-44ab-9418-d5aa86ffca2a.mp4", + "tMs": 562701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144923_00b28dca-9f09-47d3-8e57-6349167a9978.mp4", + "tMs": 572689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144923_393495bb-a94f-4f9d-bc33-94a4cf8ffc4b.mp4", + "tMs": 572692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144923_9e5b26c8-4914-4133-b31c-f54cb92ae7f5.mp4", + "tMs": 572693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144923_8c09669e-4f8b-47dd-a169-1a1b24a8eb22.mp4", + "tMs": 572711 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144933_e17276da-7bcf-452c-bc47-8611719a0679.mp4", + "tMs": 582689 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144933_70d3b6ec-7fd8-4dc1-ad78-6c7f90b683d0.mp4", + "tMs": 582693 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144933_3cf86838-3210-473c-a7ea-d08115f4e962.mp4", + "tMs": 582693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144933_1c5123bf-7959-46fd-bbaa-bbd3246d55af.mp4", + "tMs": 582696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144943_9e3cfc54-fb44-4f12-8641-a0a40ccf17a7.mp4", + "tMs": 592689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144943_ce4f2e7e-66df-413c-abae-486b8c9ab5c7.mp4", + "tMs": 592700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144943_8fbce0c3-4a75-49ab-b16f-7665c45e233e.mp4", + "tMs": 592711 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144943_59a58afe-bb4a-4c32-b205-ed8c431cfa04.mp4", + "tMs": 592717 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_144953_201d37fd-e9cc-45a9-b46e-3c761b4c6b65.mp4", + "tMs": 602689 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_144953_b0109e4e-efe0-4d7e-a364-b00acb338e58.mp4", + "tMs": 602698 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_144953_e0618c13-836b-4e72-bf7f-f8cdcbc9cb2b.mp4", + "tMs": 602700 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_144953_a92e817a-eb48-4659-8d2a-023dc85db3ea.mp4", + "tMs": 602701 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 0, + "assets": { + "by_state_reason": {}, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 0, + "ready_asset_bytes_max": 0, + "detections_total": 2532, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2532 + } + }, + "finished_at": "2026-11-08T19:49:58.682Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-enabled-defaults.json b/tests/performance/observations/results/2026-11-08-x2-enabled-defaults.json new file mode 100644 index 000000000..d75070eea --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-enabled-defaults.json @@ -0,0 +1,8618 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "enabled", + "status": "completed", + "started_at": "2026-11-08T19:05:53.800Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 6.43, + 6.85, + 5.48 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "405055d8456e5a351ce4dc41ae15057c8885b2ab150071a9a27c201cf6e8e4d9", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": { + "observation_evidence_enabled": true, + "observation_max_bytes": 67108864 + }, + "config_extra": null, + "system_settings": {} + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-WNuZ0O", + "detector": { + "inferences_in_window": 2408, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 2408, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 2408, + "dropped": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 2408, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 302926228, + "high_water_items": 4, + "high_water_bytes": 11086848, + "camera_high_water_items": 1, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 33554432, + "camera_bytes": 33554432, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 0, + "inline": 0 + }, + "discard_pending_bytes_max": 0, + "backlog_items_max": 3, + "backlog_bytes_max": 8315136, + "drop_ring_depth_max": 0, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0.002, + "queue_allocated_end": 1, + "worker_running_end": 1, + "retained_bytes_by_camera_end": { + "04d81f96-3182-4658-9f1e-17fdb0815ebd": 44780793, + "7fbd8adf-f2a0-4973-b9ca-36e2bc5f60b3": 44780793, + "99b4798a-cb78-4a5c-b437-6257bd2f6f8d": 44780793, + "ffbb4681-7a86-415c-89c9-d7fe9a037c5d": 44780793 + }, + "budget_bytes_by_camera_end": { + "04d81f96-3182-4658-9f1e-17fdb0815ebd": 67108864, + "7fbd8adf-f2a0-4973-b9ca-36e2bc5f60b3": 67108864, + "99b4798a-cb78-4a5c-b437-6257bd2f6f8d": 67108864, + "ffbb4681-7a86-415c-89c9-d7fe9a037c5d": 67108864 + }, + "history_observations_end": 2512, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 2408, + "sum_seconds": 0.010498273, + "mean_seconds": 0.00000436, + "max_seconds": 0.000340905, + "p50_upper_seconds": 0.000005, + "p99_upper_seconds": 0.00005, + "p999_upper_seconds": 0.00005, + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 1571 + }, + { + "le": 0.00001, + "count": 2361 + }, + { + "le": 0.00005, + "count": 2408 + }, + { + "le": 0.0001, + "count": 2408 + }, + { + "le": 0.00025, + "count": 2408 + }, + { + "le": 0.0005, + "count": 2408 + }, + { + "le": 0.001, + "count": 2408 + }, + { + "le": 0.01, + "count": 2408 + }, + { + "le": "+Inf", + "count": 2408 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "window_unix": { + "start": 1794164793, + "end": 1794165395 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9974.82, + "p50": 9999.944, + "p95": 10008.535, + "p99": 10022.941, + "max": 10027.417, + "mean": 10000.005 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -25.18, + "p50": -0.056, + "p95": 8.535, + "p99": 22.941, + "max": 27.417, + "mean": 0.005 + }, + "db_probe_ms": { + "count": 2314, + "min": 7.008, + "p50": 8.351, + "p95": 14.834, + "p99": 19.929, + "max": 36.451, + "mean": 9.298 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9974.82, + "p50": 9999.888, + "p95": 10005.117, + "p99": 10024.585, + "max": 10027.417, + "mean": 9999.991 + }, + "Load1": { + "count": 60, + "min": 9982.594, + "p50": 10000.072, + "p95": 10005.815, + "p99": 10019.061, + "max": 10022.384, + "mean": 9999.965 + }, + "Load2": { + "count": 60, + "min": 9976.887, + "p50": 10000.047, + "p95": 10008.54, + "p99": 10021.478, + "max": 10023.148, + "mean": 10000.079 + }, + "Load3": { + "count": 60, + "min": 9977.582, + "p50": 9999.708, + "p95": 10008.535, + "p99": 10022.703, + "max": 10026.377, + "mean": 9999.986 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032217280, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.3, + "mean": 15.101 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 599.643, + "cpu_percent_one_core": { + "count": 512, + "min": 116.851, + "p50": 147.332, + "p95": 204.547, + "p99": 246.013, + "max": 266.654, + "mean": 157.382 + }, + "cpu_percent_one_core_avg": 158.046, + "rss_kib": { + "count": 513, + "min": 761804, + "p50": 841992, + "p95": 866656, + "p99": 868126.08, + "max": 880188, + "mean": 828204.218 + }, + "rss_high_water_kib": 891636, + "disk_write_bytes": 1990287360, + "disk_write_bytes_per_sec": 3319119.836, + "threads_max": 35, + "fds_max": 38 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.208, + "processes": 5 + }, + "storage_bytes_end": 1404466987, + "loadavg_at_end": [ + 7.67, + 7.69, + 6.41 + ] + }, + "window": { + "start": "2026-11-08T19:06:33.001Z", + "end": "2026-11-08T19:16:34.297Z", + "seconds": 601.3 + }, + "samples": { + "proc": [ + { + "tMs": 11, + "cpuTicks": 4535, + "rssKiB": 861296, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 87601152 + }, + { + "tMs": 1193, + "cpuTicks": 4701, + "rssKiB": 838368, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 91975680 + }, + { + "tMs": 2346, + "cpuTicks": 4857, + "rssKiB": 838684, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 94896128 + }, + { + "tMs": 3503, + "cpuTicks": 5002, + "rssKiB": 838972, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 99536896 + }, + { + "tMs": 4663, + "cpuTicks": 5180, + "rssKiB": 839120, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 102420480 + }, + { + "tMs": 5863, + "cpuTicks": 5407, + "rssKiB": 863728, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 106651648 + }, + { + "tMs": 7028, + "cpuTicks": 5611, + "rssKiB": 863828, + "peakRssKiB": 881496, + "threads": 35, + "fds": 37, + "writeBytes": 110116864 + }, + { + "tMs": 8176, + "cpuTicks": 5765, + "rssKiB": 839108, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 114102272 + }, + { + "tMs": 9321, + "cpuTicks": 5928, + "rssKiB": 839152, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 117014528 + }, + { + "tMs": 10472, + "cpuTicks": 6083, + "rssKiB": 839488, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 119980032 + }, + { + "tMs": 11608, + "cpuTicks": 6236, + "rssKiB": 839520, + "peakRssKiB": 881496, + "threads": 35, + "fds": 36, + "writeBytes": 122880000 + }, + { + "tMs": 12788, + "cpuTicks": 6437, + "rssKiB": 863704, + "peakRssKiB": 883768, + "threads": 35, + "fds": 36, + "writeBytes": 129187840 + }, + { + "tMs": 13945, + "cpuTicks": 6648, + "rssKiB": 863708, + "peakRssKiB": 883768, + "threads": 35, + "fds": 36, + "writeBytes": 132071424 + }, + { + "tMs": 15142, + "cpuTicks": 6826, + "rssKiB": 839276, + "peakRssKiB": 883768, + "threads": 35, + "fds": 36, + "writeBytes": 136310784 + }, + { + "tMs": 16296, + "cpuTicks": 6978, + "rssKiB": 839280, + "peakRssKiB": 883768, + "threads": 35, + "fds": 36, + "writeBytes": 139321344 + }, + { + "tMs": 17443, + "cpuTicks": 7130, + "rssKiB": 839304, + "peakRssKiB": 883836, + "threads": 35, + "fds": 36, + "writeBytes": 142589952 + }, + { + "tMs": 18601, + "cpuTicks": 7271, + "rssKiB": 839316, + "peakRssKiB": 883836, + "threads": 35, + "fds": 36, + "writeBytes": 145518592 + }, + { + "tMs": 19826, + "cpuTicks": 7489, + "rssKiB": 863496, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 149835776 + }, + { + "tMs": 20996, + "cpuTicks": 7707, + "rssKiB": 863560, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 152403968 + }, + { + "tMs": 22182, + "cpuTicks": 7903, + "rssKiB": 839388, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 157175808 + }, + { + "tMs": 23336, + "cpuTicks": 8069, + "rssKiB": 839388, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 161841152 + }, + { + "tMs": 24458, + "cpuTicks": 8228, + "rssKiB": 839388, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 164728832 + }, + { + "tMs": 25578, + "cpuTicks": 8386, + "rssKiB": 839740, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 167727104 + }, + { + "tMs": 26751, + "cpuTicks": 8584, + "rssKiB": 863916, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 171933696 + }, + { + "tMs": 27920, + "cpuTicks": 8811, + "rssKiB": 863916, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 175243264 + }, + { + "tMs": 29086, + "cpuTicks": 9022, + "rssKiB": 839776, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 179556352 + }, + { + "tMs": 30231, + "cpuTicks": 9190, + "rssKiB": 840912, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 182431744 + }, + { + "tMs": 31347, + "cpuTicks": 9356, + "rssKiB": 840912, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 185335808 + }, + { + "tMs": 32491, + "cpuTicks": 9536, + "rssKiB": 841236, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 188624896 + }, + { + "tMs": 33618, + "cpuTicks": 9697, + "rssKiB": 841564, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 193318912 + }, + { + "tMs": 34837, + "cpuTicks": 9923, + "rssKiB": 865748, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 197660672 + }, + { + "tMs": 36004, + "cpuTicks": 10155, + "rssKiB": 865748, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 200605696 + }, + { + "tMs": 37151, + "cpuTicks": 10321, + "rssKiB": 841580, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 205275136 + }, + { + "tMs": 38270, + "cpuTicks": 10472, + "rssKiB": 841580, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 208183296 + }, + { + "tMs": 39401, + "cpuTicks": 10631, + "rssKiB": 841612, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 211214336 + }, + { + "tMs": 40573, + "cpuTicks": 10782, + "rssKiB": 841892, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 214151168 + }, + { + "tMs": 41755, + "cpuTicks": 10968, + "rssKiB": 866064, + "peakRssKiB": 886868, + "threads": 35, + "fds": 37, + "writeBytes": 218423296 + }, + { + "tMs": 42921, + "cpuTicks": 11201, + "rssKiB": 866072, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 223551488 + }, + { + "tMs": 44072, + "cpuTicks": 11415, + "rssKiB": 865776, + "peakRssKiB": 886868, + "threads": 35, + "fds": 37, + "writeBytes": 227250176 + }, + { + "tMs": 45208, + "cpuTicks": 11578, + "rssKiB": 841640, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 230776832 + }, + { + "tMs": 46329, + "cpuTicks": 11729, + "rssKiB": 841640, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 233734144 + }, + { + "tMs": 47459, + "cpuTicks": 11891, + "rssKiB": 841660, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 237187072 + }, + { + "tMs": 48616, + "cpuTicks": 12045, + "rssKiB": 841664, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 240164864 + }, + { + "tMs": 49802, + "cpuTicks": 12241, + "rssKiB": 865856, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 244441088 + }, + { + "tMs": 50965, + "cpuTicks": 12458, + "rssKiB": 865876, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 247066624 + }, + { + "tMs": 52132, + "cpuTicks": 12640, + "rssKiB": 841728, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 251961344 + }, + { + "tMs": 53292, + "cpuTicks": 12801, + "rssKiB": 841728, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 256696320 + }, + { + "tMs": 54422, + "cpuTicks": 12955, + "rssKiB": 841404, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 259624960 + }, + { + "tMs": 55542, + "cpuTicks": 13124, + "rssKiB": 841432, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 262631424 + }, + { + "tMs": 56661, + "cpuTicks": 13267, + "rssKiB": 841432, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 265551872 + }, + { + "tMs": 57863, + "cpuTicks": 13496, + "rssKiB": 865604, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 270368768 + }, + { + "tMs": 59034, + "cpuTicks": 13728, + "rssKiB": 865928, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 273399808 + }, + { + "tMs": 60176, + "cpuTicks": 13903, + "rssKiB": 841768, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 277467136 + }, + { + "tMs": 61304, + "cpuTicks": 14048, + "rssKiB": 841772, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 280428544 + }, + { + "tMs": 62436, + "cpuTicks": 14194, + "rssKiB": 841792, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 283873280 + }, + { + "tMs": 63570, + "cpuTicks": 14351, + "rssKiB": 841792, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 288604160 + }, + { + "tMs": 64715, + "cpuTicks": 14532, + "rssKiB": 866288, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 292933632 + }, + { + "tMs": 65910, + "cpuTicks": 14762, + "rssKiB": 866300, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 295948288 + }, + { + "tMs": 67063, + "cpuTicks": 14971, + "rssKiB": 842128, + "peakRssKiB": 886868, + "threads": 35, + "fds": 37, + "writeBytes": 300433408 + }, + { + "tMs": 68207, + "cpuTicks": 15136, + "rssKiB": 842128, + "peakRssKiB": 886868, + "threads": 35, + "fds": 36, + "writeBytes": 303714304 + }, + { + "tMs": 69348, + "cpuTicks": 15296, + "rssKiB": 842128, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 306626560 + }, + { + "tMs": 70495, + "cpuTicks": 15441, + "rssKiB": 842132, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 309567488 + }, + { + "tMs": 71632, + "cpuTicks": 15592, + "rssKiB": 842132, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 312451072 + }, + { + "tMs": 72838, + "cpuTicks": 15825, + "rssKiB": 866304, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 318906368 + }, + { + "tMs": 74002, + "cpuTicks": 16032, + "rssKiB": 866304, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 321785856 + }, + { + "tMs": 75168, + "cpuTicks": 16232, + "rssKiB": 841812, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 326135808 + }, + { + "tMs": 76317, + "cpuTicks": 16382, + "rssKiB": 841812, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 329129984 + }, + { + "tMs": 77463, + "cpuTicks": 16529, + "rssKiB": 841812, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 332627968 + }, + { + "tMs": 78601, + "cpuTicks": 16674, + "rssKiB": 841812, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 335572992 + }, + { + "tMs": 79822, + "cpuTicks": 16897, + "rssKiB": 865984, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 339853312 + }, + { + "tMs": 81011, + "cpuTicks": 17125, + "rssKiB": 866000, + "peakRssKiB": 887920, + "threads": 35, + "fds": 37, + "writeBytes": 342716416 + }, + { + "tMs": 82178, + "cpuTicks": 17297, + "rssKiB": 841828, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 347529216 + }, + { + "tMs": 83334, + "cpuTicks": 17452, + "rssKiB": 841820, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 352342016 + }, + { + "tMs": 84475, + "cpuTicks": 17608, + "rssKiB": 841820, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 355352576 + }, + { + "tMs": 85607, + "cpuTicks": 17770, + "rssKiB": 841828, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 358330368 + }, + { + "tMs": 86813, + "cpuTicks": 17990, + "rssKiB": 866004, + "peakRssKiB": 887920, + "threads": 35, + "fds": 36, + "writeBytes": 363208704 + }, + { + "tMs": 87979, + "cpuTicks": 18214, + "rssKiB": 866004, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 366088192 + }, + { + "tMs": 89144, + "cpuTicks": 18413, + "rssKiB": 841828, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 370282496 + }, + { + "tMs": 90289, + "cpuTicks": 18564, + "rssKiB": 841884, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 373198848 + }, + { + "tMs": 91439, + "cpuTicks": 18719, + "rssKiB": 841884, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 376111104 + }, + { + "tMs": 92572, + "cpuTicks": 18877, + "rssKiB": 841884, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 379551744 + }, + { + "tMs": 93732, + "cpuTicks": 19058, + "rssKiB": 865736, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 385544192 + }, + { + "tMs": 94921, + "cpuTicks": 19294, + "rssKiB": 865736, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 388530176 + }, + { + "tMs": 96083, + "cpuTicks": 19511, + "rssKiB": 841688, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 392331264 + }, + { + "tMs": 97222, + "cpuTicks": 19669, + "rssKiB": 841688, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 396480512 + }, + { + "tMs": 98337, + "cpuTicks": 19827, + "rssKiB": 841684, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 399400960 + }, + { + "tMs": 99459, + "cpuTicks": 19985, + "rssKiB": 841684, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 402350080 + }, + { + "tMs": 100581, + "cpuTicks": 20144, + "rssKiB": 841700, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 405327872 + }, + { + "tMs": 101763, + "cpuTicks": 20345, + "rssKiB": 865872, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 409681920 + }, + { + "tMs": 102936, + "cpuTicks": 20571, + "rssKiB": 865872, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 414978048 + }, + { + "tMs": 104086, + "cpuTicks": 20762, + "rssKiB": 841700, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 418996224 + }, + { + "tMs": 105242, + "cpuTicks": 20915, + "rssKiB": 841700, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 422330368 + }, + { + "tMs": 106394, + "cpuTicks": 21070, + "rssKiB": 841700, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 425992192 + }, + { + "tMs": 107549, + "cpuTicks": 21225, + "rssKiB": 841700, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 428945408 + }, + { + "tMs": 108739, + "cpuTicks": 21412, + "rssKiB": 865872, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 433147904 + }, + { + "tMs": 109915, + "cpuTicks": 21626, + "rssKiB": 865872, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 436088832 + }, + { + "tMs": 111076, + "cpuTicks": 21812, + "rssKiB": 841704, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 440438784 + }, + { + "tMs": 112229, + "cpuTicks": 21963, + "rssKiB": 841704, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 443686912 + }, + { + "tMs": 113370, + "cpuTicks": 22131, + "rssKiB": 841704, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 448393216 + }, + { + "tMs": 114533, + "cpuTicks": 22280, + "rssKiB": 841704, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 451362816 + }, + { + "tMs": 115683, + "cpuTicks": 22424, + "rssKiB": 880188, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 455745536 + }, + { + "tMs": 116877, + "cpuTicks": 22656, + "rssKiB": 866200, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 459292672 + }, + { + "tMs": 118033, + "cpuTicks": 22870, + "rssKiB": 866524, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 462217216 + }, + { + "tMs": 119187, + "cpuTicks": 23025, + "rssKiB": 842352, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 466472960 + }, + { + "tMs": 120334, + "cpuTicks": 23197, + "rssKiB": 842364, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 469368832 + }, + { + "tMs": 121486, + "cpuTicks": 23345, + "rssKiB": 842364, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 472932352 + }, + { + "tMs": 122637, + "cpuTicks": 23497, + "rssKiB": 842364, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 475885568 + }, + { + "tMs": 123837, + "cpuTicks": 23707, + "rssKiB": 866536, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 481886208 + }, + { + "tMs": 124998, + "cpuTicks": 23914, + "rssKiB": 866536, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 484868096 + }, + { + "tMs": 126159, + "cpuTicks": 24085, + "rssKiB": 842364, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 489877504 + }, + { + "tMs": 127314, + "cpuTicks": 24220, + "rssKiB": 842364, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 492867584 + }, + { + "tMs": 128462, + "cpuTicks": 24355, + "rssKiB": 842364, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 495882240 + }, + { + "tMs": 129611, + "cpuTicks": 24513, + "rssKiB": 842040, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 498868224 + }, + { + "tMs": 130797, + "cpuTicks": 24710, + "rssKiB": 866212, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 503279616 + }, + { + "tMs": 131952, + "cpuTicks": 24922, + "rssKiB": 866212, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 506916864 + }, + { + "tMs": 133099, + "cpuTicks": 25095, + "rssKiB": 842040, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 512970752 + }, + { + "tMs": 134253, + "cpuTicks": 25239, + "rssKiB": 842040, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 515899392 + }, + { + "tMs": 135409, + "cpuTicks": 25377, + "rssKiB": 842040, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 519585792 + }, + { + "tMs": 136561, + "cpuTicks": 25525, + "rssKiB": 842040, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 522665984 + }, + { + "tMs": 137715, + "cpuTicks": 25687, + "rssKiB": 866212, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 526970880 + }, + { + "tMs": 138907, + "cpuTicks": 25913, + "rssKiB": 865888, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 530001920 + }, + { + "tMs": 140106, + "cpuTicks": 26134, + "rssKiB": 841716, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 535056384 + }, + { + "tMs": 141292, + "cpuTicks": 26273, + "rssKiB": 841716, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 537899008 + }, + { + "tMs": 142425, + "cpuTicks": 26427, + "rssKiB": 841716, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 540844032 + }, + { + "tMs": 143557, + "cpuTicks": 26574, + "rssKiB": 841716, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 545550336 + }, + { + "tMs": 144747, + "cpuTicks": 26752, + "rssKiB": 866212, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 549842944 + }, + { + "tMs": 145928, + "cpuTicks": 26964, + "rssKiB": 866216, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 553500672 + }, + { + "tMs": 147089, + "cpuTicks": 27168, + "rssKiB": 842368, + "peakRssKiB": 890592, + "threads": 35, + "fds": 38, + "writeBytes": 557834240 + }, + { + "tMs": 148256, + "cpuTicks": 27312, + "rssKiB": 842368, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 560873472 + }, + { + "tMs": 149414, + "cpuTicks": 27479, + "rssKiB": 842368, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 563851264 + }, + { + "tMs": 150570, + "cpuTicks": 27624, + "rssKiB": 842392, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 567513088 + }, + { + "tMs": 151744, + "cpuTicks": 27805, + "rssKiB": 866564, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 571809792 + }, + { + "tMs": 152907, + "cpuTicks": 28035, + "rssKiB": 866564, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 576507904 + }, + { + "tMs": 154061, + "cpuTicks": 28227, + "rssKiB": 842392, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 580194304 + }, + { + "tMs": 155215, + "cpuTicks": 28384, + "rssKiB": 842392, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 584617984 + }, + { + "tMs": 156357, + "cpuTicks": 28542, + "rssKiB": 842392, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 587681792 + }, + { + "tMs": 157482, + "cpuTicks": 28685, + "rssKiB": 842068, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 590630912 + }, + { + "tMs": 158626, + "cpuTicks": 28826, + "rssKiB": 842068, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 593711104 + }, + { + "tMs": 159849, + "cpuTicks": 29062, + "rssKiB": 866240, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 598654976 + }, + { + "tMs": 161031, + "cpuTicks": 29277, + "rssKiB": 842068, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 602435584 + }, + { + "tMs": 162184, + "cpuTicks": 29432, + "rssKiB": 842068, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 605986816 + }, + { + "tMs": 163329, + "cpuTicks": 29583, + "rssKiB": 842068, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 610799616 + }, + { + "tMs": 164468, + "cpuTicks": 29735, + "rssKiB": 842068, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 614490112 + }, + { + "tMs": 165591, + "cpuTicks": 29883, + "rssKiB": 842104, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 617553920 + }, + { + "tMs": 166785, + "cpuTicks": 30078, + "rssKiB": 866276, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 621871104 + }, + { + "tMs": 167928, + "cpuTicks": 30287, + "rssKiB": 866276, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 624836608 + }, + { + "tMs": 169106, + "cpuTicks": 30503, + "rssKiB": 866276, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 629391360 + }, + { + "tMs": 170251, + "cpuTicks": 30660, + "rssKiB": 842104, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 632774656 + }, + { + "tMs": 171407, + "cpuTicks": 30816, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 635588608 + }, + { + "tMs": 172586, + "cpuTicks": 30969, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 638500864 + }, + { + "tMs": 173771, + "cpuTicks": 31161, + "rssKiB": 866284, + "peakRssKiB": 890592, + "threads": 35, + "fds": 37, + "writeBytes": 645312512 + }, + { + "tMs": 174935, + "cpuTicks": 31389, + "rssKiB": 866284, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 648314880 + }, + { + "tMs": 176100, + "cpuTicks": 31570, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 652623872 + }, + { + "tMs": 177239, + "cpuTicks": 31718, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 655634432 + }, + { + "tMs": 178370, + "cpuTicks": 31875, + "rssKiB": 841788, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 659365888 + }, + { + "tMs": 179506, + "cpuTicks": 32047, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 662261760 + }, + { + "tMs": 180644, + "cpuTicks": 32202, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 665239552 + }, + { + "tMs": 181851, + "cpuTicks": 32427, + "rssKiB": 866284, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 669474816 + }, + { + "tMs": 183016, + "cpuTicks": 32653, + "rssKiB": 866284, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 674418688 + }, + { + "tMs": 184167, + "cpuTicks": 32815, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 679403520 + }, + { + "tMs": 185330, + "cpuTicks": 32962, + "rssKiB": 842112, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 682373120 + }, + { + "tMs": 186464, + "cpuTicks": 33129, + "rssKiB": 842120, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 685371392 + }, + { + "tMs": 187578, + "cpuTicks": 33269, + "rssKiB": 842120, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 688324608 + }, + { + "tMs": 188744, + "cpuTicks": 33455, + "rssKiB": 866292, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 693362688 + }, + { + "tMs": 189929, + "cpuTicks": 33684, + "rssKiB": 865968, + "peakRssKiB": 890592, + "threads": 35, + "fds": 36, + "writeBytes": 696307712 + }, + { + "tMs": 191107, + "cpuTicks": 33899, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 700751872 + }, + { + "tMs": 192241, + "cpuTicks": 34070, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 703778816 + }, + { + "tMs": 193379, + "cpuTicks": 34232, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 709312512 + }, + { + "tMs": 194518, + "cpuTicks": 34385, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 712306688 + }, + { + "tMs": 195672, + "cpuTicks": 34538, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 715689984 + }, + { + "tMs": 196871, + "cpuTicks": 34762, + "rssKiB": 865968, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 719675392 + }, + { + "tMs": 198040, + "cpuTicks": 34958, + "rssKiB": 865968, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 724402176 + }, + { + "tMs": 199199, + "cpuTicks": 35094, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 727752704 + }, + { + "tMs": 200364, + "cpuTicks": 35254, + "rssKiB": 841796, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 730759168 + }, + { + "tMs": 201533, + "cpuTicks": 35397, + "rssKiB": 841800, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 733642752 + }, + { + "tMs": 202678, + "cpuTicks": 35550, + "rssKiB": 856252, + "peakRssKiB": 891636, + "threads": 35, + "fds": 34, + "writeBytes": 738369536 + }, + { + "tMs": 203867, + "cpuTicks": 35774, + "rssKiB": 866296, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 743419904 + }, + { + "tMs": 205026, + "cpuTicks": 35985, + "rssKiB": 866296, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 746459136 + }, + { + "tMs": 206185, + "cpuTicks": 36167, + "rssKiB": 842136, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 750809088 + }, + { + "tMs": 207324, + "cpuTicks": 36327, + "rssKiB": 842460, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 754483200 + }, + { + "tMs": 208451, + "cpuTicks": 36482, + "rssKiB": 842684, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 757415936 + }, + { + "tMs": 209582, + "cpuTicks": 36641, + "rssKiB": 842684, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 760373248 + }, + { + "tMs": 210789, + "cpuTicks": 36848, + "rssKiB": 866644, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 764694528 + }, + { + "tMs": 211963, + "cpuTicks": 37070, + "rssKiB": 866644, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 767688704 + }, + { + "tMs": 213122, + "cpuTicks": 37259, + "rssKiB": 842472, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 774623232 + }, + { + "tMs": 214271, + "cpuTicks": 37421, + "rssKiB": 842472, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 777568256 + }, + { + "tMs": 215423, + "cpuTicks": 37584, + "rssKiB": 842472, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 780558336 + }, + { + "tMs": 216567, + "cpuTicks": 37744, + "rssKiB": 842472, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 783564800 + }, + { + "tMs": 217725, + "cpuTicks": 37922, + "rssKiB": 866320, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 788668416 + }, + { + "tMs": 218912, + "cpuTicks": 38150, + "rssKiB": 866320, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 791695360 + }, + { + "tMs": 220081, + "cpuTicks": 38350, + "rssKiB": 842148, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 795979776 + }, + { + "tMs": 221229, + "cpuTicks": 38510, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 799817728 + }, + { + "tMs": 222365, + "cpuTicks": 38666, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 802934784 + }, + { + "tMs": 223514, + "cpuTicks": 38818, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 807669760 + }, + { + "tMs": 224665, + "cpuTicks": 38983, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 810614784 + }, + { + "tMs": 225883, + "cpuTicks": 39215, + "rssKiB": 866328, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 815009792 + }, + { + "tMs": 227060, + "cpuTicks": 39438, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 819134464 + }, + { + "tMs": 228208, + "cpuTicks": 39607, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 823197696 + }, + { + "tMs": 229363, + "cpuTicks": 39760, + "rssKiB": 842156, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 826302464 + }, + { + "tMs": 230498, + "cpuTicks": 39923, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 829374464 + }, + { + "tMs": 231634, + "cpuTicks": 40078, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 833028096 + }, + { + "tMs": 232813, + "cpuTicks": 40283, + "rssKiB": 866332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 839143424 + }, + { + "tMs": 233974, + "cpuTicks": 40496, + "rssKiB": 866332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 842158080 + }, + { + "tMs": 235131, + "cpuTicks": 40673, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 847364096 + }, + { + "tMs": 236264, + "cpuTicks": 40826, + "rssKiB": 841836, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 850391040 + }, + { + "tMs": 237405, + "cpuTicks": 40985, + "rssKiB": 841836, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 853352448 + }, + { + "tMs": 238564, + "cpuTicks": 41144, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 856305664 + }, + { + "tMs": 239725, + "cpuTicks": 41323, + "rssKiB": 866332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 860557312 + }, + { + "tMs": 240904, + "cpuTicks": 41562, + "rssKiB": 866332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 864329728 + }, + { + "tMs": 242084, + "cpuTicks": 41748, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 868679680 + }, + { + "tMs": 243233, + "cpuTicks": 41902, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 873422848 + }, + { + "tMs": 244389, + "cpuTicks": 42057, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 877195264 + }, + { + "tMs": 245549, + "cpuTicks": 42227, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 880197632 + }, + { + "tMs": 246721, + "cpuTicks": 42391, + "rssKiB": 866656, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 884441088 + }, + { + "tMs": 247918, + "cpuTicks": 42619, + "rssKiB": 866656, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 887439360 + }, + { + "tMs": 249081, + "cpuTicks": 42824, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 38, + "writeBytes": 891412480 + }, + { + "tMs": 250239, + "cpuTicks": 42987, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 895541248 + }, + { + "tMs": 251389, + "cpuTicks": 43153, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 898551808 + }, + { + "tMs": 252538, + "cpuTicks": 43307, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 901541888 + }, + { + "tMs": 253670, + "cpuTicks": 43467, + "rssKiB": 842484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 906653696 + }, + { + "tMs": 254941, + "cpuTicks": 43753, + "rssKiB": 866332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 911474688 + }, + { + "tMs": 256104, + "cpuTicks": 43950, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 38, + "writeBytes": 915767296 + }, + { + "tMs": 257250, + "cpuTicks": 44091, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 918839296 + }, + { + "tMs": 258394, + "cpuTicks": 44242, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 921829376 + }, + { + "tMs": 259546, + "cpuTicks": 44393, + "rssKiB": 842160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 925749248 + }, + { + "tMs": 260720, + "cpuTicks": 44563, + "rssKiB": 866340, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 929136640 + }, + { + "tMs": 261938, + "cpuTicks": 44802, + "rssKiB": 866340, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 932925440 + }, + { + "tMs": 263120, + "cpuTicks": 44999, + "rssKiB": 841844, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 939032576 + }, + { + "tMs": 264290, + "cpuTicks": 45137, + "rssKiB": 841844, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 942927872 + }, + { + "tMs": 265445, + "cpuTicks": 45293, + "rssKiB": 841844, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 945950720 + }, + { + "tMs": 266592, + "cpuTicks": 45453, + "rssKiB": 842068, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 948932608 + }, + { + "tMs": 267777, + "cpuTicks": 45639, + "rssKiB": 866564, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 953200640 + }, + { + "tMs": 268970, + "cpuTicks": 45874, + "rssKiB": 866564, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 956194816 + }, + { + "tMs": 270131, + "cpuTicks": 46068, + "rssKiB": 842540, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 961413120 + }, + { + "tMs": 271287, + "cpuTicks": 46209, + "rssKiB": 842540, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 964513792 + }, + { + "tMs": 272437, + "cpuTicks": 46356, + "rssKiB": 842864, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 967520256 + }, + { + "tMs": 273572, + "cpuTicks": 46506, + "rssKiB": 843188, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 973172736 + }, + { + "tMs": 274764, + "cpuTicks": 46708, + "rssKiB": 867360, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 977588224 + }, + { + "tMs": 275946, + "cpuTicks": 46938, + "rssKiB": 867360, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 980680704 + }, + { + "tMs": 277118, + "cpuTicks": 47120, + "rssKiB": 843188, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 985886720 + }, + { + "tMs": 278286, + "cpuTicks": 47278, + "rssKiB": 843188, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 988983296 + }, + { + "tMs": 279435, + "cpuTicks": 47429, + "rssKiB": 843188, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 991961088 + }, + { + "tMs": 280578, + "cpuTicks": 47589, + "rssKiB": 843188, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 994996224 + }, + { + "tMs": 281767, + "cpuTicks": 47775, + "rssKiB": 867360, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 999313408 + }, + { + "tMs": 282946, + "cpuTicks": 48010, + "rssKiB": 867360, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1004838912 + }, + { + "tMs": 284112, + "cpuTicks": 48212, + "rssKiB": 842316, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1008951296 + }, + { + "tMs": 285263, + "cpuTicks": 48369, + "rssKiB": 842316, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1012121600 + }, + { + "tMs": 286407, + "cpuTicks": 48517, + "rssKiB": 842316, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1015160832 + }, + { + "tMs": 287544, + "cpuTicks": 48674, + "rssKiB": 842316, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1018937344 + }, + { + "tMs": 288707, + "cpuTicks": 48835, + "rssKiB": 866164, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1023164416 + }, + { + "tMs": 289897, + "cpuTicks": 49066, + "rssKiB": 866164, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1026187264 + }, + { + "tMs": 291056, + "cpuTicks": 49275, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1029357568 + }, + { + "tMs": 292218, + "cpuTicks": 49423, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1034342400 + }, + { + "tMs": 293355, + "cpuTicks": 49579, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1039089664 + }, + { + "tMs": 294493, + "cpuTicks": 49743, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1042046976 + }, + { + "tMs": 295655, + "cpuTicks": 49899, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1045061632 + }, + { + "tMs": 296853, + "cpuTicks": 50113, + "rssKiB": 866164, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1049305088 + }, + { + "tMs": 298025, + "cpuTicks": 50333, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1053454336 + }, + { + "tMs": 299175, + "cpuTicks": 50488, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1057394688 + }, + { + "tMs": 300334, + "cpuTicks": 50657, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1060282368 + }, + { + "tMs": 301484, + "cpuTicks": 50815, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1063264256 + }, + { + "tMs": 302626, + "cpuTicks": 50981, + "rssKiB": 841992, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1067028480 + }, + { + "tMs": 303842, + "cpuTicks": 51206, + "rssKiB": 866164, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1073086464 + }, + { + "tMs": 305014, + "cpuTicks": 51433, + "rssKiB": 866164, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1076465664 + }, + { + "tMs": 306164, + "cpuTicks": 51591, + "rssKiB": 841996, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1080459264 + }, + { + "tMs": 307327, + "cpuTicks": 51732, + "rssKiB": 841996, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1084497920 + }, + { + "tMs": 308485, + "cpuTicks": 51882, + "rssKiB": 841996, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1087553536 + }, + { + "tMs": 309640, + "cpuTicks": 52031, + "rssKiB": 842220, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1090666496 + }, + { + "tMs": 310816, + "cpuTicks": 52236, + "rssKiB": 866168, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1095024640 + }, + { + "tMs": 311989, + "cpuTicks": 52455, + "rssKiB": 866496, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1098956800 + }, + { + "tMs": 313150, + "cpuTicks": 52626, + "rssKiB": 842324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1105297408 + }, + { + "tMs": 314286, + "cpuTicks": 52778, + "rssKiB": 842324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1108340736 + }, + { + "tMs": 315433, + "cpuTicks": 52925, + "rssKiB": 842324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1112395776 + }, + { + "tMs": 316566, + "cpuTicks": 53072, + "rssKiB": 842332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1115475968 + }, + { + "tMs": 317733, + "cpuTicks": 53258, + "rssKiB": 866504, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1119830016 + }, + { + "tMs": 318926, + "cpuTicks": 53484, + "rssKiB": 866504, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1122844672 + }, + { + "tMs": 320097, + "cpuTicks": 53663, + "rssKiB": 842332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1128120320 + }, + { + "tMs": 321254, + "cpuTicks": 53815, + "rssKiB": 842332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1131016192 + }, + { + "tMs": 322408, + "cpuTicks": 53961, + "rssKiB": 842008, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1134014464 + }, + { + "tMs": 323563, + "cpuTicks": 54129, + "rssKiB": 842008, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1138757632 + }, + { + "tMs": 324725, + "cpuTicks": 54303, + "rssKiB": 866504, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1143042048 + }, + { + "tMs": 325927, + "cpuTicks": 54554, + "rssKiB": 866504, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1147056128 + }, + { + "tMs": 327101, + "cpuTicks": 54774, + "rssKiB": 842332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1151201280 + }, + { + "tMs": 328261, + "cpuTicks": 54936, + "rssKiB": 842332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1154371584 + }, + { + "tMs": 329422, + "cpuTicks": 55081, + "rssKiB": 842332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1157378048 + }, + { + "tMs": 330572, + "cpuTicks": 55229, + "rssKiB": 843588, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1161203712 + }, + { + "tMs": 331758, + "cpuTicks": 55424, + "rssKiB": 868336, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1165520896 + }, + { + "tMs": 332941, + "cpuTicks": 55650, + "rssKiB": 868112, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1170268160 + }, + { + "tMs": 334102, + "cpuTicks": 55837, + "rssKiB": 843940, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1175519232 + }, + { + "tMs": 335247, + "cpuTicks": 55987, + "rssKiB": 843940, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1178718208 + }, + { + "tMs": 336404, + "cpuTicks": 56132, + "rssKiB": 843956, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1181769728 + }, + { + "tMs": 337554, + "cpuTicks": 56279, + "rssKiB": 843956, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1184681984 + }, + { + "tMs": 338739, + "cpuTicks": 56465, + "rssKiB": 868128, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1189019648 + }, + { + "tMs": 339961, + "cpuTicks": 56722, + "rssKiB": 868128, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1192816640 + }, + { + "tMs": 341117, + "cpuTicks": 56901, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1197277184 + }, + { + "tMs": 342270, + "cpuTicks": 57053, + "rssKiB": 843648, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1200259072 + }, + { + "tMs": 343420, + "cpuTicks": 57215, + "rssKiB": 843648, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1205035008 + }, + { + "tMs": 344560, + "cpuTicks": 57371, + "rssKiB": 843648, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1209044992 + }, + { + "tMs": 345753, + "cpuTicks": 57551, + "rssKiB": 867820, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1213427712 + }, + { + "tMs": 346949, + "cpuTicks": 57794, + "rssKiB": 867496, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1216602112 + }, + { + "tMs": 348122, + "cpuTicks": 57990, + "rssKiB": 843324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1221922816 + }, + { + "tMs": 349284, + "cpuTicks": 58137, + "rssKiB": 843324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1224945664 + }, + { + "tMs": 350417, + "cpuTicks": 58289, + "rssKiB": 843324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1227923456 + }, + { + "tMs": 351557, + "cpuTicks": 58438, + "rssKiB": 843324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1230839808 + }, + { + "tMs": 352706, + "cpuTicks": 58610, + "rssKiB": 867496, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1236926464 + }, + { + "tMs": 353927, + "cpuTicks": 58855, + "rssKiB": 867496, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1240797184 + }, + { + "tMs": 355098, + "cpuTicks": 59045, + "rssKiB": 843324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1245184000 + }, + { + "tMs": 356245, + "cpuTicks": 59205, + "rssKiB": 843324, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1248223232 + }, + { + "tMs": 357374, + "cpuTicks": 59366, + "rssKiB": 843648, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1251221504 + }, + { + "tMs": 358538, + "cpuTicks": 59522, + "rssKiB": 843648, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1254998016 + }, + { + "tMs": 359673, + "cpuTicks": 59678, + "rssKiB": 844916, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1258602496 + }, + { + "tMs": 360886, + "cpuTicks": 59923, + "rssKiB": 867820, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1262211072 + }, + { + "tMs": 362064, + "cpuTicks": 60141, + "rssKiB": 867820, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1266274304 + }, + { + "tMs": 363218, + "cpuTicks": 60295, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1272152064 + }, + { + "tMs": 364358, + "cpuTicks": 60451, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1275129856 + }, + { + "tMs": 365499, + "cpuTicks": 60603, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1278099456 + }, + { + "tMs": 366629, + "cpuTicks": 60772, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1281069056 + }, + { + "tMs": 367908, + "cpuTicks": 61045, + "rssKiB": 868144, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1286418432 + }, + { + "tMs": 369091, + "cpuTicks": 61269, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1290625024 + }, + { + "tMs": 370254, + "cpuTicks": 61431, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1293656064 + }, + { + "tMs": 371392, + "cpuTicks": 61592, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1296617472 + }, + { + "tMs": 372537, + "cpuTicks": 61739, + "rssKiB": 843972, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1299574784 + }, + { + "tMs": 373706, + "cpuTicks": 61915, + "rssKiB": 867820, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1306636288 + }, + { + "tMs": 374915, + "cpuTicks": 62150, + "rssKiB": 867820, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1309671424 + }, + { + "tMs": 376087, + "cpuTicks": 62352, + "rssKiB": 843652, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1313988608 + }, + { + "tMs": 377231, + "cpuTicks": 62526, + "rssKiB": 843652, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1317896192 + }, + { + "tMs": 378429, + "cpuTicks": 62689, + "rssKiB": 843328, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1320886272 + }, + { + "tMs": 379577, + "cpuTicks": 62836, + "rssKiB": 843652, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1323884544 + }, + { + "tMs": 380783, + "cpuTicks": 63037, + "rssKiB": 867828, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1327325184 + }, + { + "tMs": 382005, + "cpuTicks": 63281, + "rssKiB": 867828, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1331167232 + }, + { + "tMs": 383194, + "cpuTicks": 63456, + "rssKiB": 843656, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1338183680 + }, + { + "tMs": 384370, + "cpuTicks": 63620, + "rssKiB": 843656, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1341267968 + }, + { + "tMs": 385530, + "cpuTicks": 63772, + "rssKiB": 843656, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1344327680 + }, + { + "tMs": 386683, + "cpuTicks": 63955, + "rssKiB": 874368, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1349644288 + }, + { + "tMs": 387897, + "cpuTicks": 64203, + "rssKiB": 867828, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1352687616 + }, + { + "tMs": 389066, + "cpuTicks": 64435, + "rssKiB": 867828, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1355759616 + }, + { + "tMs": 390256, + "cpuTicks": 64624, + "rssKiB": 843356, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1360068608 + }, + { + "tMs": 391418, + "cpuTicks": 64795, + "rssKiB": 843360, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1364201472 + }, + { + "tMs": 392573, + "cpuTicks": 64960, + "rssKiB": 843584, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1367203840 + }, + { + "tMs": 393783, + "cpuTicks": 65165, + "rssKiB": 868080, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1377869824 + }, + { + "tMs": 394969, + "cpuTicks": 65394, + "rssKiB": 868080, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1380900864 + }, + { + "tMs": 396150, + "cpuTicks": 65582, + "rssKiB": 843920, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1386307584 + }, + { + "tMs": 397306, + "cpuTicks": 65744, + "rssKiB": 843920, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1389314048 + }, + { + "tMs": 398459, + "cpuTicks": 65902, + "rssKiB": 843920, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1392328704 + }, + { + "tMs": 399614, + "cpuTicks": 66070, + "rssKiB": 761804, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1395302400 + }, + { + "tMs": 400818, + "cpuTicks": 66291, + "rssKiB": 798076, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1399672832 + }, + { + "tMs": 402015, + "cpuTicks": 66519, + "rssKiB": 798448, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1403654144 + }, + { + "tMs": 403190, + "cpuTicks": 66711, + "rssKiB": 774568, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1422327808 + }, + { + "tMs": 404359, + "cpuTicks": 66872, + "rssKiB": 774704, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1425383424 + }, + { + "tMs": 405548, + "cpuTicks": 67036, + "rssKiB": 774712, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1429463040 + }, + { + "tMs": 406776, + "cpuTicks": 67247, + "rssKiB": 799012, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1433886720 + }, + { + "tMs": 408030, + "cpuTicks": 67527, + "rssKiB": 774848, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1437196288 + }, + { + "tMs": 409230, + "cpuTicks": 67713, + "rssKiB": 774848, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1441394688 + }, + { + "tMs": 410411, + "cpuTicks": 67876, + "rssKiB": 774916, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1445318656 + }, + { + "tMs": 411579, + "cpuTicks": 68036, + "rssKiB": 774988, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1448194048 + }, + { + "tMs": 412769, + "cpuTicks": 68236, + "rssKiB": 798336, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1454342144 + }, + { + "tMs": 413966, + "cpuTicks": 68473, + "rssKiB": 798336, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1457389568 + }, + { + "tMs": 415142, + "cpuTicks": 68677, + "rssKiB": 774488, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1462759424 + }, + { + "tMs": 416298, + "cpuTicks": 68845, + "rssKiB": 774500, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1465790464 + }, + { + "tMs": 417458, + "cpuTicks": 68992, + "rssKiB": 774828, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1468735488 + }, + { + "tMs": 418609, + "cpuTicks": 69137, + "rssKiB": 774828, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1471725568 + }, + { + "tMs": 419838, + "cpuTicks": 69364, + "rssKiB": 799000, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1476894720 + }, + { + "tMs": 421011, + "cpuTicks": 69586, + "rssKiB": 799344, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1479901184 + }, + { + "tMs": 422158, + "cpuTicks": 69768, + "rssKiB": 775180, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1484226560 + }, + { + "tMs": 423304, + "cpuTicks": 69920, + "rssKiB": 775180, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1489965056 + }, + { + "tMs": 424447, + "cpuTicks": 70076, + "rssKiB": 775180, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1492992000 + }, + { + "tMs": 425586, + "cpuTicks": 70219, + "rssKiB": 775180, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1495986176 + }, + { + "tMs": 426789, + "cpuTicks": 70414, + "rssKiB": 799656, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1500327936 + }, + { + "tMs": 427973, + "cpuTicks": 70630, + "rssKiB": 799332, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1503342592 + }, + { + "tMs": 429136, + "cpuTicks": 70813, + "rssKiB": 775160, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1508700160 + }, + { + "tMs": 430278, + "cpuTicks": 70976, + "rssKiB": 775484, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1511698432 + }, + { + "tMs": 431429, + "cpuTicks": 71115, + "rssKiB": 775492, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1514774528 + }, + { + "tMs": 432587, + "cpuTicks": 71255, + "rssKiB": 775492, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1517768704 + }, + { + "tMs": 433782, + "cpuTicks": 71450, + "rssKiB": 799664, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1524785152 + }, + { + "tMs": 434965, + "cpuTicks": 71671, + "rssKiB": 799664, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1527803904 + }, + { + "tMs": 436138, + "cpuTicks": 71849, + "rssKiB": 775500, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1532186624 + }, + { + "tMs": 437273, + "cpuTicks": 71995, + "rssKiB": 775500, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1535197184 + }, + { + "tMs": 438418, + "cpuTicks": 72136, + "rssKiB": 775500, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1539047424 + }, + { + "tMs": 439572, + "cpuTicks": 72280, + "rssKiB": 775500, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1542066176 + }, + { + "tMs": 440757, + "cpuTicks": 72475, + "rssKiB": 799352, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1545420800 + }, + { + "tMs": 442069, + "cpuTicks": 72810, + "rssKiB": 799368, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1549234176 + }, + { + "tMs": 443298, + "cpuTicks": 73042, + "rssKiB": 775196, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1556340736 + }, + { + "tMs": 444508, + "cpuTicks": 73261, + "rssKiB": 775196, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1559343104 + }, + { + "tMs": 445773, + "cpuTicks": 73545, + "rssKiB": 799368, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1563713536 + }, + { + "tMs": 447048, + "cpuTicks": 73860, + "rssKiB": 799384, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1566773248 + }, + { + "tMs": 448301, + "cpuTicks": 74160, + "rssKiB": 775536, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1572241408 + }, + { + "tMs": 449485, + "cpuTicks": 74393, + "rssKiB": 775536, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1575313408 + }, + { + "tMs": 450685, + "cpuTicks": 74589, + "rssKiB": 813116, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1579544576 + }, + { + "tMs": 452016, + "cpuTicks": 74904, + "rssKiB": 799780, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1582632960 + }, + { + "tMs": 453231, + "cpuTicks": 75201, + "rssKiB": 775628, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1589460992 + }, + { + "tMs": 454443, + "cpuTicks": 75415, + "rssKiB": 775628, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1592881152 + }, + { + "tMs": 455631, + "cpuTicks": 75623, + "rssKiB": 775632, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1595965440 + }, + { + "tMs": 456971, + "cpuTicks": 75926, + "rssKiB": 799808, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1601445888 + }, + { + "tMs": 458201, + "cpuTicks": 76178, + "rssKiB": 775312, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1605799936 + }, + { + "tMs": 459414, + "cpuTicks": 76394, + "rssKiB": 775312, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1608945664 + }, + { + "tMs": 460614, + "cpuTicks": 76611, + "rssKiB": 774988, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1612021760 + }, + { + "tMs": 461910, + "cpuTicks": 76923, + "rssKiB": 799172, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1617391616 + }, + { + "tMs": 463195, + "cpuTicks": 77254, + "rssKiB": 775000, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1622888448 + }, + { + "tMs": 464426, + "cpuTicks": 77478, + "rssKiB": 775000, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1626566656 + }, + { + "tMs": 465617, + "cpuTicks": 77706, + "rssKiB": 775000, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1630564352 + }, + { + "tMs": 466971, + "cpuTicks": 78035, + "rssKiB": 799192, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1634918400 + }, + { + "tMs": 468231, + "cpuTicks": 78316, + "rssKiB": 775020, + "peakRssKiB": 891636, + "threads": 35, + "fds": 38, + "writeBytes": 1639325696 + }, + { + "tMs": 469455, + "cpuTicks": 78506, + "rssKiB": 775020, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1642459136 + }, + { + "tMs": 470677, + "cpuTicks": 78700, + "rssKiB": 790348, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1646665728 + }, + { + "tMs": 472017, + "cpuTicks": 79030, + "rssKiB": 799220, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1650556928 + }, + { + "tMs": 473305, + "cpuTicks": 79321, + "rssKiB": 775048, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1656655872 + }, + { + "tMs": 474527, + "cpuTicks": 79540, + "rssKiB": 775372, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1659686912 + }, + { + "tMs": 475788, + "cpuTicks": 79810, + "rssKiB": 799544, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1665036288 + }, + { + "tMs": 477056, + "cpuTicks": 80148, + "rssKiB": 799548, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1668108288 + }, + { + "tMs": 478300, + "cpuTicks": 80403, + "rssKiB": 775376, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1672491008 + }, + { + "tMs": 479500, + "cpuTicks": 80589, + "rssKiB": 775376, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1675464704 + }, + { + "tMs": 480688, + "cpuTicks": 80818, + "rssKiB": 812312, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1680670720 + }, + { + "tMs": 482021, + "cpuTicks": 81148, + "rssKiB": 799560, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1683722240 + }, + { + "tMs": 483252, + "cpuTicks": 81430, + "rssKiB": 775388, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1689800704 + }, + { + "tMs": 484497, + "cpuTicks": 81633, + "rssKiB": 775064, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1693831168 + }, + { + "tMs": 485748, + "cpuTicks": 81850, + "rssKiB": 799236, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1698172928 + }, + { + "tMs": 487066, + "cpuTicks": 82166, + "rssKiB": 799240, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1701408768 + }, + { + "tMs": 488232, + "cpuTicks": 82343, + "rssKiB": 775068, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1705656320 + }, + { + "tMs": 489395, + "cpuTicks": 82491, + "rssKiB": 775068, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1709666304 + }, + { + "tMs": 490541, + "cpuTicks": 82650, + "rssKiB": 775392, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1712750592 + }, + { + "tMs": 491723, + "cpuTicks": 82816, + "rssKiB": 799580, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1717059584 + }, + { + "tMs": 492939, + "cpuTicks": 83052, + "rssKiB": 799580, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1721819136 + }, + { + "tMs": 494123, + "cpuTicks": 83257, + "rssKiB": 775408, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1727197184 + }, + { + "tMs": 495311, + "cpuTicks": 83426, + "rssKiB": 775408, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1730318336 + }, + { + "tMs": 496464, + "cpuTicks": 83584, + "rssKiB": 775420, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1733402624 + }, + { + "tMs": 497615, + "cpuTicks": 83740, + "rssKiB": 775420, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1736433664 + }, + { + "tMs": 498812, + "cpuTicks": 83954, + "rssKiB": 799592, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1741684736 + }, + { + "tMs": 499986, + "cpuTicks": 84189, + "rssKiB": 775420, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1745006592 + }, + { + "tMs": 501157, + "cpuTicks": 84379, + "rssKiB": 775112, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1749037056 + }, + { + "tMs": 502323, + "cpuTicks": 84523, + "rssKiB": 775112, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1752076288 + }, + { + "tMs": 503476, + "cpuTicks": 84666, + "rssKiB": 775112, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1757790208 + }, + { + "tMs": 504666, + "cpuTicks": 84820, + "rssKiB": 775436, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1760821248 + }, + { + "tMs": 505884, + "cpuTicks": 85057, + "rssKiB": 799840, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1765175296 + }, + { + "tMs": 507069, + "cpuTicks": 85270, + "rssKiB": 776004, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1769279488 + }, + { + "tMs": 508242, + "cpuTicks": 85439, + "rssKiB": 776004, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1773494272 + }, + { + "tMs": 509401, + "cpuTicks": 85592, + "rssKiB": 776316, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1776381952 + }, + { + "tMs": 510538, + "cpuTicks": 85761, + "rssKiB": 776344, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1779359744 + }, + { + "tMs": 511675, + "cpuTicks": 85917, + "rssKiB": 783036, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1783025664 + }, + { + "tMs": 512905, + "cpuTicks": 86186, + "rssKiB": 800880, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1789390848 + }, + { + "tMs": 514077, + "cpuTicks": 86389, + "rssKiB": 776708, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1793757184 + }, + { + "tMs": 515218, + "cpuTicks": 86544, + "rssKiB": 776708, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1796792320 + }, + { + "tMs": 516367, + "cpuTicks": 86713, + "rssKiB": 776712, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1799753728 + }, + { + "tMs": 517526, + "cpuTicks": 86870, + "rssKiB": 776712, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1803788288 + }, + { + "tMs": 518684, + "cpuTicks": 87025, + "rssKiB": 815568, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1808199680 + }, + { + "tMs": 519897, + "cpuTicks": 87255, + "rssKiB": 800884, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1811169280 + }, + { + "tMs": 521077, + "cpuTicks": 87463, + "rssKiB": 776712, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1815232512 + }, + { + "tMs": 522262, + "cpuTicks": 87614, + "rssKiB": 776176, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1819553792 + }, + { + "tMs": 523430, + "cpuTicks": 87766, + "rssKiB": 776176, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1824378880 + }, + { + "tMs": 524579, + "cpuTicks": 87931, + "rssKiB": 776176, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1827344384 + }, + { + "tMs": 525775, + "cpuTicks": 88122, + "rssKiB": 800348, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1831641088 + }, + { + "tMs": 526970, + "cpuTicks": 88357, + "rssKiB": 800356, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1835597824 + }, + { + "tMs": 528169, + "cpuTicks": 88558, + "rssKiB": 775860, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1839927296 + }, + { + "tMs": 529322, + "cpuTicks": 88715, + "rssKiB": 776084, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1842941952 + }, + { + "tMs": 530456, + "cpuTicks": 88883, + "rssKiB": 775860, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1845936128 + }, + { + "tMs": 531596, + "cpuTicks": 89028, + "rssKiB": 775884, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1849839616 + }, + { + "tMs": 532815, + "cpuTicks": 89251, + "rssKiB": 800056, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1855979520 + }, + { + "tMs": 533995, + "cpuTicks": 89473, + "rssKiB": 800056, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1858953216 + }, + { + "tMs": 535166, + "cpuTicks": 89649, + "rssKiB": 775884, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1863286784 + }, + { + "tMs": 536363, + "cpuTicks": 89823, + "rssKiB": 775904, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1867354112 + }, + { + "tMs": 537538, + "cpuTicks": 89980, + "rssKiB": 775904, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1870336000 + }, + { + "tMs": 538716, + "cpuTicks": 90155, + "rssKiB": 800076, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1874649088 + }, + { + "tMs": 539935, + "cpuTicks": 90398, + "rssKiB": 800076, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1877557248 + }, + { + "tMs": 541118, + "cpuTicks": 90601, + "rssKiB": 775904, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1882898432 + }, + { + "tMs": 542286, + "cpuTicks": 90752, + "rssKiB": 775924, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1885974528 + }, + { + "tMs": 543447, + "cpuTicks": 90904, + "rssKiB": 776248, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1890729984 + }, + { + "tMs": 544598, + "cpuTicks": 91066, + "rssKiB": 776248, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1893781504 + }, + { + "tMs": 545810, + "cpuTicks": 91282, + "rssKiB": 800420, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1899102208 + }, + { + "tMs": 546972, + "cpuTicks": 91491, + "rssKiB": 800424, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1902133248 + }, + { + "tMs": 548153, + "cpuTicks": 91681, + "rssKiB": 776252, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1906491392 + }, + { + "tMs": 549311, + "cpuTicks": 91826, + "rssKiB": 776252, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1909493760 + }, + { + "tMs": 550476, + "cpuTicks": 91987, + "rssKiB": 776252, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1913479168 + }, + { + "tMs": 551648, + "cpuTicks": 92137, + "rssKiB": 776256, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1916493824 + }, + { + "tMs": 552889, + "cpuTicks": 92376, + "rssKiB": 800428, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1922629632 + }, + { + "tMs": 554066, + "cpuTicks": 92583, + "rssKiB": 775932, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 1927917568 + }, + { + "tMs": 555220, + "cpuTicks": 92741, + "rssKiB": 775932, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1931071488 + }, + { + "tMs": 556395, + "cpuTicks": 92889, + "rssKiB": 775944, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1934110720 + }, + { + "tMs": 557548, + "cpuTicks": 93041, + "rssKiB": 775944, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1937108992 + }, + { + "tMs": 558745, + "cpuTicks": 93220, + "rssKiB": 800116, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1941422080 + }, + { + "tMs": 559933, + "cpuTicks": 93444, + "rssKiB": 800116, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1945415680 + }, + { + "tMs": 561094, + "cpuTicks": 93623, + "rssKiB": 775944, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1949577216 + }, + { + "tMs": 562278, + "cpuTicks": 93781, + "rssKiB": 775628, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1952878592 + }, + { + "tMs": 563461, + "cpuTicks": 93927, + "rssKiB": 775628, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1958744064 + }, + { + "tMs": 564617, + "cpuTicks": 94079, + "rssKiB": 775852, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1961807872 + }, + { + "tMs": 565849, + "cpuTicks": 94302, + "rssKiB": 800576, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1966145536 + }, + { + "tMs": 567006, + "cpuTicks": 94502, + "rssKiB": 800596, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1969369088 + }, + { + "tMs": 568154, + "cpuTicks": 94656, + "rssKiB": 776424, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1974644736 + }, + { + "tMs": 569313, + "cpuTicks": 94814, + "rssKiB": 777072, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1977573376 + }, + { + "tMs": 570468, + "cpuTicks": 94971, + "rssKiB": 777088, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1980485632 + }, + { + "tMs": 571635, + "cpuTicks": 95147, + "rssKiB": 777120, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1983528960 + }, + { + "tMs": 572827, + "cpuTicks": 95354, + "rssKiB": 801292, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1990668288 + }, + { + "tMs": 573990, + "cpuTicks": 95561, + "rssKiB": 801292, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1994051584 + }, + { + "tMs": 575161, + "cpuTicks": 95718, + "rssKiB": 777120, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 1998188544 + }, + { + "tMs": 576319, + "cpuTicks": 95872, + "rssKiB": 777124, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2001125376 + }, + { + "tMs": 577475, + "cpuTicks": 96022, + "rssKiB": 777124, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2005307392 + }, + { + "tMs": 578643, + "cpuTicks": 96201, + "rssKiB": 777124, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2008313856 + }, + { + "tMs": 579851, + "cpuTicks": 96417, + "rssKiB": 800844, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2012553216 + }, + { + "tMs": 581018, + "cpuTicks": 96631, + "rssKiB": 800844, + "peakRssKiB": 891636, + "threads": 35, + "fds": 37, + "writeBytes": 2016227328 + }, + { + "tMs": 582181, + "cpuTicks": 96781, + "rssKiB": 776684, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2020835328 + }, + { + "tMs": 583332, + "cpuTicks": 96930, + "rssKiB": 776684, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2025689088 + }, + { + "tMs": 584489, + "cpuTicks": 97076, + "rssKiB": 776684, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2028773376 + }, + { + "tMs": 585636, + "cpuTicks": 97218, + "rssKiB": 776684, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2031861760 + }, + { + "tMs": 586824, + "cpuTicks": 97424, + "rssKiB": 800872, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2037239808 + }, + { + "tMs": 587989, + "cpuTicks": 97635, + "rssKiB": 800548, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2040197120 + }, + { + "tMs": 589157, + "cpuTicks": 97805, + "rssKiB": 776376, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2044526592 + }, + { + "tMs": 590315, + "cpuTicks": 97963, + "rssKiB": 776376, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2047557632 + }, + { + "tMs": 591485, + "cpuTicks": 98110, + "rssKiB": 776384, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2051391488 + }, + { + "tMs": 592649, + "cpuTicks": 98264, + "rssKiB": 776384, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2054377472 + }, + { + "tMs": 593856, + "cpuTicks": 98487, + "rssKiB": 800556, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2060529664 + }, + { + "tMs": 595016, + "cpuTicks": 98696, + "rssKiB": 800556, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2063560704 + }, + { + "tMs": 596184, + "cpuTicks": 98859, + "rssKiB": 776384, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2068910080 + }, + { + "tMs": 597337, + "cpuTicks": 99007, + "rssKiB": 776400, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2071949312 + }, + { + "tMs": 598499, + "cpuTicks": 99159, + "rssKiB": 776724, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2074931200 + }, + { + "tMs": 599654, + "cpuTicks": 99306, + "rssKiB": 776724, + "peakRssKiB": 891636, + "threads": 35, + "fds": 36, + "writeBytes": 2077888512 + } + ], + "db_probe_ms": [ + 16.498, + 9.361, + 9.055, + 8.549, + 12.874, + 8.012, + 7.671, + 7.772, + 10.145, + 7.731, + 8.288, + 9.083, + 7.372, + 7.385, + 8.28, + 12.9, + 7.398, + 7.506, + 11.82, + 10.1, + 8.057, + 7.434, + 8.482, + 8.951, + 7.674, + 8.072, + 10.009, + 7.77, + 9.31, + 7.987, + 8.85, + 7.757, + 7.828, + 8.354, + 8.334, + 14.431, + 7.35, + 8.677, + 15.315, + 8.58, + 7.752, + 8.148, + 10.203, + 7.634, + 7.349, + 10.694, + 8.536, + 8.423, + 7.743, + 11.925, + 10.258, + 9.048, + 7.518, + 8.903, + 19.891, + 9.865, + 7.93, + 8.261, + 7.338, + 7.702, + 7.557, + 8.418, + 9.761, + 8.415, + 9.303, + 9.112, + 8.166, + 7.495, + 8.637, + 8.468, + 8.001, + 7.246, + 10.127, + 13.517, + 7.504, + 7.725, + 10.097, + 10.978, + 8.293, + 7.938, + 10.441, + 14.185, + 7.968, + 7.395, + 8.697, + 16.553, + 7.934, + 7.606, + 10.5, + 12.851, + 7.619, + 8.541, + 8.292, + 7.262, + 7.147, + 9.163, + 7.609, + 7.257, + 7.911, + 9.272, + 7.741, + 7.399, + 7.819, + 11.169, + 10.522, + 7.935, + 7.406, + 14.852, + 13.835, + 7.62, + 7.684, + 11.436, + 17.476, + 8.586, + 7.622, + 8.495, + 12.776, + 9.167, + 7.976, + 8.826, + 7.563, + 7.597, + 8.539, + 8.886, + 9.117, + 7.421, + 20.869, + 9.117, + 7.589, + 7.447, + 11.449, + 8.554, + 8.121, + 7.315, + 8.11, + 12.237, + 8.746, + 7.638, + 8.811, + 10.416, + 7.559, + 7.771, + 7.968, + 7.964, + 9.36, + 7.713, + 8.371, + 7.547, + 9.339, + 8.902, + 8.321, + 7.75, + 7.459, + 9.378, + 10.579, + 7.653, + 7.852, + 17.211, + 8.264, + 7.451, + 7.738, + 9.146, + 10.826, + 7.103, + 7.53, + 15.765, + 14.413, + 8.456, + 7.705, + 15.035, + 16.444, + 9.382, + 8.726, + 7.531, + 7.303, + 8.72, + 7.644, + 8.432, + 7.462, + 8.443, + 12.982, + 7.802, + 7.436, + 8.259, + 11.157, + 8.132, + 7.823, + 7.892, + 10.439, + 12.756, + 7.27, + 8.139, + 11.602, + 13.837, + 7.845, + 7.989, + 14.554, + 8.469, + 7.96, + 9.021, + 11.472, + 8.744, + 8.252, + 7.35, + 8.973, + 7.287, + 8.171, + 17.696, + 11.104, + 10.671, + 8.624, + 16.718, + 13.532, + 8.001, + 7.759, + 9.663, + 13.356, + 7.393, + 7.753, + 10.328, + 17.195, + 7.517, + 7.515, + 11.439, + 9.717, + 7.166, + 7.378, + 10.705, + 9.085, + 7.468, + 10.077, + 7.771, + 8.914, + 9.266, + 9.178, + 18.373, + 7.622, + 8.614, + 12.09, + 22.296, + 7.285, + 11.476, + 11.528, + 7.708, + 7.291, + 7.642, + 13.973, + 13.245, + 7.244, + 7.512, + 11.831, + 7.321, + 7.056, + 7.702, + 11.153, + 8.305, + 8.142, + 12.08, + 8.125, + 9.383, + 7.743, + 8.639, + 8.091, + 7.966, + 8.891, + 11.73, + 15.873, + 7.765, + 7.715, + 8.049, + 7.165, + 7.79, + 9.592, + 8.47, + 8.37, + 7.505, + 7.397, + 9.993, + 9.048, + 7.541, + 7.354, + 9.5, + 7.703, + 7.343, + 13.268, + 8.562, + 8.512, + 7.501, + 8.557, + 7.6, + 7.551, + 7.521, + 8.756, + 13.412, + 10.678, + 8.303, + 8.836, + 17.154, + 7.508, + 7.912, + 8.088, + 7.706, + 7.822, + 7.447, + 8.534, + 13.913, + 8.516, + 12.436, + 8.764, + 7.71, + 7.861, + 15.375, + 7.864, + 8.283, + 7.658, + 10.183, + 8.485, + 10.519, + 8.225, + 12.916, + 10.814, + 8.743, + 8.55, + 8.964, + 10.824, + 7.259, + 9.545, + 8.61, + 7.912, + 7.428, + 9.098, + 9.348, + 7.685, + 7.82, + 10.311, + 8.649, + 7.493, + 8.073, + 16.98, + 10.729, + 8.701, + 7.851, + 8.504, + 16.034, + 10.602, + 7.837, + 8.887, + 8.119, + 7.383, + 8.555, + 10.004, + 15.984, + 7.317, + 7.897, + 8.429, + 12.88, + 7.372, + 9.592, + 10.163, + 9.028, + 7.441, + 7.415, + 8.617, + 8.352, + 7.108, + 14.295, + 8.782, + 9.524, + 8.203, + 9.708, + 8.415, + 8.369, + 7.405, + 11.087, + 10.467, + 7.5, + 7.355, + 8.208, + 18.251, + 7.753, + 7.963, + 8.3, + 13.355, + 7.569, + 8.436, + 10.341, + 7.699, + 8.594, + 10.244, + 8.466, + 8.277, + 7.755, + 14.098, + 9.209, + 8.612, + 7.433, + 9.83, + 8.577, + 8.519, + 7.56, + 8.684, + 10.236, + 8.696, + 7.687, + 8.223, + 23.263, + 8.363, + 7.884, + 7.799, + 7.815, + 7.191, + 8.144, + 8.723, + 8.31, + 7.916, + 7.835, + 7.928, + 9.129, + 7.235, + 11.037, + 9.058, + 7.913, + 7.24, + 9.795, + 9.545, + 7.509, + 8.307, + 9.402, + 11.771, + 8.139, + 7.756, + 9.064, + 19.493, + 7.654, + 7.522, + 9.427, + 13.194, + 8.072, + 8.423, + 8.476, + 8.039, + 7.12, + 9.701, + 8.957, + 7.139, + 7.62, + 11.159, + 9.819, + 7.208, + 7.536, + 9.038, + 8.992, + 7.702, + 7.516, + 12.127, + 8.368, + 9.123, + 7.431, + 12.265, + 8.866, + 9.323, + 7.892, + 13.702, + 7.402, + 7.663, + 8.662, + 9.646, + 7.797, + 7.457, + 7.644, + 8.346, + 9.754, + 9.747, + 12.303, + 10.273, + 7.675, + 7.909, + 9.197, + 8.903, + 7.923, + 7.557, + 9.936, + 10.168, + 7.432, + 7.251, + 10.307, + 8.245, + 8.152, + 8.217, + 8.166, + 7.438, + 7.401, + 9.036, + 8.267, + 9.125, + 7.388, + 10.511, + 8.05, + 7.465, + 7.495, + 8.016, + 8.723, + 7.669, + 8.273, + 9.248, + 8.848, + 8.214, + 7.787, + 9.151, + 7.469, + 8.866, + 7.932, + 12.46, + 7.352, + 8.468, + 8.142, + 8.658, + 11.442, + 7.476, + 8.934, + 7.634, + 7.253, + 7.714, + 8.925, + 8.053, + 7.664, + 7.198, + 8.313, + 8.143, + 8.008, + 7.797, + 7.285, + 9.665, + 7.298, + 7.565, + 17.321, + 9.905, + 9.184, + 7.427, + 12.185, + 13.787, + 8.844, + 7.636, + 8.35, + 12.577, + 7.67, + 8.108, + 8.152, + 8.438, + 7.688, + 8.349, + 9.057, + 7.142, + 7.044, + 8.302, + 14.626, + 7.239, + 7.386, + 7.684, + 9.255, + 7.842, + 8.066, + 10.608, + 10.365, + 9.081, + 7.072, + 9.115, + 8.209, + 11.743, + 7.236, + 8.807, + 7.878, + 8.015, + 11.145, + 9.683, + 19.934, + 7.66, + 8.185, + 8.378, + 7.279, + 7.894, + 8.021, + 9.822, + 7.336, + 8.155, + 7.502, + 8.672, + 8.502, + 7.879, + 18.627, + 9.005, + 7.275, + 7.26, + 8.932, + 8.286, + 8.899, + 7.621, + 8.828, + 20.719, + 7.464, + 8.216, + 9.553, + 11.32, + 7.533, + 8.383, + 7.857, + 7.661, + 7.507, + 8.653, + 8.057, + 8.089, + 7.762, + 7.265, + 9.707, + 7.372, + 7.856, + 10.68, + 11.54, + 8.061, + 7.632, + 10.189, + 10.436, + 8.231, + 7.684, + 9.101, + 12.342, + 9.368, + 7.623, + 10.21, + 7.12, + 7.388, + 9.006, + 7.969, + 7.386, + 7.554, + 8.398, + 7.828, + 7.195, + 7.826, + 8.581, + 10.845, + 7.752, + 7.805, + 8.37, + 8.313, + 7.46, + 7.408, + 13.186, + 11.06, + 8.724, + 7.268, + 12.78, + 8.223, + 8.057, + 7.62, + 8.701, + 14.173, + 19.678, + 8.045, + 12.424, + 7.435, + 7.277, + 8.059, + 14.77, + 9.001, + 7.063, + 9.711, + 12.096, + 7.956, + 7.456, + 8.772, + 9.12, + 7.397, + 7.899, + 9.131, + 8.018, + 8.311, + 7.814, + 8.293, + 8.497, + 9.664, + 7.298, + 8.525, + 11.112, + 7.334, + 7.286, + 8.177, + 11.721, + 7.473, + 10.642, + 8.351, + 12.531, + 7.213, + 9.094, + 7.99, + 7.485, + 7.699, + 8.447, + 10.263, + 8.66, + 7.815, + 8.989, + 9.527, + 8.779, + 7.465, + 9.397, + 7.731, + 7.937, + 7.268, + 8.404, + 18.767, + 9.68, + 7.866, + 8.074, + 9.889, + 8.233, + 8.948, + 12.783, + 12.904, + 7.457, + 9.736, + 7.744, + 7.626, + 7.457, + 10.591, + 7.884, + 7.574, + 7.284, + 8.362, + 8.685, + 7.945, + 9.612, + 8.717, + 8.578, + 7.311, + 7.54, + 8.832, + 13.391, + 8.229, + 7.379, + 8.904, + 16, + 8.752, + 8.016, + 9.509, + 7.634, + 7.468, + 9.159, + 7.809, + 7.465, + 7.413, + 8.42, + 7.927, + 7.439, + 7.152, + 17.843, + 9.034, + 7.357, + 7.253, + 8.668, + 11.068, + 7.555, + 7.277, + 8.589, + 18.019, + 7.87, + 7.42, + 8.476, + 8.324, + 8.112, + 8.363, + 9.237, + 13.313, + 9.483, + 9.324, + 7.666, + 8.123, + 8.054, + 11.518, + 10.18, + 8.632, + 7.686, + 14.462, + 10.316, + 7.455, + 7.91, + 10.377, + 14.664, + 7.996, + 7.572, + 8.854, + 12.308, + 8.191, + 7.452, + 9.645, + 9.402, + 9.597, + 7.489, + 8.9, + 7.586, + 8.649, + 9.085, + 13.031, + 7.694, + 8.426, + 7.869, + 7.658, + 7.589, + 7.211, + 20.479, + 8.458, + 7.481, + 7.432, + 8.6, + 11.479, + 7.357, + 7.519, + 10.401, + 11.667, + 8.776, + 7.545, + 9.597, + 7.975, + 8.927, + 7.76, + 8.508, + 7.741, + 9.104, + 7.929, + 8.603, + 10.28, + 7.845, + 10.229, + 8.212, + 7.75, + 7.687, + 19.949, + 7.966, + 7.178, + 7.111, + 8.937, + 10.649, + 7.398, + 7.847, + 9.604, + 9.552, + 7.654, + 7.254, + 9.232, + 11.758, + 9.648, + 8.034, + 9.351, + 7.616, + 9.813, + 7.825, + 8.837, + 8.422, + 8.5, + 8.454, + 8.927, + 8.374, + 7.585, + 16.101, + 7.817, + 7.248, + 7.566, + 13.765, + 7.912, + 7.488, + 7.803, + 8.63, + 14.15, + 7.569, + 7.422, + 9.375, + 10.347, + 9.615, + 7.625, + 8.24, + 16.529, + 10.766, + 7.568, + 11.119, + 7.711, + 9.205, + 8.79, + 8.246, + 7.309, + 30.129, + 10.76, + 8.445, + 7.171, + 8.044, + 12.595, + 16.764, + 7.674, + 7.308, + 9.256, + 8.339, + 7.495, + 7.781, + 7.649, + 10.139, + 7.821, + 7.536, + 7.663, + 7.485, + 11.179, + 7.594, + 9.047, + 7.077, + 7.571, + 7.766, + 8.192, + 7.758, + 8.801, + 15.228, + 8.479, + 7.291, + 7.383, + 17.026, + 7.881, + 7.953, + 7.347, + 8.812, + 14.949, + 7.355, + 7.424, + 7.775, + 7.738, + 7.545, + 7.923, + 7.804, + 8.443, + 8.024, + 7.542, + 10.94, + 7.704, + 8.273, + 7.265, + 8.697, + 7.717, + 7.731, + 12.185, + 7.93, + 7.339, + 7.282, + 11.435, + 8.417, + 7.823, + 7.744, + 10.132, + 9.165, + 7.475, + 7.219, + 10.574, + 14.825, + 7.262, + 7.873, + 8.303, + 11.643, + 9.332, + 7.675, + 8.38, + 14.415, + 8.307, + 7.183, + 13.382, + 7.297, + 9.601, + 15.404, + 9.999, + 8.713, + 7.775, + 11.154, + 7.842, + 7.612, + 8.371, + 15.126, + 14.703, + 7.18, + 7.089, + 8.656, + 8.417, + 7.635, + 7.47, + 9.27, + 7.379, + 7.852, + 7.555, + 7.933, + 7.465, + 8.49, + 7.523, + 9.895, + 7.519, + 8.902, + 10.491, + 8.27, + 8.594, + 8.282, + 10.092, + 8.353, + 7.442, + 8.071, + 10.845, + 12.594, + 7.52, + 7.506, + 9.778, + 14.71, + 7.876, + 7.342, + 9.075, + 7.54, + 8.397, + 8.168, + 7.603, + 7.686, + 10.103, + 8.114, + 11.046, + 7.558, + 7.627, + 10.551, + 8.258, + 7.999, + 7.946, + 16.536, + 13.795, + 7.112, + 7.63, + 12.289, + 9.452, + 7.377, + 7.798, + 9.034, + 19.588, + 7.382, + 7.143, + 12.509, + 7.212, + 7.564, + 8.137, + 8.799, + 7.171, + 7.792, + 7.352, + 7.555, + 8.774, + 7.043, + 8.209, + 9.412, + 7.008, + 7.796, + 16.206, + 11.244, + 7.51, + 7.68, + 14.622, + 13.581, + 7.363, + 7.345, + 8.048, + 9.404, + 7.267, + 7.617, + 8.632, + 15.7, + 8.167, + 7.334, + 7.969, + 13.265, + 7.623, + 7.682, + 8.171, + 8.457, + 7.494, + 8.326, + 8.396, + 7.32, + 7.471, + 17.768, + 9.208, + 7.181, + 7.76, + 12.441, + 16.666, + 7.949, + 7.947, + 9.719, + 15.146, + 8.415, + 8.083, + 12.281, + 8.267, + 8.19, + 7.453, + 8.149, + 7.199, + 7.91, + 7.392, + 8.018, + 7.546, + 11.197, + 8.323, + 16.764, + 7.616, + 9.427, + 11.575, + 8.326, + 7.435, + 7.636, + 10.514, + 14.386, + 7.109, + 7.199, + 7.792, + 23.28, + 7.517, + 8.174, + 12.491, + 8.969, + 8.098, + 7.59, + 8.324, + 8.456, + 7.903, + 7.325, + 9.146, + 7.398, + 11.79, + 13.099, + 13.622, + 7.169, + 7.227, + 9.595, + 8.384, + 7.431, + 7.179, + 8.673, + 7.745, + 7.397, + 7.441, + 9.703, + 13.619, + 7.735, + 7.828, + 12.058, + 8.057, + 8.005, + 7.539, + 14.953, + 7.379, + 8.309, + 7.616, + 8.255, + 7.623, + 8.053, + 8.52, + 11.73, + 7.762, + 7.919, + 10.704, + 8.785, + 8.567, + 7.485, + 12.569, + 8.718, + 7.722, + 7.689, + 9.233, + 8.46, + 7.675, + 7.66, + 7.494, + 7.675, + 7.572, + 7.141, + 9.193, + 7.281, + 8.373, + 7.639, + 7.85, + 7.296, + 9.792, + 7.68, + 7.863, + 7.35, + 7.752, + 22.127, + 7.514, + 7.243, + 7.598, + 10.264, + 12.169, + 7.462, + 7.4, + 8.622, + 8.256, + 7.807, + 7.706, + 8.092, + 14.867, + 8.072, + 7.704, + 9.068, + 7.394, + 8.288, + 7.352, + 7.729, + 7.469, + 7.813, + 7.674, + 7.956, + 7.303, + 7.385, + 9.673, + 11.245, + 7.64, + 7.434, + 11.436, + 7.912, + 7.592, + 7.451, + 12.232, + 8.919, + 7.434, + 7.277, + 8.812, + 9.569, + 7.592, + 7.368, + 8.076, + 8.005, + 8.187, + 7.601, + 7.713, + 7.49, + 8.778, + 7.618, + 7.843, + 8.412, + 7.63, + 8.296, + 9.272, + 7.338, + 7.169, + 14.548, + 8.206, + 8.205, + 9.931, + 9.338, + 9.29, + 7.358, + 7.073, + 8.93, + 8.652, + 7.45, + 9.339, + 7.977, + 13.11, + 8.134, + 7.667, + 8.487, + 7.562, + 8.985, + 9.611, + 8.355, + 7.445, + 7.456, + 7.818, + 7.561, + 7.343, + 7.152, + 8.125, + 11.028, + 7.852, + 7.676, + 10.422, + 8.594, + 8.378, + 7.628, + 9.191, + 13.584, + 7.383, + 8.634, + 13.317, + 36.451, + 8.246, + 7.36, + 8.26, + 7.258, + 8.042, + 7.773, + 10.25, + 7.336, + 7.26, + 8.897, + 8.152, + 10.504, + 7.261, + 17.362, + 12.548, + 10.815, + 7.329, + 13.415, + 9.608, + 8.493, + 7.123, + 9.485, + 13.369, + 7.429, + 8.306, + 8.14, + 7.785, + 8.266, + 7.164, + 8.205, + 8.994, + 10.295, + 7.506, + 9.029, + 7.146, + 7.259, + 7.897, + 8.647, + 9.18, + 7.495, + 14.462, + 8.11, + 7.18, + 7.74, + 9.329, + 8.862, + 10.353, + 8.032, + 11.405, + 8.583, + 7.842, + 7.447, + 8.387, + 14.638, + 9.813, + 7.496, + 7.767, + 7.601, + 8.451, + 7.63, + 7.648, + 7.565, + 7.461, + 8.443, + 7.827, + 7.456, + 7.515, + 12.272, + 8.338, + 8.501, + 7.656, + 10.051, + 9.022, + 7.316, + 8.508, + 11.419, + 10.248, + 8.984, + 7.085, + 8.104, + 7.598, + 9.746, + 7.385, + 14.473, + 7.186, + 9.197, + 7.989, + 9.631, + 7.471, + 7.402, + 7.859, + 7.543, + 7.48, + 7.403, + 10.417, + 9.559, + 7.261, + 7.591, + 18.671, + 13.306, + 7.603, + 15.979, + 10.352, + 11.432, + 7.535, + 7.291, + 9.829, + 8.738, + 7.804, + 7.477, + 9.545, + 10.812, + 10.34, + 7.713, + 8.729, + 7.519, + 7.266, + 7.74, + 8.076, + 7.026, + 7.382, + 7.93, + 11.645, + 7.57, + 7.598, + 21.878, + 8.148, + 7.591, + 7.44, + 10.34, + 8.712, + 8.236, + 8.451, + 13.46, + 11.639, + 8.426, + 7.837, + 9.939, + 7.545, + 7.58, + 7.518, + 11.11, + 8.756, + 7.371, + 9.03, + 7.718, + 8.884, + 7.507, + 9.377, + 10.516, + 7.557, + 7.214, + 10.607, + 12.259, + 7.342, + 7.054, + 12.487, + 9.551, + 7.598, + 7.568, + 8.797, + 8.944, + 8.584, + 8.013, + 9.57, + 15.658, + 9.684, + 7.601, + 8.266, + 10.336, + 8.143, + 7.899, + 7.975, + 9.06, + 9.596, + 8.267, + 10.312, + 8.875, + 7.819, + 14.097, + 12.64, + 9.026, + 8.353, + 10.303, + 10.787, + 10.378, + 8.36, + 10.126, + 8.713, + 8.474, + 7.827, + 8.656, + 14.92, + 8.672, + 8.71, + 8.544, + 7.516, + 8.049, + 10.411, + 10.747, + 13.116, + 8.456, + 8.82, + 16.464, + 8.685, + 8.183, + 8.822, + 10.009, + 7.432, + 8.641, + 10.104, + 13.187, + 11.248, + 10.039, + 8.322, + 18.4, + 7.723, + 8.391, + 12.589, + 16.041, + 15.944, + 8.513, + 8.369, + 9.836, + 7.605, + 10.579, + 9.591, + 7.964, + 7.393, + 11.644, + 8.618, + 7.821, + 7.817, + 12.157, + 10.231, + 9.204, + 8.668, + 8.893, + 7.279, + 10.383, + 7.201, + 12.208, + 28.33, + 10.224, + 7.55, + 9.322, + 8.18, + 7.965, + 8.67, + 10.823, + 11.16, + 7.791, + 10.917, + 9.586, + 8.227, + 7.452, + 8.975, + 9.216, + 11.875, + 7.8, + 9.709, + 11.885, + 10.271, + 7.591, + 12.608, + 11.496, + 9.83, + 8.266, + 8.389, + 10.105, + 7.928, + 8.874, + 8.432, + 9.75, + 7.724, + 10.234, + 8.491, + 7.792, + 8.185, + 13.795, + 12.439, + 8.019, + 8.069, + 10.544, + 9.152, + 11.426, + 11.499, + 12.929, + 8.821, + 8.119, + 7.824, + 9.166, + 16.317, + 7.432, + 8.169, + 8.91, + 7.282, + 7.553, + 9.451, + 7.98, + 8.719, + 7.894, + 7.385, + 9.35, + 7.646, + 7.807, + 12.354, + 8.206, + 8.087, + 7.766, + 10.943, + 9.106, + 7.784, + 7.597, + 11.384, + 15.143, + 7.649, + 9.253, + 8.582, + 14.235, + 7.334, + 9.959, + 7.71, + 8.031, + 8.016, + 9.789, + 9.222, + 7.859, + 7.559, + 7.918, + 8.027, + 7.815, + 7.657, + 16.904, + 8.294, + 8.331, + 7.217, + 10.653, + 7.935, + 10.195, + 7.767, + 10.912, + 13.002, + 7.504, + 8.349, + 7.894, + 12.019, + 8.087, + 8.859, + 8.275, + 7.763, + 7.474, + 8.186, + 7.711, + 7.577, + 8.104, + 7.393, + 7.631, + 8.439, + 7.537, + 8.982, + 8.511, + 8.481, + 7.531, + 10.993, + 9.126, + 9.604, + 9.483, + 11.508, + 8.903, + 7.752, + 8.096, + 8.971, + 8.164, + 7.85, + 7.942, + 11.573, + 8.443, + 8.048, + 9.294, + 11.151, + 8.141, + 8.016, + 7.383, + 9.272, + 7.742, + 9.024, + 7.544, + 8.393, + 8.352, + 7.535, + 11.213, + 8.715, + 10.048, + 8.137, + 8.524, + 8.335, + 7.709, + 8.917, + 8.447, + 7.302, + 7.513, + 8.781, + 8.579, + 7.367, + 7.909, + 7.277, + 10.07, + 11.139, + 8.566, + 8.968, + 35.5, + 9.302, + 8.004, + 9.025, + 8.96, + 12.297, + 8.107, + 12.46, + 10.375, + 7.924, + 12.157, + 10.597, + 9.238, + 8.879, + 14.482, + 9.257, + 12.908, + 7.916, + 7.945, + 13.993, + 11.52, + 9.779, + 8.966, + 11.349, + 11.165, + 8.708, + 9.593, + 9.627, + 18.283, + 12.637, + 22.78, + 10.564, + 8.311, + 11.659, + 13.265, + 11.346, + 8.397, + 11.854, + 10.876, + 13.029, + 8.993, + 8.809, + 8.201, + 14.225, + 8.044, + 8.161, + 9.856, + 11.438, + 17.098, + 17.144, + 9.767, + 8.788, + 9.017, + 14.334, + 8.437, + 8.359, + 8.849, + 12.344, + 10.165, + 11.068, + 8.647, + 10.237, + 11.018, + 8.711, + 10.404, + 8.93, + 9.169, + 13.292, + 9.136, + 8.517, + 15.954, + 8.464, + 14.945, + 13.576, + 12.885, + 8.439, + 8.766, + 8.376, + 9.034, + 11.53, + 11.322, + 11.123, + 8.088, + 7.908, + 11.253, + 9.5, + 11.602, + 9.443, + 8.368, + 8.619, + 8.625, + 9.156, + 8.366, + 8.5, + 7.893, + 10.108, + 16.429, + 18.825, + 8.706, + 13.146, + 10.817, + 9.241, + 8.796, + 17.896, + 17.509, + 10.785, + 8.831, + 12.122, + 19.351, + 8.522, + 9.581, + 11.299, + 8.662, + 9.992, + 9.73, + 11.281, + 27.843, + 10.115, + 21.382, + 9.138, + 11.718, + 8.259, + 21.508, + 12.543, + 8.28, + 11.988, + 8.829, + 9.495, + 8.958, + 10.349, + 11.153, + 14.37, + 8.338, + 8.64, + 19.82, + 23.37, + 10.464, + 15.094, + 8.296, + 11.212, + 9.004, + 14.988, + 8.712, + 7.815, + 9.447, + 13.737, + 11.529, + 10.446, + 10.185, + 11.247, + 10.036, + 7.937, + 8.946, + 12.756, + 9.202, + 8.743, + 9.539, + 10.106, + 22.512, + 9.686, + 30.838, + 20.581, + 17.144, + 8.873, + 8.057, + 9.243, + 7.958, + 9.14, + 9.327, + 8.649, + 8.246, + 8.67, + 10.371, + 16.212, + 9.618, + 8.892, + 8.379, + 7.573, + 7.365, + 7.588, + 8.377, + 7.769, + 8.34, + 12.84, + 9.064, + 7.48, + 9.82, + 13.036, + 8.108, + 7.3, + 7.835, + 8.986, + 7.758, + 7.759, + 8.814, + 10.876, + 8.444, + 7.611, + 7.343, + 9.202, + 12.08, + 8.153, + 7.387, + 8.541, + 7.91, + 8.352, + 8.244, + 10.565, + 7.812, + 9.244, + 10.625, + 16.569, + 7.604, + 7.264, + 13.233, + 8.048, + 7.508, + 7.248, + 15.555, + 8.265, + 7.553, + 7.679, + 8.309, + 9.171, + 7.328, + 7.204, + 8.035, + 10.28, + 7.92, + 8.424, + 8.833, + 8.012, + 7.648, + 7.952, + 11.059, + 8.708, + 8.326, + 12.622, + 9.725, + 7.601, + 7.585, + 12.247, + 8.015, + 7.311, + 8.023, + 11.171, + 13.282, + 7.415, + 7.649, + 8.972, + 15.469, + 7.219, + 7.568, + 12.627, + 16.231, + 8.91, + 7.544, + 11.339, + 8.101, + 8.458, + 7.732, + 10.482, + 7.567, + 9.526, + 13.963, + 8.262, + 7.295, + 7.233, + 8.966, + 8.621, + 7.775, + 7.375, + 12.946, + 9.603, + 7.246, + 7.564, + 10.845, + 10.105, + 7.814, + 8.069, + 7.989, + 9.092, + 8.656, + 7.454, + 8.214, + 7.457, + 8.385, + 7.659, + 7.774, + 7.443, + 8.714, + 11.047, + 8.156, + 7.674, + 7.327, + 16.199, + 8.262, + 7.579, + 7.259, + 9.481, + 18.464, + 7.13, + 7.467, + 10.673, + 8.332, + 7.452, + 8.814, + 8.863, + 8.065, + 7.763, + 7.887, + 8.468, + 8.797, + 7.587, + 7.45, + 9.444, + 7.498, + 7.779, + 8.121, + 7.8, + 7.52, + 7.282, + 15.346, + 8.218, + 7.52, + 7.249, + 9.422, + 9.838, + 7.471, + 7.59, + 12.519, + 14.056, + 7.328, + 7.526, + 12.738, + 19.726, + 7.968, + 7.868, + 14.342, + 7.39, + 8.997, + 8.183, + 8.476, + 8.505, + 7.528, + 12.487, + 9.91, + 7.418, + 8.81, + 14.381, + 15.12, + 8.39, + 7.467, + 9.669, + 24.542, + 7.324, + 7.359, + 8.477, + 9.229, + 7.708, + 7.445, + 8.631, + 9.959, + 8.862, + 7.471, + 8.706, + 15.383, + 9.171, + 8.085, + 7.852, + 7.22, + 8.292, + 15.775, + 8.16, + 7.679, + 7.625, + 14.098, + 10.865, + 7.906, + 8.507, + 8.799, + 9.668, + 10.589, + 8.637, + 8.612, + 7.856, + 8.016, + 7.426, + 7.769, + 8.319, + 7.786, + 8.168, + 13.688, + 7.375, + 7.367, + 8.831, + 9.814, + 7.354, + 7.157, + 18.145, + 8.228, + 7.332, + 7.697, + 9.592, + 9.29, + 7.376, + 7.464, + 9.066, + 12.494, + 7.779, + 7.663, + 8.854, + 8.115, + 8.137, + 7.507, + 7.831, + 7.773, + 10.47, + 7.43, + 7.745, + 8.352, + 7.528, + 8.274, + 8.372, + 7.313, + 7.597, + 10.425, + 8.785, + 8.723, + 7.462, + 13.624, + 8.283, + 7.493, + 7.701, + 13.609, + 9.025, + 7.775, + 7.747, + 10.724, + 8.487, + 9.163, + 7.501, + 8.221, + 13.088, + 8.158, + 8.039, + 8.133, + 7.875, + 7.922, + 8.429, + 8.032, + 7.523, + 7.94, + 7.663, + 8.15, + 7.676, + 7.241, + 13.807, + 8.693, + 7.767, + 7.388, + 8.534, + 8.51, + 9.443, + 7.798, + 8.709, + 16.085, + 8.763, + 8.217, + 9.23, + 8.637, + 7.413, + 8.478, + 7.964, + 8.623, + 7.917, + 9.814, + 8.328, + 7.399, + 7.69, + 7.361, + 9.406, + 7.406, + 7.294, + 11.571, + 8.034, + 7.736, + 7.125, + 8.485, + 8.404, + 8.639, + 7.24, + 9.428, + 16.191, + 7.536, + 7.959, + 8.75, + 8.748, + 7.41, + 8.574, + 9.608, + 24.522, + 7.637, + 7.472, + 8.69, + 8.372, + 7.535, + 7.491, + 10.059, + 7.229, + 7.428, + 10.267, + 8.343, + 7.834, + 7.139, + 8.563, + 8.084, + 7.649, + 8.18, + 9.237, + 9.477, + 7.217, + 8.835, + 9.404, + 18.763, + 7.47, + 7.718, + 10.86, + 15.249, + 7.68, + 7.334, + 8.344, + 7.637, + 7.436, + 8.124, + 8.113, + 7.645, + 7.586, + 7.886, + 7.851, + 7.601, + 8.03, + 10.949, + 11.157, + 14.179, + 9.358, + 10.351, + 19.486, + 7.49, + 8.606, + 8.357, + 18.381, + 8.147, + 8.456, + 9.479, + 7.349, + 7.445, + 7.675, + 9.61, + 7.958, + 7.979, + 7.452, + 9.508, + 7.969, + 7.073, + 9.929, + 7.803, + 8.257, + 9.832, + 10.782, + 7.866, + 7.51, + 7.778, + 11.207, + 12.676, + 7.519, + 7.82, + 9.001, + 8.411, + 7.337, + 7.517, + 12.273, + 9.432, + 7.177, + 8.228, + 10.181, + 7.783, + 7.502, + 7.482, + 12.783, + 7.792, + 7.148, + 8.07, + 7.808, + 8.012, + 7.619, + 12.299, + 8.036, + 7.653, + 7.902, + 9.168, + 8.164, + 7.968, + 7.925, + 8.469, + 8.786, + 8.764, + 7.93, + 10.229, + 13.741, + 7.202, + 7.474, + 9.585 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140602_9656f323-0df3-489e-95ce-b230a5b59a28.mp4", + "tMs": -27531 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140602_5a440ca9-699f-4cb5-8ee8-72b79fc99294.mp4", + "tMs": -27528 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140602_c0f6e568-007b-46ab-8664-df2e9a5703ef.mp4", + "tMs": -27524 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140602_6ccdec06-843d-4109-99ef-f4dd40919396.mp4", + "tMs": -27521 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140615_a091dd17-9578-462e-b2ea-e7e49a2a7ccf.mp4", + "tMs": -17322 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140615_e5b2daa3-3b3a-4f3e-980d-cb7ea10eeb4c.mp4", + "tMs": -17317 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140615_e7d5bab4-dcd8-449d-bb5c-c48b3fa86989.mp4", + "tMs": -17311 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140615_d9990542-7609-4409-b29f-8fcba23b7f7d.mp4", + "tMs": -17305 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140625_0895cb92-a6d4-4509-953d-81c24a41c2c0.mp4", + "tMs": -7319 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140625_02a969fc-d2a6-47a4-baa3-b078ba7afc57.mp4", + "tMs": -7311 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140625_1dc52974-cbef-4be7-a9b3-ae957944c21c.mp4", + "tMs": -7308 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140625_4a944635-e19f-4e8c-ae4e-7b01f3ccc82a.mp4", + "tMs": -7303 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140635_8e9eebcb-8c2b-400a-aa68-c35259747e4c.mp4", + "tMs": 2679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140635_2795c09f-31b3-4216-af09-2273f214a6de.mp4", + "tMs": 2682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140635_c8db159e-073f-4e83-9c73-4349223321da.mp4", + "tMs": 2689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140635_d9b36231-3ef2-4eb3-97a5-15f6fb3badcb.mp4", + "tMs": 2694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140645_103148b6-0721-434b-b03b-0a670f813708.mp4", + "tMs": 12681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140645_e8d4d0b2-1060-4ed8-9f43-5cd67399351b.mp4", + "tMs": 12684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140645_513dc141-5293-4a50-a4f2-1fcfc659c787.mp4", + "tMs": 12688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140645_4ac26ef0-c42f-436b-8825-c5c11a890c09.mp4", + "tMs": 12694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140655_fbce8be5-9e25-4188-aa70-7c03d7177193.mp4", + "tMs": 22680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140655_8315bf70-120a-4388-af34-2e19359134b1.mp4", + "tMs": 22682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140655_910a901c-faff-49bf-b96a-11a185c91a33.mp4", + "tMs": 22691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140655_eec9c6f0-a463-4ce2-955e-f449937e1039.mp4", + "tMs": 22720 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140705_db8b658e-b28b-4429-91f5-655c530ab802.mp4", + "tMs": 32680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140705_0e942c68-78ed-459c-8bd9-3521f88bed4b.mp4", + "tMs": 32683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140705_f6209836-8430-46ca-a4ff-b3f7d770500c.mp4", + "tMs": 32691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140705_357a0d51-c375-459a-a9fd-88e075d28e6e.mp4", + "tMs": 32698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140715_e5540ce0-3c0a-4944-ab38-1258ed9d8978.mp4", + "tMs": 42677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140715_2805c0b1-7976-429c-9ff6-e51f80910e0b.mp4", + "tMs": 42683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140715_cd3ba53f-07f3-4250-960a-f83c925c574d.mp4", + "tMs": 42687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140715_84ae6687-ead3-4ca5-9132-93f1aa7db9c8.mp4", + "tMs": 42697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140725_ee1daf85-1008-4794-b3fc-ae1bbaa6ed51.mp4", + "tMs": 52681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140725_d229dc79-4e50-423c-8ba6-7bd4d24ced4b.mp4", + "tMs": 52683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140725_131f28cb-c42f-4b30-a3d2-ca17170fdd9c.mp4", + "tMs": 52691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140725_99e5c764-2cff-4337-803b-e5f73e50c5b8.mp4", + "tMs": 52697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140735_71a31ef3-1bc7-4139-9d2c-d5900e0e2f92.mp4", + "tMs": 62677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140735_15a142bb-6d75-4ca1-92d1-f5ab960df95f.mp4", + "tMs": 62683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140735_bec798f7-cdf4-4cd6-ae9b-3031bc9f5787.mp4", + "tMs": 62689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140735_b46eb240-c7c4-4b84-b103-98b6351f1724.mp4", + "tMs": 62696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140745_ba5c27d5-3bf0-435e-9c35-796b02ee96b3.mp4", + "tMs": 72677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140745_d522e7f7-ad5a-4fd4-bbdb-6d75d7f308bb.mp4", + "tMs": 72683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140745_20806c52-b6fe-4acf-96b1-e957892ac06c.mp4", + "tMs": 72688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140745_0425eeac-b26a-4d03-b2f1-2458337ce134.mp4", + "tMs": 72694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140755_55db6be2-edba-40a3-9c62-41e9eef4aa98.mp4", + "tMs": 82678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140755_7d8c4f9f-7d5e-48df-85e2-8d5948d30bdd.mp4", + "tMs": 82683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140755_a0322542-e7b7-4d0d-9dd1-8172464bc9ba.mp4", + "tMs": 82690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140755_019a0f09-76c4-4ad0-b3b9-e7bd6444a173.mp4", + "tMs": 82696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140805_afe58aab-eea2-49bc-9224-59bd5d3d142b.mp4", + "tMs": 92681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140805_d6966e88-661c-4094-9bf4-4b0e9d4a21e2.mp4", + "tMs": 92683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140805_6b53585b-d7c0-4444-ab16-a214e62bf8be.mp4", + "tMs": 92690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140805_0a7b8b98-f526-4c52-9ba8-c2cf8622435c.mp4", + "tMs": 92694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140815_d969f65d-9b19-47d7-824f-352db13dd69e.mp4", + "tMs": 102677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140815_8100054b-bc50-4932-8224-6c5baf91bd08.mp4", + "tMs": 102683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140815_5e3af976-8c88-40d7-bd2a-5e017dbf0b6c.mp4", + "tMs": 102688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140815_7d462293-c29e-4b92-9f77-128a20d68fec.mp4", + "tMs": 102693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140825_215cfec4-3e0e-4957-9084-8895912a1c16.mp4", + "tMs": 112679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140825_2a943243-0d31-4723-a9ae-637a4a54a50d.mp4", + "tMs": 112682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140825_bb89e6f4-e17a-493d-859c-5c007e12f77f.mp4", + "tMs": 112687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140825_6350246f-b6b5-456d-9eae-9b3cc02f0113.mp4", + "tMs": 112692 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140835_2ffa1779-0a32-464d-997d-7a8db659711a.mp4", + "tMs": 122678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140835_4ba42c62-678d-4392-9a8a-d20d6f0ac83c.mp4", + "tMs": 122684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140835_41fc4b9e-703a-4ecb-92e2-4597600984cb.mp4", + "tMs": 122690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140835_65603827-8815-4b3f-a92e-704ca24d02b3.mp4", + "tMs": 122695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140845_fea36af6-0e71-4e8b-b1d1-105502e88f0b.mp4", + "tMs": 132678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140845_337a1541-bf6c-4a39-9876-119f62408db2.mp4", + "tMs": 132683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140845_055c3708-5119-4ba2-b9d9-309dea1ad89c.mp4", + "tMs": 132690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140845_e6134f18-a5ea-4b9d-9224-c5edb66bf52b.mp4", + "tMs": 132697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140855_e4c1bd4b-86a9-4e01-96b9-3dfa66b73bdc.mp4", + "tMs": 142678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140855_0195b1df-e039-467e-a5f9-a3fe3a274cc0.mp4", + "tMs": 142683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140855_2c6a4fd7-aea2-499c-a2b7-66621d4fe2ba.mp4", + "tMs": 142689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140855_1ece1901-7509-44a4-9f11-5f9f69da33e3.mp4", + "tMs": 142693 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140905_a1deda54-7e81-44f8-a1a5-6827603af2aa.mp4", + "tMs": 152683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140905_2cb2a0bd-29c5-4a86-aae5-63398ce595fb.mp4", + "tMs": 152690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140905_9426e443-f7b9-459c-9f73-472408a7f1fe.mp4", + "tMs": 152699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140905_cd2adb06-591d-4e2d-8de7-1f69a3c65597.mp4", + "tMs": 152705 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140915_a60a0d58-c2cd-4a7b-bcfd-e1ab700952cf.mp4", + "tMs": 162680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140915_f824090e-dbaf-4382-bfaa-7021bfdf0084.mp4", + "tMs": 162683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140915_bd33c859-5e05-4207-99cf-58a052512737.mp4", + "tMs": 162688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140915_11a0b924-e7cd-4451-9381-d6007bd112c6.mp4", + "tMs": 162701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140925_fe97fe06-5df2-40de-a56a-9fb7bf4342de.mp4", + "tMs": 172679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140925_68005bc3-27ee-4794-81c7-7ed1e411661d.mp4", + "tMs": 172682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140925_d33d1b57-a128-4c67-9e8e-aefec2f835b2.mp4", + "tMs": 172688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140925_b1165765-b779-4bde-a851-eef11e67208b.mp4", + "tMs": 172696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140935_47e4a951-ca86-4925-9a43-63daaf488c9a.mp4", + "tMs": 182677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140935_8e48cd98-18be-4c9a-a40f-bd0feefc54d0.mp4", + "tMs": 182683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140935_5f47be19-b8ff-4125-ad16-fdd7b900aacf.mp4", + "tMs": 182688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140935_6a8cdf0f-9daf-4e05-90a1-c1ed60d29652.mp4", + "tMs": 182693 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140945_a90a1210-e268-43af-9a4b-65398802f4c0.mp4", + "tMs": 192684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140945_b797be28-4edd-4fd6-a4d8-b9318e1b0d71.mp4", + "tMs": 192687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140945_476f6255-a30f-46ad-877d-5912dcf48692.mp4", + "tMs": 192697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140945_7fa5f06e-292a-4f63-98d3-515f6b920c81.mp4", + "tMs": 192700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_140955_9dd95420-cca1-42c2-ac61-9abae56f6bd7.mp4", + "tMs": 202678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_140955_63bcf8b9-37ab-44a9-91c3-6043ac17269b.mp4", + "tMs": 202686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_140955_05645606-c40b-4dec-94c2-d43b24bb13e4.mp4", + "tMs": 202690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_140955_fb28cda1-dcc5-48a6-92ba-b057c89eb930.mp4", + "tMs": 202698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141005_393ae14a-18d6-459e-bb8f-70271349e45e.mp4", + "tMs": 212679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141005_ef9e577e-a42c-4421-ba2b-b5c9fe38d131.mp4", + "tMs": 212683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141005_a60393ff-f239-429c-b3d6-2c9f00539201.mp4", + "tMs": 212689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141005_afe8d881-96fc-4f53-a918-c505308e1eeb.mp4", + "tMs": 212698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141015_cbc53417-4bc8-44a2-9eab-627ced55dca5.mp4", + "tMs": 222679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141015_3f6a88c1-e030-4bc9-bc1d-8268211f7bc5.mp4", + "tMs": 222684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141015_dc168e9d-3759-4d18-af69-860727808804.mp4", + "tMs": 222689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141015_b50add76-fa47-4fd0-b726-1b7ede1da0eb.mp4", + "tMs": 222697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141025_e7c6900c-ab3e-43da-8456-15cbaf099f0a.mp4", + "tMs": 232678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141025_221bbd72-5b1b-4ec5-9576-ce9e919c9971.mp4", + "tMs": 232686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141025_f4f18bac-1909-47d1-ab9a-85cb6f640841.mp4", + "tMs": 232692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141025_41407e17-bba3-41ee-b8a1-096489db2fcb.mp4", + "tMs": 232695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141035_c4faa7a4-ef8f-46a4-9f9a-c1166d99436f.mp4", + "tMs": 242678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141035_e7039ceb-d9b6-455d-9807-1e51e2285a26.mp4", + "tMs": 242683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141035_eaef2a91-cd24-4dd7-aa12-e93fe63ed510.mp4", + "tMs": 242689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141035_dcb0ccfc-4aa3-4bcd-98d5-b172f3c62f8d.mp4", + "tMs": 242694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141045_aafced86-a738-461f-9516-d956cf21040c.mp4", + "tMs": 252678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141045_e4d2521f-499c-4714-bdcb-7961d09ba37f.mp4", + "tMs": 252683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141045_d122bcaf-885e-4c47-8ed9-3a4ad571a3a6.mp4", + "tMs": 252691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141045_e5d63474-2160-45a3-a700-f06aac5864d1.mp4", + "tMs": 252695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141055_b6708537-41c0-45fe-871f-8c7ff2114849.mp4", + "tMs": 262678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141055_e5583541-633d-4e9f-9dda-b17a3f18e940.mp4", + "tMs": 262682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141055_4875016c-6afa-4cab-a3b5-7b888da38c6a.mp4", + "tMs": 262687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141055_86860bff-bfee-4d55-939c-3d58fa082030.mp4", + "tMs": 262695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141105_72c27204-d685-4790-9acb-d109c195aea7.mp4", + "tMs": 272680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141105_f58778aa-276b-4ba0-a8bb-02ae5cb46b6d.mp4", + "tMs": 272683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141105_a472e5e6-bf3d-47c7-860e-3e9578610722.mp4", + "tMs": 272689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141105_266f7053-0895-4f72-a225-81179172646b.mp4", + "tMs": 272696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141115_38f567b4-8264-41da-9ccb-438a3dd49119.mp4", + "tMs": 282683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141115_29976b68-a171-4331-8af5-77435e76c281.mp4", + "tMs": 282685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141115_9410580c-37f6-4e67-9979-ffa26c153a95.mp4", + "tMs": 282688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141115_9e765f0c-3cfa-40bb-891f-2a14ab5b4b73.mp4", + "tMs": 282695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141125_59f5877e-5cf9-4acc-a915-75246caeead1.mp4", + "tMs": 292680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141125_225d39b4-79bb-4c77-8628-9deba11cda13.mp4", + "tMs": 292682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141125_08a8a587-1179-477b-b1ac-0a0fae030cc6.mp4", + "tMs": 292691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141125_13e8c55f-3beb-45c3-aea9-d90fd072c9d2.mp4", + "tMs": 292695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141135_9b9e6ebe-4185-4450-8898-549d8ecc911d.mp4", + "tMs": 302679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141135_e768e8ac-07d9-4ece-89a1-a7dc851a0b3b.mp4", + "tMs": 302683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141135_5bc80869-9599-4026-b231-5a662c614df6.mp4", + "tMs": 302688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141135_d7337a5c-0b26-4360-ad0a-5f7caad6af2c.mp4", + "tMs": 302700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141145_e4759896-8168-4d34-a60e-c99d2706a603.mp4", + "tMs": 312680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141145_ca717a52-ce5c-46cd-8ba2-33fcce8b39c7.mp4", + "tMs": 312682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141145_7d48ecf1-9b83-4fcd-b758-962b991d8a40.mp4", + "tMs": 312690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141145_35d24921-c132-458c-bc24-ca44aad3e142.mp4", + "tMs": 312695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141155_90057fdc-d608-4d6c-87ec-e94801602314.mp4", + "tMs": 322680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141155_bc92a3e5-42fc-4f93-a04d-b723288055de.mp4", + "tMs": 322683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141155_405661c4-0792-46d0-a22d-a516f7dee4c0.mp4", + "tMs": 322688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141155_06711159-5d72-4360-bac5-7e2ee26b09d4.mp4", + "tMs": 322693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141205_d057066a-b587-40e1-ac45-50ac04d3ca7a.mp4", + "tMs": 332677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141205_333c5d40-33eb-4f73-8569-23e82814f60b.mp4", + "tMs": 332684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141205_c938b572-af5d-4580-be44-f2dff2564793.mp4", + "tMs": 332688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141205_73a557a1-9f61-4191-9a1b-597c5104d28e.mp4", + "tMs": 332699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141215_9d082936-3beb-4eca-827d-b07bfb9edd18.mp4", + "tMs": 342679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141215_6f8c7dbd-ce85-4fdd-804f-a39369606cdc.mp4", + "tMs": 342683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141215_6d78f63d-672a-4fee-b6a1-961166f18b12.mp4", + "tMs": 342689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141215_78b96ad1-6f6a-470b-b83f-407edede6a4f.mp4", + "tMs": 342693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141225_ce06426c-815a-4ee0-b07b-c66b1839a6fa.mp4", + "tMs": 352679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141225_f8f015c5-4600-40b1-8ebe-3c2627a02df5.mp4", + "tMs": 352682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141225_853b99f6-9c32-4ab2-83ff-1fc96feb84d9.mp4", + "tMs": 352688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141225_9f2fcc5a-f312-45a5-a607-73664b43e7ab.mp4", + "tMs": 352696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141235_03c457cd-14fc-4b20-a732-a5df8d937a61.mp4", + "tMs": 362681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141235_346c22e0-ef74-409f-9c67-3175a35b491e.mp4", + "tMs": 362684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141235_75ae9972-82f3-45b2-89a6-7edb8ad945f4.mp4", + "tMs": 362687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141235_513165a7-9611-45e6-a102-ecfbf579c7ef.mp4", + "tMs": 362704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141245_e86337cb-3887-4ecb-be49-450361967205.mp4", + "tMs": 372677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141245_5e2392e1-3548-4b3c-9fbe-a40459d29c2f.mp4", + "tMs": 372701 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141245_d238ee1f-d39a-40a7-8301-e84f4ffecd47.mp4", + "tMs": 372705 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141245_5488382f-5553-4dde-bb2f-0265911c26bf.mp4", + "tMs": 372705 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141255_684765d3-4300-428f-ac22-a11655a82ebf.mp4", + "tMs": 382681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141255_91e83e12-2325-4308-be95-942eb41a1b08.mp4", + "tMs": 382683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141255_9ef76cf7-9e13-431d-9057-9ce6741a34f6.mp4", + "tMs": 382687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141255_8fd7bd20-f8be-4387-b908-b242e3073aed.mp4", + "tMs": 382695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141305_918b560e-8c4f-4272-b281-53200cbe4a4b.mp4", + "tMs": 392681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141305_58bb7e00-8304-46e0-9741-154578fcf7a5.mp4", + "tMs": 392682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141305_0afc10de-c68c-43f4-800c-2d5c9c02bc9b.mp4", + "tMs": 392694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141305_29829b33-fa59-4a51-9393-20d9326f193c.mp4", + "tMs": 392696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141315_73f3fb7a-43de-47ee-ba47-b40989187af3.mp4", + "tMs": 402679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141315_e6fde183-5323-4919-9d43-616792b87d51.mp4", + "tMs": 402682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141315_e8e49c87-008f-42ab-a616-5820659ac027.mp4", + "tMs": 402687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141315_b167d1cc-ea77-4dd9-9f1d-bf14f16df4f5.mp4", + "tMs": 402694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141325_8db124f2-3af2-47ca-8e5e-f5ae980d813e.mp4", + "tMs": 412678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141325_afd03016-4e26-4b59-ae0f-08ff373b6b06.mp4", + "tMs": 412682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141325_213d2054-0157-40bc-a90c-2d9421e873a6.mp4", + "tMs": 412688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141325_b31ec32d-5a61-4db2-a863-9a4d9e940cf2.mp4", + "tMs": 412696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141335_685e73c8-1cce-428b-9943-ddddacd94da8.mp4", + "tMs": 422679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141335_d3ee2c90-4d24-41c5-badd-4e3faac1bc4b.mp4", + "tMs": 422683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141335_fed1f275-f626-4b91-b5cd-afe6e5051df4.mp4", + "tMs": 422689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141335_c0decd17-4b63-49ef-8b3d-cb320ba7d839.mp4", + "tMs": 422698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141345_6f240143-23f4-47f7-a901-a3dce7f5b870.mp4", + "tMs": 432681 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141345_1fe603b7-3c41-45b2-8bcd-25fa9e1764ac.mp4", + "tMs": 432684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141345_1dc5c819-a88d-4ca1-b431-a5b737c4335d.mp4", + "tMs": 432690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141345_81b1013c-205b-4251-b30f-0a8110d87f6b.mp4", + "tMs": 432694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141355_fb41de90-609d-4e9c-a42e-cd9598766081.mp4", + "tMs": 442685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141355_8fccf282-a142-4812-ae4c-649c9f322f8c.mp4", + "tMs": 442706 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141355_31549552-95ec-4c1a-87e5-8d9b09def158.mp4", + "tMs": 442714 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141355_6b4a5e4c-9cef-42ad-a7e1-9e4586a04df5.mp4", + "tMs": 442714 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141405_a8826b75-5c78-4f9e-b45e-e5119e79dd79.mp4", + "tMs": 452681 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141405_5f56d92f-770d-4f41-8f63-8aadd65b5161.mp4", + "tMs": 452694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141405_71a53da1-753b-4b4f-a1b4-90890605f37b.mp4", + "tMs": 452700 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141405_66275464-5bbc-4561-9098-10febb7443d1.mp4", + "tMs": 452701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141415_ee5ab712-0d30-4107-a7a5-c41e318c383a.mp4", + "tMs": 462679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141415_d0750275-bdab-43f2-8a6e-1c44a0568624.mp4", + "tMs": 462687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141415_3594ba2e-409d-47b0-808b-1f8f4484ce98.mp4", + "tMs": 462691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141415_537353fd-ddd5-4f2a-98dc-6a68847fb39f.mp4", + "tMs": 462699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141425_38562c60-6dc5-4594-8e7a-87b90b477986.mp4", + "tMs": 472680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141425_6d957cd5-76db-407b-b0d7-7b68d0daf276.mp4", + "tMs": 472686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141425_c35c6845-a729-4117-9935-72f8ffe33282.mp4", + "tMs": 472692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141425_17d380c5-427f-43db-8fa5-97f529dc9d92.mp4", + "tMs": 472695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141435_eec0c311-861f-4ebc-9a9f-318eee91d905.mp4", + "tMs": 482679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141435_6814239c-241c-4b65-bbef-1b14483fd705.mp4", + "tMs": 482689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141435_c09f666c-c3a5-4627-96cf-c474daf9ee06.mp4", + "tMs": 482704 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141435_6ac86a4e-5f91-4b2d-812c-0c768fa5f157.mp4", + "tMs": 482712 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141445_d0a69ce5-3ba5-483e-90ff-daf8b44ceacb.mp4", + "tMs": 492677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141445_b5b1af73-cf69-44d1-b972-6203f7719eac.mp4", + "tMs": 492683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141445_c8b798e1-e759-4556-a0bd-792f361e6b84.mp4", + "tMs": 492689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141445_651f96bd-c64c-4cff-a6b4-7260883469dd.mp4", + "tMs": 492694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141455_5f6e21fe-ab3e-411d-a7da-3c57dbec10aa.mp4", + "tMs": 502680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141455_bec246ef-486a-4596-bbda-bb9dd2f7e7c7.mp4", + "tMs": 502683 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141455_d9ce2f0d-a566-48e9-a6c8-4b496cc112b1.mp4", + "tMs": 502695 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141455_3cdbdd9b-776c-41bf-b4ab-4e20c3470bc9.mp4", + "tMs": 502697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141505_971081eb-64c9-419e-89fa-2032b8e89b49.mp4", + "tMs": 512698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141505_ae622f19-f651-471b-ab39-02cfa29aa12e.mp4", + "tMs": 512702 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141505_1ca4751b-0568-4bad-b75f-e251e02dae9a.mp4", + "tMs": 512702 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141505_caf234fe-08b9-4f59-87b0-8527a528b312.mp4", + "tMs": 512703 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141515_ddbb4e0c-53ff-4504-b2f3-400435c88aa8.mp4", + "tMs": 522684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141515_a5889e45-9d82-4674-9d79-a57aba83ecd5.mp4", + "tMs": 522691 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141515_596d39b2-c70b-4d68-949c-2d7031c31e35.mp4", + "tMs": 522694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141515_4019d400-9c2b-43a2-a78f-218177c039f5.mp4", + "tMs": 522698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141525_a8bf1729-e840-4b82-8fcf-5d02a6532a4b.mp4", + "tMs": 532679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141525_e886f238-e2db-4bb0-b947-70c1ad9c15ae.mp4", + "tMs": 532683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141525_6f821aa1-9352-4bc7-9fb8-665473c0ea78.mp4", + "tMs": 532687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141525_2282f906-4e55-4fbb-acf1-72522523d588.mp4", + "tMs": 532694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141535_784049e8-484d-4b67-97a4-aad9ddaaa341.mp4", + "tMs": 542679 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141535_e1f8b00d-fc75-4290-b3c8-64801c4c99b1.mp4", + "tMs": 542684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141535_2f8a96db-fd2b-4da0-b83d-550d4923b0bd.mp4", + "tMs": 542689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141535_5f34342b-7626-4c78-81d1-8c848daa6df7.mp4", + "tMs": 542693 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141545_f7d019ab-f4ac-4d2d-a760-a4850c79c2cf.mp4", + "tMs": 552680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141545_4502a170-5efb-464a-95ce-3af6a1bc6cc3.mp4", + "tMs": 552682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141545_fbc01f83-7fa5-4b63-a195-a30afd2b7bc8.mp4", + "tMs": 552688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141545_641ece08-772e-4a10-8952-f6fc2a590e0b.mp4", + "tMs": 552696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141555_ace2f061-a578-4d19-9c0f-03ddcb2d63d1.mp4", + "tMs": 562677 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141555_ef29739e-8cc0-4b29-bf7f-1a5368712dfb.mp4", + "tMs": 562682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141555_19b6b70e-46f1-4507-a75f-8a4b8d97073a.mp4", + "tMs": 562693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141555_393fb656-79f8-4164-ab8e-7cc38aed65bb.mp4", + "tMs": 562697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141605_d8b19ac2-e648-4b0e-8f85-3f156a2b6b64.mp4", + "tMs": 572680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141605_23eb7644-e45f-45c7-b8eb-7cdc4cd294a8.mp4", + "tMs": 572687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141605_ab350c58-e4ea-414a-9b67-bcf70ff5a6c7.mp4", + "tMs": 572695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141605_02118f8e-cceb-4bb9-b174-efaa8e3e918c.mp4", + "tMs": 572700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141615_e17c4876-768f-44dd-a96e-002991c66c3d.mp4", + "tMs": 582678 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141615_090fd1d8-3897-46c8-8263-5d4fc03e8ff3.mp4", + "tMs": 582686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141615_d312fc90-3b39-4543-8340-4570c9f67a84.mp4", + "tMs": 582690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141615_97d6cc3b-6231-4193-84ed-17afd3028914.mp4", + "tMs": 582697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141625_62850002-fe87-45e4-8ace-f629795f7794.mp4", + "tMs": 592680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141625_d704eb12-8eb1-40d3-a04c-79db567fe706.mp4", + "tMs": 592687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141625_22957976-d8e0-413d-a300-4aa9e1298ff9.mp4", + "tMs": 592696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141625_9c0fa721-9a9a-4a15-a523-c52f4e984dfe.mp4", + "tMs": 592697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141635_3bab9f8c-fdab-4f10-ba57-d39bcfd45706.mp4", + "tMs": 602680 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141635_05d2dbcf-363c-4d2a-9efa-33b7de8101ff.mp4", + "tMs": 602682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141635_178da837-2f52-4cbd-94aa-c1c4e24f690c.mp4", + "tMs": 602691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141635_1df30561-487a-4283-a70f-5c7cc0333de6.mp4", + "tMs": 602695 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 2524, + "assets": { + "by_state_reason": { + "ready": 2524 + }, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 317514972, + "ready_asset_bytes_max": 126889, + "detections_total": 2524, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2524 + } + }, + "finished_at": "2026-11-08T19:16:41.780Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-enabled-defaults2.json b/tests/performance/observations/results/2026-11-08-x2-enabled-defaults2.json new file mode 100644 index 000000000..86e713d22 --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-enabled-defaults2.json @@ -0,0 +1,8683 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "enabled", + "status": "completed", + "started_at": "2026-11-08T19:50:01.448Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 7.32, + 7.34, + 5.74 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "405055d8456e5a351ce4dc41ae15057c8885b2ab150071a9a27c201cf6e8e4d9", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": { + "observation_evidence_enabled": true, + "observation_max_bytes": 67108864 + }, + "config_extra": null, + "system_settings": {} + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-i0NCC4", + "detector": { + "inferences_in_window": 2407, + "inferences_per_stream_per_second": 1.003, + "expected_per_stream_per_second": 1, + "detections_in_window": 2407, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 2407, + "dropped": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 2408, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 302926228, + "high_water_items": 4, + "high_water_bytes": 11086848, + "camera_high_water_items": 1, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 33554432, + "camera_bytes": 33554432, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 0, + "inline": 0 + }, + "discard_pending_bytes_max": 0, + "backlog_items_max": 3, + "backlog_bytes_max": 8315136, + "drop_ring_depth_max": 0, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0, + "queue_allocated_end": 1, + "worker_running_end": 1, + "retained_bytes_by_camera_end": { + "045a5efe-9497-49c2-8f7c-312ada673959": 45033659, + "23af6aba-6ab6-43f8-aa3d-ea8e69cee722": 45033659, + "5461ebb5-a5b2-4260-a003-8733ca20cd76": 45033659, + "ad8e2697-eb67-42b1-b749-d6734c80a09f": 45033659 + }, + "budget_bytes_by_camera_end": { + "045a5efe-9497-49c2-8f7c-312ada673959": 67108864, + "23af6aba-6ab6-43f8-aa3d-ea8e69cee722": 67108864, + "5461ebb5-a5b2-4260-a003-8733ca20cd76": 67108864, + "ad8e2697-eb67-42b1-b749-d6734c80a09f": 67108864 + }, + "history_observations_end": 2520, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 2407, + "sum_seconds": 0.009794221, + "mean_seconds": 0.000004069, + "max_seconds": 0.000396048, + "p50_upper_seconds": 0.000005, + "p99_upper_seconds": 0.00005, + "p999_upper_seconds": 0.00005, + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 1648 + }, + { + "le": 0.00001, + "count": 2371 + }, + { + "le": 0.00005, + "count": 2407 + }, + { + "le": 0.0001, + "count": 2407 + }, + { + "le": 0.00025, + "count": 2407 + }, + { + "le": 0.0005, + "count": 2407 + }, + { + "le": 0.001, + "count": 2407 + }, + { + "le": 0.01, + "count": 2407 + }, + { + "le": "+Inf", + "count": 2407 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 33554432 bytes, 8 per camera)", + "window_unix": { + "start": 1794167440, + "end": 1794168042 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9970.055, + "p50": 10000.254, + "p95": 10013.473, + "p99": 10021.568, + "max": 10029.62, + "mean": 10000.032 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -29.945, + "p50": 0.254, + "p95": 13.473, + "p99": 21.568, + "max": 29.62, + "mean": 0.032 + }, + "db_probe_ms": { + "count": 2316, + "min": 6.885, + "p50": 8.207, + "p95": 13.991, + "p99": 19.714, + "max": 31.125, + "mean": 9.091 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9973.267, + "p50": 10000.363, + "p95": 10014.238, + "p99": 10020.97, + "max": 10029.304, + "mean": 10000.032 + }, + "Load1": { + "count": 60, + "min": 9970.055, + "p50": 10000.232, + "p95": 10011.949, + "p99": 10025.252, + "max": 10029.62, + "mean": 10000.01 + }, + "Load2": { + "count": 60, + "min": 9971.149, + "p50": 10000.437, + "p95": 10008.32, + "p99": 10015.712, + "max": 10018.628, + "mean": 10000.043 + }, + "Load3": { + "count": 60, + "min": 9982.013, + "p50": 9999.647, + "p95": 10013.122, + "p99": 10016.969, + "max": 10019.137, + "mean": 10000.043 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032217280, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.2, + "mean": 15.101 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 599.449, + "cpu_percent_one_core": { + "count": 519, + "min": 119.342, + "p50": 144.429, + "p95": 196.537, + "p99": 205.621, + "max": 214.498, + "mean": 153.811 + }, + "cpu_percent_one_core_avg": 154.136, + "rss_kib": { + "count": 520, + "min": 834640, + "p50": 840584, + "p95": 889053.8, + "p99": 889981.2, + "max": 890552, + "mean": 854693.677 + }, + "rss_high_water_kib": 890552, + "disk_write_bytes": 1973444608, + "disk_write_bytes_per_sec": 3292095.025, + "threads_max": 35, + "fds_max": 39 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.12, + "processes": 5 + }, + "storage_bytes_end": 1405466453, + "loadavg_at_end": [ + 4.06, + 3.67, + 4.4 + ] + }, + "window": { + "start": "2026-11-08T19:50:40.628Z", + "end": "2026-11-08T20:00:41.901Z", + "seconds": 601.27 + }, + "samples": { + "proc": [ + { + "tMs": 13, + "cpuTicks": 4762, + "rssKiB": 881736, + "peakRssKiB": 881736, + "threads": 35, + "fds": 37, + "writeBytes": 90570752 + }, + { + "tMs": 1164, + "cpuTicks": 4918, + "rssKiB": 834640, + "peakRssKiB": 876796, + "threads": 35, + "fds": 36, + "writeBytes": 94433280 + }, + { + "tMs": 2285, + "cpuTicks": 5067, + "rssKiB": 835008, + "peakRssKiB": 877512, + "threads": 35, + "fds": 36, + "writeBytes": 97296384 + }, + { + "tMs": 3427, + "cpuTicks": 5221, + "rssKiB": 835236, + "peakRssKiB": 877512, + "threads": 35, + "fds": 36, + "writeBytes": 101916672 + }, + { + "tMs": 4570, + "cpuTicks": 5367, + "rssKiB": 835708, + "peakRssKiB": 878304, + "threads": 35, + "fds": 36, + "writeBytes": 105197568 + }, + { + "tMs": 5757, + "cpuTicks": 5545, + "rssKiB": 884484, + "peakRssKiB": 884484, + "threads": 35, + "fds": 36, + "writeBytes": 109477888 + }, + { + "tMs": 6916, + "cpuTicks": 5754, + "rssKiB": 884600, + "peakRssKiB": 884600, + "threads": 35, + "fds": 36, + "writeBytes": 112361472 + }, + { + "tMs": 8074, + "cpuTicks": 5951, + "rssKiB": 836104, + "peakRssKiB": 878896, + "threads": 35, + "fds": 38, + "writeBytes": 116502528 + }, + { + "tMs": 9211, + "cpuTicks": 6097, + "rssKiB": 836168, + "peakRssKiB": 878896, + "threads": 35, + "fds": 36, + "writeBytes": 119484416 + }, + { + "tMs": 10358, + "cpuTicks": 6253, + "rssKiB": 836816, + "peakRssKiB": 879180, + "threads": 35, + "fds": 36, + "writeBytes": 122691584 + }, + { + "tMs": 11485, + "cpuTicks": 6394, + "rssKiB": 836840, + "peakRssKiB": 879220, + "threads": 35, + "fds": 36, + "writeBytes": 125665280 + }, + { + "tMs": 12594, + "cpuTicks": 6534, + "rssKiB": 836852, + "peakRssKiB": 879220, + "threads": 35, + "fds": 36, + "writeBytes": 128610304 + }, + { + "tMs": 13735, + "cpuTicks": 6716, + "rssKiB": 885196, + "peakRssKiB": 885196, + "threads": 35, + "fds": 36, + "writeBytes": 134606848 + }, + { + "tMs": 14908, + "cpuTicks": 6935, + "rssKiB": 884848, + "peakRssKiB": 884848, + "threads": 35, + "fds": 36, + "writeBytes": 137515008 + }, + { + "tMs": 16054, + "cpuTicks": 7132, + "rssKiB": 836600, + "peakRssKiB": 879220, + "threads": 35, + "fds": 38, + "writeBytes": 141721600 + }, + { + "tMs": 17195, + "cpuTicks": 7280, + "rssKiB": 836604, + "peakRssKiB": 879220, + "threads": 35, + "fds": 37, + "writeBytes": 144994304 + }, + { + "tMs": 18297, + "cpuTicks": 7440, + "rssKiB": 836608, + "peakRssKiB": 879272, + "threads": 35, + "fds": 37, + "writeBytes": 147922944 + }, + { + "tMs": 19465, + "cpuTicks": 7612, + "rssKiB": 836616, + "peakRssKiB": 879316, + "threads": 35, + "fds": 36, + "writeBytes": 150884352 + }, + { + "tMs": 20604, + "cpuTicks": 7786, + "rssKiB": 836684, + "peakRssKiB": 879524, + "threads": 35, + "fds": 36, + "writeBytes": 154103808 + }, + { + "tMs": 21790, + "cpuTicks": 7982, + "rssKiB": 884704, + "peakRssKiB": 884704, + "threads": 35, + "fds": 37, + "writeBytes": 158240768 + }, + { + "tMs": 22951, + "cpuTicks": 8203, + "rssKiB": 884700, + "peakRssKiB": 884700, + "threads": 35, + "fds": 36, + "writeBytes": 162934784 + }, + { + "tMs": 24123, + "cpuTicks": 8395, + "rssKiB": 836488, + "peakRssKiB": 879536, + "threads": 35, + "fds": 37, + "writeBytes": 167190528 + }, + { + "tMs": 25256, + "cpuTicks": 8542, + "rssKiB": 836516, + "peakRssKiB": 879676, + "threads": 35, + "fds": 36, + "writeBytes": 170467328 + }, + { + "tMs": 26390, + "cpuTicks": 8693, + "rssKiB": 836516, + "peakRssKiB": 879676, + "threads": 35, + "fds": 36, + "writeBytes": 173457408 + }, + { + "tMs": 27517, + "cpuTicks": 8844, + "rssKiB": 836516, + "peakRssKiB": 879676, + "threads": 35, + "fds": 36, + "writeBytes": 176353280 + }, + { + "tMs": 28650, + "cpuTicks": 8991, + "rssKiB": 836196, + "peakRssKiB": 879676, + "threads": 35, + "fds": 36, + "writeBytes": 179216384 + }, + { + "tMs": 29852, + "cpuTicks": 9211, + "rssKiB": 885600, + "peakRssKiB": 885600, + "threads": 35, + "fds": 36, + "writeBytes": 183382016 + }, + { + "tMs": 30999, + "cpuTicks": 9423, + "rssKiB": 885492, + "peakRssKiB": 885492, + "threads": 35, + "fds": 37, + "writeBytes": 186744832 + }, + { + "tMs": 32140, + "cpuTicks": 9585, + "rssKiB": 837472, + "peakRssKiB": 880800, + "threads": 35, + "fds": 37, + "writeBytes": 190992384 + }, + { + "tMs": 33289, + "cpuTicks": 9751, + "rssKiB": 838024, + "peakRssKiB": 881096, + "threads": 35, + "fds": 37, + "writeBytes": 195653632 + }, + { + "tMs": 34408, + "cpuTicks": 9904, + "rssKiB": 838120, + "peakRssKiB": 881752, + "threads": 35, + "fds": 36, + "writeBytes": 198578176 + }, + { + "tMs": 35531, + "cpuTicks": 10052, + "rssKiB": 838120, + "peakRssKiB": 881752, + "threads": 35, + "fds": 36, + "writeBytes": 202006528 + }, + { + "tMs": 36662, + "cpuTicks": 10214, + "rssKiB": 838120, + "peakRssKiB": 881752, + "threads": 35, + "fds": 36, + "writeBytes": 204931072 + }, + { + "tMs": 37863, + "cpuTicks": 10434, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 35, + "fds": 36, + "writeBytes": 209215488 + }, + { + "tMs": 39022, + "cpuTicks": 10646, + "rssKiB": 886688, + "peakRssKiB": 886688, + "threads": 35, + "fds": 38, + "writeBytes": 212426752 + }, + { + "tMs": 40155, + "cpuTicks": 10803, + "rssKiB": 838628, + "peakRssKiB": 882172, + "threads": 35, + "fds": 36, + "writeBytes": 216813568 + }, + { + "tMs": 41274, + "cpuTicks": 10959, + "rssKiB": 838404, + "peakRssKiB": 882172, + "threads": 35, + "fds": 36, + "writeBytes": 219783168 + }, + { + "tMs": 42398, + "cpuTicks": 11131, + "rssKiB": 839060, + "peakRssKiB": 882804, + "threads": 35, + "fds": 36, + "writeBytes": 222769152 + }, + { + "tMs": 43524, + "cpuTicks": 11288, + "rssKiB": 839060, + "peakRssKiB": 882804, + "threads": 35, + "fds": 36, + "writeBytes": 227401728 + }, + { + "tMs": 44652, + "cpuTicks": 11442, + "rssKiB": 839184, + "peakRssKiB": 883192, + "threads": 35, + "fds": 36, + "writeBytes": 230350848 + }, + { + "tMs": 45839, + "cpuTicks": 11654, + "rssKiB": 887528, + "peakRssKiB": 887528, + "threads": 35, + "fds": 36, + "writeBytes": 235094016 + }, + { + "tMs": 46996, + "cpuTicks": 11861, + "rssKiB": 887528, + "peakRssKiB": 887528, + "threads": 35, + "fds": 36, + "writeBytes": 238067712 + }, + { + "tMs": 48153, + "cpuTicks": 12029, + "rssKiB": 839184, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 242368512 + }, + { + "tMs": 49300, + "cpuTicks": 12189, + "rssKiB": 839184, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 245436416 + }, + { + "tMs": 50443, + "cpuTicks": 12351, + "rssKiB": 839188, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 248827904 + }, + { + "tMs": 51577, + "cpuTicks": 12513, + "rssKiB": 839188, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 251580416 + }, + { + "tMs": 52723, + "cpuTicks": 12684, + "rssKiB": 886792, + "peakRssKiB": 886792, + "threads": 35, + "fds": 36, + "writeBytes": 257568768 + }, + { + "tMs": 53884, + "cpuTicks": 12896, + "rssKiB": 886792, + "peakRssKiB": 886792, + "threads": 35, + "fds": 36, + "writeBytes": 260509696 + }, + { + "tMs": 55043, + "cpuTicks": 13093, + "rssKiB": 838448, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 265007104 + }, + { + "tMs": 56182, + "cpuTicks": 13239, + "rssKiB": 838772, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 268263424 + }, + { + "tMs": 57314, + "cpuTicks": 13395, + "rssKiB": 838772, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 271151104 + }, + { + "tMs": 58449, + "cpuTicks": 13548, + "rssKiB": 839096, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 274063360 + }, + { + "tMs": 59623, + "cpuTicks": 13698, + "rssKiB": 839096, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 277028864 + }, + { + "tMs": 60805, + "cpuTicks": 13909, + "rssKiB": 887440, + "peakRssKiB": 887440, + "threads": 35, + "fds": 36, + "writeBytes": 281776128 + }, + { + "tMs": 61955, + "cpuTicks": 14118, + "rssKiB": 887020, + "peakRssKiB": 887020, + "threads": 35, + "fds": 36, + "writeBytes": 284762112 + }, + { + "tMs": 63118, + "cpuTicks": 14293, + "rssKiB": 838676, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 290820096 + }, + { + "tMs": 64279, + "cpuTicks": 14448, + "rssKiB": 838676, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 293789696 + }, + { + "tMs": 65426, + "cpuTicks": 14616, + "rssKiB": 838700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 297263104 + }, + { + "tMs": 66570, + "cpuTicks": 14762, + "rssKiB": 838700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 300142592 + }, + { + "tMs": 67731, + "cpuTicks": 14930, + "rssKiB": 886720, + "peakRssKiB": 886720, + "threads": 35, + "fds": 36, + "writeBytes": 304357376 + }, + { + "tMs": 68894, + "cpuTicks": 15147, + "rssKiB": 886072, + "peakRssKiB": 886072, + "threads": 35, + "fds": 36, + "writeBytes": 307314688 + }, + { + "tMs": 70047, + "cpuTicks": 15366, + "rssKiB": 886072, + "peakRssKiB": 886072, + "threads": 35, + "fds": 36, + "writeBytes": 310222848 + }, + { + "tMs": 71188, + "cpuTicks": 15536, + "rssKiB": 837732, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 314994688 + }, + { + "tMs": 72310, + "cpuTicks": 15698, + "rssKiB": 838056, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 317931520 + }, + { + "tMs": 73429, + "cpuTicks": 15864, + "rssKiB": 838284, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 322691072 + }, + { + "tMs": 74547, + "cpuTicks": 16029, + "rssKiB": 838052, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 325709824 + }, + { + "tMs": 75692, + "cpuTicks": 16204, + "rssKiB": 886388, + "peakRssKiB": 886388, + "threads": 35, + "fds": 36, + "writeBytes": 330571776 + }, + { + "tMs": 76893, + "cpuTicks": 16451, + "rssKiB": 886388, + "peakRssKiB": 886388, + "threads": 35, + "fds": 36, + "writeBytes": 333467648 + }, + { + "tMs": 78061, + "cpuTicks": 16677, + "rssKiB": 862216, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 337043456 + }, + { + "tMs": 79212, + "cpuTicks": 16843, + "rssKiB": 838044, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 340729856 + }, + { + "tMs": 80340, + "cpuTicks": 16998, + "rssKiB": 838044, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 344219648 + }, + { + "tMs": 81466, + "cpuTicks": 17143, + "rssKiB": 838044, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 347025408 + }, + { + "tMs": 82619, + "cpuTicks": 17293, + "rssKiB": 837992, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 349982720 + }, + { + "tMs": 83834, + "cpuTicks": 17516, + "rssKiB": 886012, + "peakRssKiB": 886012, + "threads": 35, + "fds": 36, + "writeBytes": 356012032 + }, + { + "tMs": 84985, + "cpuTicks": 17735, + "rssKiB": 885688, + "peakRssKiB": 885688, + "threads": 35, + "fds": 36, + "writeBytes": 358973440 + }, + { + "tMs": 86147, + "cpuTicks": 17908, + "rssKiB": 837676, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 363847680 + }, + { + "tMs": 87301, + "cpuTicks": 18064, + "rssKiB": 837996, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 366804992 + }, + { + "tMs": 88446, + "cpuTicks": 18208, + "rssKiB": 837996, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 369700864 + }, + { + "tMs": 89596, + "cpuTicks": 18369, + "rssKiB": 837996, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 372654080 + }, + { + "tMs": 90784, + "cpuTicks": 18561, + "rssKiB": 886408, + "peakRssKiB": 886408, + "threads": 35, + "fds": 36, + "writeBytes": 377491456 + }, + { + "tMs": 91952, + "cpuTicks": 18786, + "rssKiB": 886408, + "peakRssKiB": 886408, + "threads": 35, + "fds": 36, + "writeBytes": 380399616 + }, + { + "tMs": 93095, + "cpuTicks": 18965, + "rssKiB": 838384, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 386379776 + }, + { + "tMs": 94248, + "cpuTicks": 19113, + "rssKiB": 838704, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 389287936 + }, + { + "tMs": 95388, + "cpuTicks": 19262, + "rssKiB": 838704, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 392876032 + }, + { + "tMs": 96541, + "cpuTicks": 19411, + "rssKiB": 838704, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 395812864 + }, + { + "tMs": 97681, + "cpuTicks": 19563, + "rssKiB": 862028, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 400064512 + }, + { + "tMs": 98867, + "cpuTicks": 19790, + "rssKiB": 886712, + "peakRssKiB": 886712, + "threads": 35, + "fds": 37, + "writeBytes": 403034112 + }, + { + "tMs": 99998, + "cpuTicks": 19991, + "rssKiB": 887040, + "peakRssKiB": 887040, + "threads": 35, + "fds": 36, + "writeBytes": 406040576 + }, + { + "tMs": 101145, + "cpuTicks": 20166, + "rssKiB": 838716, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 410959872 + }, + { + "tMs": 102292, + "cpuTicks": 20313, + "rssKiB": 838716, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 413958144 + }, + { + "tMs": 103435, + "cpuTicks": 20473, + "rssKiB": 838716, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 418680832 + }, + { + "tMs": 104565, + "cpuTicks": 20630, + "rssKiB": 838716, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 421576704 + }, + { + "tMs": 105739, + "cpuTicks": 20808, + "rssKiB": 886748, + "peakRssKiB": 886748, + "threads": 35, + "fds": 36, + "writeBytes": 426471424 + }, + { + "tMs": 106927, + "cpuTicks": 21030, + "rssKiB": 886748, + "peakRssKiB": 886748, + "threads": 35, + "fds": 36, + "writeBytes": 429432832 + }, + { + "tMs": 108075, + "cpuTicks": 21229, + "rssKiB": 838400, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 433655808 + }, + { + "tMs": 109219, + "cpuTicks": 21388, + "rssKiB": 838400, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 436666368 + }, + { + "tMs": 110362, + "cpuTicks": 21544, + "rssKiB": 838404, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 440188928 + }, + { + "tMs": 111503, + "cpuTicks": 21712, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 442978304 + }, + { + "tMs": 112666, + "cpuTicks": 21862, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 445935616 + }, + { + "tMs": 113885, + "cpuTicks": 22104, + "rssKiB": 886412, + "peakRssKiB": 886412, + "threads": 35, + "fds": 36, + "writeBytes": 451981312 + }, + { + "tMs": 115031, + "cpuTicks": 22299, + "rssKiB": 861916, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 456228864 + }, + { + "tMs": 116181, + "cpuTicks": 22452, + "rssKiB": 837744, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 459907072 + }, + { + "tMs": 117307, + "cpuTicks": 22610, + "rssKiB": 837744, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 462843904 + }, + { + "tMs": 118433, + "cpuTicks": 22761, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 465752064 + }, + { + "tMs": 119567, + "cpuTicks": 22910, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 468729856 + }, + { + "tMs": 120691, + "cpuTicks": 23071, + "rssKiB": 886412, + "peakRssKiB": 886412, + "threads": 35, + "fds": 36, + "writeBytes": 473600000 + }, + { + "tMs": 121855, + "cpuTicks": 23284, + "rssKiB": 886412, + "peakRssKiB": 886412, + "threads": 35, + "fds": 36, + "writeBytes": 476590080 + }, + { + "tMs": 123016, + "cpuTicks": 23505, + "rssKiB": 886412, + "peakRssKiB": 886412, + "threads": 35, + "fds": 36, + "writeBytes": 481435648 + }, + { + "tMs": 124158, + "cpuTicks": 23677, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 486195200 + }, + { + "tMs": 125293, + "cpuTicks": 23831, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 489254912 + }, + { + "tMs": 126425, + "cpuTicks": 23986, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 492187648 + }, + { + "tMs": 127565, + "cpuTicks": 24128, + "rssKiB": 838392, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 495136768 + }, + { + "tMs": 128697, + "cpuTicks": 24294, + "rssKiB": 886412, + "peakRssKiB": 886412, + "threads": 35, + "fds": 36, + "writeBytes": 499425280 + }, + { + "tMs": 129877, + "cpuTicks": 24512, + "rssKiB": 886736, + "peakRssKiB": 886736, + "threads": 35, + "fds": 36, + "writeBytes": 503062528 + }, + { + "tMs": 131036, + "cpuTicks": 24721, + "rssKiB": 886768, + "peakRssKiB": 886768, + "threads": 35, + "fds": 37, + "writeBytes": 506621952 + }, + { + "tMs": 132180, + "cpuTicks": 24877, + "rssKiB": 838424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 510410752 + }, + { + "tMs": 133323, + "cpuTicks": 25030, + "rssKiB": 838424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 515072000 + }, + { + "tMs": 134462, + "cpuTicks": 25168, + "rssKiB": 838424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 518840320 + }, + { + "tMs": 135601, + "cpuTicks": 25331, + "rssKiB": 838424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 521809920 + }, + { + "tMs": 136757, + "cpuTicks": 25526, + "rssKiB": 886768, + "peakRssKiB": 886768, + "threads": 35, + "fds": 36, + "writeBytes": 526086144 + }, + { + "tMs": 137919, + "cpuTicks": 25735, + "rssKiB": 886444, + "peakRssKiB": 886444, + "threads": 35, + "fds": 36, + "writeBytes": 529014784 + }, + { + "tMs": 139067, + "cpuTicks": 25929, + "rssKiB": 838100, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 533725184 + }, + { + "tMs": 140208, + "cpuTicks": 26090, + "rssKiB": 838100, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 536928256 + }, + { + "tMs": 141359, + "cpuTicks": 26238, + "rssKiB": 837096, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 539828224 + }, + { + "tMs": 142506, + "cpuTicks": 26385, + "rssKiB": 837096, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 542769152 + }, + { + "tMs": 143645, + "cpuTicks": 26531, + "rssKiB": 837324, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 547536896 + }, + { + "tMs": 144842, + "cpuTicks": 26745, + "rssKiB": 886088, + "peakRssKiB": 886088, + "threads": 35, + "fds": 36, + "writeBytes": 552595456 + }, + { + "tMs": 145999, + "cpuTicks": 26961, + "rssKiB": 886088, + "peakRssKiB": 886088, + "threads": 35, + "fds": 36, + "writeBytes": 555610112 + }, + { + "tMs": 147163, + "cpuTicks": 27152, + "rssKiB": 837744, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 560070656 + }, + { + "tMs": 148298, + "cpuTicks": 27301, + "rssKiB": 837744, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 563011584 + }, + { + "tMs": 149419, + "cpuTicks": 27439, + "rssKiB": 838068, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 566603776 + }, + { + "tMs": 150543, + "cpuTicks": 27594, + "rssKiB": 838108, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 569602048 + }, + { + "tMs": 151676, + "cpuTicks": 27740, + "rssKiB": 838108, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 573288448 + }, + { + "tMs": 152874, + "cpuTicks": 27976, + "rssKiB": 887000, + "peakRssKiB": 887000, + "threads": 35, + "fds": 36, + "writeBytes": 578740224 + }, + { + "tMs": 154034, + "cpuTicks": 28194, + "rssKiB": 886776, + "peakRssKiB": 886776, + "threads": 35, + "fds": 36, + "writeBytes": 582545408 + }, + { + "tMs": 155161, + "cpuTicks": 28368, + "rssKiB": 838432, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 586723328 + }, + { + "tMs": 156281, + "cpuTicks": 28512, + "rssKiB": 838432, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 589664256 + }, + { + "tMs": 157403, + "cpuTicks": 28668, + "rssKiB": 838756, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 592605184 + }, + { + "tMs": 158527, + "cpuTicks": 28817, + "rssKiB": 838756, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 596316160 + }, + { + "tMs": 159666, + "cpuTicks": 28971, + "rssKiB": 839080, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 599318528 + }, + { + "tMs": 160864, + "cpuTicks": 29207, + "rssKiB": 887756, + "peakRssKiB": 887756, + "threads": 35, + "fds": 36, + "writeBytes": 603570176 + }, + { + "tMs": 162028, + "cpuTicks": 29447, + "rssKiB": 887756, + "peakRssKiB": 887756, + "threads": 35, + "fds": 36, + "writeBytes": 606588928 + }, + { + "tMs": 163149, + "cpuTicks": 29604, + "rssKiB": 839416, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 613347328 + }, + { + "tMs": 164271, + "cpuTicks": 29766, + "rssKiB": 839416, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 616312832 + }, + { + "tMs": 165395, + "cpuTicks": 29913, + "rssKiB": 839424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 619339776 + }, + { + "tMs": 166515, + "cpuTicks": 30060, + "rssKiB": 839424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 622305280 + }, + { + "tMs": 167642, + "cpuTicks": 30219, + "rssKiB": 839424, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 625319936 + }, + { + "tMs": 168841, + "cpuTicks": 30450, + "rssKiB": 887768, + "peakRssKiB": 887768, + "threads": 35, + "fds": 36, + "writeBytes": 630427648 + }, + { + "tMs": 170003, + "cpuTicks": 30671, + "rssKiB": 886892, + "peakRssKiB": 886892, + "threads": 35, + "fds": 36, + "writeBytes": 633389056 + }, + { + "tMs": 171140, + "cpuTicks": 30831, + "rssKiB": 838228, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 637538304 + }, + { + "tMs": 172275, + "cpuTicks": 30989, + "rssKiB": 838228, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 640552960 + }, + { + "tMs": 173410, + "cpuTicks": 31141, + "rssKiB": 838228, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 646045696 + }, + { + "tMs": 174534, + "cpuTicks": 31295, + "rssKiB": 838228, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 649019392 + }, + { + "tMs": 175663, + "cpuTicks": 31445, + "rssKiB": 837916, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 652075008 + }, + { + "tMs": 176863, + "cpuTicks": 31676, + "rssKiB": 886584, + "peakRssKiB": 886584, + "threads": 35, + "fds": 36, + "writeBytes": 656404480 + }, + { + "tMs": 178021, + "cpuTicks": 31893, + "rssKiB": 862412, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 660373504 + }, + { + "tMs": 179172, + "cpuTicks": 32051, + "rssKiB": 838564, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 664383488 + }, + { + "tMs": 180298, + "cpuTicks": 32202, + "rssKiB": 838564, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 667377664 + }, + { + "tMs": 181431, + "cpuTicks": 32354, + "rssKiB": 838564, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 670441472 + }, + { + "tMs": 182580, + "cpuTicks": 32500, + "rssKiB": 838564, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 674299904 + }, + { + "tMs": 183772, + "cpuTicks": 32701, + "rssKiB": 887556, + "peakRssKiB": 887556, + "threads": 35, + "fds": 37, + "writeBytes": 680394752 + }, + { + "tMs": 184931, + "cpuTicks": 32919, + "rssKiB": 887556, + "peakRssKiB": 887556, + "threads": 35, + "fds": 36, + "writeBytes": 683409408 + }, + { + "tMs": 186086, + "cpuTicks": 33123, + "rssKiB": 839212, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 688652288 + }, + { + "tMs": 187219, + "cpuTicks": 33270, + "rssKiB": 839212, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 691654656 + }, + { + "tMs": 188382, + "cpuTicks": 33417, + "rssKiB": 839212, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 694632448 + }, + { + "tMs": 189549, + "cpuTicks": 33574, + "rssKiB": 838888, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 697688064 + }, + { + "tMs": 190725, + "cpuTicks": 33743, + "rssKiB": 887232, + "peakRssKiB": 887232, + "threads": 35, + "fds": 36, + "writeBytes": 701939712 + }, + { + "tMs": 191881, + "cpuTicks": 33964, + "rssKiB": 887552, + "peakRssKiB": 887552, + "threads": 35, + "fds": 36, + "writeBytes": 705650688 + }, + { + "tMs": 193050, + "cpuTicks": 34176, + "rssKiB": 839208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 711397376 + }, + { + "tMs": 194207, + "cpuTicks": 34324, + "rssKiB": 839208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 714735616 + }, + { + "tMs": 195346, + "cpuTicks": 34480, + "rssKiB": 839208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 717791232 + }, + { + "tMs": 196495, + "cpuTicks": 34638, + "rssKiB": 839208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 721580032 + }, + { + "tMs": 197632, + "cpuTicks": 34803, + "rssKiB": 839208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 724508672 + }, + { + "tMs": 198815, + "cpuTicks": 35001, + "rssKiB": 887036, + "peakRssKiB": 887036, + "threads": 35, + "fds": 37, + "writeBytes": 728793088 + }, + { + "tMs": 199980, + "cpuTicks": 35219, + "rssKiB": 887036, + "peakRssKiB": 887036, + "threads": 35, + "fds": 36, + "writeBytes": 731799552 + }, + { + "tMs": 201141, + "cpuTicks": 35398, + "rssKiB": 838464, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 736825344 + }, + { + "tMs": 202292, + "cpuTicks": 35542, + "rssKiB": 838692, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 739840000 + }, + { + "tMs": 203438, + "cpuTicks": 35709, + "rssKiB": 838372, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 744656896 + }, + { + "tMs": 204590, + "cpuTicks": 35857, + "rssKiB": 838696, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 747675648 + }, + { + "tMs": 205781, + "cpuTicks": 36050, + "rssKiB": 887688, + "peakRssKiB": 887688, + "threads": 35, + "fds": 36, + "writeBytes": 752795648 + }, + { + "tMs": 206957, + "cpuTicks": 36272, + "rssKiB": 887688, + "peakRssKiB": 887688, + "threads": 35, + "fds": 36, + "writeBytes": 755802112 + }, + { + "tMs": 208127, + "cpuTicks": 36471, + "rssKiB": 839344, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 760045568 + }, + { + "tMs": 209249, + "cpuTicks": 36631, + "rssKiB": 839664, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 763031552 + }, + { + "tMs": 210371, + "cpuTicks": 36791, + "rssKiB": 839700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 766767104 + }, + { + "tMs": 211514, + "cpuTicks": 36944, + "rssKiB": 839700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 769695744 + }, + { + "tMs": 212658, + "cpuTicks": 37107, + "rssKiB": 839700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 772616192 + }, + { + "tMs": 213886, + "cpuTicks": 37352, + "rssKiB": 888044, + "peakRssKiB": 888044, + "threads": 35, + "fds": 37, + "writeBytes": 778612736 + }, + { + "tMs": 215079, + "cpuTicks": 37589, + "rssKiB": 839700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 38, + "writeBytes": 783405056 + }, + { + "tMs": 216215, + "cpuTicks": 37742, + "rssKiB": 839700, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 786714624 + }, + { + "tMs": 217342, + "cpuTicks": 37894, + "rssKiB": 839380, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 789671936 + }, + { + "tMs": 218492, + "cpuTicks": 38058, + "rssKiB": 839380, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 792633344 + }, + { + "tMs": 219629, + "cpuTicks": 38217, + "rssKiB": 839380, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 795668480 + }, + { + "tMs": 220799, + "cpuTicks": 38420, + "rssKiB": 886632, + "peakRssKiB": 886632, + "threads": 35, + "fds": 36, + "writeBytes": 800829440 + }, + { + "tMs": 221953, + "cpuTicks": 38632, + "rssKiB": 886856, + "peakRssKiB": 886856, + "threads": 35, + "fds": 36, + "writeBytes": 803786752 + }, + { + "tMs": 223110, + "cpuTicks": 38821, + "rssKiB": 838836, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 809885696 + }, + { + "tMs": 224238, + "cpuTicks": 38973, + "rssKiB": 838836, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 813735936 + }, + { + "tMs": 225427, + "cpuTicks": 39117, + "rssKiB": 838836, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 816787456 + }, + { + "tMs": 226587, + "cpuTicks": 39256, + "rssKiB": 838188, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 819789824 + }, + { + "tMs": 227761, + "cpuTicks": 39433, + "rssKiB": 886532, + "peakRssKiB": 886532, + "threads": 35, + "fds": 36, + "writeBytes": 824115200 + }, + { + "tMs": 228917, + "cpuTicks": 39642, + "rssKiB": 886532, + "peakRssKiB": 886532, + "threads": 35, + "fds": 36, + "writeBytes": 827195392 + }, + { + "tMs": 230076, + "cpuTicks": 39837, + "rssKiB": 838188, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 832385024 + }, + { + "tMs": 231218, + "cpuTicks": 39982, + "rssKiB": 838208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 835325952 + }, + { + "tMs": 232358, + "cpuTicks": 40126, + "rssKiB": 838208, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 838361088 + }, + { + "tMs": 233500, + "cpuTicks": 40276, + "rssKiB": 837884, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 843091968 + }, + { + "tMs": 234643, + "cpuTicks": 40426, + "rssKiB": 837560, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 846946304 + }, + { + "tMs": 235830, + "cpuTicks": 40635, + "rssKiB": 886560, + "peakRssKiB": 886560, + "threads": 35, + "fds": 36, + "writeBytes": 851243008 + }, + { + "tMs": 236993, + "cpuTicks": 40851, + "rssKiB": 886660, + "peakRssKiB": 886660, + "threads": 35, + "fds": 36, + "writeBytes": 854224896 + }, + { + "tMs": 238130, + "cpuTicks": 41025, + "rssKiB": 838316, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 858517504 + }, + { + "tMs": 239258, + "cpuTicks": 41174, + "rssKiB": 838316, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 862212096 + }, + { + "tMs": 240403, + "cpuTicks": 41318, + "rssKiB": 838316, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 865140736 + }, + { + "tMs": 241563, + "cpuTicks": 41484, + "rssKiB": 838316, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 868167680 + }, + { + "tMs": 242745, + "cpuTicks": 41664, + "rssKiB": 886980, + "peakRssKiB": 886980, + "threads": 35, + "fds": 36, + "writeBytes": 874307584 + }, + { + "tMs": 243909, + "cpuTicks": 41877, + "rssKiB": 886980, + "peakRssKiB": 886980, + "threads": 35, + "fds": 36, + "writeBytes": 878104576 + }, + { + "tMs": 245029, + "cpuTicks": 42060, + "rssKiB": 838636, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 881991680 + }, + { + "tMs": 246169, + "cpuTicks": 42210, + "rssKiB": 838636, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 885424128 + }, + { + "tMs": 247326, + "cpuTicks": 42355, + "rssKiB": 838636, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 888401920 + }, + { + "tMs": 248482, + "cpuTicks": 42503, + "rssKiB": 838312, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 892293120 + }, + { + "tMs": 249627, + "cpuTicks": 42650, + "rssKiB": 838312, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 895279104 + }, + { + "tMs": 250803, + "cpuTicks": 42849, + "rssKiB": 886656, + "peakRssKiB": 886656, + "threads": 35, + "fds": 36, + "writeBytes": 899493888 + }, + { + "tMs": 251970, + "cpuTicks": 43063, + "rssKiB": 886656, + "peakRssKiB": 886656, + "threads": 35, + "fds": 36, + "writeBytes": 902549504 + }, + { + "tMs": 253118, + "cpuTicks": 43228, + "rssKiB": 838312, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 909537280 + }, + { + "tMs": 254268, + "cpuTicks": 43379, + "rssKiB": 838312, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 912502784 + }, + { + "tMs": 255408, + "cpuTicks": 43526, + "rssKiB": 838312, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 915501056 + }, + { + "tMs": 256554, + "cpuTicks": 43670, + "rssKiB": 838316, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 918515712 + }, + { + "tMs": 257687, + "cpuTicks": 43834, + "rssKiB": 886660, + "peakRssKiB": 886660, + "threads": 35, + "fds": 36, + "writeBytes": 922828800 + }, + { + "tMs": 258885, + "cpuTicks": 44078, + "rssKiB": 886336, + "peakRssKiB": 886336, + "threads": 35, + "fds": 36, + "writeBytes": 926642176 + }, + { + "tMs": 260040, + "cpuTicks": 44299, + "rssKiB": 886336, + "peakRssKiB": 886336, + "threads": 35, + "fds": 36, + "writeBytes": 929677312 + }, + { + "tMs": 261187, + "cpuTicks": 44472, + "rssKiB": 837992, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 933863424 + }, + { + "tMs": 262327, + "cpuTicks": 44632, + "rssKiB": 837992, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 936873984 + }, + { + "tMs": 263467, + "cpuTicks": 44796, + "rssKiB": 837992, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 942518272 + }, + { + "tMs": 264607, + "cpuTicks": 44957, + "rssKiB": 837992, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 945479680 + }, + { + "tMs": 265820, + "cpuTicks": 45182, + "rssKiB": 886676, + "peakRssKiB": 886676, + "threads": 35, + "fds": 36, + "writeBytes": 949731328 + }, + { + "tMs": 266985, + "cpuTicks": 45397, + "rssKiB": 887000, + "peakRssKiB": 887000, + "threads": 35, + "fds": 36, + "writeBytes": 952709120 + }, + { + "tMs": 268136, + "cpuTicks": 45573, + "rssKiB": 838656, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 957730816 + }, + { + "tMs": 269284, + "cpuTicks": 45729, + "rssKiB": 838656, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 960770048 + }, + { + "tMs": 270426, + "cpuTicks": 45879, + "rssKiB": 838804, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 963760128 + }, + { + "tMs": 271585, + "cpuTicks": 46038, + "rssKiB": 838804, + "peakRssKiB": 883356, + "threads": 35, + "fds": 37, + "writeBytes": 966758400 + }, + { + "tMs": 272745, + "cpuTicks": 46214, + "rssKiB": 887796, + "peakRssKiB": 887796, + "threads": 35, + "fds": 37, + "writeBytes": 973602816 + }, + { + "tMs": 273926, + "cpuTicks": 46447, + "rssKiB": 887796, + "peakRssKiB": 887796, + "threads": 35, + "fds": 37, + "writeBytes": 976646144 + }, + { + "tMs": 275095, + "cpuTicks": 46640, + "rssKiB": 839452, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 981045248 + }, + { + "tMs": 276254, + "cpuTicks": 46787, + "rssKiB": 839456, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 984043520 + }, + { + "tMs": 277386, + "cpuTicks": 46953, + "rssKiB": 839456, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 987897856 + }, + { + "tMs": 278520, + "cpuTicks": 47110, + "rssKiB": 839456, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 990969856 + }, + { + "tMs": 279652, + "cpuTicks": 47270, + "rssKiB": 839456, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 993894400 + }, + { + "tMs": 280832, + "cpuTicks": 47481, + "rssKiB": 887800, + "peakRssKiB": 887800, + "threads": 35, + "fds": 36, + "writeBytes": 998154240 + }, + { + "tMs": 281988, + "cpuTicks": 47700, + "rssKiB": 887800, + "peakRssKiB": 887800, + "threads": 35, + "fds": 36, + "writeBytes": 1001127936 + }, + { + "tMs": 283136, + "cpuTicks": 47891, + "rssKiB": 839132, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1008066560 + }, + { + "tMs": 284282, + "cpuTicks": 48046, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1011097600 + }, + { + "tMs": 285412, + "cpuTicks": 48201, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1014087680 + }, + { + "tMs": 286528, + "cpuTicks": 48363, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1017868288 + }, + { + "tMs": 287671, + "cpuTicks": 48516, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1020858368 + }, + { + "tMs": 288861, + "cpuTicks": 48734, + "rssKiB": 887152, + "peakRssKiB": 887152, + "threads": 35, + "fds": 36, + "writeBytes": 1025110016 + }, + { + "tMs": 290035, + "cpuTicks": 48964, + "rssKiB": 862980, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1028489216 + }, + { + "tMs": 291185, + "cpuTicks": 49137, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1033162752 + }, + { + "tMs": 292327, + "cpuTicks": 49288, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1036099584 + }, + { + "tMs": 293460, + "cpuTicks": 49447, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1040826368 + }, + { + "tMs": 294586, + "cpuTicks": 49612, + "rssKiB": 838808, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1043849216 + }, + { + "tMs": 295779, + "cpuTicks": 49805, + "rssKiB": 887472, + "peakRssKiB": 887472, + "threads": 35, + "fds": 36, + "writeBytes": 1048215552 + }, + { + "tMs": 296969, + "cpuTicks": 50041, + "rssKiB": 887472, + "peakRssKiB": 887472, + "threads": 35, + "fds": 37, + "writeBytes": 1052217344 + }, + { + "tMs": 298124, + "cpuTicks": 50239, + "rssKiB": 839128, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1056538624 + }, + { + "tMs": 299280, + "cpuTicks": 50405, + "rssKiB": 839128, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1059610624 + }, + { + "tMs": 300419, + "cpuTicks": 50571, + "rssKiB": 838804, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1062522880 + }, + { + "tMs": 301559, + "cpuTicks": 50728, + "rssKiB": 838804, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1066504192 + }, + { + "tMs": 302688, + "cpuTicks": 50893, + "rssKiB": 870872, + "peakRssKiB": 883356, + "threads": 35, + "fds": 33, + "writeBytes": 1071243264 + }, + { + "tMs": 303883, + "cpuTicks": 51126, + "rssKiB": 887792, + "peakRssKiB": 887792, + "threads": 35, + "fds": 36, + "writeBytes": 1075589120 + }, + { + "tMs": 305042, + "cpuTicks": 51334, + "rssKiB": 887792, + "peakRssKiB": 887792, + "threads": 35, + "fds": 36, + "writeBytes": 1078591488 + }, + { + "tMs": 306206, + "cpuTicks": 51508, + "rssKiB": 839452, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1084022784 + }, + { + "tMs": 307349, + "cpuTicks": 51656, + "rssKiB": 839452, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1087004672 + }, + { + "tMs": 308483, + "cpuTicks": 51812, + "rssKiB": 839452, + "peakRssKiB": 883356, + "threads": 35, + "fds": 36, + "writeBytes": 1090043904 + }, + { + "tMs": 309625, + "cpuTicks": 51965, + "rssKiB": 839452, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1092997120 + }, + { + "tMs": 310809, + "cpuTicks": 52167, + "rssKiB": 887796, + "peakRssKiB": 887796, + "threads": 35, + "fds": 36, + "writeBytes": 1098194944 + }, + { + "tMs": 311982, + "cpuTicks": 52378, + "rssKiB": 887796, + "peakRssKiB": 887796, + "threads": 35, + "fds": 36, + "writeBytes": 1101242368 + }, + { + "tMs": 313126, + "cpuTicks": 52552, + "rssKiB": 839032, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1107476480 + }, + { + "tMs": 314268, + "cpuTicks": 52706, + "rssKiB": 839032, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1111457792 + }, + { + "tMs": 315407, + "cpuTicks": 52859, + "rssKiB": 838708, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1114562560 + }, + { + "tMs": 316551, + "cpuTicks": 53010, + "rssKiB": 838716, + "peakRssKiB": 883408, + "threads": 35, + "fds": 37, + "writeBytes": 1117609984 + }, + { + "tMs": 317698, + "cpuTicks": 53177, + "rssKiB": 886188, + "peakRssKiB": 886188, + "threads": 35, + "fds": 37, + "writeBytes": 1121878016 + }, + { + "tMs": 318889, + "cpuTicks": 53407, + "rssKiB": 886188, + "peakRssKiB": 886188, + "threads": 35, + "fds": 37, + "writeBytes": 1124913152 + }, + { + "tMs": 320043, + "cpuTicks": 53623, + "rssKiB": 886188, + "peakRssKiB": 886188, + "threads": 35, + "fds": 37, + "writeBytes": 1128833024 + }, + { + "tMs": 321209, + "cpuTicks": 53814, + "rssKiB": 837844, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1133076480 + }, + { + "tMs": 322351, + "cpuTicks": 53967, + "rssKiB": 837844, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1136050176 + }, + { + "tMs": 323499, + "cpuTicks": 54113, + "rssKiB": 837844, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1141809152 + }, + { + "tMs": 324645, + "cpuTicks": 54255, + "rssKiB": 838164, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1144741888 + }, + { + "tMs": 325869, + "cpuTicks": 54489, + "rssKiB": 886832, + "peakRssKiB": 886832, + "threads": 35, + "fds": 36, + "writeBytes": 1149104128 + }, + { + "tMs": 327047, + "cpuTicks": 54712, + "rssKiB": 862660, + "peakRssKiB": 883408, + "threads": 35, + "fds": 38, + "writeBytes": 1152794624 + }, + { + "tMs": 328176, + "cpuTicks": 54869, + "rssKiB": 838492, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1157222400 + }, + { + "tMs": 329297, + "cpuTicks": 55019, + "rssKiB": 838492, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1160122368 + }, + { + "tMs": 330484, + "cpuTicks": 55173, + "rssKiB": 839740, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1163169792 + }, + { + "tMs": 331626, + "cpuTicks": 55329, + "rssKiB": 839748, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1166139392 + }, + { + "tMs": 332823, + "cpuTicks": 55537, + "rssKiB": 888416, + "peakRssKiB": 888416, + "threads": 35, + "fds": 36, + "writeBytes": 1172246528 + }, + { + "tMs": 333978, + "cpuTicks": 55758, + "rssKiB": 888740, + "peakRssKiB": 888740, + "threads": 35, + "fds": 36, + "writeBytes": 1176133632 + }, + { + "tMs": 335142, + "cpuTicks": 55962, + "rssKiB": 839748, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1180499968 + }, + { + "tMs": 336279, + "cpuTicks": 56114, + "rssKiB": 839748, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1183592448 + }, + { + "tMs": 337432, + "cpuTicks": 56277, + "rssKiB": 840068, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1186480128 + }, + { + "tMs": 338574, + "cpuTicks": 56436, + "rssKiB": 840292, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1190338560 + }, + { + "tMs": 339738, + "cpuTicks": 56620, + "rssKiB": 888416, + "peakRssKiB": 888416, + "threads": 35, + "fds": 36, + "writeBytes": 1194622976 + }, + { + "tMs": 340907, + "cpuTicks": 56847, + "rssKiB": 888648, + "peakRssKiB": 888648, + "threads": 35, + "fds": 36, + "writeBytes": 1197703168 + }, + { + "tMs": 342088, + "cpuTicks": 57078, + "rssKiB": 840072, + "peakRssKiB": 883408, + "threads": 35, + "fds": 37, + "writeBytes": 1201778688 + }, + { + "tMs": 343231, + "cpuTicks": 57244, + "rssKiB": 840396, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1207844864 + }, + { + "tMs": 344369, + "cpuTicks": 57401, + "rssKiB": 840396, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1210826752 + }, + { + "tMs": 345502, + "cpuTicks": 57555, + "rssKiB": 840396, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1213775872 + }, + { + "tMs": 346653, + "cpuTicks": 57712, + "rssKiB": 840396, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1216790528 + }, + { + "tMs": 347858, + "cpuTicks": 57939, + "rssKiB": 888096, + "peakRssKiB": 888096, + "threads": 35, + "fds": 36, + "writeBytes": 1222021120 + }, + { + "tMs": 349028, + "cpuTicks": 58164, + "rssKiB": 888096, + "peakRssKiB": 888096, + "threads": 35, + "fds": 36, + "writeBytes": 1225048064 + }, + { + "tMs": 350181, + "cpuTicks": 58357, + "rssKiB": 839752, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1229402112 + }, + { + "tMs": 351333, + "cpuTicks": 58513, + "rssKiB": 839756, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1232261120 + }, + { + "tMs": 352467, + "cpuTicks": 58672, + "rssKiB": 839756, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1236160512 + }, + { + "tMs": 353601, + "cpuTicks": 58837, + "rssKiB": 839756, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1240895488 + }, + { + "tMs": 354769, + "cpuTicks": 59030, + "rssKiB": 887440, + "peakRssKiB": 887440, + "threads": 35, + "fds": 36, + "writeBytes": 1245192192 + }, + { + "tMs": 355938, + "cpuTicks": 59266, + "rssKiB": 887428, + "peakRssKiB": 887428, + "threads": 35, + "fds": 36, + "writeBytes": 1248178176 + }, + { + "tMs": 357102, + "cpuTicks": 59468, + "rssKiB": 839408, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1253191680 + }, + { + "tMs": 358236, + "cpuTicks": 59627, + "rssKiB": 839408, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1256329216 + }, + { + "tMs": 359355, + "cpuTicks": 59777, + "rssKiB": 839732, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1259376640 + }, + { + "tMs": 360502, + "cpuTicks": 59936, + "rssKiB": 839732, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1262305280 + }, + { + "tMs": 361650, + "cpuTicks": 60089, + "rssKiB": 839732, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1266151424 + }, + { + "tMs": 362859, + "cpuTicks": 60316, + "rssKiB": 888724, + "peakRssKiB": 888724, + "threads": 35, + "fds": 36, + "writeBytes": 1272242176 + }, + { + "tMs": 364023, + "cpuTicks": 60537, + "rssKiB": 888724, + "peakRssKiB": 888724, + "threads": 35, + "fds": 36, + "writeBytes": 1275617280 + }, + { + "tMs": 365168, + "cpuTicks": 60688, + "rssKiB": 840380, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1279672320 + }, + { + "tMs": 366316, + "cpuTicks": 60840, + "rssKiB": 840380, + "peakRssKiB": 883408, + "threads": 35, + "fds": 36, + "writeBytes": 1283674112 + }, + { + "tMs": 367452, + "cpuTicks": 61010, + "rssKiB": 840380, + "peakRssKiB": 883512, + "threads": 35, + "fds": 36, + "writeBytes": 1286647808 + }, + { + "tMs": 368598, + "cpuTicks": 61162, + "rssKiB": 840380, + "peakRssKiB": 883512, + "threads": 35, + "fds": 36, + "writeBytes": 1289682944 + }, + { + "tMs": 369792, + "cpuTicks": 61355, + "rssKiB": 889044, + "peakRssKiB": 889044, + "threads": 35, + "fds": 36, + "writeBytes": 1293979648 + }, + { + "tMs": 370977, + "cpuTicks": 61590, + "rssKiB": 889052, + "peakRssKiB": 889052, + "threads": 35, + "fds": 36, + "writeBytes": 1297018880 + }, + { + "tMs": 372135, + "cpuTicks": 61792, + "rssKiB": 840708, + "peakRssKiB": 883512, + "threads": 35, + "fds": 36, + "writeBytes": 1302351872 + }, + { + "tMs": 373281, + "cpuTicks": 61950, + "rssKiB": 840384, + "peakRssKiB": 883512, + "threads": 35, + "fds": 36, + "writeBytes": 1307095040 + }, + { + "tMs": 374448, + "cpuTicks": 62100, + "rssKiB": 840384, + "peakRssKiB": 883512, + "threads": 35, + "fds": 36, + "writeBytes": 1310101504 + }, + { + "tMs": 375603, + "cpuTicks": 62240, + "rssKiB": 840384, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1314004992 + }, + { + "tMs": 376794, + "cpuTicks": 62432, + "rssKiB": 888728, + "peakRssKiB": 888728, + "threads": 35, + "fds": 36, + "writeBytes": 1318367232 + }, + { + "tMs": 377954, + "cpuTicks": 62644, + "rssKiB": 888728, + "peakRssKiB": 888728, + "threads": 35, + "fds": 36, + "writeBytes": 1321381888 + }, + { + "tMs": 379120, + "cpuTicks": 62817, + "rssKiB": 840060, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1325973504 + }, + { + "tMs": 380263, + "cpuTicks": 62965, + "rssKiB": 840060, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1329893376 + }, + { + "tMs": 381419, + "cpuTicks": 63115, + "rssKiB": 839748, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1332727808 + }, + { + "tMs": 382571, + "cpuTicks": 63281, + "rssKiB": 839748, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1335652352 + }, + { + "tMs": 383749, + "cpuTicks": 63475, + "rssKiB": 888092, + "peakRssKiB": 888092, + "threads": 35, + "fds": 36, + "writeBytes": 1341820928 + }, + { + "tMs": 384924, + "cpuTicks": 63687, + "rssKiB": 888092, + "peakRssKiB": 888092, + "threads": 35, + "fds": 36, + "writeBytes": 1345683456 + }, + { + "tMs": 386093, + "cpuTicks": 63867, + "rssKiB": 839756, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1350279168 + }, + { + "tMs": 387244, + "cpuTicks": 64010, + "rssKiB": 839756, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1353256960 + }, + { + "tMs": 388390, + "cpuTicks": 64160, + "rssKiB": 839756, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1356197888 + }, + { + "tMs": 389546, + "cpuTicks": 64309, + "rssKiB": 839756, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1360109568 + }, + { + "tMs": 390719, + "cpuTicks": 64478, + "rssKiB": 888120, + "peakRssKiB": 888120, + "threads": 35, + "fds": 36, + "writeBytes": 1364393984 + }, + { + "tMs": 391920, + "cpuTicks": 64713, + "rssKiB": 888120, + "peakRssKiB": 888120, + "threads": 35, + "fds": 36, + "writeBytes": 1367384064 + }, + { + "tMs": 393084, + "cpuTicks": 64897, + "rssKiB": 840424, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1373585408 + }, + { + "tMs": 394228, + "cpuTicks": 65039, + "rssKiB": 840744, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1377447936 + }, + { + "tMs": 395405, + "cpuTicks": 65194, + "rssKiB": 840744, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1380532224 + }, + { + "tMs": 396558, + "cpuTicks": 65342, + "rssKiB": 840744, + "peakRssKiB": 883688, + "threads": 35, + "fds": 36, + "writeBytes": 1383501824 + }, + { + "tMs": 397729, + "cpuTicks": 65534, + "rssKiB": 889088, + "peakRssKiB": 889088, + "threads": 35, + "fds": 36, + "writeBytes": 1387786240 + }, + { + "tMs": 398941, + "cpuTicks": 65781, + "rssKiB": 889088, + "peakRssKiB": 889088, + "threads": 35, + "fds": 36, + "writeBytes": 1390809088 + }, + { + "tMs": 400099, + "cpuTicks": 65960, + "rssKiB": 840744, + "peakRssKiB": 883920, + "threads": 35, + "fds": 36, + "writeBytes": 1396133888 + }, + { + "tMs": 401244, + "cpuTicks": 66109, + "rssKiB": 840756, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1399160832 + }, + { + "tMs": 402365, + "cpuTicks": 66254, + "rssKiB": 840756, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1402138624 + }, + { + "tMs": 403513, + "cpuTicks": 66432, + "rssKiB": 840756, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1407897600 + }, + { + "tMs": 404643, + "cpuTicks": 66583, + "rssKiB": 840432, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1410891776 + }, + { + "tMs": 405845, + "cpuTicks": 66811, + "rssKiB": 888132, + "peakRssKiB": 888132, + "threads": 35, + "fds": 36, + "writeBytes": 1415184384 + }, + { + "tMs": 407028, + "cpuTicks": 67039, + "rssKiB": 888132, + "peakRssKiB": 888132, + "threads": 35, + "fds": 36, + "writeBytes": 1418199040 + }, + { + "tMs": 408191, + "cpuTicks": 67213, + "rssKiB": 839788, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1423429632 + }, + { + "tMs": 409327, + "cpuTicks": 67376, + "rssKiB": 839788, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1426440192 + }, + { + "tMs": 410472, + "cpuTicks": 67534, + "rssKiB": 839788, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1429430272 + }, + { + "tMs": 411613, + "cpuTicks": 67700, + "rssKiB": 839804, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1432334336 + }, + { + "tMs": 412783, + "cpuTicks": 67912, + "rssKiB": 888148, + "peakRssKiB": 888148, + "threads": 35, + "fds": 36, + "writeBytes": 1439256576 + }, + { + "tMs": 413975, + "cpuTicks": 68153, + "rssKiB": 888148, + "peakRssKiB": 888148, + "threads": 35, + "fds": 36, + "writeBytes": 1442287616 + }, + { + "tMs": 415132, + "cpuTicks": 68349, + "rssKiB": 839156, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1446551552 + }, + { + "tMs": 416275, + "cpuTicks": 68518, + "rssKiB": 840044, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1449582592 + }, + { + "tMs": 417430, + "cpuTicks": 68666, + "rssKiB": 840688, + "peakRssKiB": 884048, + "threads": 35, + "fds": 38, + "writeBytes": 1452584960 + }, + { + "tMs": 418579, + "cpuTicks": 68809, + "rssKiB": 840688, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1456484352 + }, + { + "tMs": 419739, + "cpuTicks": 68984, + "rssKiB": 889032, + "peakRssKiB": 889032, + "threads": 35, + "fds": 37, + "writeBytes": 1460756480 + }, + { + "tMs": 420926, + "cpuTicks": 69221, + "rssKiB": 889144, + "peakRssKiB": 889144, + "threads": 35, + "fds": 36, + "writeBytes": 1463730176 + }, + { + "tMs": 422093, + "cpuTicks": 69428, + "rssKiB": 864972, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1467944960 + }, + { + "tMs": 423238, + "cpuTicks": 69587, + "rssKiB": 840800, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1473822720 + }, + { + "tMs": 424371, + "cpuTicks": 69750, + "rssKiB": 840800, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1476890624 + }, + { + "tMs": 425508, + "cpuTicks": 69905, + "rssKiB": 840800, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1479897088 + }, + { + "tMs": 426642, + "cpuTicks": 70051, + "rssKiB": 840800, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1483010048 + }, + { + "tMs": 427832, + "cpuTicks": 70276, + "rssKiB": 888276, + "peakRssKiB": 888276, + "threads": 35, + "fds": 36, + "writeBytes": 1488351232 + }, + { + "tMs": 429012, + "cpuTicks": 70495, + "rssKiB": 888276, + "peakRssKiB": 888276, + "threads": 35, + "fds": 36, + "writeBytes": 1491337216 + }, + { + "tMs": 430168, + "cpuTicks": 70677, + "rssKiB": 840576, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1495724032 + }, + { + "tMs": 431332, + "cpuTicks": 70840, + "rssKiB": 840592, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1498808320 + }, + { + "tMs": 432462, + "cpuTicks": 70995, + "rssKiB": 840592, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1502720000 + }, + { + "tMs": 433593, + "cpuTicks": 71148, + "rssKiB": 840592, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1507495936 + }, + { + "tMs": 434770, + "cpuTicks": 71348, + "rssKiB": 888936, + "peakRssKiB": 888936, + "threads": 35, + "fds": 37, + "writeBytes": 1511870464 + }, + { + "tMs": 435959, + "cpuTicks": 71603, + "rssKiB": 888940, + "peakRssKiB": 888940, + "threads": 35, + "fds": 36, + "writeBytes": 1514967040 + }, + { + "tMs": 437112, + "cpuTicks": 71813, + "rssKiB": 840596, + "peakRssKiB": 884048, + "threads": 35, + "fds": 38, + "writeBytes": 1520435200 + }, + { + "tMs": 438252, + "cpuTicks": 71972, + "rssKiB": 840596, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1523580928 + }, + { + "tMs": 439381, + "cpuTicks": 72139, + "rssKiB": 840596, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1526677504 + }, + { + "tMs": 440521, + "cpuTicks": 72301, + "rssKiB": 840596, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1530724352 + }, + { + "tMs": 441663, + "cpuTicks": 72459, + "rssKiB": 839956, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1533538304 + }, + { + "tMs": 442855, + "cpuTicks": 72684, + "rssKiB": 888300, + "peakRssKiB": 888300, + "threads": 35, + "fds": 36, + "writeBytes": 1539616768 + }, + { + "tMs": 444013, + "cpuTicks": 72901, + "rssKiB": 888300, + "peakRssKiB": 888300, + "threads": 35, + "fds": 36, + "writeBytes": 1542901760 + }, + { + "tMs": 445162, + "cpuTicks": 73092, + "rssKiB": 840180, + "peakRssKiB": 884048, + "threads": 35, + "fds": 37, + "writeBytes": 1548091392 + }, + { + "tMs": 446314, + "cpuTicks": 73268, + "rssKiB": 840284, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1551249408 + }, + { + "tMs": 447464, + "cpuTicks": 73419, + "rssKiB": 840284, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1554280448 + }, + { + "tMs": 448601, + "cpuTicks": 73580, + "rssKiB": 840284, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1557192704 + }, + { + "tMs": 449777, + "cpuTicks": 73769, + "rssKiB": 888952, + "peakRssKiB": 888952, + "threads": 35, + "fds": 36, + "writeBytes": 1561460736 + }, + { + "tMs": 450970, + "cpuTicks": 74011, + "rssKiB": 888980, + "peakRssKiB": 888980, + "threads": 35, + "fds": 37, + "writeBytes": 1565429760 + }, + { + "tMs": 452134, + "cpuTicks": 74200, + "rssKiB": 840636, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1569800192 + }, + { + "tMs": 453258, + "cpuTicks": 74370, + "rssKiB": 840636, + "peakRssKiB": 884048, + "threads": 35, + "fds": 36, + "writeBytes": 1574522880 + }, + { + "tMs": 454389, + "cpuTicks": 74532, + "rssKiB": 841284, + "peakRssKiB": 884156, + "threads": 35, + "fds": 36, + "writeBytes": 1578569728 + }, + { + "tMs": 455523, + "cpuTicks": 74684, + "rssKiB": 841284, + "peakRssKiB": 884852, + "threads": 35, + "fds": 36, + "writeBytes": 1581719552 + }, + { + "tMs": 456675, + "cpuTicks": 74835, + "rssKiB": 841284, + "peakRssKiB": 884908, + "threads": 35, + "fds": 36, + "writeBytes": 1585373184 + }, + { + "tMs": 457891, + "cpuTicks": 75074, + "rssKiB": 889628, + "peakRssKiB": 889628, + "threads": 35, + "fds": 36, + "writeBytes": 1589043200 + }, + { + "tMs": 459098, + "cpuTicks": 75316, + "rssKiB": 865456, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1593589760 + }, + { + "tMs": 460257, + "cpuTicks": 75471, + "rssKiB": 841284, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1597321216 + }, + { + "tMs": 461403, + "cpuTicks": 75621, + "rssKiB": 841288, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1600299008 + }, + { + "tMs": 462531, + "cpuTicks": 75792, + "rssKiB": 841288, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1603256320 + }, + { + "tMs": 463668, + "cpuTicks": 75952, + "rssKiB": 841288, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1609072640 + }, + { + "tMs": 464890, + "cpuTicks": 76193, + "rssKiB": 888984, + "peakRssKiB": 888984, + "threads": 35, + "fds": 36, + "writeBytes": 1613365248 + }, + { + "tMs": 466059, + "cpuTicks": 76406, + "rssKiB": 864828, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1617145856 + }, + { + "tMs": 467214, + "cpuTicks": 76561, + "rssKiB": 840656, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1620844544 + }, + { + "tMs": 468339, + "cpuTicks": 76715, + "rssKiB": 840656, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1624731648 + }, + { + "tMs": 469468, + "cpuTicks": 76873, + "rssKiB": 840656, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1627738112 + }, + { + "tMs": 470635, + "cpuTicks": 77031, + "rssKiB": 840332, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1630650368 + }, + { + "tMs": 471826, + "cpuTicks": 77248, + "rssKiB": 888692, + "peakRssKiB": 888692, + "threads": 35, + "fds": 36, + "writeBytes": 1634820096 + }, + { + "tMs": 472999, + "cpuTicks": 77479, + "rssKiB": 888692, + "peakRssKiB": 888692, + "threads": 35, + "fds": 36, + "writeBytes": 1639555072 + }, + { + "tMs": 474149, + "cpuTicks": 77659, + "rssKiB": 840348, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1644867584 + }, + { + "tMs": 475279, + "cpuTicks": 77814, + "rssKiB": 840348, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1647808512 + }, + { + "tMs": 476405, + "cpuTicks": 77973, + "rssKiB": 840356, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1650839552 + }, + { + "tMs": 477541, + "cpuTicks": 78128, + "rssKiB": 839712, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1653862400 + }, + { + "tMs": 478685, + "cpuTicks": 78303, + "rssKiB": 881972, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1659105280 + }, + { + "tMs": 479916, + "cpuTicks": 78562, + "rssKiB": 888380, + "peakRssKiB": 888380, + "threads": 35, + "fds": 36, + "writeBytes": 1661956096 + }, + { + "tMs": 481084, + "cpuTicks": 78765, + "rssKiB": 840056, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1666093056 + }, + { + "tMs": 482231, + "cpuTicks": 78925, + "rssKiB": 840056, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1669234688 + }, + { + "tMs": 483389, + "cpuTicks": 79095, + "rssKiB": 840380, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1674932224 + }, + { + "tMs": 484540, + "cpuTicks": 79251, + "rssKiB": 840056, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1677893632 + }, + { + "tMs": 485702, + "cpuTicks": 79428, + "rssKiB": 888400, + "peakRssKiB": 888400, + "threads": 35, + "fds": 36, + "writeBytes": 1682137088 + }, + { + "tMs": 486898, + "cpuTicks": 79656, + "rssKiB": 889044, + "peakRssKiB": 889044, + "threads": 35, + "fds": 36, + "writeBytes": 1685164032 + }, + { + "tMs": 488077, + "cpuTicks": 79871, + "rssKiB": 864872, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1690099712 + }, + { + "tMs": 489239, + "cpuTicks": 80028, + "rssKiB": 840700, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1693437952 + }, + { + "tMs": 490375, + "cpuTicks": 80176, + "rssKiB": 840700, + "peakRssKiB": 885292, + "threads": 35, + "fds": 36, + "writeBytes": 1696436224 + }, + { + "tMs": 491520, + "cpuTicks": 80339, + "rssKiB": 840700, + "peakRssKiB": 885292, + "threads": 35, + "fds": 38, + "writeBytes": 1699471360 + }, + { + "tMs": 492664, + "cpuTicks": 80508, + "rssKiB": 840700, + "peakRssKiB": 885292, + "threads": 35, + "fds": 38, + "writeBytes": 1702465536 + }, + { + "tMs": 493878, + "cpuTicks": 80741, + "rssKiB": 889044, + "peakRssKiB": 889044, + "threads": 35, + "fds": 38, + "writeBytes": 1709510656 + }, + { + "tMs": 495039, + "cpuTicks": 80958, + "rssKiB": 864872, + "peakRssKiB": 885292, + "threads": 35, + "fds": 39, + "writeBytes": 1713274880 + }, + { + "tMs": 496199, + "cpuTicks": 81124, + "rssKiB": 840720, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1716948992 + }, + { + "tMs": 497346, + "cpuTicks": 81281, + "rssKiB": 840720, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1721028608 + }, + { + "tMs": 498480, + "cpuTicks": 81445, + "rssKiB": 840076, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1724026880 + }, + { + "tMs": 499626, + "cpuTicks": 81602, + "rssKiB": 840076, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1726947328 + }, + { + "tMs": 500819, + "cpuTicks": 81810, + "rssKiB": 888420, + "peakRssKiB": 888420, + "threads": 35, + "fds": 37, + "writeBytes": 1730502656 + }, + { + "tMs": 501985, + "cpuTicks": 82037, + "rssKiB": 888456, + "peakRssKiB": 888456, + "threads": 35, + "fds": 37, + "writeBytes": 1734213632 + }, + { + "tMs": 503140, + "cpuTicks": 82240, + "rssKiB": 840112, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1741271040 + }, + { + "tMs": 504280, + "cpuTicks": 82395, + "rssKiB": 840112, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1744289792 + }, + { + "tMs": 505411, + "cpuTicks": 82543, + "rssKiB": 839788, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1747308544 + }, + { + "tMs": 506555, + "cpuTicks": 82694, + "rssKiB": 840124, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1750388736 + }, + { + "tMs": 507756, + "cpuTicks": 82885, + "rssKiB": 889112, + "peakRssKiB": 889112, + "threads": 35, + "fds": 37, + "writeBytes": 1755770880 + }, + { + "tMs": 508936, + "cpuTicks": 83130, + "rssKiB": 889112, + "peakRssKiB": 889112, + "threads": 35, + "fds": 37, + "writeBytes": 1758830592 + }, + { + "tMs": 510107, + "cpuTicks": 83322, + "rssKiB": 840776, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1763201024 + }, + { + "tMs": 511255, + "cpuTicks": 83493, + "rssKiB": 840812, + "peakRssKiB": 885292, + "threads": 35, + "fds": 38, + "writeBytes": 1767215104 + }, + { + "tMs": 512402, + "cpuTicks": 83647, + "rssKiB": 840812, + "peakRssKiB": 885292, + "threads": 35, + "fds": 37, + "writeBytes": 1770287104 + }, + { + "tMs": 513561, + "cpuTicks": 83793, + "rssKiB": 841136, + "peakRssKiB": 885344, + "threads": 35, + "fds": 37, + "writeBytes": 1775079424 + }, + { + "tMs": 514710, + "cpuTicks": 83953, + "rssKiB": 889480, + "peakRssKiB": 889480, + "threads": 35, + "fds": 37, + "writeBytes": 1779392512 + }, + { + "tMs": 515925, + "cpuTicks": 84204, + "rssKiB": 889480, + "peakRssKiB": 889480, + "threads": 35, + "fds": 37, + "writeBytes": 1783459840 + }, + { + "tMs": 517093, + "cpuTicks": 84406, + "rssKiB": 840812, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1787928576 + }, + { + "tMs": 518250, + "cpuTicks": 84544, + "rssKiB": 840812, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1791000576 + }, + { + "tMs": 519393, + "cpuTicks": 84698, + "rssKiB": 840812, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1793949696 + }, + { + "tMs": 520548, + "cpuTicks": 84845, + "rssKiB": 841136, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1798000640 + }, + { + "tMs": 521725, + "cpuTicks": 85014, + "rssKiB": 889828, + "peakRssKiB": 889828, + "threads": 35, + "fds": 37, + "writeBytes": 1802375168 + }, + { + "tMs": 522926, + "cpuTicks": 85261, + "rssKiB": 889828, + "peakRssKiB": 889828, + "threads": 35, + "fds": 37, + "writeBytes": 1807118336 + }, + { + "tMs": 524094, + "cpuTicks": 85459, + "rssKiB": 865332, + "peakRssKiB": 885424, + "threads": 35, + "fds": 38, + "writeBytes": 1810907136 + }, + { + "tMs": 525239, + "cpuTicks": 85614, + "rssKiB": 841160, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1815572480 + }, + { + "tMs": 526387, + "cpuTicks": 85769, + "rssKiB": 841160, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1818644480 + }, + { + "tMs": 527550, + "cpuTicks": 85915, + "rssKiB": 841160, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1821720576 + }, + { + "tMs": 528717, + "cpuTicks": 86097, + "rssKiB": 889504, + "peakRssKiB": 889504, + "threads": 35, + "fds": 37, + "writeBytes": 1825984512 + }, + { + "tMs": 529918, + "cpuTicks": 86340, + "rssKiB": 888860, + "peakRssKiB": 888860, + "threads": 35, + "fds": 37, + "writeBytes": 1830035456 + }, + { + "tMs": 531099, + "cpuTicks": 86564, + "rssKiB": 864688, + "peakRssKiB": 885424, + "threads": 35, + "fds": 39, + "writeBytes": 1833971712 + }, + { + "tMs": 532264, + "cpuTicks": 86721, + "rssKiB": 840536, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1837248512 + }, + { + "tMs": 533400, + "cpuTicks": 86884, + "rssKiB": 839888, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1842040832 + }, + { + "tMs": 534551, + "cpuTicks": 87039, + "rssKiB": 839888, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1846091776 + }, + { + "tMs": 535729, + "cpuTicks": 87232, + "rssKiB": 888880, + "peakRssKiB": 888880, + "threads": 35, + "fds": 37, + "writeBytes": 1850413056 + }, + { + "tMs": 536912, + "cpuTicks": 87453, + "rssKiB": 889532, + "peakRssKiB": 889532, + "threads": 35, + "fds": 37, + "writeBytes": 1853472768 + }, + { + "tMs": 538073, + "cpuTicks": 87657, + "rssKiB": 865684, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1857261568 + }, + { + "tMs": 539211, + "cpuTicks": 87804, + "rssKiB": 841836, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1861595136 + }, + { + "tMs": 540359, + "cpuTicks": 87951, + "rssKiB": 841836, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1864482816 + }, + { + "tMs": 541510, + "cpuTicks": 88110, + "rssKiB": 841860, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1867554816 + }, + { + "tMs": 542665, + "cpuTicks": 88278, + "rssKiB": 841860, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1870528512 + }, + { + "tMs": 543862, + "cpuTicks": 88515, + "rssKiB": 890524, + "peakRssKiB": 890524, + "threads": 35, + "fds": 37, + "writeBytes": 1877667840 + }, + { + "tMs": 545048, + "cpuTicks": 88742, + "rssKiB": 866352, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1881849856 + }, + { + "tMs": 546195, + "cpuTicks": 88891, + "rssKiB": 842188, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1885114368 + }, + { + "tMs": 547316, + "cpuTicks": 89034, + "rssKiB": 842188, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1888059392 + }, + { + "tMs": 548466, + "cpuTicks": 89193, + "rssKiB": 842188, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1892073472 + }, + { + "tMs": 549621, + "cpuTicks": 89359, + "rssKiB": 842188, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1895059456 + }, + { + "tMs": 550820, + "cpuTicks": 89571, + "rssKiB": 889884, + "peakRssKiB": 889884, + "threads": 35, + "fds": 37, + "writeBytes": 1899331584 + }, + { + "tMs": 551978, + "cpuTicks": 89797, + "rssKiB": 890220, + "peakRssKiB": 890220, + "threads": 35, + "fds": 37, + "writeBytes": 1902403584 + }, + { + "tMs": 553128, + "cpuTicks": 90000, + "rssKiB": 842200, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1909682176 + }, + { + "tMs": 554257, + "cpuTicks": 90168, + "rssKiB": 842200, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1912762368 + }, + { + "tMs": 555382, + "cpuTicks": 90336, + "rssKiB": 842200, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1915756544 + }, + { + "tMs": 556520, + "cpuTicks": 90501, + "rssKiB": 842208, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1918799872 + }, + { + "tMs": 557653, + "cpuTicks": 90666, + "rssKiB": 842208, + "peakRssKiB": 885424, + "threads": 35, + "fds": 37, + "writeBytes": 1922875392 + }, + { + "tMs": 558867, + "cpuTicks": 90903, + "rssKiB": 890552, + "peakRssKiB": 890552, + "threads": 35, + "fds": 37, + "writeBytes": 1927180288 + }, + { + "tMs": 560020, + "cpuTicks": 91120, + "rssKiB": 890552, + "peakRssKiB": 890552, + "threads": 35, + "fds": 37, + "writeBytes": 1930219520 + }, + { + "tMs": 561169, + "cpuTicks": 91302, + "rssKiB": 842208, + "peakRssKiB": 885660, + "threads": 35, + "fds": 37, + "writeBytes": 1934458880 + }, + { + "tMs": 562344, + "cpuTicks": 91459, + "rssKiB": 842228, + "peakRssKiB": 885660, + "threads": 35, + "fds": 37, + "writeBytes": 1938604032 + }, + { + "tMs": 563490, + "cpuTicks": 91620, + "rssKiB": 841160, + "peakRssKiB": 885660, + "threads": 35, + "fds": 37, + "writeBytes": 1943445504 + }, + { + "tMs": 564632, + "cpuTicks": 91784, + "rssKiB": 840808, + "peakRssKiB": 885660, + "threads": 35, + "fds": 37, + "writeBytes": 1946517504 + }, + { + "tMs": 565857, + "cpuTicks": 92021, + "rssKiB": 889476, + "peakRssKiB": 889476, + "threads": 35, + "fds": 37, + "writeBytes": 1950945280 + }, + { + "tMs": 567031, + "cpuTicks": 92239, + "rssKiB": 889808, + "peakRssKiB": 889808, + "threads": 35, + "fds": 37, + "writeBytes": 1955184640 + }, + { + "tMs": 568206, + "cpuTicks": 92407, + "rssKiB": 841464, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1959415808 + }, + { + "tMs": 569355, + "cpuTicks": 92569, + "rssKiB": 841464, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1962418176 + }, + { + "tMs": 570497, + "cpuTicks": 92730, + "rssKiB": 841472, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1965355008 + }, + { + "tMs": 571644, + "cpuTicks": 92888, + "rssKiB": 841496, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1969315840 + }, + { + "tMs": 572847, + "cpuTicks": 93120, + "rssKiB": 889224, + "peakRssKiB": 889224, + "threads": 35, + "fds": 37, + "writeBytes": 1975492608 + }, + { + "tMs": 574027, + "cpuTicks": 93344, + "rssKiB": 865376, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1979174912 + }, + { + "tMs": 575160, + "cpuTicks": 93490, + "rssKiB": 841204, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1982935040 + }, + { + "tMs": 576305, + "cpuTicks": 93649, + "rssKiB": 841212, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1987022848 + }, + { + "tMs": 577447, + "cpuTicks": 93811, + "rssKiB": 841212, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1990017024 + }, + { + "tMs": 578598, + "cpuTicks": 93972, + "rssKiB": 840988, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 1993052160 + }, + { + "tMs": 579781, + "cpuTicks": 94162, + "rssKiB": 889332, + "peakRssKiB": 889332, + "threads": 35, + "fds": 37, + "writeBytes": 1997348864 + }, + { + "tMs": 580961, + "cpuTicks": 94387, + "rssKiB": 889332, + "peakRssKiB": 889332, + "threads": 35, + "fds": 37, + "writeBytes": 2000285696 + }, + { + "tMs": 582112, + "cpuTicks": 94578, + "rssKiB": 841324, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2005753856 + }, + { + "tMs": 583250, + "cpuTicks": 94733, + "rssKiB": 841644, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2010611712 + }, + { + "tMs": 584395, + "cpuTicks": 94913, + "rssKiB": 841644, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2013679616 + }, + { + "tMs": 585552, + "cpuTicks": 95070, + "rssKiB": 841644, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2017800192 + }, + { + "tMs": 586678, + "cpuTicks": 95237, + "rssKiB": 844092, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2021482496 + }, + { + "tMs": 587865, + "cpuTicks": 95460, + "rssKiB": 890004, + "peakRssKiB": 890004, + "threads": 35, + "fds": 37, + "writeBytes": 2025209856 + }, + { + "tMs": 589016, + "cpuTicks": 95664, + "rssKiB": 890004, + "peakRssKiB": 890004, + "threads": 35, + "fds": 37, + "writeBytes": 2028281856 + }, + { + "tMs": 590180, + "cpuTicks": 95830, + "rssKiB": 841656, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2033668096 + }, + { + "tMs": 591318, + "cpuTicks": 95987, + "rssKiB": 841680, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2036637696 + }, + { + "tMs": 592453, + "cpuTicks": 96129, + "rssKiB": 841680, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2039586816 + }, + { + "tMs": 593613, + "cpuTicks": 96285, + "rssKiB": 841356, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2044456960 + }, + { + "tMs": 594795, + "cpuTicks": 96477, + "rssKiB": 889604, + "peakRssKiB": 889604, + "threads": 35, + "fds": 37, + "writeBytes": 2049732608 + }, + { + "tMs": 595959, + "cpuTicks": 96689, + "rssKiB": 889604, + "peakRssKiB": 889604, + "threads": 35, + "fds": 38, + "writeBytes": 2052734976 + }, + { + "tMs": 597140, + "cpuTicks": 96872, + "rssKiB": 841272, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2057039872 + }, + { + "tMs": 598310, + "cpuTicks": 97013, + "rssKiB": 841596, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2060013568 + }, + { + "tMs": 599463, + "cpuTicks": 97159, + "rssKiB": 841276, + "peakRssKiB": 885732, + "threads": 35, + "fds": 37, + "writeBytes": 2064015360 + } + ], + "db_probe_ms": [ + 17.011, + 8.42, + 7.837, + 8.287, + 7.4, + 7.989, + 7.508, + 8.223, + 7.761, + 7.359, + 8.927, + 11.798, + 7.308, + 7.49, + 8.633, + 8.276, + 7.651, + 7.854, + 7.35, + 13.155, + 7.516, + 7.51, + 11.806, + 9.528, + 7.782, + 7.186, + 8.558, + 17.555, + 9.358, + 7.746, + 10.064, + 17.942, + 9.332, + 7.874, + 7.952, + 7.584, + 8.243, + 8.995, + 9.156, + 7.251, + 7.564, + 8.63, + 8.161, + 7.401, + 7.382, + 8.716, + 8.66, + 7.337, + 8.826, + 12.028, + 8.638, + 7.611, + 7.977, + 10.01, + 13.407, + 7.386, + 7.165, + 10.496, + 17.04, + 7.51, + 7.794, + 7.942, + 9.213, + 7.577, + 7.931, + 8.438, + 7.408, + 8.772, + 8.046, + 8.406, + 7.539, + 10.696, + 9.722, + 7.934, + 10.107, + 7.846, + 24.314, + 8.246, + 7.822, + 7.766, + 12.724, + 8.469, + 7.555, + 7.897, + 10.781, + 9.688, + 8.434, + 8.841, + 8.93, + 15.21, + 7.871, + 10.278, + 8.089, + 7.532, + 10.565, + 7.459, + 12.945, + 12.392, + 7.847, + 7.862, + 14.959, + 7.456, + 8.338, + 12.092, + 9.285, + 9.505, + 7.831, + 14.537, + 8.454, + 7.8, + 7.409, + 8.727, + 12.285, + 8.6, + 7.656, + 7.841, + 21.198, + 7.427, + 7.614, + 7.941, + 9.005, + 7.646, + 8.003, + 7.603, + 10.022, + 9.723, + 20.971, + 8.908, + 7.757, + 9.229, + 9.171, + 18.077, + 7.215, + 8.324, + 8.537, + 8.001, + 7.663, + 8.618, + 11.948, + 12.791, + 7.444, + 7.507, + 8.58, + 8.049, + 9.04, + 7.523, + 7.837, + 8.555, + 7.717, + 7.196, + 7.829, + 9.138, + 7.588, + 12.077, + 11.873, + 9.385, + 8.822, + 9.304, + 9.227, + 7.477, + 8.971, + 10.347, + 8.354, + 8.503, + 9.143, + 11.847, + 8.651, + 7.588, + 7.323, + 14.19, + 7.498, + 7.437, + 6.996, + 12.604, + 6.954, + 8.07, + 7.751, + 8.57, + 7.643, + 7.207, + 11.943, + 7.964, + 8.266, + 7.606, + 10.598, + 8.457, + 8.176, + 8.198, + 14.159, + 9.483, + 7.297, + 8.63, + 11.015, + 13.163, + 7.464, + 8.166, + 8.56, + 8.606, + 7.179, + 7.755, + 8.773, + 7.131, + 8.185, + 7.232, + 10.643, + 7.436, + 7.302, + 7.611, + 9.239, + 9.349, + 7.602, + 10.267, + 14.644, + 8.888, + 7.215, + 11.033, + 14.422, + 8.426, + 7.725, + 11.237, + 14.218, + 8.18, + 8.399, + 9.858, + 11.195, + 7.778, + 8.203, + 15.124, + 7.549, + 10.213, + 7.644, + 8.726, + 6.885, + 7.687, + 9.563, + 10.582, + 7.595, + 8.087, + 9.272, + 13.348, + 7.966, + 8.33, + 9.608, + 8.254, + 8.055, + 7.422, + 9.904, + 24.572, + 7.261, + 9.067, + 11.247, + 10.407, + 7.396, + 8.748, + 8.147, + 7.284, + 7.85, + 8.536, + 8.229, + 7.963, + 7.299, + 14.583, + 9.369, + 9.681, + 7.076, + 9.85, + 9.164, + 7.686, + 7.784, + 11.524, + 16.325, + 7.654, + 9.577, + 11.945, + 8.013, + 8.32, + 8.279, + 12.65, + 12.081, + 12.113, + 8.37, + 13.308, + 7.189, + 9.393, + 9.166, + 15.626, + 7.709, + 8.186, + 10.692, + 14.702, + 7.276, + 8.063, + 11.129, + 9.451, + 7.296, + 7.795, + 9.165, + 7.925, + 7.603, + 9.238, + 8.159, + 19.396, + 8.913, + 9.812, + 11.662, + 7.716, + 8.017, + 7.478, + 7.662, + 7.664, + 9.611, + 10.197, + 10.144, + 7.18, + 8.487, + 9.695, + 10.294, + 7.258, + 9.438, + 18.762, + 12.072, + 7.281, + 7.442, + 13.268, + 8.708, + 7.508, + 7.644, + 8.206, + 14.035, + 7.683, + 8.141, + 13.685, + 7.411, + 10.131, + 7.501, + 9.005, + 7.243, + 7.828, + 18.167, + 8.443, + 7.184, + 8.297, + 14.411, + 8.374, + 7.349, + 7.8, + 13.312, + 14.28, + 7.613, + 8.043, + 9.039, + 16.86, + 7.754, + 7.763, + 8.074, + 9.829, + 8.66, + 7.398, + 9.179, + 9.569, + 8.095, + 9.285, + 7.943, + 8.126, + 8.87, + 14.964, + 8.099, + 7.74, + 7.201, + 9.646, + 8.064, + 7.188, + 7.421, + 9.885, + 7.939, + 7.316, + 9.09, + 9.287, + 14.557, + 7.362, + 7.25, + 8.037, + 8.983, + 7.395, + 7.435, + 8.2, + 7.34, + 7.709, + 8.874, + 16.35, + 8.461, + 8.399, + 12.227, + 8.476, + 7.065, + 10.372, + 12.219, + 8.378, + 9.902, + 8.63, + 12.934, + 7.692, + 7.401, + 7.537, + 11.505, + 7.48, + 7.091, + 7.657, + 8.459, + 8.834, + 7.163, + 7.477, + 8.79, + 8.357, + 7.471, + 7.361, + 8.157, + 8.874, + 9.366, + 7.754, + 7.832, + 7.947, + 8.027, + 10.156, + 7.955, + 7.548, + 8.206, + 12.655, + 12.551, + 7.534, + 9.214, + 13.829, + 7.486, + 8.3, + 8.648, + 10.165, + 17.89, + 7.967, + 7.227, + 7.925, + 7.913, + 7.622, + 7.68, + 10.188, + 9.029, + 7.551, + 7.533, + 11.076, + 7.344, + 7.851, + 11.789, + 11.886, + 7.793, + 8.417, + 8.934, + 8.024, + 7.847, + 8.495, + 9.38, + 10.13, + 7.688, + 7.604, + 11.847, + 8.421, + 7.961, + 7.625, + 12.912, + 7.558, + 8.228, + 7.63, + 7.791, + 8.466, + 8.457, + 7.435, + 9.153, + 8.888, + 8.114, + 11.145, + 8.693, + 8.337, + 8.267, + 10.59, + 12.505, + 7.939, + 8.654, + 13.258, + 8.808, + 7.369, + 7.801, + 14.27, + 7.243, + 7.48, + 7.431, + 12.544, + 10.07, + 7.457, + 7.412, + 7.665, + 8.041, + 7.57, + 7.818, + 9.43, + 7.648, + 7.905, + 12.546, + 7.642, + 8.099, + 7.591, + 9.857, + 7.885, + 8.828, + 8.995, + 9.782, + 9.47, + 7.699, + 8.176, + 8.081, + 13.553, + 7.782, + 7.894, + 10.971, + 17.805, + 7.087, + 8.263, + 12.248, + 7.517, + 7.214, + 7.351, + 11.996, + 7.624, + 7.258, + 10.693, + 11.209, + 8.094, + 7.562, + 8.676, + 8.251, + 7.955, + 7.484, + 10.014, + 13.833, + 8.252, + 8.235, + 8.28, + 11.308, + 7.521, + 9.919, + 7.945, + 7.42, + 7.318, + 10.234, + 9.615, + 7.801, + 7.589, + 7.153, + 9.119, + 7.391, + 7.162, + 7.448, + 9.359, + 7.578, + 7.518, + 9.332, + 11.621, + 7.843, + 8.146, + 9.159, + 7.796, + 8.882, + 8.835, + 8.162, + 8.371, + 10.266, + 8.139, + 7.664, + 7.098, + 7.256, + 8.087, + 9.436, + 7.372, + 8.078, + 8.329, + 12.26, + 7.696, + 7.189, + 7.235, + 9.169, + 10.545, + 8.878, + 18.009, + 8.234, + 10.839, + 7.83, + 10.908, + 8.15, + 9.059, + 9.201, + 8.785, + 12.148, + 9.133, + 7.754, + 8.394, + 7.413, + 7.375, + 9.777, + 8.266, + 7.171, + 7.624, + 7.962, + 7.903, + 7.336, + 7.704, + 8.931, + 9.494, + 7.575, + 7.549, + 15.255, + 8.813, + 7.542, + 7.988, + 11.028, + 8.734, + 8.793, + 7.674, + 9.046, + 7.709, + 8.851, + 8.626, + 9.716, + 7.541, + 7.94, + 7.692, + 8.52, + 9.317, + 8.631, + 9.662, + 8.011, + 7.489, + 7.87, + 7.763, + 7.601, + 7.555, + 7.309, + 8.27, + 7.704, + 7.541, + 7.099, + 11.939, + 8.174, + 7.135, + 10.345, + 9.338, + 11.828, + 8.386, + 7.393, + 9.051, + 7.343, + 8.307, + 7.785, + 8.673, + 7.493, + 8.297, + 7.98, + 8.036, + 13.604, + 7.783, + 8.47, + 9.266, + 7.136, + 7.293, + 7.415, + 7.648, + 7.497, + 7.07, + 10.254, + 8.174, + 7.163, + 8.785, + 10.929, + 10.666, + 7.887, + 8.169, + 8.474, + 10.477, + 7.696, + 7.417, + 9.724, + 9.11, + 8.394, + 7.703, + 8.183, + 7.252, + 9.167, + 7.944, + 9.001, + 7.736, + 19.275, + 8.43, + 8.237, + 7.499, + 7.505, + 8.53, + 7.837, + 7.658, + 8.93, + 18.906, + 9.353, + 7.59, + 7.487, + 12.227, + 9.525, + 7.374, + 7.187, + 15.076, + 9.909, + 8.5, + 7.62, + 7.879, + 10.214, + 8.271, + 7.433, + 8.282, + 7.531, + 8.128, + 7.399, + 8.414, + 7.977, + 7.461, + 7.805, + 8.168, + 7.583, + 7.639, + 13.994, + 8.361, + 7.067, + 7.996, + 8.803, + 9.734, + 7.443, + 7.313, + 9.823, + 9.805, + 7.404, + 8.154, + 10.39, + 25.013, + 7.932, + 7.593, + 7.974, + 7.425, + 7.955, + 7.626, + 7.622, + 8.159, + 9.402, + 8.161, + 7.849, + 7.574, + 7.262, + 18.463, + 8.465, + 7.551, + 7.318, + 11.135, + 8.416, + 7.921, + 7.473, + 12.215, + 9.017, + 7.708, + 7.597, + 8.113, + 18.691, + 9.482, + 7.701, + 7.721, + 7.209, + 9, + 7.908, + 7.976, + 7.818, + 9.143, + 8.093, + 16.332, + 7.171, + 8.48, + 17.928, + 7.739, + 7.531, + 7.547, + 9.72, + 9.671, + 7.295, + 7.541, + 8.001, + 9.517, + 7.815, + 7.597, + 10.53, + 7.474, + 8.381, + 7.543, + 9.657, + 9.763, + 8.135, + 7.554, + 8.994, + 8.024, + 7.712, + 7.674, + 8.243, + 7.43, + 11.847, + 12.845, + 7.873, + 7.502, + 7.196, + 13.764, + 9.882, + 7.436, + 8.077, + 15.017, + 8.612, + 7.596, + 7.288, + 9.437, + 16.36, + 7.596, + 7.374, + 8.498, + 13.326, + 7.588, + 7.515, + 7.839, + 7.447, + 8.166, + 7.778, + 7.605, + 7.548, + 8.335, + 8.401, + 12.839, + 7.519, + 7.35, + 17.244, + 9.476, + 7.528, + 7.519, + 10.323, + 8.65, + 7.353, + 7.733, + 9.435, + 7.455, + 7.253, + 7.598, + 8.249, + 7.933, + 7.947, + 8.892, + 9.178, + 7.906, + 9.161, + 8.444, + 8.328, + 7.185, + 11.166, + 7.534, + 8.855, + 7.298, + 7.289, + 10.884, + 8.191, + 7.31, + 7.279, + 11.233, + 7.594, + 7.989, + 7.424, + 9.474, + 15.332, + 7.32, + 7.253, + 9.204, + 18.051, + 7.815, + 7.525, + 13.251, + 7.968, + 8.424, + 7.536, + 7.873, + 7.502, + 8.92, + 7.162, + 7.562, + 8.065, + 8.139, + 21.039, + 7.656, + 7.711, + 7.129, + 10.896, + 13.796, + 7.708, + 7.593, + 8.865, + 13.233, + 7.394, + 8.099, + 8.129, + 17.46, + 7.77, + 7.619, + 8.221, + 7.501, + 7.433, + 7.377, + 7.55, + 8.043, + 8.041, + 7.396, + 8.215, + 7.362, + 8.221, + 13.926, + 9.651, + 7.141, + 9.21, + 11.592, + 8.763, + 8.082, + 7.684, + 15.481, + 7.825, + 7.66, + 9.249, + 9.436, + 14.727, + 7.837, + 7.415, + 7.965, + 8.548, + 7.618, + 7.411, + 7.827, + 11.443, + 8.378, + 7.304, + 10.883, + 7.744, + 8.347, + 9.569, + 7.845, + 7.346, + 8.79, + 16.962, + 11.398, + 7.589, + 7.731, + 12.775, + 8.702, + 7.731, + 7.175, + 8.249, + 22.976, + 7.629, + 7.957, + 8.15, + 8.172, + 8.41, + 7.392, + 7.858, + 8.933, + 8.33, + 7.39, + 9.111, + 8.003, + 9.034, + 9.141, + 8.428, + 7.325, + 8.466, + 8.312, + 8.28, + 7.928, + 8.115, + 9.002, + 7.627, + 7.584, + 7.972, + 12.946, + 7.834, + 7.202, + 7.644, + 11.772, + 13.899, + 7.106, + 7.159, + 13.93, + 9.249, + 7.836, + 7.413, + 7.585, + 8.49, + 8.242, + 8.401, + 9.23, + 7.691, + 9.189, + 10.398, + 7.962, + 7.335, + 11.159, + 9.819, + 7.985, + 7.618, + 10.674, + 15.542, + 14.455, + 8.162, + 7.621, + 12.322, + 18.156, + 8.611, + 7.099, + 9.338, + 8.807, + 7.948, + 7.162, + 8.131, + 9.125, + 7.639, + 10.616, + 7.954, + 9.036, + 13.028, + 8.231, + 7.696, + 8.014, + 9.547, + 8.268, + 11.153, + 7.529, + 31.125, + 8.818, + 9.308, + 7.443, + 9.325, + 8.894, + 7.409, + 7.262, + 8.473, + 10.638, + 8.428, + 7.105, + 7.495, + 7.893, + 7.997, + 7.93, + 13.179, + 8.154, + 9.994, + 8.079, + 8.706, + 14.432, + 7.739, + 9.304, + 9.857, + 8.341, + 7.263, + 8.11, + 8.678, + 19.019, + 7.535, + 7.832, + 8.179, + 9.192, + 7.349, + 7.557, + 8.776, + 7.376, + 7.938, + 7.334, + 11.393, + 7.379, + 7.65, + 9.537, + 7.611, + 9.654, + 8.501, + 10.461, + 7.97, + 7.487, + 7.656, + 8.3, + 8.152, + 7.539, + 9.542, + 8.241, + 11.403, + 7.377, + 10.357, + 9.356, + 7.81, + 7.55, + 7.713, + 9.036, + 9.261, + 7.981, + 7.416, + 9.446, + 7.531, + 7.128, + 7.587, + 11.738, + 7.546, + 7.234, + 17.541, + 9.569, + 8.448, + 7.441, + 17.956, + 7.948, + 9.029, + 8.861, + 11.122, + 7.396, + 7.343, + 9.919, + 9.725, + 8.229, + 7.233, + 8.102, + 8.395, + 7.095, + 7.92, + 8.714, + 9.448, + 7.512, + 7.49, + 8.114, + 16.376, + 7.374, + 9.541, + 12.686, + 7.867, + 8.1, + 7.5, + 14.522, + 8.586, + 7.8, + 8.334, + 13.47, + 7.923, + 7.931, + 8.376, + 10.755, + 7.511, + 8.483, + 8.657, + 7.771, + 7.127, + 7.692, + 11.363, + 8.332, + 7.356, + 11.221, + 7.403, + 10.096, + 7.481, + 7.94, + 10.89, + 9.41, + 7.313, + 8.454, + 8.988, + 7.653, + 8.768, + 8.25, + 9.05, + 8.213, + 8.524, + 8.181, + 10.248, + 12.237, + 7.303, + 9.895, + 8.182, + 12.453, + 7.244, + 8.398, + 9.129, + 7.269, + 7.203, + 7.65, + 9.711, + 7.423, + 7.674, + 12.569, + 11.191, + 7.485, + 8.906, + 12.808, + 8.065, + 8.236, + 8.281, + 13.672, + 8.19, + 8.138, + 7.472, + 13.074, + 21.443, + 8.213, + 8.08, + 8.507, + 7.393, + 7.91, + 23.516, + 7.602, + 8.082, + 8.131, + 8.706, + 7.987, + 10.145, + 7.218, + 11.981, + 9.247, + 7.767, + 7.095, + 11.169, + 8.365, + 8.088, + 7.289, + 10.039, + 11.292, + 9.701, + 7.57, + 8.952, + 12.252, + 7.866, + 8.372, + 9.49, + 7.298, + 8.118, + 9.118, + 8.722, + 7.17, + 7.179, + 8.335, + 7.968, + 7.667, + 8.649, + 16.559, + 8.797, + 7.409, + 7.324, + 9.656, + 8.15, + 7.808, + 7.965, + 9.925, + 14.844, + 9.279, + 7.43, + 13.159, + 13.363, + 7.974, + 7.389, + 9.402, + 7.567, + 9.559, + 8.113, + 7.63, + 7.673, + 7.409, + 10.559, + 7.696, + 7.449, + 8.283, + 13.99, + 9.057, + 7.288, + 8.632, + 9.633, + 8.808, + 9.754, + 9.079, + 9.534, + 8.357, + 8.695, + 7.544, + 11.358, + 12.956, + 9.111, + 7.424, + 8.811, + 7.434, + 7.639, + 7.532, + 8.165, + 7.278, + 7.549, + 7.965, + 8.188, + 8.236, + 7.76, + 10.078, + 12.431, + 7.669, + 8.355, + 8.703, + 10.75, + 7.353, + 8.27, + 11.591, + 9.874, + 8.756, + 7.581, + 11.699, + 17.269, + 8.38, + 8.798, + 10.967, + 7.473, + 10.811, + 8.852, + 8.155, + 7.391, + 8.274, + 7.593, + 7.894, + 7.162, + 7.785, + 14.978, + 15.536, + 7.606, + 7.833, + 10.729, + 9.249, + 7.226, + 7.444, + 9.419, + 12.895, + 10.346, + 7.494, + 11.955, + 9.6, + 9.645, + 7.592, + 9.915, + 8.267, + 8.44, + 7.638, + 8.29, + 7.393, + 9.842, + 7.953, + 7.925, + 7.83, + 8.324, + 18.126, + 9.971, + 7.324, + 7.445, + 8.256, + 8.722, + 7.57, + 9.554, + 9.083, + 8.942, + 7.593, + 8.604, + 9.93, + 12.232, + 9.229, + 8.114, + 11.398, + 12.047, + 8.035, + 7.355, + 7.91, + 7.888, + 9.206, + 7.584, + 7.569, + 7.548, + 8.139, + 10.777, + 7.821, + 7.995, + 9.065, + 11.944, + 12.183, + 7.784, + 7.743, + 10.683, + 12.381, + 7.525, + 8.716, + 10.936, + 9.612, + 7.461, + 7.492, + 7.936, + 7.687, + 7.87, + 7.703, + 7.749, + 7.576, + 9.505, + 7.828, + 7.916, + 7.57, + 7.67, + 9.703, + 9.142, + 8.25, + 7.58, + 11.523, + 8.047, + 7.539, + 7.574, + 12.428, + 9.483, + 7.428, + 8.075, + 8.83, + 21.323, + 7.484, + 7.885, + 8.34, + 9.64, + 8.37, + 7.993, + 8.678, + 7.85, + 10.729, + 7.685, + 9.1, + 8.41, + 9.394, + 8.359, + 7.82, + 7.418, + 7.723, + 16.44, + 7.858, + 7.564, + 7.586, + 9.81, + 8.962, + 8.126, + 7.534, + 10.451, + 8.981, + 7.642, + 8.354, + 7.925, + 7.611, + 7.525, + 7.506, + 8.009, + 7.391, + 9.316, + 7.65, + 7.698, + 7.388, + 9.79, + 7.717, + 8.014, + 7.407, + 8.015, + 8.835, + 7.786, + 8.162, + 8.588, + 19.779, + 9.491, + 7.809, + 7.502, + 9.03, + 8.768, + 8.272, + 7.926, + 8.315, + 7.659, + 7.803, + 7.335, + 8.103, + 7.255, + 8.483, + 8.009, + 7.989, + 7.414, + 8.963, + 7.761, + 8.282, + 7.321, + 7.519, + 8.993, + 9.034, + 8.225, + 7.29, + 9.962, + 8.866, + 7.83, + 7.362, + 11.155, + 8.447, + 7.304, + 7.288, + 8.277, + 9.116, + 7.927, + 8.576, + 9.346, + 10.46, + 8.172, + 7.119, + 8.317, + 7.451, + 8.396, + 8.517, + 10.767, + 8.054, + 7.663, + 8.573, + 7.864, + 7.991, + 7.402, + 8.213, + 8.685, + 7.544, + 7.089, + 11, + 9.111, + 8.827, + 9.598, + 11.326, + 21.016, + 7.976, + 7.754, + 9.804, + 7.444, + 7.706, + 8.744, + 17.569, + 7.332, + 7.88, + 8.285, + 8.244, + 7.345, + 10.939, + 8.083, + 8.959, + 10.982, + 7.335, + 9.785, + 8.764, + 8.684, + 8.063, + 11.057, + 7.942, + 7.673, + 7.195, + 9.416, + 10.386, + 7.289, + 7.361, + 8.39, + 9.228, + 8.023, + 8.056, + 7.813, + 10.658, + 9.925, + 8.493, + 7.782, + 7.419, + 9.89, + 9.059, + 8.28, + 7.625, + 7.753, + 8.99, + 8.61, + 7.248, + 8.087, + 29.855, + 8.319, + 7.475, + 8.284, + 13.085, + 10.526, + 7.657, + 7.514, + 8.538, + 16.297, + 10.774, + 7.825, + 9.663, + 10.818, + 8.178, + 7.465, + 19.192, + 10.313, + 8.917, + 8.002, + 8.569, + 8.343, + 9.047, + 13.38, + 8.298, + 7.587, + 7.658, + 9.272, + 14.507, + 7.202, + 7.392, + 10.669, + 9.739, + 7.418, + 7.679, + 10.887, + 24.884, + 8.43, + 7.813, + 8.572, + 8.817, + 8.331, + 8.352, + 9.143, + 7.169, + 8.915, + 7.381, + 7.895, + 7.397, + 9.118, + 14.527, + 8.256, + 7.435, + 7.863, + 14.378, + 7.675, + 7.112, + 7.444, + 9.563, + 16.683, + 7.753, + 7.532, + 8.376, + 9.332, + 7.448, + 8.308, + 7.839, + 9.054, + 7.706, + 7.969, + 8.046, + 8.588, + 9.364, + 8.08, + 22.45, + 7.855, + 7.924, + 28.344, + 8.128, + 7.707, + 9.479, + 16.106, + 14.097, + 7.486, + 8.13, + 16.645, + 12.956, + 7.543, + 7.254, + 13.175, + 9.122, + 7.287, + 7.405, + 8.348, + 11.966, + 7.965, + 7.694, + 8.262, + 7.89, + 7.507, + 9.247, + 8.196, + 7.439, + 9.24, + 11.095, + 8.529, + 7.554, + 9.174, + 12.337, + 9.418, + 7.298, + 7.416, + 15.476, + 8.282, + 7.271, + 7.248, + 10.079, + 7.54, + 7.29, + 9.22, + 9.167, + 8.593, + 7.595, + 7.235, + 7.772, + 8.084, + 9.079, + 7.783, + 8.559, + 7.995, + 9.279, + 15.683, + 9.296, + 7.128, + 9.787, + 9.791, + 7.501, + 7.624, + 9.16, + 13.451, + 8.39, + 7.468, + 7.567, + 9.167, + 12.733, + 7.328, + 7.577, + 8.817, + 17.134, + 8.667, + 7.52, + 8.018, + 8.303, + 7.471, + 7.648, + 15.361, + 8.864, + 9.851, + 11.805, + 10.694, + 7.781, + 8.819, + 9.781, + 11.206, + 7.731, + 11.324, + 9.873, + 9, + 7.369, + 8.159, + 8.505, + 7.655, + 7.468, + 7.848, + 13.935, + 7.809, + 7.389, + 7.581, + 9.559, + 8.15, + 7.47, + 7.805, + 11.075, + 7.608, + 7.654, + 11.593, + 8.835, + 7.331, + 9.568, + 9.386, + 10.487, + 7.953, + 8.279, + 10.184, + 8.433, + 7.418, + 9.122, + 10.723, + 9.571, + 7.44, + 7.992, + 8.708, + 7.653, + 7.445, + 7.774, + 8.731, + 11.163, + 7.544, + 7.277, + 8.658, + 9.604, + 8.293, + 14.887, + 8.257, + 9.177, + 11.399, + 9.778, + 8.253, + 7.47, + 8.791, + 11.191, + 9.005, + 7.34, + 11.295, + 10.06, + 9.3, + 7.242, + 7.783, + 8.71, + 9.849, + 7.261, + 7.635, + 8.283, + 9.171, + 9.86, + 7.175, + 9.095, + 8.099, + 8.934, + 13.099, + 8.525, + 8.821, + 10.93, + 9.123, + 7.773, + 7.594, + 8.137, + 9.078, + 9.602, + 7.355, + 8.841, + 8.499, + 11.064, + 7.54, + 8.111, + 8.377, + 9.785, + 7.226, + 7.885, + 9.015, + 9.312, + 7.585, + 7.343, + 7.676, + 8.619, + 7.699, + 7.624, + 7.946, + 9.478, + 8.323, + 11.336, + 12.381, + 9.183, + 9.448, + 12.894, + 10.327, + 7.14, + 7.902, + 10.189, + 16.854, + 7.274, + 7.72, + 12.668, + 16.638, + 7.618, + 7.439, + 9.305, + 7.873, + 7.338, + 7.259, + 11.927, + 8.365, + 7.645, + 9.17, + 8.09, + 8.183, + 7.64, + 11.154, + 7.925, + 7.588, + 8.418, + 8.282, + 11.969, + 7.382, + 8.188, + 9.877, + 22.517, + 7.533, + 8.792, + 9.426, + 12.563, + 7.464, + 8.397, + 9.959, + 7.272, + 7.261, + 7.497, + 8.1, + 7.755, + 7.345, + 7.473, + 8.271, + 8.012, + 7.428, + 10.684, + 8.791, + 7.328, + 7.868, + 9.361, + 9.599, + 8.037, + 8.39, + 9.343, + 9.355, + 7.338, + 8.072, + 8.082, + 10.548, + 7.291, + 7.769, + 9.177, + 7.145, + 7.162, + 7.869, + 8.006, + 7.576, + 7.525, + 7.349, + 12.354, + 8.15, + 9.324, + 20.006, + 10.578, + 7.421, + 11.255, + 9.6, + 18.759, + 7.787, + 8.402, + 10.756, + 7.243, + 8.192, + 7.509, + 10.011, + 11.986, + 7.826, + 9.157, + 10.51, + 7.518, + 7.186, + 7.725, + 8.112, + 9.279, + 7.622, + 7.837, + 7.497, + 8.049, + 7.308, + 10.622, + 7.851, + 7.553, + 8.237, + 8.905, + 12.507, + 7.944, + 8.797, + 9.406, + 14.999, + 7.296, + 9.096, + 8.952, + 7.487, + 7.16, + 7.415, + 8.559, + 7.658, + 7.211, + 7.145, + 8.647, + 9.182, + 7.317, + 7.166, + 9, + 7.666, + 7.313, + 7.93, + 14.326, + 7.49, + 8.052, + 8.09, + 12.433, + 7.743, + 7.979, + 8.706, + 7.853, + 7.327, + 7.364, + 10.37, + 7.494, + 7.345, + 9.265, + 8.663, + 13.09, + 7.587, + 7.614, + 12.42, + 7.513, + 8.813, + 8.041, + 9.803, + 8.275, + 7.587, + 7.805, + 7.968, + 7.964, + 7.285, + 11.612, + 8.487, + 7.288, + 8.104, + 10.011, + 8.127, + 7.27, + 10.193, + 8.338, + 18.64, + 7.329, + 7.5, + 12.318, + 12.686, + 8.228, + 8.043, + 11.074, + 10.128, + 9.154, + 8.118, + 8.142, + 7.943, + 7.698, + 7.767, + 8.227, + 10.143, + 7.937, + 9.912, + 8.065, + 7.326, + 9.357, + 9.9, + 8.343, + 7.278, + 7.965, + 9.043, + 12.946, + 7.32, + 7.359, + 8.137, + 10.926, + 7.28, + 7.958, + 8.065, + 7.587, + 7.473, + 7.713, + 8.322, + 8.389, + 7.587, + 7.42, + 7.796, + 7.642, + 10.007, + 8.769, + 7.709, + 7.749, + 10.087, + 8.309, + 8.259, + 7.836, + 8.395, + 12.346, + 8.185, + 7.914, + 7.862, + 11.154, + 8.125, + 7.399, + 8.781, + 12.435, + 19.802, + 7.171, + 7.694, + 10.094, + 8.237, + 7.663, + 7.145, + 7.826, + 7.664, + 8.171, + 7.709, + 7.943, + 8.048, + 8.098, + 10.757, + 7.856, + 7.528, + 9.938, + 9.934, + 7.534, + 7.191, + 7.642, + 9.965, + 8.519, + 7.932, + 7.29, + 9.221, + 13.428, + 7.218, + 7.586, + 10.323, + 12.404, + 7.611, + 7.424, + 13.043, + 8.235, + 8.767, + 8.517, + 9.042, + 8.993, + 9.495, + 13.331, + 8.201, + 7.598, + 9.877, + 12.582, + 7.855, + 7.42, + 10.158, + 9.663, + 8.397, + 7.229, + 9.668, + 10.838, + 30.011, + 8.486, + 7.142, + 9.673, + 7.16, + 7.368, + 7.129, + 8.389, + 7.525, + 7.609, + 7.39, + 14.465, + 10.468, + 7.599, + 9.193, + 9.789, + 8.883, + 8.288, + 13.844, + 9.083, + 7.394, + 10.049, + 12.258, + 7.746, + 7.447, + 8.203, + 8.499, + 18.372, + 7.31, + 8.074, + 8.263, + 13.483, + 7.205, + 7.892, + 7.707, + 7.496, + 7.246, + 8.322, + 12.547, + 10.402, + 7.328, + 11.039, + 9.511, + 8.613, + 7.824, + 11.096, + 10.79, + 7.23, + 8.435, + 28.574, + 10.892, + 7.316, + 9.689, + 8.35, + 10.703, + 7.567, + 8.591, + 9.328, + 7.184, + 8.82, + 7.22, + 12.329, + 7.878, + 8.363, + 15.502, + 8.357, + 7.154, + 7.424, + 10.013, + 22.887, + 7.809, + 7.822, + 8.907, + 19.77, + 7.402, + 8.059, + 8.127, + 11.234, + 7.481, + 9.121, + 8.025, + 7.31, + 7.507, + 8.208, + 8.464, + 7.241, + 7.149, + 7.293, + 9.595, + 7.408, + 8.342, + 9.767, + 7.83, + 8.143, + 7.652, + 8.176, + 11.254, + 8.172, + 8.743, + 9.446, + 11.75, + 7.969, + 8.124, + 9.895, + 7.684, + 7.381, + 9.047, + 9.366, + 7.221, + 7.647, + 8.641, + 7.754, + 14.15, + 7.568, + 7.771, + 8.208, + 7.274, + 7.156, + 8.819, + 10.58, + 10.695, + 8.021, + 10.434, + 8.966, + 10.948, + 8.066, + 9.48, + 12.203, + 8.325, + 7.516, + 8.673, + 10.698, + 7.313, + 8.317, + 7.739, + 7.282, + 7.536, + 9.621, + 9.593, + 7.505, + 7.701, + 8.542, + 8.736, + 7.671, + 7.205, + 8.179, + 8.683, + 7.599, + 7.187, + 11.627, + 11.45, + 10.069, + 8.096, + 9.732, + 14.351, + 10.195, + 7.994, + 8.86, + 7.229, + 7.665, + 8.815, + 8.076, + 9.005, + 8.24, + 11.858, + 11.454 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145010_ae6aba74-70ae-46ee-ba68-c744bc46ae25.mp4", + "tMs": -27527 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145010_8d55f288-2e68-4c7f-9c6d-733caf51fd3a.mp4", + "tMs": -27527 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145010_56d21df3-05a5-4593-81ec-45012e8e207a.mp4", + "tMs": -27520 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145010_f5178039-b645-44bd-9c91-f59d71d3d5b2.mp4", + "tMs": -27519 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145023_30deed36-3f9b-4635-baa4-b307ae1b7637.mp4", + "tMs": -17318 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145023_070f5ace-8396-4bec-8ceb-2953ae5650b5.mp4", + "tMs": -17312 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145023_40014231-e21f-4252-bfc8-2fa74fa61bfc.mp4", + "tMs": -17302 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145023_d02ace66-b354-4495-87d1-68095fa20208.mp4", + "tMs": -17297 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145033_3acb7993-954d-49fb-bceb-2452a3d71473.mp4", + "tMs": -7318 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145033_bd91b045-fd9f-4738-a53b-d44ca5b685cd.mp4", + "tMs": -7313 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145033_9ae9e3d3-c2d2-4f8b-9315-817be16ce7aa.mp4", + "tMs": -7307 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145033_c1be8d20-01db-49c4-863f-e66d892d2926.mp4", + "tMs": -7307 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145043_bfd21a70-7cb1-4d7a-99a2-1fe46226e517.mp4", + "tMs": 2683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145043_d3503c16-4612-4097-8560-737cc6c83efa.mp4", + "tMs": 2686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145043_4b912550-3e3f-4f50-967f-55dd783d9229.mp4", + "tMs": 2693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145043_7dfc1764-93f9-4f86-937a-e18a3498e58e.mp4", + "tMs": 2698 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145053_eef874ca-6174-43a3-babf-efa692aef7f8.mp4", + "tMs": 12682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145053_c5725b32-b5ad-49d4-a20a-57703d062270.mp4", + "tMs": 12689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145053_cf3322a0-2195-4f50-b34b-e45eb95d4ce1.mp4", + "tMs": 12694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145053_f960bb5f-ce07-4769-985a-f428c7a29686.mp4", + "tMs": 12695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145103_4e034809-54b8-41c7-a7d3-7023bda60965.mp4", + "tMs": 22683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145103_213facd3-9e34-44e7-8a40-e4700e0b8fcf.mp4", + "tMs": 22683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145103_f947bf0b-f833-4aa2-966b-7eb06d702745.mp4", + "tMs": 22692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145103_21d69f5c-7567-4067-a658-674dcabd5cc3.mp4", + "tMs": 22694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145113_92a40f9f-3bae-4303-b10d-48565420c579.mp4", + "tMs": 32682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145113_fc496a71-5164-4be0-84ec-d5e862b0196b.mp4", + "tMs": 32687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145113_39d5f795-3131-47e1-b0eb-a59f1c58e6e8.mp4", + "tMs": 32699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145113_c3a1038e-591f-4d88-9a3a-2506e25ebe63.mp4", + "tMs": 32699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145123_3605d99f-b337-4841-b963-af18fbd784f3.mp4", + "tMs": 42686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145123_e207ae57-9992-4573-a09c-62f18d159ed2.mp4", + "tMs": 42689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145123_8da4b3eb-d0eb-471c-8e30-f845e117b691.mp4", + "tMs": 42695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145123_a940fbfc-5dfb-4082-8727-a20e4dfb9849.mp4", + "tMs": 42714 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145133_1b2202cd-4549-4339-a48e-169108c5aef9.mp4", + "tMs": 52682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145133_fbcfa5f2-488c-473e-8edd-bfa4bd0ab60f.mp4", + "tMs": 52686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145133_61d090b3-f549-4d32-9294-7fdc49aaa24c.mp4", + "tMs": 52696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145133_4eca2ace-a3b3-479a-9674-fa7fc855fe30.mp4", + "tMs": 52696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145143_e031a1df-ccfc-4580-b6d4-8a0ee2c41104.mp4", + "tMs": 62682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145143_e721bb61-db68-4e3c-92ee-e1ee498fdc2d.mp4", + "tMs": 62692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145143_497419d1-1fdd-4c8c-acfd-6e77af0d4486.mp4", + "tMs": 62709 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145143_7772fc05-1c68-4bb3-bd22-4c98a1094e3c.mp4", + "tMs": 62709 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145153_814a341c-6a3c-4a02-8bbc-8d03348bb5b4.mp4", + "tMs": 72682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145153_c5548ec2-5cb5-48c0-8177-0bde5ba4e82c.mp4", + "tMs": 72692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145153_0c8a93c5-5a97-4d93-aba2-035457429385.mp4", + "tMs": 72699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145153_210a355a-ef90-4c13-8cb5-dc09cdf4bb6e.mp4", + "tMs": 72700 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145203_62b891fb-0175-4184-a327-0db7665fcb9c.mp4", + "tMs": 82682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145203_ba202d3a-d8da-4545-b1c7-95c9fadf87cb.mp4", + "tMs": 82687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145203_1a3742a9-b946-47a1-b02f-38793c9b91ff.mp4", + "tMs": 82691 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145203_98873109-6481-42e7-acaa-8ee13170721e.mp4", + "tMs": 82701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145213_f7a0e6f8-9060-46e6-97a7-11db6059733e.mp4", + "tMs": 92682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145213_ccbf840c-cc5b-4b11-b783-9c77797d9912.mp4", + "tMs": 92691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145213_64141da2-6c66-4615-a95f-bd2c742d72d5.mp4", + "tMs": 92706 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145213_ce0e387d-b647-4850-bf04-318cb3cdf629.mp4", + "tMs": 92707 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145223_068d63aa-bc7a-4a84-b4cf-0f7957fcde02.mp4", + "tMs": 102685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145223_d4da982b-f77d-4d1b-9804-a57745769dd8.mp4", + "tMs": 102687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145223_0f37238f-36d0-4a35-9bd3-6bd93d5bb239.mp4", + "tMs": 102692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145223_49890e37-c863-4838-b599-3e78c1a89dd2.mp4", + "tMs": 102698 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145233_e7abb2ea-a1b1-485b-8f7a-217cf6da9352.mp4", + "tMs": 112685 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145233_cd88f6e0-42ee-461c-8f1e-539d90b6052d.mp4", + "tMs": 112687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145233_b9acc096-dc93-422a-81ee-2dcd0d15e850.mp4", + "tMs": 112696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145233_301ab344-1cc8-474a-a897-1332940af31b.mp4", + "tMs": 112700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145243_d8eee073-3bca-40fe-8941-31befb1118c6.mp4", + "tMs": 122686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145243_4b3479f2-39c3-422a-8708-41a8ac9dda9a.mp4", + "tMs": 122688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145243_6588bc00-5765-4519-91aa-84fccf86b590.mp4", + "tMs": 122692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145243_bfb9cbc4-490a-4da0-9ebb-a9c86cd22f92.mp4", + "tMs": 122701 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145253_6f291634-e21f-4a04-ad95-e85c9bd6ddd0.mp4", + "tMs": 132682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145253_504b4e1f-d0cb-41aa-a6fe-37385b633b50.mp4", + "tMs": 132691 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145253_b1a9cc26-1265-45f0-99e4-02bce809a604.mp4", + "tMs": 132694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145253_bdd9ae66-7aa6-44d1-8eba-b728a999ca86.mp4", + "tMs": 132695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145303_a740c687-3e9c-43dd-ae9a-d91be2a0fda4.mp4", + "tMs": 142685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145303_e5b2573a-bdb7-45f5-b2af-30f2a1de9ca9.mp4", + "tMs": 142686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145303_01125b25-60f8-4403-abc3-cbfddd873369.mp4", + "tMs": 142694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145303_8ef1cdd3-1db0-4493-8242-d258ef62d811.mp4", + "tMs": 142694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145313_0bdf95ff-417c-464c-b058-bf18a6b5846d.mp4", + "tMs": 152683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145313_664079dc-3fb2-47c2-a367-2e591205561a.mp4", + "tMs": 152684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145313_24d07eff-68d4-4dd8-92b3-6e70f79679ff.mp4", + "tMs": 152694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145313_737f5f95-70e3-4af6-8a4b-b1ab6df312ed.mp4", + "tMs": 152697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145323_28525ace-6da0-470c-94a9-a27164a685a9.mp4", + "tMs": 162684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145323_4292292e-edd3-4cd6-b041-056737df5898.mp4", + "tMs": 162686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145323_39bba6fd-fbcb-4d0f-b70a-e3f627019158.mp4", + "tMs": 162694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145323_8b980fb6-5ec9-4ff2-9901-c027c66ff56a.mp4", + "tMs": 162694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145333_e408ee44-dff8-40db-8679-ad96bd6acc54.mp4", + "tMs": 172695 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145333_eb7daa6e-a76d-4fd2-bb34-93ba46ea92d8.mp4", + "tMs": 172713 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145333_48710250-f9fa-46cf-bdd7-0ce0a60ebb60.mp4", + "tMs": 172713 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145333_10823e07-5781-4f70-a46a-a99adbba1cc7.mp4", + "tMs": 172713 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145343_6c610f73-53f2-4a5c-b3c8-af081f9ae387.mp4", + "tMs": 182686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145343_e8449efe-30e7-447b-8d57-7846bc91c0ea.mp4", + "tMs": 182688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145343_dac3b1b0-f443-4d19-adea-4afb1df12447.mp4", + "tMs": 182696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145343_29ea8327-6f78-472e-b79b-1759950e306e.mp4", + "tMs": 182727 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145353_7db36d50-eb7c-4848-a2aa-a871b6373afc.mp4", + "tMs": 192683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145353_af1095df-0540-45d5-a01d-2baac8c96ae7.mp4", + "tMs": 192690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145353_b9c78221-26c3-4ee5-aeaf-1212a4b29efe.mp4", + "tMs": 192698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145353_f9b8fccc-40de-4d7f-8057-c5a6e25bcaf7.mp4", + "tMs": 192698 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145403_c22d8f8f-1456-43f4-8bf7-14bdbed19bdd.mp4", + "tMs": 202684 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145403_a23aadab-ba2e-4ab5-ab47-81d881f23cca.mp4", + "tMs": 202690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145403_1104f704-ceba-4dee-a731-43d84e32847f.mp4", + "tMs": 202696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145403_1b7cf0fa-924b-4c84-84d8-306d370c9591.mp4", + "tMs": 202696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145413_766141e8-f1ad-4f1b-904f-433d6ce42440.mp4", + "tMs": 212682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145413_87a99a25-69c4-40fb-a0c5-8ca1d5f0bba1.mp4", + "tMs": 212689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145413_b5711df5-f548-42e5-ab82-07b2c3d0860b.mp4", + "tMs": 212697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145413_e5c89e76-c78e-4a53-9436-55d1175ac5c0.mp4", + "tMs": 212699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145423_a9c5f3c9-6490-462f-9bc1-928d18761343.mp4", + "tMs": 222683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145423_6ec213f9-e114-47d1-8888-1d3b3b0fdfbe.mp4", + "tMs": 222690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145423_5a2fce00-1c2d-43ea-905b-5328b46a2ac0.mp4", + "tMs": 222697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145423_5ef5ee74-9099-481a-b039-56321337c153.mp4", + "tMs": 222697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145433_62802b7f-66bf-4257-9519-3f01e6a4b008.mp4", + "tMs": 232681 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145433_3240aa26-bf9c-41ec-9a1a-7e3f6cd27724.mp4", + "tMs": 232691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145433_57e4f757-652e-40de-91c4-d636f4c1c7db.mp4", + "tMs": 232696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145433_7beafc8a-0423-4516-8b9a-6ecd5e62ec8e.mp4", + "tMs": 232696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145443_e2ce5a12-0873-4be9-842f-dc0f18fc99a9.mp4", + "tMs": 242681 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145443_3b8a1fc3-f82e-4b9b-8ccd-d4e0b9f7f690.mp4", + "tMs": 242686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145443_4f6735e7-22cc-4507-809b-00681a4c3189.mp4", + "tMs": 242693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145443_c5479821-fe47-4d36-a455-1e97a0783831.mp4", + "tMs": 242693 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145453_6b076d81-7d47-4b5a-bd5b-3b7d3f1ea456.mp4", + "tMs": 252686 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145453_b218c8cf-c92c-4e65-b41e-03c96b5d19d4.mp4", + "tMs": 252689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145453_2f1c244a-5e50-4051-abe7-3e8e733b9a75.mp4", + "tMs": 252696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145453_bde07e27-655a-4846-a8cb-1c5652ca478e.mp4", + "tMs": 252696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145503_56643d30-0d7d-4e80-a809-3270371898ed.mp4", + "tMs": 262685 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145503_874cf8f8-3eab-46c6-90fc-4eca5fe04462.mp4", + "tMs": 262704 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145503_bb3efae3-3541-49fe-8065-64ab782ad091.mp4", + "tMs": 262704 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145503_bd99220f-6da5-45c3-bf87-285ff16be253.mp4", + "tMs": 262704 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145513_dbe3343c-17f3-4399-b3ac-fb7a374a714f.mp4", + "tMs": 272682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145513_bf241a66-fd79-48e1-800b-320a19fb8c68.mp4", + "tMs": 272696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145513_e646b6d1-b05e-4c3c-b1eb-f4afc7df0385.mp4", + "tMs": 272696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145513_edb12b4e-2bb5-4b62-a946-3b605ae1e077.mp4", + "tMs": 272704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145523_5e95ee2c-e573-4052-99f5-0df92eaff8bd.mp4", + "tMs": 282684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145523_7ba66fe4-4986-415a-9daf-5e1f491459d2.mp4", + "tMs": 282689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145523_bed6b7ad-29dd-49ce-8680-452e22076e53.mp4", + "tMs": 282692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145523_6124a43b-53aa-4467-b575-c158719e8d80.mp4", + "tMs": 282695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145533_00b8b3e7-bcbf-49fa-8b13-26d6b7aedc5b.mp4", + "tMs": 292683 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145533_8fbab908-51f0-4029-b985-08bbd5cfe59e.mp4", + "tMs": 292686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145533_2ba5e8de-498b-4b56-96e9-591fd0273660.mp4", + "tMs": 292692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145533_ed36e5bc-7a07-43cc-8952-811d6b8d4432.mp4", + "tMs": 292697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145543_1f947c1e-4222-427f-9d5d-8f87fcd062ce.mp4", + "tMs": 302685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145543_58a0127e-c14b-4a74-b4bc-ca5323df6a55.mp4", + "tMs": 302689 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145543_c2f6dbc8-acd9-450e-89fc-64f538226881.mp4", + "tMs": 302693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145543_c92de115-fbf6-4b93-95ab-684cc02497bf.mp4", + "tMs": 302695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145553_14aba4b8-4867-416a-87e0-100ada81ec87.mp4", + "tMs": 312681 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145553_5c2486af-3704-48c5-8639-be0f8b9600e2.mp4", + "tMs": 312687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145553_ff73c0f5-02bd-4de4-91d0-72ccd8401d12.mp4", + "tMs": 312691 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145553_d326f6f5-e02b-4204-a9f0-cb0acc23cd08.mp4", + "tMs": 312699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145603_02b22e94-e3ae-444e-8ab1-1c75eb109771.mp4", + "tMs": 322685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145603_002b40ac-98ae-4be3-b1c5-c170565c94b8.mp4", + "tMs": 322688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145603_d25387a5-265c-46bd-941f-bdd98e3c01ae.mp4", + "tMs": 322692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145603_8d95a584-9dda-45b5-883d-dacbf6294ac8.mp4", + "tMs": 322694 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145613_2c8d4e36-92e9-48d1-aa85-44b2c0bb3e3e.mp4", + "tMs": 332684 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145613_016c5764-0b39-4cec-9711-d6d8457e25c9.mp4", + "tMs": 332687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145613_c0a02bc6-7953-443d-846c-34c52f0a553b.mp4", + "tMs": 332697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145613_ff8ae6b4-5c89-41b3-9734-481843a19739.mp4", + "tMs": 332697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145623_2db66288-a321-4f90-8977-a9b1f98afdc7.mp4", + "tMs": 342686 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145623_2d587857-5ce6-414d-b0e0-bc6eba2a6bbd.mp4", + "tMs": 342702 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145623_6d2c6269-6ce8-4a30-9cfc-b86ffd55ae2e.mp4", + "tMs": 342704 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145623_99b86f27-b9af-4243-a1eb-99ddfb062d65.mp4", + "tMs": 342710 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145633_f5360d02-e3a2-4ff5-8e49-97677529bde1.mp4", + "tMs": 352689 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145633_34225f32-1399-4a5c-a416-e41fe67e0194.mp4", + "tMs": 352690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145633_69c1dfc9-a6cf-43cc-9b0a-f306da6fa335.mp4", + "tMs": 352703 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145633_baa11d70-5e32-4c86-98c1-d3b1729f71ce.mp4", + "tMs": 352708 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145643_a70b8b57-aac0-4ce1-b1ff-0c00dcf473d3.mp4", + "tMs": 362688 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145643_786fdb14-22f9-4ba7-8e80-a6e964ab2885.mp4", + "tMs": 362690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145643_af138d15-34f0-4ea8-beac-e34051a412e4.mp4", + "tMs": 362697 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145643_1bdd20e7-d98c-47d0-841d-a88a6a43b5c2.mp4", + "tMs": 362697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145653_56648880-a3d2-42c9-b981-d1665de0ec37.mp4", + "tMs": 372682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145653_f6bb0793-bb31-4844-9b54-0b415cf283a1.mp4", + "tMs": 372683 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145653_dd3aa563-731f-4406-ab84-4943f9ec257b.mp4", + "tMs": 372692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145653_5410049b-8c78-4030-9515-d16eb6549aa5.mp4", + "tMs": 372697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145703_7e6c9cd0-e660-4ecb-8ff1-a6496367ccb5.mp4", + "tMs": 382686 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145703_53249cb1-7a33-4c54-968b-007828a49dbd.mp4", + "tMs": 382695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145703_1bf7cd1d-d2e9-4def-a401-442a854a283e.mp4", + "tMs": 382695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145703_d4818c6f-2e7b-46a9-a616-317bc629d55f.mp4", + "tMs": 382703 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145713_ffb1f162-9bd1-4e8d-95be-d779389f06cc.mp4", + "tMs": 392684 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145713_ce89dea2-0614-43d8-9138-41884d5c9390.mp4", + "tMs": 392685 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145713_ad3d0631-2b86-4418-b66f-bb59f401498a.mp4", + "tMs": 392688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145713_ae96d942-9a44-48c9-8b88-6480f7f807bb.mp4", + "tMs": 392694 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145723_464a84e1-1361-4288-ab36-166a99ad286e.mp4", + "tMs": 402683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145723_31f8737c-5c01-4454-b6d1-1993553cac4d.mp4", + "tMs": 402690 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145723_9b27bba7-545a-41e4-b8a9-144621431514.mp4", + "tMs": 402694 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145723_4739fdce-08d5-4425-bc7f-459887f5acd3.mp4", + "tMs": 402696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145733_514ee2f4-a2cf-4fdf-8ff8-1f6d49823ad4.mp4", + "tMs": 412685 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145733_5aaea48f-f495-44ad-9f13-fb1c9849ab0a.mp4", + "tMs": 412688 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145733_67f717e9-73a0-4243-90ba-8dcb93f809a3.mp4", + "tMs": 412699 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145733_dbfdd60b-138e-4d97-9b4d-0b459a638af3.mp4", + "tMs": 412699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145743_1eb8a4b2-543f-4d02-9bb4-8f3cf85c0ef4.mp4", + "tMs": 422686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145743_5b814958-0b92-475a-ab7f-162094efe65f.mp4", + "tMs": 422687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145743_ea9c8b0f-6ff3-43ef-80b0-c52c53a2e831.mp4", + "tMs": 422697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145743_90627206-3f37-4e19-8ee3-df8921152cc9.mp4", + "tMs": 422697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145753_879e2cfb-c039-489a-84f0-8091413d6902.mp4", + "tMs": 432686 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145753_de6b2ce3-a38a-4e6f-a376-4ed8710f59c2.mp4", + "tMs": 432694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145753_459989f8-7970-427a-8361-69b3a3bdd1c8.mp4", + "tMs": 432698 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145753_534dd8f7-d897-435b-ab75-f087d42123cb.mp4", + "tMs": 432700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145803_1662f7e1-6fa6-4ec9-8524-75927414d8bf.mp4", + "tMs": 442683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145803_e1620ec8-e20f-4709-93bb-312485ccfb0d.mp4", + "tMs": 442688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145803_94591e2e-23f5-41ea-bc35-0f1f2498399f.mp4", + "tMs": 442693 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145803_6aa76432-dc36-4af4-a12a-50562e5487cc.mp4", + "tMs": 442698 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145813_2284c062-cfaa-4ef5-a5e8-865d9948455b.mp4", + "tMs": 452685 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145813_fa432e5e-4e60-4789-95ef-b08e4c883b35.mp4", + "tMs": 452698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145813_3ca8453c-a9b1-4c52-988f-660dd6aa1c0b.mp4", + "tMs": 452699 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145813_89fdc322-bed4-4399-8f12-9f2a590e0330.mp4", + "tMs": 452710 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145823_5b841d68-6c17-4e3c-ac6f-e9c65250c156.mp4", + "tMs": 462684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145823_ffaccab4-4fb7-4b5f-b9a1-4ba989dd81ac.mp4", + "tMs": 462687 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145823_ee6d5416-7342-43a7-9b34-d00f4fd247ac.mp4", + "tMs": 462693 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145823_c3c57181-b496-4acc-88ad-6652b0de32d3.mp4", + "tMs": 462694 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145833_b70c0626-8025-4367-b5af-b0f1baa7c833.mp4", + "tMs": 472697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145833_7e315992-4465-4f0f-869b-9db17a38588d.mp4", + "tMs": 472699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145833_b6f9ccf1-ce9c-4743-af1f-dd910f23ab92.mp4", + "tMs": 472699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145833_cd50d198-2b74-4381-9b86-b35eb5cc9cff.mp4", + "tMs": 472700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145843_a87db35e-1211-4dd6-a476-828d02e368e8.mp4", + "tMs": 482682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145843_9bae83fb-6553-482e-845c-268134923d08.mp4", + "tMs": 482686 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145843_47ac82d4-0b89-4a71-91eb-bac1040f698e.mp4", + "tMs": 482696 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145843_fc04f291-adff-4eae-b2f9-04add361543b.mp4", + "tMs": 482696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145853_2f5e93d6-2ba3-4b01-89bd-9cda9c37b03b.mp4", + "tMs": 492683 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145853_e913ec5f-0d2c-4ad4-a572-7654d2e5ed20.mp4", + "tMs": 492692 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145853_1d543bac-e5ef-4afd-8e93-f151f513d3f5.mp4", + "tMs": 492697 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145853_f621ba5a-351a-44ec-a602-1baff65e8ff2.mp4", + "tMs": 492697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145903_9e1eff05-141e-4060-90fe-c69e18bb67c8.mp4", + "tMs": 502682 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145903_92f22975-afe0-425c-afee-b1480ca41578.mp4", + "tMs": 502695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145903_5729c525-17b3-4e67-9185-3e2cb29190a8.mp4", + "tMs": 502697 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145903_aff0d098-6053-4adc-a228-a997f5d65e3c.mp4", + "tMs": 502712 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145913_edf6b36f-6d77-4dd0-82ac-779856936f17.mp4", + "tMs": 512682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145913_f7a12829-7285-4374-beb4-330efc647f69.mp4", + "tMs": 512688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145913_4c697177-452e-4784-bf48-04162b7d9017.mp4", + "tMs": 512699 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145913_680ed487-eef7-4b1b-9c9d-5924288b6c3d.mp4", + "tMs": 512699 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145923_314e349f-2eb8-4293-9e3c-4843a1e44bfc.mp4", + "tMs": 522682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145923_2aa593ab-b467-4668-9f84-d7a5e4b4f40b.mp4", + "tMs": 522688 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145923_1e362b51-70d0-4f40-8991-6b66bb7132ed.mp4", + "tMs": 522696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145923_ef9b9b3e-9022-4661-9395-e75460f0b750.mp4", + "tMs": 522696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145933_4d1cc851-b7cb-464e-a45b-a0e46ef0bd96.mp4", + "tMs": 532682 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145933_c06aeba5-69b4-4ca5-964c-af0e1d5ecf43.mp4", + "tMs": 532687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145933_857c9311-d40a-43fe-8a50-9595cae6ba69.mp4", + "tMs": 532696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145933_e29cd965-c31b-4e7a-92fd-4da7107ede05.mp4", + "tMs": 532700 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145943_5dbb7b78-de7f-4a89-a66a-5a837a3f1ca9.mp4", + "tMs": 542683 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145943_cfb570d9-e5a5-4573-9f3c-726029d4436f.mp4", + "tMs": 542687 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145943_ca2c0ddf-cfc3-4326-8476-f492f8c110e7.mp4", + "tMs": 542700 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145943_222984f6-53f4-4eab-8a2e-8f911362a37a.mp4", + "tMs": 542704 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_145953_7f40665e-7ed9-4c46-a8fa-eac5472f452b.mp4", + "tMs": 552682 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_145953_c565d2ee-c755-49e7-8988-42c7978d4508.mp4", + "tMs": 552695 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_145953_e50b1d51-9242-4ccc-b868-9972c5ea3e9e.mp4", + "tMs": 552695 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_145953_283ff3e7-e519-4a67-86e3-a59d0661cda8.mp4", + "tMs": 552697 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150003_fdad176a-2972-4a1f-8a5a-bf4ee2428f67.mp4", + "tMs": 562684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150003_a47b7d42-cf58-4a6c-95b8-49ac235c5442.mp4", + "tMs": 562690 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150003_cc431460-c97f-4ad3-9d2f-c7dd3c0c7397.mp4", + "tMs": 562698 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150003_51ea8a52-32e4-4026-a02a-61ed48f3a824.mp4", + "tMs": 562701 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150013_1e50c839-4619-47ac-a006-9db447252bf6.mp4", + "tMs": 572686 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150013_a042d0c5-0b87-45e4-9446-1c9b232e04b7.mp4", + "tMs": 572692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150013_797e0ddd-7a30-45a5-be10-8703ad150899.mp4", + "tMs": 572696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150013_25ca1b07-db0d-499e-b8aa-d3a2ec1eae76.mp4", + "tMs": 572696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150023_dfebcdb6-79a3-47ca-ba98-d75ef7893451.mp4", + "tMs": 582684 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150023_53f09164-b47e-4864-9f10-afc6693ef193.mp4", + "tMs": 582691 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150023_44abf75b-e324-4a48-b295-64b7c7361ee9.mp4", + "tMs": 582692 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150023_f6dd9ee8-4fc4-4edf-a528-bb89d8ab9d36.mp4", + "tMs": 582696 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150033_cd9443c5-ca08-4826-b81f-e45cc9de9042.mp4", + "tMs": 592683 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150033_5b33535c-68a6-48c0-bfed-cbe90a357260.mp4", + "tMs": 592689 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150033_6fcdb7c4-c802-4634-83a2-edb2510d6d5b.mp4", + "tMs": 592696 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150033_0a63f122-8d0d-4d25-84b0-0db58321e872.mp4", + "tMs": 592696 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150043_9e2ee160-9f44-4a8e-a7c4-76a6e31a004d.mp4", + "tMs": 602682 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150043_74474ae6-7670-4917-8f27-042f8bf0746b.mp4", + "tMs": 602691 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150043_046d742f-ffb5-47bd-8d2d-5ce54e10c64a.mp4", + "tMs": 602695 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150043_210ea1ce-f67b-4b5d-b3a2-3a49a5e733c1.mp4", + "tMs": 602695 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 2532, + "assets": { + "by_state_reason": { + "ready": 2532 + }, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 318526436, + "ready_asset_bytes_max": 126889, + "detections_total": 2532, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2532 + } + }, + "finished_at": "2026-11-08T20:00:48.915Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-enabled-saturated.json b/tests/performance/observations/results/2026-11-08-x2-enabled-saturated.json new file mode 100644 index 000000000..e2715f53f --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-enabled-saturated.json @@ -0,0 +1,8678 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "enabled", + "status": "completed", + "started_at": "2026-11-08T19:16:44.359Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 6.87, + 7.52, + 6.37 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "405055d8456e5a351ce4dc41ae15057c8885b2ab150071a9a27c201cf6e8e4d9", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": { + "observation_evidence_enabled": true, + "observation_max_bytes": 67108864 + }, + "config_extra": null, + "system_settings": { + "observation_global_queue_bytes": "4194304" + } + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-tqih19", + "detector": { + "inferences_in_window": 2404, + "inferences_per_stream_per_second": 1.002, + "expected_per_stream_per_second": 1, + "detections_in_window": 2404, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 1063, + "dropped": { + "queue_full": 1341, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 1063, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 133708910, + "high_water_items": 1, + "high_water_bytes": 2771712, + "camera_high_water_items": 1, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 4194304, + "camera_bytes": 4194304, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 1341, + "inline": 0 + }, + "discard_pending_bytes_max": 5529600, + "backlog_items_max": 1, + "backlog_bytes_max": 2771712, + "drop_ring_depth_max": 2, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0, + "queue_allocated_end": 1, + "worker_running_end": 1, + "retained_bytes_by_camera_end": { + "30907631-8d90-4370-b0f8-cf5d315e8db0": 18449169, + "54e8f216-2bf8-455e-9b47-bb0909e9ca89": 20600601, + "5df2d0dc-1510-4281-9427-f15ed0adc3a8": 20117045, + "f3441b87-9a13-4fb7-a7a1-60ffb577016d": 21121451 + }, + "budget_bytes_by_camera_end": { + "30907631-8d90-4370-b0f8-cf5d315e8db0": 67108864, + "54e8f216-2bf8-455e-9b47-bb0909e9ca89": 67108864, + "5df2d0dc-1510-4281-9427-f15ed0adc3a8": 67108864, + "f3441b87-9a13-4fb7-a7a1-60ffb577016d": 67108864 + }, + "history_observations_end": 2516, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 2404, + "sum_seconds": 0.010316419, + "mean_seconds": 0.000004291, + "max_seconds": 0.000404625, + "p50_upper_seconds": 0.000005, + "p99_upper_seconds": 0.00005, + "p999_upper_seconds": 0.00005, + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 1543 + }, + { + "le": 0.00001, + "count": 2369 + }, + { + "le": 0.00005, + "count": 2404 + }, + { + "le": 0.0001, + "count": 2404 + }, + { + "le": 0.00025, + "count": 2404 + }, + { + "le": 0.0005, + "count": 2404 + }, + { + "le": 0.001, + "count": 2404 + }, + { + "le": 0.01, + "count": 2404 + }, + { + "le": "+Inf", + "count": 2404 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 4194304 bytes, 8 per camera)", + "window_unix": { + "start": 1794165447, + "end": 1794166049 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9973.657, + "p50": 9999.806, + "p95": 10012.879, + "p99": 10017.007, + "max": 10018.563, + "mean": 9999.98 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -26.343, + "p50": -0.194, + "p95": 12.879, + "p99": 17.007, + "max": 18.563, + "mean": -0.02 + }, + "db_probe_ms": { + "count": 2317, + "min": 7.008, + "p50": 8.18, + "p95": 14.23, + "p99": 18.628, + "max": 32.759, + "mean": 9.015 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9991.015, + "p50": 9999.901, + "p95": 10012.57, + "p99": 10015.95, + "max": 10016.875, + "mean": 9999.867 + }, + "Load1": { + "count": 60, + "min": 9973.657, + "p50": 10000.072, + "p95": 10012.061, + "p99": 10015.864, + "max": 10017.445, + "mean": 10000.014 + }, + "Load2": { + "count": 60, + "min": 9982.466, + "p50": 9999.202, + "p95": 10015.588, + "p99": 10017.695, + "max": 10018.563, + "mean": 10000.059 + }, + "Load3": { + "count": 60, + "min": 9987.651, + "p50": 9999.751, + "p95": 10011.037, + "p99": 10013.016, + "max": 10013.594, + "mean": 9999.98 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032278160, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15.1, + "p50": 15.1, + "p95": 15.1, + "p99": 15.1, + "max": 15.1, + "mean": 15.1 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 599.081, + "cpu_percent_one_core": { + "count": 518, + "min": 121.439, + "p50": 145.406, + "p95": 198.528, + "p99": 209.451, + "max": 228.718, + "mean": 154.587 + }, + "cpu_percent_one_core_avg": 154.937, + "rss_kib": { + "count": 519, + "min": 777256, + "p50": 784384, + "p95": 881300.4, + "p99": 882364, + "max": 882468, + "mean": 813639.252 + }, + "rss_high_water_kib": 882468, + "disk_write_bytes": 1716695040, + "disk_write_bytes_per_sec": 2865549.623, + "threads_max": 35, + "fds_max": 38 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.068, + "processes": 5 + }, + "storage_bytes_end": 1228278486, + "loadavg_at_end": [ + 2.8, + 3.17, + 4.45 + ] + }, + "window": { + "start": "2026-11-08T19:17:27.273Z", + "end": "2026-11-08T19:27:28.490Z", + "seconds": 601.22 + }, + "samples": { + "proc": [ + { + "tMs": 21, + "cpuTicks": 4667, + "rssKiB": 871844, + "peakRssKiB": 871844, + "threads": 35, + "fds": 36, + "writeBytes": 74113024 + }, + { + "tMs": 1243, + "cpuTicks": 4914, + "rssKiB": 873396, + "peakRssKiB": 873396, + "threads": 35, + "fds": 36, + "writeBytes": 76652544 + }, + { + "tMs": 2399, + "cpuTicks": 5112, + "rssKiB": 777280, + "peakRssKiB": 867772, + "threads": 35, + "fds": 36, + "writeBytes": 79896576 + }, + { + "tMs": 3553, + "cpuTicks": 5264, + "rssKiB": 777776, + "peakRssKiB": 869148, + "threads": 35, + "fds": 36, + "writeBytes": 84516864 + }, + { + "tMs": 4705, + "cpuTicks": 5409, + "rssKiB": 777500, + "peakRssKiB": 869148, + "threads": 35, + "fds": 36, + "writeBytes": 87048192 + }, + { + "tMs": 5857, + "cpuTicks": 5566, + "rssKiB": 777884, + "peakRssKiB": 869148, + "threads": 35, + "fds": 36, + "writeBytes": 89632768 + }, + { + "tMs": 7019, + "cpuTicks": 5735, + "rssKiB": 875292, + "peakRssKiB": 875292, + "threads": 35, + "fds": 37, + "writeBytes": 93466624 + }, + { + "tMs": 8202, + "cpuTicks": 5972, + "rssKiB": 875696, + "peakRssKiB": 875696, + "threads": 35, + "fds": 36, + "writeBytes": 95977472 + }, + { + "tMs": 9352, + "cpuTicks": 6183, + "rssKiB": 851564, + "peakRssKiB": 870836, + "threads": 35, + "fds": 38, + "writeBytes": 99160064 + }, + { + "tMs": 10492, + "cpuTicks": 6347, + "rssKiB": 779400, + "peakRssKiB": 871236, + "threads": 35, + "fds": 36, + "writeBytes": 101945344 + }, + { + "tMs": 11628, + "cpuTicks": 6485, + "rssKiB": 779428, + "peakRssKiB": 871236, + "threads": 35, + "fds": 36, + "writeBytes": 104534016 + }, + { + "tMs": 12771, + "cpuTicks": 6638, + "rssKiB": 779456, + "peakRssKiB": 871236, + "threads": 35, + "fds": 36, + "writeBytes": 107094016 + }, + { + "tMs": 13906, + "cpuTicks": 6789, + "rssKiB": 779460, + "peakRssKiB": 871292, + "threads": 35, + "fds": 36, + "writeBytes": 111570944 + }, + { + "tMs": 15121, + "cpuTicks": 7014, + "rssKiB": 876388, + "peakRssKiB": 876388, + "threads": 35, + "fds": 37, + "writeBytes": 115466240 + }, + { + "tMs": 16274, + "cpuTicks": 7236, + "rssKiB": 876416, + "peakRssKiB": 876416, + "threads": 35, + "fds": 37, + "writeBytes": 118194176 + }, + { + "tMs": 17428, + "cpuTicks": 7402, + "rssKiB": 777256, + "peakRssKiB": 872036, + "threads": 35, + "fds": 37, + "writeBytes": 121679872 + }, + { + "tMs": 18561, + "cpuTicks": 7550, + "rssKiB": 777488, + "peakRssKiB": 872036, + "threads": 35, + "fds": 37, + "writeBytes": 124465152 + }, + { + "tMs": 19696, + "cpuTicks": 7700, + "rssKiB": 777392, + "peakRssKiB": 872036, + "threads": 35, + "fds": 36, + "writeBytes": 127057920 + }, + { + "tMs": 20845, + "cpuTicks": 7854, + "rssKiB": 777792, + "peakRssKiB": 872036, + "threads": 35, + "fds": 36, + "writeBytes": 129474560 + }, + { + "tMs": 21984, + "cpuTicks": 8015, + "rssKiB": 843264, + "peakRssKiB": 872036, + "threads": 35, + "fds": 36, + "writeBytes": 133476352 + }, + { + "tMs": 23167, + "cpuTicks": 8243, + "rssKiB": 876328, + "peakRssKiB": 876328, + "threads": 35, + "fds": 36, + "writeBytes": 137588736 + }, + { + "tMs": 24318, + "cpuTicks": 8455, + "rssKiB": 876656, + "peakRssKiB": 876656, + "threads": 35, + "fds": 36, + "writeBytes": 140181504 + }, + { + "tMs": 25468, + "cpuTicks": 8617, + "rssKiB": 780020, + "peakRssKiB": 872308, + "threads": 35, + "fds": 36, + "writeBytes": 143441920 + }, + { + "tMs": 26604, + "cpuTicks": 8759, + "rssKiB": 780024, + "peakRssKiB": 872308, + "threads": 35, + "fds": 36, + "writeBytes": 145948672 + }, + { + "tMs": 27742, + "cpuTicks": 8907, + "rssKiB": 780028, + "peakRssKiB": 872308, + "threads": 35, + "fds": 36, + "writeBytes": 148713472 + }, + { + "tMs": 28903, + "cpuTicks": 9051, + "rssKiB": 780472, + "peakRssKiB": 872752, + "threads": 35, + "fds": 36, + "writeBytes": 151478272 + }, + { + "tMs": 30080, + "cpuTicks": 9252, + "rssKiB": 878188, + "peakRssKiB": 878188, + "threads": 35, + "fds": 36, + "writeBytes": 155164672 + }, + { + "tMs": 31227, + "cpuTicks": 9461, + "rssKiB": 878244, + "peakRssKiB": 878244, + "threads": 35, + "fds": 36, + "writeBytes": 157736960 + }, + { + "tMs": 32401, + "cpuTicks": 9665, + "rssKiB": 781564, + "peakRssKiB": 873984, + "threads": 35, + "fds": 37, + "writeBytes": 161304576 + }, + { + "tMs": 33531, + "cpuTicks": 9814, + "rssKiB": 780252, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 166068224 + }, + { + "tMs": 34652, + "cpuTicks": 9970, + "rssKiB": 780252, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 168640512 + }, + { + "tMs": 35778, + "cpuTicks": 10130, + "rssKiB": 779944, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 171139072 + }, + { + "tMs": 36899, + "cpuTicks": 10297, + "rssKiB": 780268, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 173621248 + }, + { + "tMs": 38065, + "cpuTicks": 10474, + "rssKiB": 876960, + "peakRssKiB": 876960, + "threads": 35, + "fds": 36, + "writeBytes": 177508352 + }, + { + "tMs": 39231, + "cpuTicks": 10688, + "rssKiB": 876964, + "peakRssKiB": 876964, + "threads": 35, + "fds": 36, + "writeBytes": 180097024 + }, + { + "tMs": 40387, + "cpuTicks": 10900, + "rssKiB": 780316, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 184090624 + }, + { + "tMs": 41510, + "cpuTicks": 11058, + "rssKiB": 780320, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 186679296 + }, + { + "tMs": 42644, + "cpuTicks": 11214, + "rssKiB": 780324, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 189333504 + }, + { + "tMs": 43779, + "cpuTicks": 11373, + "rssKiB": 780004, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 193638400 + }, + { + "tMs": 44909, + "cpuTicks": 11527, + "rssKiB": 779912, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 196644864 + }, + { + "tMs": 46082, + "cpuTicks": 11719, + "rssKiB": 876516, + "peakRssKiB": 876516, + "threads": 35, + "fds": 36, + "writeBytes": 200773632 + }, + { + "tMs": 47239, + "cpuTicks": 11934, + "rssKiB": 876196, + "peakRssKiB": 876196, + "threads": 35, + "fds": 36, + "writeBytes": 202731520 + }, + { + "tMs": 48379, + "cpuTicks": 12116, + "rssKiB": 779740, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 206721024 + }, + { + "tMs": 49524, + "cpuTicks": 12260, + "rssKiB": 779968, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 209272832 + }, + { + "tMs": 50662, + "cpuTicks": 12403, + "rssKiB": 779496, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 212312064 + }, + { + "tMs": 51801, + "cpuTicks": 12566, + "rssKiB": 780364, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 214908928 + }, + { + "tMs": 52929, + "cpuTicks": 12715, + "rssKiB": 781336, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 217473024 + }, + { + "tMs": 54130, + "cpuTicks": 12933, + "rssKiB": 878028, + "peakRssKiB": 878028, + "threads": 35, + "fds": 36, + "writeBytes": 223170560 + }, + { + "tMs": 55270, + "cpuTicks": 13151, + "rssKiB": 878276, + "peakRssKiB": 878276, + "threads": 35, + "fds": 36, + "writeBytes": 225640448 + }, + { + "tMs": 56426, + "cpuTicks": 13321, + "rssKiB": 781592, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 229777408 + }, + { + "tMs": 57563, + "cpuTicks": 13470, + "rssKiB": 781368, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 232493056 + }, + { + "tMs": 58705, + "cpuTicks": 13609, + "rssKiB": 782020, + "peakRssKiB": 873984, + "threads": 35, + "fds": 36, + "writeBytes": 234885120 + }, + { + "tMs": 59850, + "cpuTicks": 13764, + "rssKiB": 782348, + "peakRssKiB": 874380, + "threads": 35, + "fds": 36, + "writeBytes": 237457408 + }, + { + "tMs": 60989, + "cpuTicks": 13930, + "rssKiB": 873080, + "peakRssKiB": 874420, + "threads": 35, + "fds": 36, + "writeBytes": 241143808 + }, + { + "tMs": 62170, + "cpuTicks": 14150, + "rssKiB": 879372, + "peakRssKiB": 879372, + "threads": 35, + "fds": 36, + "writeBytes": 243744768 + }, + { + "tMs": 63315, + "cpuTicks": 14367, + "rssKiB": 879056, + "peakRssKiB": 879056, + "threads": 35, + "fds": 36, + "writeBytes": 248385536 + }, + { + "tMs": 64461, + "cpuTicks": 14530, + "rssKiB": 782372, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 251912192 + }, + { + "tMs": 65594, + "cpuTicks": 14682, + "rssKiB": 782380, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 254365696 + }, + { + "tMs": 66705, + "cpuTicks": 14829, + "rssKiB": 782396, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 256847872 + }, + { + "tMs": 67833, + "cpuTicks": 14980, + "rssKiB": 782400, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 259936256 + }, + { + "tMs": 68960, + "cpuTicks": 15152, + "rssKiB": 782404, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 262561792 + }, + { + "tMs": 70153, + "cpuTicks": 15377, + "rssKiB": 878444, + "peakRssKiB": 878444, + "threads": 35, + "fds": 36, + "writeBytes": 266682368 + }, + { + "tMs": 71312, + "cpuTicks": 15590, + "rssKiB": 853552, + "peakRssKiB": 874500, + "threads": 35, + "fds": 37, + "writeBytes": 269660160 + }, + { + "tMs": 72455, + "cpuTicks": 15744, + "rssKiB": 781064, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 272842752 + }, + { + "tMs": 73614, + "cpuTicks": 15901, + "rssKiB": 780516, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 277721088 + }, + { + "tMs": 74783, + "cpuTicks": 16059, + "rssKiB": 779940, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 280133632 + }, + { + "tMs": 75935, + "cpuTicks": 16224, + "rssKiB": 779976, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 282771456 + }, + { + "tMs": 77106, + "cpuTicks": 16423, + "rssKiB": 876668, + "peakRssKiB": 876668, + "threads": 35, + "fds": 36, + "writeBytes": 285569024 + }, + { + "tMs": 78298, + "cpuTicks": 16673, + "rssKiB": 876896, + "peakRssKiB": 876896, + "threads": 35, + "fds": 36, + "writeBytes": 288919552 + }, + { + "tMs": 79447, + "cpuTicks": 16842, + "rssKiB": 780336, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 292888576 + }, + { + "tMs": 80572, + "cpuTicks": 16996, + "rssKiB": 780364, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 295518208 + }, + { + "tMs": 81686, + "cpuTicks": 17148, + "rssKiB": 780688, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 298106880 + }, + { + "tMs": 82817, + "cpuTicks": 17302, + "rssKiB": 781340, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 300912640 + }, + { + "tMs": 83942, + "cpuTicks": 17469, + "rssKiB": 781664, + "peakRssKiB": 874500, + "threads": 35, + "fds": 37, + "writeBytes": 305246208 + }, + { + "tMs": 85120, + "cpuTicks": 17693, + "rssKiB": 879004, + "peakRssKiB": 879004, + "threads": 35, + "fds": 36, + "writeBytes": 309669888 + }, + { + "tMs": 86281, + "cpuTicks": 17903, + "rssKiB": 879008, + "peakRssKiB": 879008, + "threads": 35, + "fds": 36, + "writeBytes": 312332288 + }, + { + "tMs": 87435, + "cpuTicks": 18093, + "rssKiB": 782004, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 315674624 + }, + { + "tMs": 88586, + "cpuTicks": 18260, + "rssKiB": 782008, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 318242816 + }, + { + "tMs": 89716, + "cpuTicks": 18422, + "rssKiB": 782332, + "peakRssKiB": 874500, + "threads": 35, + "fds": 36, + "writeBytes": 320823296 + }, + { + "tMs": 90851, + "cpuTicks": 18586, + "rssKiB": 783036, + "peakRssKiB": 874812, + "threads": 35, + "fds": 36, + "writeBytes": 323977216 + }, + { + "tMs": 91986, + "cpuTicks": 18759, + "rssKiB": 867020, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 327716864 + }, + { + "tMs": 93172, + "cpuTicks": 18989, + "rssKiB": 879732, + "peakRssKiB": 879732, + "threads": 35, + "fds": 36, + "writeBytes": 331853824 + }, + { + "tMs": 94350, + "cpuTicks": 19231, + "rssKiB": 855564, + "peakRssKiB": 874896, + "threads": 35, + "fds": 37, + "writeBytes": 334573568 + }, + { + "tMs": 95498, + "cpuTicks": 19396, + "rssKiB": 783048, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 337575936 + }, + { + "tMs": 96636, + "cpuTicks": 19542, + "rssKiB": 781424, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 340713472 + }, + { + "tMs": 97765, + "cpuTicks": 19705, + "rssKiB": 781424, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 343113728 + }, + { + "tMs": 98884, + "cpuTicks": 19869, + "rssKiB": 781756, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 345743360 + }, + { + "tMs": 100055, + "cpuTicks": 20062, + "rssKiB": 878452, + "peakRssKiB": 878452, + "threads": 35, + "fds": 36, + "writeBytes": 349573120 + }, + { + "tMs": 101226, + "cpuTicks": 20293, + "rssKiB": 877808, + "peakRssKiB": 877808, + "threads": 35, + "fds": 36, + "writeBytes": 352182272 + }, + { + "tMs": 102367, + "cpuTicks": 20501, + "rssKiB": 862816, + "peakRssKiB": 874896, + "threads": 35, + "fds": 37, + "writeBytes": 355172352 + }, + { + "tMs": 103495, + "cpuTicks": 20674, + "rssKiB": 781148, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 360583168 + }, + { + "tMs": 104624, + "cpuTicks": 20841, + "rssKiB": 781148, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 363294720 + }, + { + "tMs": 105755, + "cpuTicks": 20995, + "rssKiB": 780632, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 365961216 + }, + { + "tMs": 106883, + "cpuTicks": 21152, + "rssKiB": 780352, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 368513024 + }, + { + "tMs": 108072, + "cpuTicks": 21348, + "rssKiB": 876712, + "peakRssKiB": 876712, + "threads": 35, + "fds": 36, + "writeBytes": 372375552 + }, + { + "tMs": 109236, + "cpuTicks": 21565, + "rssKiB": 876732, + "peakRssKiB": 876732, + "threads": 35, + "fds": 36, + "writeBytes": 375631872 + }, + { + "tMs": 110397, + "cpuTicks": 21755, + "rssKiB": 779208, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 379109376 + }, + { + "tMs": 111551, + "cpuTicks": 21900, + "rssKiB": 779868, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 381710336 + }, + { + "tMs": 112695, + "cpuTicks": 22074, + "rssKiB": 780512, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 384348160 + }, + { + "tMs": 113818, + "cpuTicks": 22232, + "rssKiB": 781184, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 389173248 + }, + { + "tMs": 114971, + "cpuTicks": 22391, + "rssKiB": 781184, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 391671808 + }, + { + "tMs": 116178, + "cpuTicks": 22627, + "rssKiB": 877876, + "peakRssKiB": 877876, + "threads": 35, + "fds": 37, + "writeBytes": 395460608 + }, + { + "tMs": 117356, + "cpuTicks": 22872, + "rssKiB": 877876, + "peakRssKiB": 877876, + "threads": 35, + "fds": 38, + "writeBytes": 398204928 + }, + { + "tMs": 118492, + "cpuTicks": 23045, + "rssKiB": 781188, + "peakRssKiB": 874896, + "threads": 35, + "fds": 37, + "writeBytes": 401317888 + }, + { + "tMs": 119623, + "cpuTicks": 23213, + "rssKiB": 782160, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 404242432 + }, + { + "tMs": 120750, + "cpuTicks": 23370, + "rssKiB": 782168, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 406888448 + }, + { + "tMs": 121868, + "cpuTicks": 23528, + "rssKiB": 782192, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 409325568 + }, + { + "tMs": 123023, + "cpuTicks": 23708, + "rssKiB": 878844, + "peakRssKiB": 878844, + "threads": 35, + "fds": 36, + "writeBytes": 415039488 + }, + { + "tMs": 124205, + "cpuTicks": 23932, + "rssKiB": 878844, + "peakRssKiB": 878844, + "threads": 35, + "fds": 36, + "writeBytes": 417599488 + }, + { + "tMs": 125365, + "cpuTicks": 24145, + "rssKiB": 854996, + "peakRssKiB": 874896, + "threads": 35, + "fds": 37, + "writeBytes": 421044224 + }, + { + "tMs": 126497, + "cpuTicks": 24306, + "rssKiB": 782804, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 424140800 + }, + { + "tMs": 127633, + "cpuTicks": 24468, + "rssKiB": 782804, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 426602496 + }, + { + "tMs": 128754, + "cpuTicks": 24616, + "rssKiB": 783148, + "peakRssKiB": 874896, + "threads": 35, + "fds": 36, + "writeBytes": 429281280 + }, + { + "tMs": 129882, + "cpuTicks": 24768, + "rssKiB": 783148, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 431697920 + }, + { + "tMs": 131056, + "cpuTicks": 24963, + "rssKiB": 878964, + "peakRssKiB": 878964, + "threads": 35, + "fds": 37, + "writeBytes": 435445760 + }, + { + "tMs": 132229, + "cpuTicks": 25185, + "rssKiB": 878976, + "peakRssKiB": 878976, + "threads": 35, + "fds": 36, + "writeBytes": 438685696 + }, + { + "tMs": 133399, + "cpuTicks": 25403, + "rssKiB": 782308, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 443850752 + }, + { + "tMs": 134546, + "cpuTicks": 25552, + "rssKiB": 782308, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 446472192 + }, + { + "tMs": 135698, + "cpuTicks": 25699, + "rssKiB": 782004, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 449024000 + }, + { + "tMs": 136852, + "cpuTicks": 25844, + "rssKiB": 782004, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 452366336 + }, + { + "tMs": 138034, + "cpuTicks": 26014, + "rssKiB": 878012, + "peakRssKiB": 878012, + "threads": 35, + "fds": 36, + "writeBytes": 456155136 + }, + { + "tMs": 139235, + "cpuTicks": 26256, + "rssKiB": 878052, + "peakRssKiB": 878052, + "threads": 35, + "fds": 36, + "writeBytes": 458702848 + }, + { + "tMs": 140381, + "cpuTicks": 26456, + "rssKiB": 782016, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 462172160 + }, + { + "tMs": 141529, + "cpuTicks": 26615, + "rssKiB": 782016, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 464793600 + }, + { + "tMs": 142663, + "cpuTicks": 26760, + "rssKiB": 782664, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 468013056 + }, + { + "tMs": 143808, + "cpuTicks": 26921, + "rssKiB": 782664, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 472465408 + }, + { + "tMs": 144946, + "cpuTicks": 27071, + "rssKiB": 783312, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 475090944 + }, + { + "tMs": 146150, + "cpuTicks": 27293, + "rssKiB": 880000, + "peakRssKiB": 880000, + "threads": 35, + "fds": 36, + "writeBytes": 478990336 + }, + { + "tMs": 147296, + "cpuTicks": 27500, + "rssKiB": 880000, + "peakRssKiB": 880000, + "threads": 35, + "fds": 37, + "writeBytes": 481701888 + }, + { + "tMs": 148437, + "cpuTicks": 27667, + "rssKiB": 783312, + "peakRssKiB": 875168, + "threads": 35, + "fds": 36, + "writeBytes": 486060032 + }, + { + "tMs": 149593, + "cpuTicks": 27819, + "rssKiB": 783312, + "peakRssKiB": 875492, + "threads": 35, + "fds": 36, + "writeBytes": 488689664 + }, + { + "tMs": 150736, + "cpuTicks": 27969, + "rssKiB": 783344, + "peakRssKiB": 875684, + "threads": 35, + "fds": 36, + "writeBytes": 491159552 + }, + { + "tMs": 151891, + "cpuTicks": 28131, + "rssKiB": 783344, + "peakRssKiB": 875684, + "threads": 35, + "fds": 36, + "writeBytes": 493637632 + }, + { + "tMs": 153064, + "cpuTicks": 28315, + "rssKiB": 879384, + "peakRssKiB": 879384, + "threads": 35, + "fds": 36, + "writeBytes": 499343360 + }, + { + "tMs": 154241, + "cpuTicks": 28539, + "rssKiB": 879384, + "peakRssKiB": 879384, + "threads": 35, + "fds": 36, + "writeBytes": 502472704 + }, + { + "tMs": 155402, + "cpuTicks": 28736, + "rssKiB": 781508, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 505950208 + }, + { + "tMs": 156529, + "cpuTicks": 28882, + "rssKiB": 781508, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 508514304 + }, + { + "tMs": 157654, + "cpuTicks": 29030, + "rssKiB": 781508, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 511004672 + }, + { + "tMs": 158775, + "cpuTicks": 29189, + "rssKiB": 782480, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 513449984 + }, + { + "tMs": 159893, + "cpuTicks": 29341, + "rssKiB": 782808, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 516894720 + }, + { + "tMs": 161043, + "cpuTicks": 29516, + "rssKiB": 879620, + "peakRssKiB": 879620, + "threads": 35, + "fds": 36, + "writeBytes": 520884224 + }, + { + "tMs": 162219, + "cpuTicks": 29745, + "rssKiB": 879620, + "peakRssKiB": 879620, + "threads": 35, + "fds": 36, + "writeBytes": 523657216 + }, + { + "tMs": 163378, + "cpuTicks": 29945, + "rssKiB": 782932, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 529092608 + }, + { + "tMs": 164508, + "cpuTicks": 30085, + "rssKiB": 782932, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 532422656 + }, + { + "tMs": 165643, + "cpuTicks": 30250, + "rssKiB": 782932, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 535068672 + }, + { + "tMs": 166786, + "cpuTicks": 30410, + "rssKiB": 782932, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 537485312 + }, + { + "tMs": 167941, + "cpuTicks": 30558, + "rssKiB": 782932, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 539955200 + }, + { + "tMs": 169130, + "cpuTicks": 30769, + "rssKiB": 878648, + "peakRssKiB": 878648, + "threads": 35, + "fds": 36, + "writeBytes": 543858688 + }, + { + "tMs": 170302, + "cpuTicks": 30992, + "rssKiB": 878648, + "peakRssKiB": 878648, + "threads": 35, + "fds": 36, + "writeBytes": 546299904 + }, + { + "tMs": 171475, + "cpuTicks": 31165, + "rssKiB": 782284, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 550690816 + }, + { + "tMs": 172623, + "cpuTicks": 31308, + "rssKiB": 782512, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 553304064 + }, + { + "tMs": 173774, + "cpuTicks": 31455, + "rssKiB": 782832, + "peakRssKiB": 875896, + "threads": 35, + "fds": 36, + "writeBytes": 557707264 + }, + { + "tMs": 174920, + "cpuTicks": 31614, + "rssKiB": 783480, + "peakRssKiB": 876200, + "threads": 35, + "fds": 36, + "writeBytes": 560099328 + }, + { + "tMs": 176121, + "cpuTicks": 31829, + "rssKiB": 880492, + "peakRssKiB": 880492, + "threads": 35, + "fds": 36, + "writeBytes": 564830208 + }, + { + "tMs": 177275, + "cpuTicks": 32039, + "rssKiB": 880492, + "peakRssKiB": 880492, + "threads": 35, + "fds": 36, + "writeBytes": 567390208 + }, + { + "tMs": 178438, + "cpuTicks": 32206, + "rssKiB": 784028, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 570785792 + }, + { + "tMs": 179577, + "cpuTicks": 32358, + "rssKiB": 784028, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 573267968 + }, + { + "tMs": 180692, + "cpuTicks": 32514, + "rssKiB": 784028, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 575930368 + }, + { + "tMs": 181819, + "cpuTicks": 32670, + "rssKiB": 784028, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 579137536 + }, + { + "tMs": 182964, + "cpuTicks": 32827, + "rssKiB": 784028, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 581685248 + }, + { + "tMs": 184160, + "cpuTicks": 33052, + "rssKiB": 880716, + "peakRssKiB": 880716, + "threads": 35, + "fds": 36, + "writeBytes": 587583488 + }, + { + "tMs": 185307, + "cpuTicks": 33263, + "rssKiB": 879844, + "peakRssKiB": 879844, + "threads": 35, + "fds": 36, + "writeBytes": 590024704 + }, + { + "tMs": 186454, + "cpuTicks": 33439, + "rssKiB": 782832, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 594325504 + }, + { + "tMs": 187578, + "cpuTicks": 33594, + "rssKiB": 782832, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 596905984 + }, + { + "tMs": 188710, + "cpuTicks": 33734, + "rssKiB": 783480, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 599519232 + }, + { + "tMs": 189865, + "cpuTicks": 33905, + "rssKiB": 783480, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 602353664 + }, + { + "tMs": 191037, + "cpuTicks": 34082, + "rssKiB": 879748, + "peakRssKiB": 879748, + "threads": 35, + "fds": 36, + "writeBytes": 606498816 + }, + { + "tMs": 192224, + "cpuTicks": 34312, + "rssKiB": 879524, + "peakRssKiB": 879524, + "threads": 35, + "fds": 36, + "writeBytes": 609103872 + }, + { + "tMs": 193386, + "cpuTicks": 34529, + "rssKiB": 782836, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 615317504 + }, + { + "tMs": 194537, + "cpuTicks": 34699, + "rssKiB": 782512, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 617926656 + }, + { + "tMs": 195681, + "cpuTicks": 34846, + "rssKiB": 782520, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 620564480 + }, + { + "tMs": 196825, + "cpuTicks": 34996, + "rssKiB": 782744, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 623251456 + }, + { + "tMs": 197950, + "cpuTicks": 35149, + "rssKiB": 781968, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 626511872 + }, + { + "tMs": 199154, + "cpuTicks": 35370, + "rssKiB": 878236, + "peakRssKiB": 878236, + "threads": 35, + "fds": 36, + "writeBytes": 630272000 + }, + { + "tMs": 200304, + "cpuTicks": 35577, + "rssKiB": 854388, + "peakRssKiB": 876924, + "threads": 35, + "fds": 37, + "writeBytes": 633208832 + }, + { + "tMs": 201487, + "cpuTicks": 35750, + "rssKiB": 782188, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 636747776 + }, + { + "tMs": 202632, + "cpuTicks": 35902, + "rssKiB": 782188, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 639430656 + }, + { + "tMs": 203775, + "cpuTicks": 36057, + "rssKiB": 783160, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 644714496 + }, + { + "tMs": 204929, + "cpuTicks": 36214, + "rssKiB": 783388, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 647282688 + }, + { + "tMs": 206124, + "cpuTicks": 36434, + "rssKiB": 880396, + "peakRssKiB": 880396, + "threads": 35, + "fds": 36, + "writeBytes": 651177984 + }, + { + "tMs": 207287, + "cpuTicks": 36657, + "rssKiB": 880396, + "peakRssKiB": 880396, + "threads": 35, + "fds": 36, + "writeBytes": 653594624 + }, + { + "tMs": 208424, + "cpuTicks": 36855, + "rssKiB": 851256, + "peakRssKiB": 876924, + "threads": 35, + "fds": 37, + "writeBytes": 657641472 + }, + { + "tMs": 209557, + "cpuTicks": 37021, + "rssKiB": 784356, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 660570112 + }, + { + "tMs": 210681, + "cpuTicks": 37189, + "rssKiB": 784388, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 663093248 + }, + { + "tMs": 211823, + "cpuTicks": 37344, + "rssKiB": 784388, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 665784320 + }, + { + "tMs": 212960, + "cpuTicks": 37513, + "rssKiB": 784616, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 668196864 + }, + { + "tMs": 214163, + "cpuTicks": 37743, + "rssKiB": 880528, + "peakRssKiB": 880528, + "threads": 35, + "fds": 36, + "writeBytes": 674820096 + }, + { + "tMs": 215331, + "cpuTicks": 37981, + "rssKiB": 879460, + "peakRssKiB": 879460, + "threads": 35, + "fds": 36, + "writeBytes": 677335040 + }, + { + "tMs": 216460, + "cpuTicks": 38150, + "rssKiB": 783000, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 680767488 + }, + { + "tMs": 217583, + "cpuTicks": 38308, + "rssKiB": 782772, + "peakRssKiB": 876924, + "threads": 35, + "fds": 37, + "writeBytes": 683212800 + }, + { + "tMs": 218707, + "cpuTicks": 38476, + "rssKiB": 783096, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 685649920 + }, + { + "tMs": 219838, + "cpuTicks": 38617, + "rssKiB": 783420, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 688979968 + }, + { + "tMs": 220974, + "cpuTicks": 38784, + "rssKiB": 783128, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 692477952 + }, + { + "tMs": 222182, + "cpuTicks": 39018, + "rssKiB": 879372, + "peakRssKiB": 879372, + "threads": 35, + "fds": 36, + "writeBytes": 695414784 + }, + { + "tMs": 223316, + "cpuTicks": 39236, + "rssKiB": 879372, + "peakRssKiB": 879372, + "threads": 35, + "fds": 36, + "writeBytes": 699580416 + }, + { + "tMs": 224453, + "cpuTicks": 39431, + "rssKiB": 782684, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 702803968 + }, + { + "tMs": 225578, + "cpuTicks": 39581, + "rssKiB": 782264, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 706326528 + }, + { + "tMs": 226696, + "cpuTicks": 39738, + "rssKiB": 782036, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 708747264 + }, + { + "tMs": 227823, + "cpuTicks": 39881, + "rssKiB": 781488, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 711258112 + }, + { + "tMs": 228953, + "cpuTicks": 40048, + "rssKiB": 781488, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 713666560 + }, + { + "tMs": 230145, + "cpuTicks": 40287, + "rssKiB": 878176, + "peakRssKiB": 878176, + "threads": 35, + "fds": 36, + "writeBytes": 717639680 + }, + { + "tMs": 231302, + "cpuTicks": 40522, + "rssKiB": 877948, + "peakRssKiB": 877948, + "threads": 35, + "fds": 36, + "writeBytes": 720199680 + }, + { + "tMs": 232431, + "cpuTicks": 40707, + "rssKiB": 781576, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 724344832 + }, + { + "tMs": 233572, + "cpuTicks": 40869, + "rssKiB": 782224, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 728539136 + }, + { + "tMs": 234691, + "cpuTicks": 41026, + "rssKiB": 782548, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 731144192 + }, + { + "tMs": 235815, + "cpuTicks": 41189, + "rssKiB": 782548, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 733536256 + }, + { + "tMs": 236940, + "cpuTicks": 41352, + "rssKiB": 782548, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 736792576 + }, + { + "tMs": 238173, + "cpuTicks": 41604, + "rssKiB": 879560, + "peakRssKiB": 879560, + "threads": 35, + "fds": 36, + "writeBytes": 740638720 + }, + { + "tMs": 239341, + "cpuTicks": 41834, + "rssKiB": 879560, + "peakRssKiB": 879560, + "threads": 35, + "fds": 36, + "writeBytes": 743079936 + }, + { + "tMs": 240500, + "cpuTicks": 42013, + "rssKiB": 782644, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 746340352 + }, + { + "tMs": 241652, + "cpuTicks": 42175, + "rssKiB": 782652, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 748834816 + }, + { + "tMs": 242800, + "cpuTicks": 42338, + "rssKiB": 782652, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 752037888 + }, + { + "tMs": 243939, + "cpuTicks": 42505, + "rssKiB": 782976, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 756436992 + }, + { + "tMs": 245132, + "cpuTicks": 42727, + "rssKiB": 879020, + "peakRssKiB": 879020, + "threads": 35, + "fds": 36, + "writeBytes": 760479744 + }, + { + "tMs": 246303, + "cpuTicks": 42969, + "rssKiB": 879664, + "peakRssKiB": 879664, + "threads": 35, + "fds": 36, + "writeBytes": 762937344 + }, + { + "tMs": 247450, + "cpuTicks": 43143, + "rssKiB": 782976, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 766558208 + }, + { + "tMs": 248589, + "cpuTicks": 43299, + "rssKiB": 782976, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 769822720 + }, + { + "tMs": 249725, + "cpuTicks": 43457, + "rssKiB": 782612, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 772292608 + }, + { + "tMs": 250880, + "cpuTicks": 43615, + "rssKiB": 782612, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 774877184 + }, + { + "tMs": 252064, + "cpuTicks": 43805, + "rssKiB": 879300, + "peakRssKiB": 879300, + "threads": 35, + "fds": 36, + "writeBytes": 778895360 + }, + { + "tMs": 253227, + "cpuTicks": 44019, + "rssKiB": 879300, + "peakRssKiB": 879300, + "threads": 35, + "fds": 36, + "writeBytes": 783298560 + }, + { + "tMs": 254392, + "cpuTicks": 44211, + "rssKiB": 781968, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 787681280 + }, + { + "tMs": 255551, + "cpuTicks": 44375, + "rssKiB": 781968, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 790220800 + }, + { + "tMs": 256697, + "cpuTicks": 44520, + "rssKiB": 782292, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 792817664 + }, + { + "tMs": 257851, + "cpuTicks": 44678, + "rssKiB": 781968, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 795328512 + }, + { + "tMs": 259041, + "cpuTicks": 44849, + "rssKiB": 878880, + "peakRssKiB": 878880, + "threads": 35, + "fds": 36, + "writeBytes": 799277056 + }, + { + "tMs": 260199, + "cpuTicks": 45060, + "rssKiB": 879100, + "peakRssKiB": 879100, + "threads": 35, + "fds": 36, + "writeBytes": 802496512 + }, + { + "tMs": 261366, + "cpuTicks": 45256, + "rssKiB": 783056, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 806137856 + }, + { + "tMs": 262521, + "cpuTicks": 45402, + "rssKiB": 783704, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 808804352 + }, + { + "tMs": 263668, + "cpuTicks": 45558, + "rssKiB": 783704, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 813223936 + }, + { + "tMs": 264794, + "cpuTicks": 45707, + "rssKiB": 783704, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 816635904 + }, + { + "tMs": 265942, + "cpuTicks": 45858, + "rssKiB": 784352, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 819236864 + }, + { + "tMs": 267157, + "cpuTicks": 46093, + "rssKiB": 880716, + "peakRssKiB": 880716, + "threads": 35, + "fds": 36, + "writeBytes": 823205888 + }, + { + "tMs": 268314, + "cpuTicks": 46303, + "rssKiB": 881040, + "peakRssKiB": 881040, + "threads": 35, + "fds": 36, + "writeBytes": 825856000 + }, + { + "tMs": 269469, + "cpuTicks": 46484, + "rssKiB": 784992, + "peakRssKiB": 876924, + "threads": 35, + "fds": 36, + "writeBytes": 829513728 + }, + { + "tMs": 270605, + "cpuTicks": 46624, + "rssKiB": 785780, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 833032192 + }, + { + "tMs": 271734, + "cpuTicks": 46772, + "rssKiB": 785780, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 835747840 + }, + { + "tMs": 272866, + "cpuTicks": 46925, + "rssKiB": 785780, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 838234112 + }, + { + "tMs": 274028, + "cpuTicks": 47113, + "rssKiB": 882468, + "peakRssKiB": 882468, + "threads": 35, + "fds": 36, + "writeBytes": 843972608 + }, + { + "tMs": 275213, + "cpuTicks": 47336, + "rssKiB": 882468, + "peakRssKiB": 882468, + "threads": 35, + "fds": 36, + "writeBytes": 846651392 + }, + { + "tMs": 276372, + "cpuTicks": 47537, + "rssKiB": 784360, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 851103744 + }, + { + "tMs": 277514, + "cpuTicks": 47703, + "rssKiB": 784360, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 853811200 + }, + { + "tMs": 278638, + "cpuTicks": 47862, + "rssKiB": 784360, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 856387584 + }, + { + "tMs": 279794, + "cpuTicks": 48010, + "rssKiB": 784360, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 858857472 + }, + { + "tMs": 280924, + "cpuTicks": 48153, + "rssKiB": 784036, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 861466624 + }, + { + "tMs": 282099, + "cpuTicks": 48348, + "rssKiB": 879436, + "peakRssKiB": 879436, + "threads": 35, + "fds": 36, + "writeBytes": 866029568 + }, + { + "tMs": 283255, + "cpuTicks": 48559, + "rssKiB": 879436, + "peakRssKiB": 879436, + "threads": 35, + "fds": 36, + "writeBytes": 870461440 + }, + { + "tMs": 284416, + "cpuTicks": 48757, + "rssKiB": 782420, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 874115072 + }, + { + "tMs": 285562, + "cpuTicks": 48911, + "rssKiB": 782420, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 876728320 + }, + { + "tMs": 286714, + "cpuTicks": 49069, + "rssKiB": 782420, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 880324608 + }, + { + "tMs": 287860, + "cpuTicks": 49221, + "rssKiB": 782416, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 882872320 + }, + { + "tMs": 289060, + "cpuTicks": 49414, + "rssKiB": 879328, + "peakRssKiB": 879328, + "threads": 35, + "fds": 36, + "writeBytes": 886648832 + }, + { + "tMs": 290229, + "cpuTicks": 49634, + "rssKiB": 879644, + "peakRssKiB": 879644, + "threads": 35, + "fds": 36, + "writeBytes": 889245696 + }, + { + "tMs": 291387, + "cpuTicks": 49814, + "rssKiB": 782636, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 893800448 + }, + { + "tMs": 292524, + "cpuTicks": 49957, + "rssKiB": 782040, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 896442368 + }, + { + "tMs": 293664, + "cpuTicks": 50103, + "rssKiB": 783012, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 900816896 + }, + { + "tMs": 294804, + "cpuTicks": 50252, + "rssKiB": 783976, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 903266304 + }, + { + "tMs": 295960, + "cpuTicks": 50397, + "rssKiB": 783976, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 905961472 + }, + { + "tMs": 297152, + "cpuTicks": 50613, + "rssKiB": 880668, + "peakRssKiB": 880668, + "threads": 35, + "fds": 36, + "writeBytes": 909828096 + }, + { + "tMs": 298316, + "cpuTicks": 50826, + "rssKiB": 832324, + "peakRssKiB": 877872, + "threads": 35, + "fds": 37, + "writeBytes": 914001920 + }, + { + "tMs": 299466, + "cpuTicks": 50971, + "rssKiB": 783980, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 916860928 + }, + { + "tMs": 300616, + "cpuTicks": 51119, + "rssKiB": 783980, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 919298048 + }, + { + "tMs": 301758, + "cpuTicks": 51259, + "rssKiB": 783980, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 921997312 + }, + { + "tMs": 302908, + "cpuTicks": 51400, + "rssKiB": 783980, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 924409856 + }, + { + "tMs": 304092, + "cpuTicks": 51595, + "rssKiB": 880252, + "peakRssKiB": 880252, + "threads": 35, + "fds": 36, + "writeBytes": 930873344 + }, + { + "tMs": 305277, + "cpuTicks": 51816, + "rssKiB": 879408, + "peakRssKiB": 879408, + "threads": 35, + "fds": 36, + "writeBytes": 933568512 + }, + { + "tMs": 306433, + "cpuTicks": 51982, + "rssKiB": 783044, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 937005056 + }, + { + "tMs": 307575, + "cpuTicks": 52126, + "rssKiB": 783044, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 939577344 + }, + { + "tMs": 308740, + "cpuTicks": 52276, + "rssKiB": 783368, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 943124480 + }, + { + "tMs": 309882, + "cpuTicks": 52415, + "rssKiB": 783592, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 945872896 + }, + { + "tMs": 311062, + "cpuTicks": 52594, + "rssKiB": 880284, + "peakRssKiB": 880284, + "threads": 35, + "fds": 37, + "writeBytes": 949800960 + }, + { + "tMs": 312241, + "cpuTicks": 52820, + "rssKiB": 880284, + "peakRssKiB": 880284, + "threads": 35, + "fds": 36, + "writeBytes": 952455168 + }, + { + "tMs": 313398, + "cpuTicks": 53001, + "rssKiB": 783596, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 958791680 + }, + { + "tMs": 314563, + "cpuTicks": 53146, + "rssKiB": 783372, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 961478656 + }, + { + "tMs": 315727, + "cpuTicks": 53306, + "rssKiB": 782496, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 964255744 + }, + { + "tMs": 316876, + "cpuTicks": 53453, + "rssKiB": 782488, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 966897664 + }, + { + "tMs": 318041, + "cpuTicks": 53618, + "rssKiB": 878400, + "peakRssKiB": 878400, + "threads": 35, + "fds": 36, + "writeBytes": 970760192 + }, + { + "tMs": 319215, + "cpuTicks": 53841, + "rssKiB": 877848, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 974270464 + }, + { + "tMs": 320384, + "cpuTicks": 54038, + "rssKiB": 781160, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 978202624 + }, + { + "tMs": 321543, + "cpuTicks": 54188, + "rssKiB": 781808, + "peakRssKiB": 877872, + "threads": 35, + "fds": 37, + "writeBytes": 980856832 + }, + { + "tMs": 322693, + "cpuTicks": 54336, + "rssKiB": 781808, + "peakRssKiB": 877872, + "threads": 35, + "fds": 37, + "writeBytes": 983498752 + }, + { + "tMs": 323845, + "cpuTicks": 54485, + "rssKiB": 782776, + "peakRssKiB": 877872, + "threads": 35, + "fds": 37, + "writeBytes": 989077504 + }, + { + "tMs": 325031, + "cpuTicks": 54669, + "rssKiB": 880436, + "peakRssKiB": 880436, + "threads": 35, + "fds": 36, + "writeBytes": 993017856 + }, + { + "tMs": 326203, + "cpuTicks": 54882, + "rssKiB": 880436, + "peakRssKiB": 880436, + "threads": 35, + "fds": 36, + "writeBytes": 995622912 + }, + { + "tMs": 327369, + "cpuTicks": 55074, + "rssKiB": 783748, + "peakRssKiB": 877872, + "threads": 35, + "fds": 37, + "writeBytes": 999022592 + }, + { + "tMs": 328510, + "cpuTicks": 55225, + "rssKiB": 783748, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 1001648128 + }, + { + "tMs": 329674, + "cpuTicks": 55381, + "rssKiB": 784072, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 1005105152 + }, + { + "tMs": 330835, + "cpuTicks": 55555, + "rssKiB": 785268, + "peakRssKiB": 877872, + "threads": 35, + "fds": 36, + "writeBytes": 1007796224 + }, + { + "tMs": 332039, + "cpuTicks": 55743, + "rssKiB": 881956, + "peakRssKiB": 881956, + "threads": 35, + "fds": 36, + "writeBytes": 1011732480 + }, + { + "tMs": 333233, + "cpuTicks": 55978, + "rssKiB": 881956, + "peakRssKiB": 881956, + "threads": 35, + "fds": 36, + "writeBytes": 1015934976 + }, + { + "tMs": 334409, + "cpuTicks": 56179, + "rssKiB": 785268, + "peakRssKiB": 878060, + "threads": 35, + "fds": 36, + "writeBytes": 1020440576 + }, + { + "tMs": 335556, + "cpuTicks": 56331, + "rssKiB": 784944, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1023115264 + }, + { + "tMs": 336694, + "cpuTicks": 56502, + "rssKiB": 784620, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1025540096 + }, + { + "tMs": 337833, + "cpuTicks": 56665, + "rssKiB": 784620, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1028345856 + }, + { + "tMs": 338954, + "cpuTicks": 56828, + "rssKiB": 784620, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1030987776 + }, + { + "tMs": 340168, + "cpuTicks": 57060, + "rssKiB": 881304, + "peakRssKiB": 881304, + "threads": 35, + "fds": 36, + "writeBytes": 1034739712 + }, + { + "tMs": 341337, + "cpuTicks": 57284, + "rssKiB": 856808, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1038667776 + }, + { + "tMs": 342490, + "cpuTicks": 57444, + "rssKiB": 784292, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1042055168 + }, + { + "tMs": 343644, + "cpuTicks": 57592, + "rssKiB": 784292, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1046704128 + }, + { + "tMs": 344799, + "cpuTicks": 57743, + "rssKiB": 784292, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1049399296 + }, + { + "tMs": 345931, + "cpuTicks": 57893, + "rssKiB": 784292, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1053253632 + }, + { + "tMs": 347131, + "cpuTicks": 58111, + "rssKiB": 881304, + "peakRssKiB": 881304, + "threads": 35, + "fds": 36, + "writeBytes": 1056493568 + }, + { + "tMs": 348299, + "cpuTicks": 58338, + "rssKiB": 881304, + "peakRssKiB": 881304, + "threads": 35, + "fds": 36, + "writeBytes": 1059840000 + }, + { + "tMs": 349469, + "cpuTicks": 58543, + "rssKiB": 784616, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1063227392 + }, + { + "tMs": 350621, + "cpuTicks": 58690, + "rssKiB": 784612, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1066778624 + }, + { + "tMs": 351763, + "cpuTicks": 58844, + "rssKiB": 784612, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1069469696 + }, + { + "tMs": 352918, + "cpuTicks": 59004, + "rssKiB": 784612, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1071939584 + }, + { + "tMs": 354115, + "cpuTicks": 59215, + "rssKiB": 881300, + "peakRssKiB": 881300, + "threads": 35, + "fds": 36, + "writeBytes": 1077653504 + }, + { + "tMs": 355270, + "cpuTicks": 59429, + "rssKiB": 881624, + "peakRssKiB": 881624, + "threads": 35, + "fds": 36, + "writeBytes": 1080074240 + }, + { + "tMs": 356418, + "cpuTicks": 59604, + "rssKiB": 784936, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1084801024 + }, + { + "tMs": 357557, + "cpuTicks": 59767, + "rssKiB": 784248, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1087451136 + }, + { + "tMs": 358699, + "cpuTicks": 59923, + "rssKiB": 784472, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1090027520 + }, + { + "tMs": 359838, + "cpuTicks": 60086, + "rssKiB": 784796, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1092444160 + }, + { + "tMs": 360969, + "cpuTicks": 60253, + "rssKiB": 784796, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1095114752 + }, + { + "tMs": 362174, + "cpuTicks": 60492, + "rssKiB": 880860, + "peakRssKiB": 880860, + "threads": 35, + "fds": 36, + "writeBytes": 1099874304 + }, + { + "tMs": 363355, + "cpuTicks": 60737, + "rssKiB": 880860, + "peakRssKiB": 880860, + "threads": 35, + "fds": 36, + "writeBytes": 1104297984 + }, + { + "tMs": 364495, + "cpuTicks": 60910, + "rssKiB": 784168, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1107537920 + }, + { + "tMs": 365616, + "cpuTicks": 61051, + "rssKiB": 784168, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1110204416 + }, + { + "tMs": 366761, + "cpuTicks": 61224, + "rssKiB": 783844, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1113821184 + }, + { + "tMs": 367889, + "cpuTicks": 61366, + "rssKiB": 783356, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1116467200 + }, + { + "tMs": 369065, + "cpuTicks": 61550, + "rssKiB": 879916, + "peakRssKiB": 879916, + "threads": 35, + "fds": 36, + "writeBytes": 1120407552 + }, + { + "tMs": 370230, + "cpuTicks": 61763, + "rssKiB": 879040, + "peakRssKiB": 879040, + "threads": 35, + "fds": 36, + "writeBytes": 1122791424 + }, + { + "tMs": 371379, + "cpuTicks": 61972, + "rssKiB": 782352, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1126227968 + }, + { + "tMs": 372529, + "cpuTicks": 62127, + "rssKiB": 782348, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1128677376 + }, + { + "tMs": 373672, + "cpuTicks": 62287, + "rssKiB": 782348, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1134055424 + }, + { + "tMs": 374797, + "cpuTicks": 62441, + "rssKiB": 782348, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1136664576 + }, + { + "tMs": 375922, + "cpuTicks": 62599, + "rssKiB": 782348, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1139306496 + }, + { + "tMs": 377105, + "cpuTicks": 62799, + "rssKiB": 879036, + "peakRssKiB": 879036, + "threads": 35, + "fds": 36, + "writeBytes": 1142337536 + }, + { + "tMs": 378299, + "cpuTicks": 63051, + "rssKiB": 879036, + "peakRssKiB": 879036, + "threads": 35, + "fds": 36, + "writeBytes": 1145643008 + }, + { + "tMs": 379471, + "cpuTicks": 63249, + "rssKiB": 781700, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1150308352 + }, + { + "tMs": 380615, + "cpuTicks": 63396, + "rssKiB": 782020, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1152974848 + }, + { + "tMs": 381743, + "cpuTicks": 63562, + "rssKiB": 782020, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1155612672 + }, + { + "tMs": 382887, + "cpuTicks": 63718, + "rssKiB": 782332, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1158197248 + }, + { + "tMs": 384114, + "cpuTicks": 63940, + "rssKiB": 880316, + "peakRssKiB": 880316, + "threads": 35, + "fds": 36, + "writeBytes": 1164918784 + }, + { + "tMs": 385290, + "cpuTicks": 64161, + "rssKiB": 880640, + "peakRssKiB": 880640, + "threads": 35, + "fds": 36, + "writeBytes": 1167699968 + }, + { + "tMs": 386433, + "cpuTicks": 64331, + "rssKiB": 783952, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1171120128 + }, + { + "tMs": 387554, + "cpuTicks": 64484, + "rssKiB": 783952, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1173766144 + }, + { + "tMs": 388683, + "cpuTicks": 64639, + "rssKiB": 783952, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1177387008 + }, + { + "tMs": 389833, + "cpuTicks": 64789, + "rssKiB": 783952, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1179947008 + }, + { + "tMs": 391013, + "cpuTicks": 64957, + "rssKiB": 880984, + "peakRssKiB": 880984, + "threads": 35, + "fds": 36, + "writeBytes": 1183981568 + }, + { + "tMs": 392215, + "cpuTicks": 65191, + "rssKiB": 880984, + "peakRssKiB": 880984, + "threads": 35, + "fds": 36, + "writeBytes": 1186607104 + }, + { + "tMs": 393399, + "cpuTicks": 65397, + "rssKiB": 784296, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1192017920 + }, + { + "tMs": 394554, + "cpuTicks": 65559, + "rssKiB": 784296, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1195651072 + }, + { + "tMs": 395691, + "cpuTicks": 65717, + "rssKiB": 783552, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1198350336 + }, + { + "tMs": 396834, + "cpuTicks": 65877, + "rssKiB": 784200, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1201037312 + }, + { + "tMs": 397963, + "cpuTicks": 66033, + "rssKiB": 784200, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1203757056 + }, + { + "tMs": 399151, + "cpuTicks": 66255, + "rssKiB": 881212, + "peakRssKiB": 881212, + "threads": 35, + "fds": 36, + "writeBytes": 1207783424 + }, + { + "tMs": 400325, + "cpuTicks": 66474, + "rssKiB": 857040, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1211826176 + }, + { + "tMs": 401475, + "cpuTicks": 66632, + "rssKiB": 784524, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1215283200 + }, + { + "tMs": 402614, + "cpuTicks": 66788, + "rssKiB": 784524, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1217904640 + }, + { + "tMs": 403767, + "cpuTicks": 66952, + "rssKiB": 784524, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1222295552 + }, + { + "tMs": 404921, + "cpuTicks": 67123, + "rssKiB": 784524, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1226006528 + }, + { + "tMs": 406094, + "cpuTicks": 67331, + "rssKiB": 880888, + "peakRssKiB": 880888, + "threads": 35, + "fds": 36, + "writeBytes": 1229971456 + }, + { + "tMs": 407244, + "cpuTicks": 67541, + "rssKiB": 879688, + "peakRssKiB": 879688, + "threads": 35, + "fds": 36, + "writeBytes": 1232162816 + }, + { + "tMs": 408412, + "cpuTicks": 67745, + "rssKiB": 783000, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1235898368 + }, + { + "tMs": 409556, + "cpuTicks": 67885, + "rssKiB": 782676, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1238511616 + }, + { + "tMs": 410686, + "cpuTicks": 68045, + "rssKiB": 783000, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1241870336 + }, + { + "tMs": 411822, + "cpuTicks": 68206, + "rssKiB": 783324, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1244397568 + }, + { + "tMs": 412975, + "cpuTicks": 68357, + "rssKiB": 783964, + "peakRssKiB": 878344, + "threads": 35, + "fds": 35, + "writeBytes": 1248104448 + }, + { + "tMs": 414193, + "cpuTicks": 68597, + "rssKiB": 880988, + "peakRssKiB": 880988, + "threads": 35, + "fds": 36, + "writeBytes": 1252990976 + }, + { + "tMs": 415340, + "cpuTicks": 68801, + "rssKiB": 809116, + "peakRssKiB": 878344, + "threads": 35, + "fds": 37, + "writeBytes": 1257164800 + }, + { + "tMs": 416495, + "cpuTicks": 68949, + "rssKiB": 784952, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1260011520 + }, + { + "tMs": 417646, + "cpuTicks": 69099, + "rssKiB": 784952, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1262678016 + }, + { + "tMs": 418813, + "cpuTicks": 69246, + "rssKiB": 785276, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1265311744 + }, + { + "tMs": 419965, + "cpuTicks": 69395, + "rssKiB": 785276, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1268072448 + }, + { + "tMs": 421190, + "cpuTicks": 69643, + "rssKiB": 881964, + "peakRssKiB": 881964, + "threads": 35, + "fds": 36, + "writeBytes": 1272999936 + }, + { + "tMs": 422338, + "cpuTicks": 69886, + "rssKiB": 857792, + "peakRssKiB": 878344, + "threads": 35, + "fds": 37, + "writeBytes": 1275891712 + }, + { + "tMs": 423497, + "cpuTicks": 70050, + "rssKiB": 785276, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1280999424 + }, + { + "tMs": 424636, + "cpuTicks": 70193, + "rssKiB": 785180, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1283682304 + }, + { + "tMs": 425783, + "cpuTicks": 70345, + "rssKiB": 785180, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1286119424 + }, + { + "tMs": 426939, + "cpuTicks": 70497, + "rssKiB": 784216, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1289564160 + }, + { + "tMs": 428138, + "cpuTicks": 70724, + "rssKiB": 881552, + "peakRssKiB": 881552, + "threads": 35, + "fds": 36, + "writeBytes": 1293340672 + }, + { + "tMs": 429299, + "cpuTicks": 70940, + "rssKiB": 881228, + "peakRssKiB": 881228, + "threads": 35, + "fds": 36, + "writeBytes": 1295937536 + }, + { + "tMs": 430470, + "cpuTicks": 71119, + "rssKiB": 784540, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1299763200 + }, + { + "tMs": 431623, + "cpuTicks": 71265, + "rssKiB": 784544, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1303363584 + }, + { + "tMs": 432769, + "cpuTicks": 71409, + "rssKiB": 784544, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1305829376 + }, + { + "tMs": 433917, + "cpuTicks": 71566, + "rssKiB": 784216, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1310253056 + }, + { + "tMs": 435109, + "cpuTicks": 71775, + "rssKiB": 881356, + "peakRssKiB": 881356, + "threads": 35, + "fds": 37, + "writeBytes": 1314213888 + }, + { + "tMs": 436272, + "cpuTicks": 72014, + "rssKiB": 880808, + "peakRssKiB": 880808, + "threads": 35, + "fds": 36, + "writeBytes": 1316667392 + }, + { + "tMs": 437415, + "cpuTicks": 72199, + "rssKiB": 783892, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1321250816 + }, + { + "tMs": 438563, + "cpuTicks": 72356, + "rssKiB": 783892, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1323991040 + }, + { + "tMs": 439706, + "cpuTicks": 72499, + "rssKiB": 783000, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1326706688 + }, + { + "tMs": 440862, + "cpuTicks": 72660, + "rssKiB": 783000, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1329258496 + }, + { + "tMs": 442038, + "cpuTicks": 72849, + "rssKiB": 880660, + "peakRssKiB": 880660, + "threads": 35, + "fds": 36, + "writeBytes": 1333035008 + }, + { + "tMs": 443212, + "cpuTicks": 73072, + "rssKiB": 880984, + "peakRssKiB": 880984, + "threads": 35, + "fds": 36, + "writeBytes": 1338421248 + }, + { + "tMs": 444378, + "cpuTicks": 73268, + "rssKiB": 784616, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1342078976 + }, + { + "tMs": 445526, + "cpuTicks": 73421, + "rssKiB": 785264, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1344647168 + }, + { + "tMs": 446694, + "cpuTicks": 73565, + "rssKiB": 784940, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1347231744 + }, + { + "tMs": 447834, + "cpuTicks": 73708, + "rssKiB": 784940, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1350823936 + }, + { + "tMs": 448985, + "cpuTicks": 73861, + "rssKiB": 860168, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1354743808 + }, + { + "tMs": 450206, + "cpuTicks": 74105, + "rssKiB": 881968, + "peakRssKiB": 881968, + "threads": 35, + "fds": 36, + "writeBytes": 1357410304 + }, + { + "tMs": 451366, + "cpuTicks": 74308, + "rssKiB": 785284, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1360723968 + }, + { + "tMs": 452506, + "cpuTicks": 74475, + "rssKiB": 785284, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1363324928 + }, + { + "tMs": 453665, + "cpuTicks": 74629, + "rssKiB": 785284, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1368965120 + }, + { + "tMs": 454818, + "cpuTicks": 74786, + "rssKiB": 785284, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1371672576 + }, + { + "tMs": 455982, + "cpuTicks": 74943, + "rssKiB": 827772, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1375666176 + }, + { + "tMs": 457185, + "cpuTicks": 75175, + "rssKiB": 880580, + "peakRssKiB": 880580, + "threads": 35, + "fds": 36, + "writeBytes": 1378189312 + }, + { + "tMs": 458361, + "cpuTicks": 75392, + "rssKiB": 808064, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1382445056 + }, + { + "tMs": 459533, + "cpuTicks": 75547, + "rssKiB": 783892, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1385009152 + }, + { + "tMs": 460673, + "cpuTicks": 75709, + "rssKiB": 783892, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1387405312 + }, + { + "tMs": 461814, + "cpuTicks": 75873, + "rssKiB": 783900, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1389907968 + }, + { + "tMs": 462949, + "cpuTicks": 76045, + "rssKiB": 783900, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1392590848 + }, + { + "tMs": 464157, + "cpuTicks": 76277, + "rssKiB": 879588, + "peakRssKiB": 879588, + "threads": 35, + "fds": 36, + "writeBytes": 1398300672 + }, + { + "tMs": 465339, + "cpuTicks": 76519, + "rssKiB": 855372, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1401962496 + }, + { + "tMs": 466483, + "cpuTicks": 76692, + "rssKiB": 782856, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1405468672 + }, + { + "tMs": 467620, + "cpuTicks": 76851, + "rssKiB": 782532, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1408073728 + }, + { + "tMs": 468762, + "cpuTicks": 77025, + "rssKiB": 782532, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1410510848 + }, + { + "tMs": 469912, + "cpuTicks": 77193, + "rssKiB": 782532, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1414189056 + }, + { + "tMs": 471104, + "cpuTicks": 77405, + "rssKiB": 879220, + "peakRssKiB": 879220, + "threads": 35, + "fds": 36, + "writeBytes": 1418125312 + }, + { + "tMs": 472262, + "cpuTicks": 77651, + "rssKiB": 879868, + "peakRssKiB": 879868, + "threads": 35, + "fds": 36, + "writeBytes": 1420738560 + }, + { + "tMs": 473412, + "cpuTicks": 77867, + "rssKiB": 783828, + "peakRssKiB": 878344, + "threads": 35, + "fds": 37, + "writeBytes": 1425924096 + }, + { + "tMs": 474563, + "cpuTicks": 78027, + "rssKiB": 783828, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1428561920 + }, + { + "tMs": 475696, + "cpuTicks": 78171, + "rssKiB": 784148, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1432096768 + }, + { + "tMs": 476845, + "cpuTicks": 78332, + "rssKiB": 784152, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1434562560 + }, + { + "tMs": 477979, + "cpuTicks": 78492, + "rssKiB": 799088, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1438683136 + }, + { + "tMs": 479202, + "cpuTicks": 78744, + "rssKiB": 881164, + "peakRssKiB": 881164, + "threads": 35, + "fds": 36, + "writeBytes": 1441234944 + }, + { + "tMs": 480372, + "cpuTicks": 78956, + "rssKiB": 808972, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1445642240 + }, + { + "tMs": 481516, + "cpuTicks": 79100, + "rssKiB": 784800, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1448448000 + }, + { + "tMs": 482662, + "cpuTicks": 79254, + "rssKiB": 784800, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1451040768 + }, + { + "tMs": 483798, + "cpuTicks": 79409, + "rssKiB": 784800, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1455460352 + }, + { + "tMs": 484995, + "cpuTicks": 79575, + "rssKiB": 881088, + "peakRssKiB": 881088, + "threads": 35, + "fds": 36, + "writeBytes": 1459449856 + }, + { + "tMs": 486218, + "cpuTicks": 79821, + "rssKiB": 880768, + "peakRssKiB": 880768, + "threads": 35, + "fds": 36, + "writeBytes": 1463119872 + }, + { + "tMs": 487403, + "cpuTicks": 80048, + "rssKiB": 784080, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1466568704 + }, + { + "tMs": 488561, + "cpuTicks": 80213, + "rssKiB": 784404, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1469231104 + }, + { + "tMs": 489734, + "cpuTicks": 80364, + "rssKiB": 784404, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1471873024 + }, + { + "tMs": 490905, + "cpuTicks": 80514, + "rssKiB": 784404, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1474453504 + }, + { + "tMs": 492098, + "cpuTicks": 80731, + "rssKiB": 880772, + "peakRssKiB": 880772, + "threads": 35, + "fds": 36, + "writeBytes": 1479352320 + }, + { + "tMs": 493270, + "cpuTicks": 80952, + "rssKiB": 880772, + "peakRssKiB": 880772, + "threads": 35, + "fds": 36, + "writeBytes": 1483714560 + }, + { + "tMs": 494432, + "cpuTicks": 81149, + "rssKiB": 784084, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1486999552 + }, + { + "tMs": 495569, + "cpuTicks": 81302, + "rssKiB": 784084, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1489686528 + }, + { + "tMs": 496695, + "cpuTicks": 81469, + "rssKiB": 783116, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1492127744 + }, + { + "tMs": 497837, + "cpuTicks": 81634, + "rssKiB": 783116, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1495556096 + }, + { + "tMs": 498969, + "cpuTicks": 81797, + "rssKiB": 783116, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1498091520 + }, + { + "tMs": 500179, + "cpuTicks": 82041, + "rssKiB": 879748, + "peakRssKiB": 879748, + "threads": 35, + "fds": 36, + "writeBytes": 1501884416 + }, + { + "tMs": 501346, + "cpuTicks": 82262, + "rssKiB": 855580, + "peakRssKiB": 878344, + "threads": 35, + "fds": 37, + "writeBytes": 1504927744 + }, + { + "tMs": 502489, + "cpuTicks": 82434, + "rssKiB": 783388, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1508028416 + }, + { + "tMs": 503625, + "cpuTicks": 82587, + "rssKiB": 784036, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1513627648 + }, + { + "tMs": 504801, + "cpuTicks": 82747, + "rssKiB": 784036, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1516224512 + }, + { + "tMs": 505961, + "cpuTicks": 82910, + "rssKiB": 785008, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1518661632 + }, + { + "tMs": 507174, + "cpuTicks": 83142, + "rssKiB": 881704, + "peakRssKiB": 881704, + "threads": 35, + "fds": 36, + "writeBytes": 1522581504 + }, + { + "tMs": 508337, + "cpuTicks": 83366, + "rssKiB": 881704, + "peakRssKiB": 881704, + "threads": 35, + "fds": 36, + "writeBytes": 1525096448 + }, + { + "tMs": 509483, + "cpuTicks": 83537, + "rssKiB": 785664, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1529565184 + }, + { + "tMs": 510658, + "cpuTicks": 83696, + "rssKiB": 786000, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1532194816 + }, + { + "tMs": 511817, + "cpuTicks": 83855, + "rssKiB": 786016, + "peakRssKiB": 878344, + "threads": 35, + "fds": 37, + "writeBytes": 1534861312 + }, + { + "tMs": 512987, + "cpuTicks": 84043, + "rssKiB": 869384, + "peakRssKiB": 878344, + "threads": 35, + "fds": 33, + "writeBytes": 1539276800 + }, + { + "tMs": 514278, + "cpuTicks": 84336, + "rssKiB": 882056, + "peakRssKiB": 882056, + "threads": 35, + "fds": 36, + "writeBytes": 1544572928 + }, + { + "tMs": 515444, + "cpuTicks": 84515, + "rssKiB": 785368, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1548402688 + }, + { + "tMs": 516573, + "cpuTicks": 84682, + "rssKiB": 785340, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1550864384 + }, + { + "tMs": 517723, + "cpuTicks": 84847, + "rssKiB": 785340, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1553403904 + }, + { + "tMs": 518862, + "cpuTicks": 85003, + "rssKiB": 785664, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1556041728 + }, + { + "tMs": 520027, + "cpuTicks": 85185, + "rssKiB": 882352, + "peakRssKiB": 882352, + "threads": 35, + "fds": 36, + "writeBytes": 1560780800 + }, + { + "tMs": 521220, + "cpuTicks": 85411, + "rssKiB": 881396, + "peakRssKiB": 881396, + "threads": 35, + "fds": 36, + "writeBytes": 1563258880 + }, + { + "tMs": 522378, + "cpuTicks": 85627, + "rssKiB": 784708, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1566781440 + }, + { + "tMs": 523536, + "cpuTicks": 85798, + "rssKiB": 784708, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1570983936 + }, + { + "tMs": 524702, + "cpuTicks": 85948, + "rssKiB": 784708, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1574572032 + }, + { + "tMs": 525857, + "cpuTicks": 86102, + "rssKiB": 784384, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1577238528 + }, + { + "tMs": 527035, + "cpuTicks": 86279, + "rssKiB": 880424, + "peakRssKiB": 880424, + "threads": 35, + "fds": 36, + "writeBytes": 1580265472 + }, + { + "tMs": 528236, + "cpuTicks": 86527, + "rssKiB": 880424, + "peakRssKiB": 880424, + "threads": 35, + "fds": 36, + "writeBytes": 1583509504 + }, + { + "tMs": 529419, + "cpuTicks": 86724, + "rssKiB": 783640, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1587494912 + }, + { + "tMs": 530568, + "cpuTicks": 86881, + "rssKiB": 783412, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1591087104 + }, + { + "tMs": 531708, + "cpuTicks": 87048, + "rssKiB": 783420, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1593581568 + }, + { + "tMs": 532855, + "cpuTicks": 87213, + "rssKiB": 783736, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1596018688 + }, + { + "tMs": 534004, + "cpuTicks": 87391, + "rssKiB": 880748, + "peakRssKiB": 880748, + "threads": 35, + "fds": 36, + "writeBytes": 1601720320 + }, + { + "tMs": 535277, + "cpuTicks": 87682, + "rssKiB": 881072, + "peakRssKiB": 881072, + "threads": 35, + "fds": 36, + "writeBytes": 1604390912 + }, + { + "tMs": 536452, + "cpuTicks": 87873, + "rssKiB": 784384, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1608966144 + }, + { + "tMs": 537576, + "cpuTicks": 88039, + "rssKiB": 784384, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1611587584 + }, + { + "tMs": 538719, + "cpuTicks": 88209, + "rssKiB": 784384, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1614303232 + }, + { + "tMs": 539870, + "cpuTicks": 88374, + "rssKiB": 784708, + "peakRssKiB": 878344, + "threads": 35, + "fds": 36, + "writeBytes": 1616707584 + }, + { + "tMs": 541057, + "cpuTicks": 88563, + "rssKiB": 882368, + "peakRssKiB": 882368, + "threads": 35, + "fds": 36, + "writeBytes": 1620676608 + }, + { + "tMs": 542246, + "cpuTicks": 88794, + "rssKiB": 882368, + "peakRssKiB": 882368, + "threads": 35, + "fds": 36, + "writeBytes": 1624252416 + }, + { + "tMs": 543417, + "cpuTicks": 88996, + "rssKiB": 785676, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1629601792 + }, + { + "tMs": 544566, + "cpuTicks": 89140, + "rssKiB": 785676, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1632239616 + }, + { + "tMs": 545717, + "cpuTicks": 89300, + "rssKiB": 785676, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1634631680 + }, + { + "tMs": 546852, + "cpuTicks": 89464, + "rssKiB": 785676, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1638244352 + }, + { + "tMs": 548040, + "cpuTicks": 89661, + "rssKiB": 882364, + "peakRssKiB": 882364, + "threads": 35, + "fds": 36, + "writeBytes": 1641975808 + }, + { + "tMs": 549220, + "cpuTicks": 89878, + "rssKiB": 882364, + "peakRssKiB": 882364, + "threads": 35, + "fds": 36, + "writeBytes": 1644584960 + }, + { + "tMs": 550380, + "cpuTicks": 90084, + "rssKiB": 785676, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1648275456 + }, + { + "tMs": 551539, + "cpuTicks": 90234, + "rssKiB": 785032, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1650991104 + }, + { + "tMs": 552680, + "cpuTicks": 90404, + "rssKiB": 784380, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1654439936 + }, + { + "tMs": 553807, + "cpuTicks": 90564, + "rssKiB": 784604, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1658884096 + }, + { + "tMs": 554943, + "cpuTicks": 90716, + "rssKiB": 784380, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1661480960 + }, + { + "tMs": 556151, + "cpuTicks": 90949, + "rssKiB": 881620, + "peakRssKiB": 881620, + "threads": 35, + "fds": 36, + "writeBytes": 1665507328 + }, + { + "tMs": 557306, + "cpuTicks": 91160, + "rssKiB": 881296, + "peakRssKiB": 881296, + "threads": 35, + "fds": 36, + "writeBytes": 1667862528 + }, + { + "tMs": 558462, + "cpuTicks": 91329, + "rssKiB": 784608, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1672908800 + }, + { + "tMs": 559594, + "cpuTicks": 91495, + "rssKiB": 784608, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1675489280 + }, + { + "tMs": 560748, + "cpuTicks": 91664, + "rssKiB": 784608, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1677991936 + }, + { + "tMs": 561881, + "cpuTicks": 91821, + "rssKiB": 784292, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1680715776 + }, + { + "tMs": 563059, + "cpuTicks": 92007, + "rssKiB": 881112, + "peakRssKiB": 881112, + "threads": 35, + "fds": 36, + "writeBytes": 1687502848 + }, + { + "tMs": 564252, + "cpuTicks": 92248, + "rssKiB": 881432, + "peakRssKiB": 881432, + "threads": 35, + "fds": 36, + "writeBytes": 1689956352 + }, + { + "tMs": 565416, + "cpuTicks": 92459, + "rssKiB": 785392, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1693593600 + }, + { + "tMs": 566544, + "cpuTicks": 92613, + "rssKiB": 785400, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1695989760 + }, + { + "tMs": 567690, + "cpuTicks": 92755, + "rssKiB": 785400, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1698639872 + }, + { + "tMs": 568834, + "cpuTicks": 92906, + "rssKiB": 785400, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1702309888 + }, + { + "tMs": 569980, + "cpuTicks": 93056, + "rssKiB": 804972, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1706254336 + }, + { + "tMs": 571213, + "cpuTicks": 93299, + "rssKiB": 882424, + "peakRssKiB": 882424, + "threads": 35, + "fds": 36, + "writeBytes": 1708896256 + }, + { + "tMs": 572383, + "cpuTicks": 93507, + "rssKiB": 785736, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1712369664 + }, + { + "tMs": 573538, + "cpuTicks": 93654, + "rssKiB": 785736, + "peakRssKiB": 878748, + "threads": 35, + "fds": 36, + "writeBytes": 1717870592 + }, + { + "tMs": 574700, + "cpuTicks": 93812, + "rssKiB": 785736, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1720557568 + }, + { + "tMs": 575843, + "cpuTicks": 93962, + "rssKiB": 785408, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1723244544 + }, + { + "tMs": 577010, + "cpuTicks": 94126, + "rssKiB": 881224, + "peakRssKiB": 881224, + "threads": 35, + "fds": 36, + "writeBytes": 1727217664 + }, + { + "tMs": 578198, + "cpuTicks": 94344, + "rssKiB": 881448, + "peakRssKiB": 881448, + "threads": 35, + "fds": 36, + "writeBytes": 1729875968 + }, + { + "tMs": 579365, + "cpuTicks": 94560, + "rssKiB": 857052, + "peakRssKiB": 878996, + "threads": 35, + "fds": 37, + "writeBytes": 1732734976 + }, + { + "tMs": 580506, + "cpuTicks": 94705, + "rssKiB": 784208, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1737043968 + }, + { + "tMs": 581638, + "cpuTicks": 94867, + "rssKiB": 783892, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1739591680 + }, + { + "tMs": 582789, + "cpuTicks": 95010, + "rssKiB": 783892, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1742102528 + }, + { + "tMs": 583938, + "cpuTicks": 95162, + "rssKiB": 783892, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1746436096 + }, + { + "tMs": 585139, + "cpuTicks": 95368, + "rssKiB": 880476, + "peakRssKiB": 880476, + "threads": 35, + "fds": 36, + "writeBytes": 1751523328 + }, + { + "tMs": 586290, + "cpuTicks": 95575, + "rssKiB": 879932, + "peakRssKiB": 879932, + "threads": 35, + "fds": 36, + "writeBytes": 1754341376 + }, + { + "tMs": 587427, + "cpuTicks": 95734, + "rssKiB": 783244, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1757507584 + }, + { + "tMs": 588593, + "cpuTicks": 95889, + "rssKiB": 783240, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1760198656 + }, + { + "tMs": 589734, + "cpuTicks": 96042, + "rssKiB": 783240, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1763729408 + }, + { + "tMs": 590896, + "cpuTicks": 96207, + "rssKiB": 783564, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1766256640 + }, + { + "tMs": 592089, + "cpuTicks": 96411, + "rssKiB": 880900, + "peakRssKiB": 880900, + "threads": 35, + "fds": 36, + "writeBytes": 1770160128 + }, + { + "tMs": 593274, + "cpuTicks": 96644, + "rssKiB": 881220, + "peakRssKiB": 881220, + "threads": 35, + "fds": 36, + "writeBytes": 1774698496 + }, + { + "tMs": 594431, + "cpuTicks": 96833, + "rssKiB": 785180, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1779376128 + }, + { + "tMs": 595590, + "cpuTicks": 96983, + "rssKiB": 785180, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1781755904 + }, + { + "tMs": 596740, + "cpuTicks": 97128, + "rssKiB": 785184, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1784422400 + }, + { + "tMs": 597907, + "cpuTicks": 97286, + "rssKiB": 785184, + "peakRssKiB": 878996, + "threads": 35, + "fds": 36, + "writeBytes": 1787047936 + }, + { + "tMs": 599102, + "cpuTicks": 97487, + "rssKiB": 882192, + "peakRssKiB": 882192, + "threads": 35, + "fds": 36, + "writeBytes": 1790808064 + } + ], + "db_probe_ms": [ + 14.499, + 9.397, + 7.689, + 7.474, + 8.321, + 16.269, + 8.302, + 7.649, + 8.773, + 9.347, + 8.492, + 9.318, + 10.439, + 7.859, + 8.243, + 8.034, + 8.047, + 7.749, + 8.818, + 9.107, + 8.21, + 7.416, + 7.855, + 11.98, + 7.736, + 7.964, + 8.135, + 8.441, + 9.075, + 7.299, + 7.484, + 9.246, + 17.124, + 7.803, + 8.201, + 8.594, + 13.931, + 9.103, + 7.891, + 10.37, + 7.53, + 8.52, + 8.15, + 7.793, + 7.384, + 7.704, + 7.919, + 7.998, + 7.516, + 7.503, + 12.04, + 11.466, + 8.328, + 7.435, + 13.055, + 8.329, + 7.691, + 7.456, + 20.839, + 10.049, + 7.764, + 7.964, + 8.825, + 7.414, + 7.751, + 7.613, + 8.388, + 7.928, + 8.165, + 8.148, + 7.901, + 7.524, + 8.515, + 7.957, + 7.652, + 7.34, + 7.733, + 8.516, + 7.626, + 7.814, + 7.891, + 18, + 7.661, + 10.41, + 7.669, + 9.788, + 7.964, + 7.484, + 8.162, + 9.342, + 20.458, + 7.934, + 7.969, + 9.418, + 9.171, + 8.598, + 7.309, + 7.364, + 7.449, + 8.18, + 7.451, + 7.558, + 7.455, + 8.537, + 8.532, + 7.981, + 7.745, + 7.686, + 11.692, + 9.953, + 7.506, + 8.237, + 8.802, + 8.064, + 7.838, + 7.23, + 9.389, + 12.682, + 7.602, + 8.317, + 8.626, + 21.183, + 8.574, + 7.51, + 7.841, + 7.137, + 9.361, + 9.335, + 8.596, + 7.087, + 7.672, + 7.425, + 8.494, + 7.486, + 7.683, + 9.314, + 7.734, + 7.614, + 7.338, + 14.845, + 7.911, + 7.299, + 7.661, + 13.369, + 7.847, + 7.515, + 8.182, + 10.437, + 8.368, + 7.482, + 7.316, + 8.211, + 20.412, + 7.767, + 7.204, + 8.437, + 11.302, + 7.967, + 7.797, + 8.012, + 7.491, + 8.227, + 8.051, + 8.441, + 7.274, + 8.977, + 10.621, + 7.94, + 8.323, + 8.468, + 11.125, + 21.075, + 7.676, + 7.433, + 10.173, + 7.967, + 7.076, + 7.69, + 8.483, + 10.496, + 7.299, + 7.507, + 14.334, + 8.474, + 7.395, + 7.874, + 8.002, + 7.932, + 8.064, + 7.641, + 8.007, + 7.981, + 9.093, + 10.869, + 7.903, + 7.45, + 9.645, + 12.379, + 7.931, + 7.37, + 10.177, + 10.392, + 19.924, + 7.39, + 8.633, + 10.619, + 7.925, + 7.897, + 7.789, + 8.936, + 9.284, + 7.527, + 7.118, + 7.684, + 11.948, + 7.36, + 7.436, + 8.027, + 11.429, + 8.42, + 10.008, + 8.206, + 7.43, + 10.222, + 8.72, + 10.685, + 9.433, + 8.512, + 9.729, + 9.396, + 10.172, + 7.289, + 9.875, + 12.815, + 7.695, + 7.644, + 10.062, + 7.926, + 7.715, + 7.641, + 7.933, + 8.053, + 8.459, + 8.657, + 9.247, + 9.16, + 7.483, + 15.869, + 8.289, + 9.047, + 7.997, + 8.457, + 7.833, + 7.719, + 9.392, + 9.614, + 7.836, + 8.877, + 9.348, + 8.54, + 9.817, + 8.032, + 7.406, + 9.317, + 19.489, + 7.503, + 7.202, + 8.376, + 7.556, + 7.156, + 7.585, + 9.481, + 8.21, + 7.468, + 11.725, + 8.169, + 8.064, + 7.426, + 8.382, + 7.878, + 9.905, + 9.529, + 10.638, + 17.307, + 7.822, + 9.605, + 16.638, + 8.376, + 8.06, + 7.606, + 9.628, + 7.316, + 7.805, + 7.52, + 9.437, + 7.992, + 7.557, + 8.062, + 8.328, + 7.839, + 7.781, + 12.413, + 8.961, + 8.09, + 7.804, + 8.693, + 8.281, + 9.883, + 8.899, + 8.477, + 9.78, + 8.038, + 9.637, + 8.809, + 7.712, + 7.882, + 9.96, + 9.682, + 8.129, + 8.522, + 9.192, + 10.362, + 7.648, + 7.886, + 7.085, + 9.356, + 7.633, + 7.453, + 7.35, + 14.662, + 7.901, + 7.217, + 11.399, + 8.072, + 8.631, + 7.624, + 10.192, + 15.525, + 8.795, + 7.488, + 11.017, + 15.69, + 8.46, + 9.163, + 8.113, + 9.696, + 8.046, + 10.254, + 7.837, + 7.244, + 7.547, + 9.278, + 8.256, + 7.555, + 7.618, + 7.528, + 17.301, + 7.96, + 8.53, + 11.776, + 8.471, + 7.988, + 7.58, + 8.998, + 7.991, + 9.286, + 7.338, + 8.681, + 8.077, + 7.837, + 8.454, + 8.626, + 7.652, + 7.68, + 8.202, + 8.63, + 8.722, + 7.332, + 9.16, + 7.77, + 8.317, + 10.134, + 7.595, + 8.242, + 7.282, + 8.086, + 9.934, + 7.838, + 7.494, + 8.563, + 9.913, + 9.142, + 7.838, + 8.052, + 8.964, + 7.803, + 9.657, + 9.097, + 10.387, + 7.913, + 11.447, + 8.496, + 11.303, + 7.588, + 8.516, + 8.699, + 8.082, + 7.409, + 7.686, + 7.917, + 8.267, + 7.321, + 7.366, + 9.834, + 8.056, + 7.941, + 7.279, + 11.152, + 8.893, + 7.65, + 7.57, + 8.86, + 9.216, + 8.11, + 7.469, + 9.74, + 8.655, + 8.268, + 7.557, + 8.369, + 7.769, + 9.513, + 9.117, + 8.949, + 17.04, + 8.09, + 9.836, + 8.737, + 7.206, + 7.68, + 11.34, + 8.279, + 8.773, + 7.704, + 10.169, + 18.556, + 7.425, + 7.48, + 9.261, + 11.417, + 8.978, + 7.578, + 12.383, + 7.751, + 9.096, + 7.628, + 9.243, + 8.266, + 8.07, + 8.086, + 9.294, + 10.571, + 10.195, + 8.688, + 7.908, + 7.769, + 7.235, + 8.572, + 8.005, + 7.17, + 7.57, + 22.343, + 9.17, + 7.389, + 8.141, + 11.814, + 10.313, + 7.134, + 7.822, + 9.085, + 8.265, + 8.578, + 7.634, + 8.336, + 15.712, + 8.106, + 7.365, + 9.437, + 7.464, + 8.024, + 7.331, + 7.831, + 7.086, + 7.382, + 8.631, + 7.992, + 7.244, + 7.73, + 13.789, + 8.296, + 7.123, + 7.457, + 11.703, + 9.813, + 7.661, + 7.358, + 8.494, + 9.934, + 7.203, + 7.063, + 9.622, + 12.042, + 8.969, + 8.517, + 12.606, + 8.625, + 8.37, + 7.345, + 7.633, + 10.929, + 7.543, + 7.727, + 7.663, + 7.515, + 9.223, + 10.716, + 8.15, + 7.219, + 7.53, + 11.981, + 8.652, + 8.394, + 7.656, + 11.468, + 8.746, + 7.431, + 7.66, + 10.332, + 21.53, + 8.509, + 7.605, + 10.601, + 8.174, + 8.673, + 7.668, + 8.369, + 7.558, + 9.243, + 7.411, + 9.795, + 7.139, + 7.19, + 10.295, + 8.776, + 7.519, + 7.286, + 16.161, + 8.353, + 7.708, + 7.301, + 15.593, + 12.265, + 7.882, + 8.026, + 8.817, + 14.301, + 7.372, + 8.926, + 8.766, + 8.751, + 8.233, + 8.366, + 7.835, + 7.612, + 8.926, + 7.81, + 7.628, + 7.163, + 8.69, + 8.019, + 8.524, + 7.096, + 7.739, + 9.483, + 7.72, + 7.305, + 7.063, + 9.366, + 14.052, + 7.527, + 8.503, + 10.87, + 14.988, + 7.305, + 7.981, + 9.845, + 16.45, + 7.679, + 7.103, + 7.653, + 7.278, + 8.175, + 7.315, + 7.764, + 7.404, + 8.603, + 7.431, + 8.041, + 7.284, + 7.561, + 12.309, + 7.925, + 7.303, + 7.291, + 10.884, + 8.824, + 8.605, + 7.098, + 11.88, + 16.439, + 7.391, + 7.483, + 14.15, + 22.637, + 9.489, + 9.129, + 8.22, + 8.212, + 7.506, + 8.472, + 9.339, + 7.907, + 8.401, + 7.565, + 8.007, + 7.619, + 9.011, + 10.402, + 10.784, + 7.538, + 9.651, + 8.347, + 9.619, + 7.674, + 8.853, + 10.354, + 10.585, + 7.248, + 7.292, + 10.439, + 10.421, + 7.422, + 7.234, + 7.83, + 8.865, + 7.066, + 7.729, + 12.52, + 7.359, + 7.968, + 7.537, + 8.378, + 7.213, + 9.585, + 8.026, + 7.86, + 7.589, + 10.677, + 13.521, + 8.273, + 7.328, + 10.197, + 17.705, + 7.782, + 7.524, + 7.35, + 11.05, + 16.53, + 7.48, + 7.791, + 8.476, + 8.253, + 7.281, + 9.018, + 7.687, + 9.424, + 7.429, + 7.631, + 7.751, + 8.27, + 7.922, + 7.109, + 8.917, + 7.498, + 8.158, + 11.629, + 11.087, + 7.403, + 7.982, + 17.607, + 8.275, + 7.603, + 7.847, + 12.762, + 7.877, + 7.274, + 8.361, + 13.518, + 8.245, + 7.928, + 7.053, + 7.778, + 8.071, + 7.155, + 7.106, + 7.823, + 8.059, + 7.26, + 7.468, + 11.192, + 8.651, + 8.431, + 11.374, + 9.12, + 7.579, + 9.13, + 8.93, + 8.228, + 7.411, + 8.849, + 10.481, + 22.474, + 9.501, + 7.896, + 11.241, + 8.883, + 8.029, + 7.954, + 10.094, + 8.28, + 7.613, + 7.556, + 8.487, + 7.785, + 7.497, + 8.594, + 8.84, + 10.43, + 7.478, + 14.496, + 8.21, + 7.365, + 8.903, + 8.995, + 8.726, + 7.605, + 11.229, + 10.715, + 10.987, + 7.314, + 7.883, + 11.73, + 13.489, + 7.167, + 7.639, + 9.49, + 7.167, + 7.02, + 8.728, + 9.06, + 8.279, + 7.505, + 7.6, + 9.589, + 8.353, + 9.691, + 11.579, + 10.634, + 7.96, + 15.025, + 9.57, + 7.696, + 7.966, + 9.863, + 9.66, + 7.804, + 7.817, + 8.475, + 9.896, + 13.704, + 7.393, + 7.892, + 11.059, + 7.897, + 8.059, + 7.41, + 8.051, + 8.509, + 7.932, + 7.565, + 7.764, + 9.875, + 7.985, + 15.329, + 12.875, + 7.24, + 7.743, + 10.766, + 7.946, + 7.616, + 8.043, + 11.769, + 8.241, + 7.312, + 8.302, + 9.463, + 7.514, + 7.518, + 7.482, + 13.913, + 9.546, + 7.263, + 8.385, + 10.827, + 7.408, + 7.345, + 7.321, + 15.217, + 7.626, + 7.842, + 13.892, + 13.227, + 7.169, + 7.892, + 8.184, + 7.985, + 7.968, + 7.669, + 9.074, + 7.656, + 7.482, + 8.98, + 11.677, + 7.935, + 7.158, + 9.408, + 8.503, + 7.765, + 7.631, + 8.71, + 11.447, + 7.578, + 7.523, + 7.516, + 9.971, + 7.575, + 7.523, + 7.459, + 7.728, + 8.068, + 8.726, + 10.464, + 8.142, + 8.47, + 7.481, + 8.774, + 14.534, + 9.555, + 9.079, + 8.715, + 8.457, + 8.723, + 8.863, + 8.7, + 7.578, + 7.333, + 9.256, + 9.287, + 7.409, + 7.059, + 9.802, + 9.852, + 9.811, + 7.234, + 8.42, + 10.228, + 8.01, + 7.321, + 13.976, + 10.682, + 7.244, + 7.377, + 8.846, + 8.711, + 11.281, + 7.465, + 11.195, + 7.768, + 8.183, + 8.485, + 9.043, + 7.752, + 9.103, + 7.604, + 8.926, + 7.588, + 7.658, + 9.031, + 7.98, + 7.281, + 7.031, + 8.069, + 14.027, + 7.124, + 7.2, + 12.42, + 9.83, + 7.302, + 8.97, + 17.312, + 14.355, + 7.61, + 7.779, + 14.843, + 7.633, + 7.901, + 7.469, + 8.79, + 7.821, + 8.04, + 8.052, + 12.796, + 7.895, + 7.915, + 7.902, + 7.912, + 7.401, + 7.701, + 10.362, + 9.172, + 8.278, + 7.274, + 9.931, + 9.246, + 7.546, + 7.548, + 10.245, + 11.472, + 7.325, + 7.38, + 9.333, + 9.118, + 8.087, + 7.719, + 10.644, + 7.803, + 7.974, + 7.475, + 8.259, + 7.156, + 9.397, + 8.596, + 8.702, + 7.31, + 7.853, + 8.872, + 8.031, + 7.306, + 7.359, + 9.572, + 8.258, + 7.481, + 7.719, + 11.921, + 9.15, + 7.015, + 8.736, + 10.704, + 10.257, + 7.952, + 7.444, + 8.461, + 16.605, + 8.327, + 8.371, + 11.858, + 8.127, + 7.98, + 9.875, + 10.186, + 7.532, + 7.832, + 8.573, + 10.523, + 8.312, + 8.35, + 8.029, + 9.215, + 9.211, + 7.794, + 10.462, + 10.265, + 7.29, + 7.274, + 10.161, + 8.473, + 7.105, + 7.783, + 8.262, + 13.474, + 9.712, + 7.736, + 7.994, + 7.45, + 7.984, + 9.373, + 9.19, + 8.206, + 7.428, + 7.966, + 8.087, + 7.224, + 7.499, + 8.717, + 8.959, + 7.171, + 7.343, + 9.892, + 8.474, + 7.34, + 7.213, + 10.475, + 9.692, + 7.759, + 7.821, + 9.282, + 11.431, + 8.158, + 7.181, + 12.118, + 7.783, + 11.444, + 12.711, + 10.334, + 7.305, + 8.603, + 10.388, + 7.694, + 7.479, + 7.433, + 8.684, + 7.982, + 10.674, + 7.105, + 9.335, + 9.559, + 8.856, + 7.877, + 11.534, + 8.611, + 9.509, + 7.756, + 9.645, + 32.759, + 7.367, + 9.028, + 8.086, + 9.831, + 8.389, + 8.869, + 9.354, + 15.728, + 9.094, + 8.369, + 14.739, + 7.865, + 8.89, + 7.654, + 7.838, + 7.268, + 8.048, + 14.962, + 8.227, + 7.181, + 7.892, + 8.369, + 8.887, + 7.522, + 7.97, + 8.657, + 10.032, + 7.715, + 9.092, + 10.512, + 16.13, + 11.284, + 7.81, + 8.762, + 7.876, + 10.205, + 7.647, + 7.607, + 7.32, + 8.526, + 7.855, + 7.851, + 7.469, + 7.211, + 9.374, + 8.383, + 7.324, + 7.797, + 16.223, + 11.005, + 7.264, + 8.429, + 10.508, + 13.66, + 7.17, + 7.374, + 9.759, + 7.721, + 7.16, + 7.465, + 7.977, + 7.457, + 8.196, + 7.591, + 7.742, + 7.802, + 8.752, + 8.152, + 9.033, + 7.17, + 8.012, + 8.742, + 7.941, + 7.146, + 7.708, + 17.431, + 8.332, + 7.299, + 7.796, + 9.693, + 8.724, + 7.719, + 8.631, + 8.653, + 16.002, + 8.053, + 7.909, + 7.518, + 7.513, + 10.04, + 7.306, + 7.517, + 7.305, + 9.638, + 7.067, + 14.229, + 10.095, + 8.011, + 7.83, + 8.375, + 7.256, + 7.19, + 18.102, + 8.891, + 7.385, + 8.198, + 13.779, + 13.839, + 7.469, + 7.997, + 8.869, + 9.025, + 7.346, + 7.212, + 8.708, + 7.339, + 7.034, + 7.245, + 8.417, + 7.629, + 8.415, + 8.074, + 7.647, + 7.829, + 9.921, + 7.863, + 8.116, + 7.471, + 7.531, + 9.17, + 8.602, + 7.446, + 7.555, + 11.659, + 24.817, + 7.398, + 7.915, + 8.483, + 10.336, + 7.861, + 7.604, + 7.845, + 9.513, + 8.068, + 8.702, + 9.776, + 8.391, + 8.72, + 8.014, + 7.601, + 7.603, + 7.49, + 8.43, + 8.098, + 7.574, + 7.492, + 9.016, + 8.267, + 7.71, + 7.402, + 17.542, + 7.498, + 8.434, + 7.689, + 11.618, + 10.359, + 8.002, + 7.943, + 8.716, + 15.689, + 7.901, + 7.32, + 8.159, + 7.608, + 14.904, + 7.16, + 7.798, + 7.408, + 8.804, + 9.301, + 7.706, + 7.092, + 7.719, + 9.198, + 9.628, + 8.089, + 7.949, + 14.274, + 10.462, + 7.606, + 7.501, + 11.552, + 12.803, + 7.237, + 7.081, + 8.427, + 8.61, + 8.86, + 7.534, + 8.967, + 7.315, + 8.621, + 7.356, + 8.212, + 7.242, + 10.105, + 8.664, + 8.244, + 7.396, + 8.061, + 8.84, + 14.506, + 8.215, + 7.945, + 13.129, + 8.916, + 7.699, + 8.113, + 9.754, + 11.184, + 7.854, + 8.647, + 11.242, + 9.367, + 7.807, + 7.565, + 11.437, + 13.745, + 8.529, + 7.631, + 8.437, + 7.367, + 7.91, + 7.867, + 9.582, + 7.885, + 7.514, + 12.008, + 12.763, + 7.634, + 7.59, + 17.952, + 8.222, + 9.904, + 7.555, + 10.773, + 10.138, + 7.228, + 7.263, + 8.155, + 16.168, + 7.322, + 7.402, + 8.801, + 7.707, + 9.042, + 8.909, + 8.04, + 7.821, + 8.708, + 8.615, + 9.093, + 7.204, + 7.697, + 8.564, + 7.717, + 7.629, + 7.37, + 8.943, + 8.176, + 8.858, + 7.282, + 14.767, + 15.847, + 7.447, + 7.619, + 9.774, + 12.103, + 7.559, + 7.612, + 9.332, + 15.116, + 7.486, + 8.057, + 8.214, + 14.014, + 8.354, + 7.643, + 7.622, + 7.633, + 8.064, + 8.313, + 8.331, + 7.512, + 7.568, + 11.789, + 8.396, + 7.579, + 8.86, + 12.337, + 10.366, + 7.592, + 7.226, + 9.491, + 9.883, + 8.074, + 7.315, + 11.852, + 16.639, + 8.856, + 7.448, + 8.453, + 8.006, + 9.5, + 7.621, + 8.524, + 7.297, + 9.109, + 7.838, + 7.725, + 7.457, + 7.739, + 8.563, + 8.001, + 8.149, + 7.483, + 20.412, + 8.906, + 7.46, + 7.252, + 9.524, + 8.631, + 7.137, + 8, + 8.104, + 9.71, + 7.652, + 8.62, + 9.668, + 7.502, + 8.422, + 7.639, + 8.095, + 7.339, + 10.034, + 7.157, + 8.077, + 7.633, + 7.962, + 8.115, + 8.038, + 7.616, + 7.451, + 14.771, + 7.737, + 7.623, + 7.2, + 9.328, + 8.842, + 7.426, + 8.225, + 8.66, + 9.604, + 7.344, + 7.646, + 8.407, + 16.604, + 9.428, + 8.209, + 8.846, + 7.681, + 8.216, + 7.387, + 7.658, + 7.184, + 9.136, + 8.624, + 9.023, + 7.342, + 7.896, + 10.074, + 9.248, + 7.356, + 7.532, + 19.787, + 10.311, + 7.171, + 7.008, + 9.805, + 8.383, + 7.704, + 7.702, + 9.515, + 9.357, + 7.567, + 7.292, + 8.538, + 10.791, + 8.676, + 9.043, + 8.068, + 7.621, + 8.459, + 7.702, + 9.061, + 8.178, + 10.37, + 7.55, + 10.114, + 8.256, + 8.47, + 17.713, + 8.268, + 7.919, + 9.714, + 18.726, + 13.456, + 7.511, + 8.012, + 8.371, + 15.577, + 7.536, + 7.384, + 7.612, + 10.019, + 8.063, + 8.246, + 13.25, + 7.949, + 8.23, + 7.869, + 8.171, + 7.476, + 9.199, + 7.884, + 8.26, + 7.224, + 8.978, + 13.692, + 9.339, + 8.59, + 7.767, + 10.028, + 8.013, + 7.564, + 7.886, + 12.103, + 8.63, + 7.448, + 7.457, + 10.869, + 10.737, + 8.727, + 8.518, + 10.302, + 7.648, + 9.052, + 7.769, + 8.639, + 7.341, + 9.568, + 7.284, + 8.197, + 7.101, + 9.273, + 12.988, + 8.062, + 7.592, + 7.397, + 10.725, + 8.077, + 8.181, + 7.715, + 13.145, + 14.195, + 8.049, + 8.122, + 8.198, + 8.605, + 7.684, + 8.153, + 7.735, + 11.716, + 9.695, + 7.665, + 8.111, + 7.547, + 10.624, + 7.434, + 7.94, + 8.116, + 9.125, + 16.012, + 8.192, + 8.774, + 8.175, + 9.188, + 8.008, + 7.419, + 7.479, + 13.146, + 10.882, + 7.202, + 7.808, + 8.918, + 10.796, + 7.564, + 7.93, + 9.17, + 7.823, + 8.437, + 7.99, + 10.535, + 8.961, + 8.306, + 7.514, + 8.398, + 7.538, + 8.06, + 10.832, + 8.125, + 7.538, + 7.525, + 12.613, + 9.281, + 8.095, + 7.623, + 13.571, + 10.565, + 7.978, + 8.23, + 15.639, + 7.544, + 7.682, + 11.427, + 11.441, + 7.971, + 7.467, + 7.513, + 7.594, + 8.7, + 8.5, + 7.567, + 10.772, + 7.843, + 9.512, + 8.098, + 9.628, + 8.216, + 7.806, + 9.414, + 9.598, + 7.383, + 7.649, + 18.457, + 8.463, + 7.323, + 7.397, + 13.39, + 8.597, + 7.537, + 7.783, + 8.299, + 10.582, + 7.187, + 7.517, + 8.8, + 9.355, + 9.443, + 9, + 9.384, + 8.193, + 8.301, + 7.894, + 8.408, + 11.033, + 8.896, + 10.764, + 8.451, + 7.451, + 9.485, + 8.904, + 8.712, + 7.413, + 7.263, + 12.481, + 10.636, + 7.572, + 7.458, + 8.504, + 10.305, + 7.406, + 7.361, + 8.275, + 8.025, + 8.11, + 7.955, + 11.398, + 7.255, + 8.371, + 7.943, + 7.89, + 7.481, + 8.467, + 17.368, + 8.405, + 7.505, + 8.28, + 13.817, + 11.794, + 7.24, + 7.838, + 10.703, + 8.628, + 7.324, + 7.786, + 8.297, + 14.732, + 7.782, + 7.626, + 8.339, + 7.787, + 7.921, + 7.842, + 7.627, + 8.893, + 8.18, + 7.691, + 8.095, + 7.839, + 8.454, + 10.096, + 8.391, + 7.465, + 7.618, + 14.343, + 7.828, + 7.192, + 7.403, + 13.862, + 8.39, + 8.033, + 7.918, + 9.892, + 13.578, + 8.012, + 9.029, + 9.087, + 8.279, + 7.466, + 7.484, + 8.077, + 7.806, + 9.418, + 7.453, + 8.554, + 7.385, + 14.112, + 13.651, + 8.12, + 7.5, + 7.547, + 12.764, + 14.443, + 8.404, + 7.373, + 10.416, + 7.641, + 8.142, + 7.539, + 10.983, + 14.285, + 7.668, + 16.495, + 7.847, + 9.49, + 7.537, + 7.856, + 8.047, + 9.705, + 9.773, + 8.786, + 7.612, + 7.738, + 9.231, + 17.605, + 8.159, + 7.566, + 7.567, + 16.699, + 7.882, + 7.44, + 8.059, + 10.852, + 19.679, + 7.229, + 7.489, + 9.641, + 7.9, + 7.632, + 8.226, + 7.814, + 7.698, + 8.079, + 7.691, + 10.319, + 8.159, + 8.168, + 7.413, + 8.54, + 7.571, + 8.825, + 12.973, + 8.186, + 7.936, + 8.172, + 8.662, + 8.561, + 7.383, + 7.574, + 11.485, + 10.428, + 7.339, + 10.28, + 14.02, + 8.442, + 7.276, + 9.465, + 8.347, + 9.561, + 7.396, + 7.693, + 7.85, + 10.336, + 9.137, + 7.776, + 8.162, + 7.776, + 7.855, + 8.311, + 7.772, + 8.011, + 9.746, + 12.698, + 9.885, + 7.304, + 8.032, + 9.563, + 8.027, + 7.307, + 8.864, + 12.41, + 8.223, + 8.244, + 8.889, + 8.866, + 13.898, + 7.393, + 8.928, + 8.894, + 8.673, + 8.016, + 8.591, + 9.1, + 7.87, + 8.138, + 10.995, + 7.859, + 7.602, + 7.679, + 11.002, + 7.8, + 7.536, + 7.775, + 8.799, + 8.13, + 7.439, + 7.504, + 8.846, + 11.466, + 7.43, + 7.911, + 9.9, + 16.138, + 7.748, + 7.665, + 12.415, + 9.584, + 8.653, + 7.616, + 14.735, + 7.752, + 8.061, + 7.454, + 9.14, + 8.221, + 8.842, + 13.325, + 8.408, + 7.431, + 9.141, + 18.642, + 19.021, + 7.498, + 12.109, + 9.593, + 15.479, + 7.91, + 7.67, + 8.623, + 7.852, + 7.34, + 8.178, + 8.216, + 8.573, + 7.806, + 7.129, + 8.167, + 7.916, + 8.604, + 7.867, + 8.481, + 7.888, + 10.143, + 9.894, + 7.88, + 7.747, + 9.685, + 12.735, + 10.574, + 7.733, + 7.297, + 12.366, + 10.382, + 7.5, + 7.326, + 8.669, + 7.806, + 7.302, + 11.222, + 9.369, + 7.796, + 7.411, + 7.33, + 8.553, + 8.11, + 8.715, + 7.568, + 8.944, + 7.564, + 8.273, + 14.634, + 7.961, + 7.365, + 9.522, + 9.708, + 8, + 7.668, + 7.413, + 10.739, + 13.528, + 10.364, + 8.826, + 11.424, + 15.733, + 7.397, + 7.663, + 8.935, + 8.614, + 8.078, + 7.601, + 8.014, + 8.551, + 7.937, + 7.505, + 8.005, + 7.675, + 10.086, + 17.085, + 9.512, + 8.319, + 9.195, + 9.526, + 7.923, + 7.751, + 7.613, + 12.955, + 11.299, + 7.55, + 7.521, + 10.452, + 8.231, + 7.492, + 7.47, + 11.057, + 9.105, + 7.381, + 7.892, + 10.857, + 9.699, + 9.745, + 9.236, + 8.129, + 7.293, + 8.493, + 10.785, + 9.121, + 7.577, + 8.926, + 9.295, + 9.018, + 7.435, + 8.132, + 14.058, + 9.071, + 8.012, + 9.456, + 21.702, + 17.749, + 8.596, + 7.814, + 7.66, + 7.965, + 7.798, + 7.414, + 8.552, + 7.508, + 9.118, + 7.465, + 8.972, + 7.237, + 9.74, + 13.981, + 8.962, + 7.738, + 9.279, + 20.736, + 9.38, + 7.278, + 7.499, + 16.826, + 9.158, + 7.346, + 7.309, + 10.254, + 8.003, + 7.384, + 8.768, + 9.124, + 7.533, + 14.286, + 7.405, + 7.79, + 9.313, + 11.02, + 22.688, + 10.526, + 8.39, + 11.668, + 9.217, + 14.324, + 7.766, + 11.616, + 13.497, + 8.661, + 8.118, + 7.752, + 8.508, + 7.602, + 7.756, + 9.587, + 8.004, + 7.738, + 7.359, + 7.241, + 7.993, + 8.455, + 7.572, + 7.426, + 8.068, + 8.301, + 8.32, + 9.291, + 8.254, + 7.616, + 7.799, + 10.829, + 7.766, + 7.552, + 9.221, + 11.917, + 7.905, + 7.953, + 8.75, + 15.716, + 15.343, + 7.755, + 8.213, + 9.963, + 9.564, + 7.297, + 7.881, + 8.268, + 9.427, + 8.562, + 7.785, + 7.768, + 7.988, + 9.101, + 11.067, + 8.074, + 7.981, + 8.623, + 11.989, + 8.166, + 7.858, + 7.93, + 14.231, + 14.586, + 8.043, + 8.022, + 11.768, + 10.951, + 7.488, + 7.645, + 8.391, + 8.682, + 7.307, + 7.508, + 8.404, + 8.482, + 7.682, + 8.291, + 9.615, + 9.231, + 8.212, + 8.416, + 7.687, + 7.377, + 8.328, + 11.619, + 7.622, + 7.633, + 8.185, + 10.979, + 11.948, + 7.974, + 8.545, + 12.812, + 7.96, + 7.843, + 7.698, + 9.217, + 8.142, + 7.459, + 9.333, + 11.597, + 8.502, + 7.551, + 7.625, + 7.795, + 7.536, + 7.578, + 7.92, + 15.136, + 7.571, + 10.133, + 11.557, + 8.524, + 7.615, + 10.73, + 13.738, + 8.672, + 7.233, + 7.789, + 14.874, + 15.003, + 8.635, + 7.497, + 13.648, + 7.574, + 7.631, + 8.894, + 9.197, + 8.874, + 7.886, + 7.505, + 8.981, + 8.392, + 7.679, + 7.626, + 7.853, + 9.169, + 8.664, + 8.616, + 8.922, + 7.9, + 9.497, + 10.36, + 17.28, + 7.827, + 7.934, + 9.584, + 14.811, + 7.505, + 7.524, + 9.128, + 7.914, + 7.654, + 8.955, + 10.908, + 8.113, + 7.9, + 7.388, + 7.552, + 8.491, + 7.162, + 7.918, + 8.006, + 7.71, + 8.578, + 12.609, + 8.066, + 8.03, + 8.516, + 10.25, + 8.431, + 7.657, + 9.291, + 8.754, + 23.32, + 7.436, + 8.958, + 9.087, + 7.627, + 7.739, + 7.201, + 9.539, + 8.342, + 7.454, + 7.924, + 7.492, + 9.287, + 7.226, + 7.631, + 7.907, + 7.962, + 10.793, + 11.67, + 7.592, + 7.332, + 8.668, + 10.373, + 15.188, + 7.573, + 8.187, + 13.343, + 17.464, + 7.669, + 9.022, + 10.795, + 7.488, + 7.261, + 7.657, + 8.186, + 7.796, + 7.543, + 7.604, + 7.885, + 8.04, + 7.502, + 7.675, + 7.92, + 8.225, + 8.017, + 13.112, + 8.507, + 7.135, + 9.211, + 9.613, + 8.989, + 8.065, + 8.184, + 7.806, + 18.951, + 7.674, + 10.538, + 12.266, + 7.554, + 8.985, + 9.451, + 9.474, + 7.238, + 7.649, + 7.685, + 9.106, + 7.991, + 8.662, + 7.855, + 8.512, + 8.377, + 7.872, + 15.016, + 7.842, + 10.304, + 7.87, + 10.942, + 8.551, + 8.7, + 9.609, + 9.455, + 14.678, + 7.826, + 8.279, + 8.856, + 7.947, + 7.581, + 7.876, + 9.448, + 7.563, + 7.365, + 9.143, + 8.802, + 8.834, + 7.402, + 8.505, + 8.999, + 8.335, + 7.453, + 9.843, + 8.058, + 7.824, + 8.048, + 9.044, + 10.124, + 7.816, + 10.624, + 10.518, + 8.022, + 7.225, + 8.612, + 9.609, + 16.834, + 8.087, + 7.071, + 10.768, + 7.42, + 7.514, + 7.678, + 10.085, + 7.763, + 8.619, + 7.706 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141657_4dafabd3-676e-4797-8bcf-ec5375b08488.mp4", + "tMs": -27228 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141657_84192298-d879-4777-8834-5bab3a4911af.mp4", + "tMs": -27227 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141657_e6f7c619-7bbb-4621-8588-ff396108e074.mp4", + "tMs": -27227 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141657_263cd37a-d42f-4cde-b540-b099084cfdd4.mp4", + "tMs": -27224 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141710_3918e03f-6f7e-4ab4-a056-3808a2e2f77a.mp4", + "tMs": -17018 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141710_903de063-c4b8-4209-9c56-427ebdc26bfa.mp4", + "tMs": -17010 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141710_fd6ec2a8-a17b-4c29-84f8-fde70e5bdc59.mp4", + "tMs": -17010 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141710_51d539e4-9383-4485-8676-9285955a44a4.mp4", + "tMs": -17009 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141720_7c27dee6-b450-4d55-ae26-df524ef71f6a.mp4", + "tMs": -7016 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141720_d5394073-b29e-42c5-a97d-a26de79f9700.mp4", + "tMs": -7012 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141720_d61470e9-5877-4c26-a2d0-dda7d2c5d4a5.mp4", + "tMs": -7011 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141720_c23176ce-984c-4a24-a48b-6e6bcc103b22.mp4", + "tMs": -7010 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141730_e3bd2e0e-766d-4b75-b60b-121315d4739e.mp4", + "tMs": 2982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141730_5d2b226d-8c7f-4cf3-9520-9672906da5da.mp4", + "tMs": 2987 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141730_e071b99b-9396-43de-98a4-fb9c8cf0bb8d.mp4", + "tMs": 3000 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141730_1f77f3dd-acb2-4801-aaf8-3e2681af1ce2.mp4", + "tMs": 3000 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141740_5bf2fc57-0ef3-41f5-acce-bd73b83fc188.mp4", + "tMs": 12982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141740_f751e553-7973-4a4b-a0a7-df9ff9db810a.mp4", + "tMs": 12989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141740_84c9fb91-ddba-42ef-b9c6-e1659ef1ab8b.mp4", + "tMs": 12989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141740_764f55bc-c45d-4244-a211-4881de4f11ee.mp4", + "tMs": 12989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141750_2ebfe408-92ab-4bd5-ab52-f3d35b7a333d.mp4", + "tMs": 22982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141750_fcda47ff-f6be-4a97-bc3a-a30e8fb15445.mp4", + "tMs": 22987 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141750_600e1dac-5838-4d3e-8d19-de894fe88e05.mp4", + "tMs": 22989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141750_9a969e2e-4277-4690-9e0f-583d0749a56c.mp4", + "tMs": 22990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141800_26cde92c-741c-4df8-9b24-5d06a589dde9.mp4", + "tMs": 32983 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141800_96f6aee3-aeac-4043-94a5-bb76bff4bfe6.mp4", + "tMs": 32983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141800_9b2cb7a0-9bf6-4197-ad3d-a15d62ba2835.mp4", + "tMs": 32987 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141800_5e78d998-f084-4b45-872b-a673289c81c3.mp4", + "tMs": 32993 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141810_23674805-a5ee-470a-b6d0-1df62b37592b.mp4", + "tMs": 42986 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141810_d3f9c75e-dab9-447b-b670-cef22fd86040.mp4", + "tMs": 42989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141810_35fb9e7b-4378-481f-8512-ac8e4f77ba45.mp4", + "tMs": 42989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141810_6a1e4f75-a66b-4d44-afd4-f4be10dcba9a.mp4", + "tMs": 42989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141820_5fcf20eb-9c0f-4a7d-8ff9-14e8363aa0ec.mp4", + "tMs": 52984 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141820_0b5f246d-7eb3-410d-8a72-8f17bb52ca81.mp4", + "tMs": 52989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141820_99a830a9-b55d-4109-811b-72080852220b.mp4", + "tMs": 52989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141820_84b159cc-5068-4aeb-9b66-64ff17a90cb2.mp4", + "tMs": 52989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141830_773a1763-11ca-4fd3-aea0-099612c80f19.mp4", + "tMs": 62982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141830_59e89b44-9175-4b6a-ba53-4e6433076a46.mp4", + "tMs": 62988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141830_94e439bd-ff7c-4faf-aed5-9750402fcf81.mp4", + "tMs": 62988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141830_a1f508c0-c970-4893-88fc-0a38c57acf85.mp4", + "tMs": 62989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141840_d9fcad0b-2812-4636-abba-333ea611e88f.mp4", + "tMs": 72982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141840_7b7a19b0-bff5-42da-bd0b-c039de56ae96.mp4", + "tMs": 72988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141840_df53f02c-e094-4001-ae1c-00515fd05aba.mp4", + "tMs": 72989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141840_81262794-7669-4ee7-b5da-3518e4e254b6.mp4", + "tMs": 72996 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141850_87ec9885-d9dd-4d89-9760-9b99b65815b0.mp4", + "tMs": 82982 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141850_326843c4-5c58-45d1-b081-72cabfeb87f8.mp4", + "tMs": 82989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141850_a0ebf8d8-6738-4d0b-ba52-3c9f14421a42.mp4", + "tMs": 82991 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141850_8ba58fc5-3bb2-4f5a-9169-5b1fd2ee1cf7.mp4", + "tMs": 82991 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141900_93c7ced1-fdf8-4801-bb53-66d3924c1226.mp4", + "tMs": 92985 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141900_6a4037b2-1b3b-4835-913b-d1e142fc07f9.mp4", + "tMs": 92991 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141900_56bc4e95-268e-4615-af62-baf290cf3bf7.mp4", + "tMs": 92991 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141900_68381981-da31-4684-af7b-dc784d78dbf9.mp4", + "tMs": 92994 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141910_1dd259a7-e14a-441a-9512-c1cb0e4bdd21.mp4", + "tMs": 102983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141910_13124f71-7c4c-43d0-859f-a209442fd118.mp4", + "tMs": 102988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141910_d291df69-b90a-473a-b634-c1d4384f105f.mp4", + "tMs": 102988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141910_c46ab1b9-deea-4bbb-a3c0-01a2473c3921.mp4", + "tMs": 102993 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141920_9f1242bb-cd9c-4a43-95a8-fa853d913b2f.mp4", + "tMs": 112983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141920_aea85559-b854-43eb-89da-a173ae92be5f.mp4", + "tMs": 112988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141920_88f064d8-7f6d-4b3e-9871-f10d75d5c75e.mp4", + "tMs": 112988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141920_3002c9bc-fe78-4fc6-805f-01331d724ecb.mp4", + "tMs": 112988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141930_17db4d8e-4a39-42dd-b7b8-5ade97502245.mp4", + "tMs": 122985 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141930_ab252ea4-fb6e-406f-a014-4be600179da4.mp4", + "tMs": 122989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141930_f4093e8f-2054-409b-9b92-4717a746a49c.mp4", + "tMs": 122989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141930_7eb1101f-e20e-41d7-a74e-28d41645f06d.mp4", + "tMs": 122990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141940_4d850f97-4f58-482e-87ed-675254d40e41.mp4", + "tMs": 132984 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141940_9d821a40-450c-4255-9352-87cdf5f00ba4.mp4", + "tMs": 132985 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141940_021d17e1-ed4a-4c61-9345-b1b6493aeae5.mp4", + "tMs": 132988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141940_33e1e4a4-5111-47b4-834e-57a744cc71c6.mp4", + "tMs": 132988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_141950_3ce6bf04-16cd-4cc4-b109-b9e26212c4da.mp4", + "tMs": 142983 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_141950_57ee383b-d7f1-449f-ba23-f1247130dcea.mp4", + "tMs": 142987 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_141950_463fc8fb-f348-4596-a996-0add9ad36b2e.mp4", + "tMs": 142989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_141950_ac13827a-02f8-488a-a9c9-ef4894321fe4.mp4", + "tMs": 142990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142000_ac75397d-bfb2-4838-b205-136329fdbcd1.mp4", + "tMs": 152983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142000_382474d9-1ac8-4abd-a002-e2d8373fe5e7.mp4", + "tMs": 152989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142000_b72bdd37-cf71-4d03-9cb6-646804a6ed5d.mp4", + "tMs": 152992 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142000_fc54d51c-4587-4235-b59d-28f4f7ce42b2.mp4", + "tMs": 152996 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142010_fb7755a7-ae6b-4acc-954a-da3d779e2a6b.mp4", + "tMs": 162982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142010_f86a7b79-5aa8-472b-90ef-38c0e7a88447.mp4", + "tMs": 162992 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142010_bf35e673-f8da-4bc5-b1a0-9f35f72b252f.mp4", + "tMs": 162992 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142010_0f108bcb-37e3-4403-a4c7-f2986b0618a5.mp4", + "tMs": 162992 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142020_efb2f9be-893c-47e7-b4a5-67795b2fdce7.mp4", + "tMs": 172984 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142020_d6d186bc-fc6e-426c-aac4-9fc2d6d12c69.mp4", + "tMs": 172989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142020_1ca9abef-582e-43c3-998b-7c6be1ac0145.mp4", + "tMs": 172989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142020_f65bcaa3-6dd6-44f1-a1b1-38bf7ed1ec9c.mp4", + "tMs": 172991 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142030_bb3d9209-d6f5-42ca-86cf-11b33d6db6b6.mp4", + "tMs": 182983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142030_327b28ab-ccaf-4fee-ba0b-a123e7cff911.mp4", + "tMs": 182988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142030_3384ad1d-03ef-499c-b7ab-85fdf6d6ebd8.mp4", + "tMs": 182988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142030_791eac79-1c29-438d-8494-047296aebffe.mp4", + "tMs": 182988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142040_d14ddda4-83a4-4c02-a426-73611f15f89b.mp4", + "tMs": 192985 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142040_42c5839f-8a36-44a2-a2d6-dc8ca238e82d.mp4", + "tMs": 192996 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142040_e9bd64d2-5acb-4462-8472-c78cbfc369f1.mp4", + "tMs": 192996 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142040_8f763d46-3716-4289-b2f7-d400aa151f43.mp4", + "tMs": 192996 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142050_0bcc6efb-0b68-410d-9d15-b7301fdd0b7d.mp4", + "tMs": 202983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142050_34f97492-f22c-4494-8a9f-884106868622.mp4", + "tMs": 203001 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142050_b541104d-74aa-40af-9908-1847990d810f.mp4", + "tMs": 203001 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142050_add90159-3621-4c61-9e65-0f9ae9cb334c.mp4", + "tMs": 203001 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142100_fd12f71e-26df-4006-a851-8e3c783ac255.mp4", + "tMs": 212983 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142100_5294f42d-1bba-4dc3-b12f-f0717439d154.mp4", + "tMs": 212993 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142100_b9213f5d-8962-43bc-8729-8d2da8ac5d72.mp4", + "tMs": 212994 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142100_7ab6c06d-3851-4eee-9dcc-f65c53a6ebb1.mp4", + "tMs": 212994 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142110_12f2c730-a8e8-4c7e-a970-ade8b534df34.mp4", + "tMs": 222983 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142110_2e256eb7-6258-4c17-9763-e41c6f73d1fd.mp4", + "tMs": 222989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142110_d86e2e78-16da-4d8b-8a40-c862c900f60e.mp4", + "tMs": 222991 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142110_b8e410f0-b711-423b-9571-e38587a2ad91.mp4", + "tMs": 222998 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142120_370189a4-c13d-426f-9b14-ab9f441234bd.mp4", + "tMs": 232982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142120_bdbbb42a-6281-4450-b5b1-50806019f782.mp4", + "tMs": 232986 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142120_d8593efc-643a-43e0-b852-4515a2888079.mp4", + "tMs": 232987 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142120_d9d39cc6-dc94-4116-bf79-f341b3521613.mp4", + "tMs": 232989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142130_71212413-86ae-4ffa-a0d2-b758a952f252.mp4", + "tMs": 242983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142130_45b441c3-8fbb-48ee-b0f6-828925eb0ad3.mp4", + "tMs": 242988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142130_a793e7e1-0705-4c3b-acba-fdd3945b74f9.mp4", + "tMs": 242988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142130_7a7ccf80-2ec0-4e9f-9f87-649b8c702cbe.mp4", + "tMs": 242988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142140_6991177e-82b4-4ae1-be01-fb5581f80b4a.mp4", + "tMs": 252985 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142140_61b2ed74-8689-48bb-8868-a98c08b10d1d.mp4", + "tMs": 252988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142140_c0b2276c-cde2-4646-ae73-01c53b0c1d8e.mp4", + "tMs": 252989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142140_3815b6da-7952-4b82-98b3-e0e3f8778028.mp4", + "tMs": 252990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142150_6166bacf-2820-44f8-a017-a8221244b29c.mp4", + "tMs": 262981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142150_7ecbe4b4-f597-4cfa-be81-d2db1e46718b.mp4", + "tMs": 262987 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142150_f4f51306-1140-48dd-a84b-d8e7619d588d.mp4", + "tMs": 262988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142150_faffa6b0-4958-4994-aaa0-0c3b50f813d2.mp4", + "tMs": 263001 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142200_79d19382-6098-4d83-881c-a9ed98c38ae7.mp4", + "tMs": 272989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142200_4fa5f35e-aab9-4f45-8b17-9802b688bc18.mp4", + "tMs": 272998 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142200_46bc8a6c-f778-4f00-ae96-ef93bcbc60bc.mp4", + "tMs": 272998 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142200_c0653649-a2ef-4a6a-850e-659263ea5f91.mp4", + "tMs": 272999 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142210_608d093a-726f-4438-9ff9-f3724d7fd079.mp4", + "tMs": 282982 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142210_72577f7b-0eae-411d-bedd-54bb311016c8.mp4", + "tMs": 282988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142210_1d13cd7e-d4d4-4fa3-bc19-9c69169d9580.mp4", + "tMs": 282989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142210_34556c3f-aae7-4420-b283-5bae285815fa.mp4", + "tMs": 282993 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142220_2d53d827-1ac6-40d3-9a9b-e081da9c2425.mp4", + "tMs": 292982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142220_b06d7834-3b8a-4957-81a7-7ae572979eb9.mp4", + "tMs": 292986 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142220_644819f2-93b7-4449-92e3-eb554f936939.mp4", + "tMs": 292987 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142220_350faf99-1511-4a4e-bab3-15a31c941163.mp4", + "tMs": 292987 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142230_a487aa17-0faa-4efe-b52a-8e1c29421634.mp4", + "tMs": 302997 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142230_b5d6808a-8096-4c68-bf6c-4cd8a2bb93c5.mp4", + "tMs": 303000 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142230_eb155d7f-c7ac-4111-aacf-1d31d644a30e.mp4", + "tMs": 303000 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142230_d0d0ee08-229b-4f59-b845-9eeb2288b22b.mp4", + "tMs": 303000 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142240_87973a4d-d0db-4c56-9465-ab6e88a7633f.mp4", + "tMs": 312986 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142240_741a0059-da0e-44a4-b7ed-18caceb8e478.mp4", + "tMs": 312990 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142240_049fa3fd-8ad5-410b-8116-31d784bc464e.mp4", + "tMs": 313001 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142240_d77760f5-0abc-4dfa-a16f-64699eef189b.mp4", + "tMs": 313015 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142250_47154dbd-5220-4a19-abbf-0ee663b549da.mp4", + "tMs": 322984 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142250_d32b059d-4865-4ae1-934c-2df3cbd3fdc9.mp4", + "tMs": 322988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142250_dade9605-2667-4d12-963d-2e0b0d85e704.mp4", + "tMs": 322988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142250_7038df3d-d717-4127-b69f-d6778572cc1c.mp4", + "tMs": 322988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142300_2cf2e9b4-6c7b-4d5b-8652-d831cc348687.mp4", + "tMs": 332984 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142300_705c25dd-b428-4f39-9f54-5f2fca71cce8.mp4", + "tMs": 332990 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142300_38300f89-ea47-4639-af3c-54d6933a67d9.mp4", + "tMs": 332990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142300_33e838f3-6799-4979-bcf8-e8cce3f91273.mp4", + "tMs": 332991 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142310_fab09847-fa7c-4eaa-a69c-7cffa2a88245.mp4", + "tMs": 342984 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142310_09b78ff2-5062-49ef-bf15-e12dd82fc7fc.mp4", + "tMs": 342996 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142310_12af68d4-1c34-4c83-bee6-3631e53497ec.mp4", + "tMs": 342996 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142310_c2e86a58-1e58-40b4-a78c-a018be5ac372.mp4", + "tMs": 342997 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142320_d60d1b20-fd7f-4a4d-ad3f-9b476da2dfd0.mp4", + "tMs": 352985 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142320_edf38593-efcc-4059-9ba9-be67266cf2b0.mp4", + "tMs": 352988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142320_14211607-0192-473f-bc3f-c80e2ad6aaaa.mp4", + "tMs": 352988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142320_3d693e1f-e1e9-4e8b-aadd-4946575f8b16.mp4", + "tMs": 352988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142330_45856b34-869a-4b80-949a-b420f20a5a4d.mp4", + "tMs": 362982 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142330_3c3d7805-a0a1-4003-be38-73918364ab76.mp4", + "tMs": 362988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142330_df837aa9-917a-48e8-bcca-82e3d3b0045b.mp4", + "tMs": 362993 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142330_571a729c-d840-4859-b9d1-a56eaea125e6.mp4", + "tMs": 362994 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142340_5945d67d-f47d-46c5-bb14-a87ae688a604.mp4", + "tMs": 372998 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142340_7c9c3d10-d56d-4189-b180-8dae09ae68b2.mp4", + "tMs": 372999 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142340_f775af00-a87c-435c-9027-e1faafd8e06f.mp4", + "tMs": 372999 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142340_2b3c689c-20a0-4d50-98c9-2321c9717e43.mp4", + "tMs": 373000 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142350_2d25bd08-7740-4ff1-afbb-2df555eceef0.mp4", + "tMs": 382983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142350_bc144426-391d-4882-a1fa-e38ae0a0aa9f.mp4", + "tMs": 382991 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142350_7aa3eda1-1dc6-468f-b157-c298896a6bbb.mp4", + "tMs": 382993 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142350_aac4f9f0-83ec-432c-88f8-95436a354a32.mp4", + "tMs": 382995 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142400_ac9498cc-6453-4173-ab6c-9f675c3e000b.mp4", + "tMs": 392983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142400_09ff0ebc-b9cd-460d-ac2e-cec8ca7fd06b.mp4", + "tMs": 392989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142400_58de9376-72e5-487f-a49d-fc795b70ce40.mp4", + "tMs": 392990 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142400_edb7f60e-aabd-4f7e-a2ac-939247388094.mp4", + "tMs": 392990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142410_6b5271e8-7732-4c9a-b5e7-7066ec113fc4.mp4", + "tMs": 402982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142410_4ec3099f-9372-4d52-83f8-fe93fc488dbb.mp4", + "tMs": 402987 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142410_91d07c76-e10c-431a-b8d8-b9130243ce01.mp4", + "tMs": 402989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142410_6648df38-0518-4c20-a87e-8d2d8ed116b0.mp4", + "tMs": 402990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142420_47979544-606e-4d50-a33e-c9d944b77485.mp4", + "tMs": 412983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142420_78770d58-34de-434c-a50b-86bd14107fe6.mp4", + "tMs": 412988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142420_fac4a55a-4aee-44c0-8400-dd9ae213a3c9.mp4", + "tMs": 412997 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142420_e22446a9-e505-4e53-a5f4-46faafe964ec.mp4", + "tMs": 412998 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142430_c40e20c5-5db9-417d-83d9-c7f8116ac69e.mp4", + "tMs": 422983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142430_a9f1f804-62d8-4a19-9818-73849fb694c4.mp4", + "tMs": 422988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142430_ac31fd48-4156-4ee4-a27d-43d4289e3e38.mp4", + "tMs": 422989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142430_b52cf388-b938-459a-97c5-fbc5aafc9cfa.mp4", + "tMs": 422990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142440_e38cdf06-add2-4bcd-a809-e7437d9a608d.mp4", + "tMs": 432985 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142440_bd9c6f8f-8e14-45dd-baa5-626456fa6752.mp4", + "tMs": 432987 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142440_848b1624-480a-41dc-b1ef-02ea129abbbd.mp4", + "tMs": 432988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142440_fe93b673-699f-4702-93a1-3966d3ba8d9a.mp4", + "tMs": 432989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142450_d06748a7-dbef-4a4f-8d2d-0d89a55ec406.mp4", + "tMs": 442982 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142450_ee0ce3d6-6165-4307-92e9-bd17495d7874.mp4", + "tMs": 442989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142450_18caa6eb-7564-416e-abe9-18f09ea00164.mp4", + "tMs": 442990 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142450_aa0fd47d-e2c0-43e4-802f-bd1eef641041.mp4", + "tMs": 442990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142500_61965c42-6630-4837-bf3d-ae9f96743308.mp4", + "tMs": 452983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142500_cab19166-3a77-4420-a821-40cbbfa7ee51.mp4", + "tMs": 452995 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142500_3bf75852-7a9e-436f-9015-82a93ef63c30.mp4", + "tMs": 452995 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142500_5ad91779-5653-4a28-adc1-4440984ed303.mp4", + "tMs": 452995 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142510_5fe51ecc-bc50-43ae-9f8c-cdd1a05a3f64.mp4", + "tMs": 462985 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142510_fa138874-c310-4024-9379-2ce994f766fb.mp4", + "tMs": 462988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142510_a850c85f-e549-4763-899c-c8cae52c1426.mp4", + "tMs": 462988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142510_2fda5e15-4d57-4ccc-ac24-ba6705176164.mp4", + "tMs": 462988 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142520_1b42efa1-d392-43fd-943f-1924b7a3da50.mp4", + "tMs": 472983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142520_96906e5e-3fda-424d-b7a1-757ad2b0ec06.mp4", + "tMs": 472989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142520_fe558ef4-053c-408f-b8d3-83555fb5095a.mp4", + "tMs": 472989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142520_b5bc5193-c6ee-4f65-b5cf-210c0eccdc39.mp4", + "tMs": 472991 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142530_eae5afcb-4e15-46fa-b323-c36c5b0fcd4d.mp4", + "tMs": 482982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142530_2b421a35-8db3-497f-b7bc-319ca67e35cb.mp4", + "tMs": 482991 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142530_a1d28b69-4b9b-4adf-8a02-175d1cfd118f.mp4", + "tMs": 482992 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142530_897663d9-b0cb-47cc-9ca2-5b72ee10d5f6.mp4", + "tMs": 482992 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142540_fcb69e54-cf2d-4792-87ca-3a890b7099f0.mp4", + "tMs": 492983 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142540_13192dd8-4c01-42db-8901-8742aacf4f59.mp4", + "tMs": 492990 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142540_9bdd28d4-9191-440f-b2f5-c9dbf7038347.mp4", + "tMs": 492990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142540_6400f15a-befb-494f-97f7-1025a29918c4.mp4", + "tMs": 492990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142550_a6405726-1fc8-46bc-bac1-21cb24e61686.mp4", + "tMs": 502982 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142550_5451682e-cd7a-4001-a346-edfc76860e3d.mp4", + "tMs": 503000 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142550_851258a0-6e0b-442b-a9ba-d0a813b8cdff.mp4", + "tMs": 503001 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142550_c205c91b-5b06-44b3-9130-58635a51db67.mp4", + "tMs": 503008 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142600_5e56d903-7f01-49fe-acd1-c9851ca2216a.mp4", + "tMs": 512983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142600_41887dff-5cdb-45c6-9065-52dee8f281ad.mp4", + "tMs": 512989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142600_be6e49c8-7def-4130-b085-0b6d8279f2f5.mp4", + "tMs": 512991 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142600_d634344e-d3b4-433c-9bea-05578151bca3.mp4", + "tMs": 512992 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142610_4c116814-8cc0-4317-bcf2-b1649ac02945.mp4", + "tMs": 522982 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142610_752c9fc5-9e27-47e5-88aa-730797566674.mp4", + "tMs": 522995 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142610_672f8113-a2bf-4a4e-b5e2-2e592a0ffca5.mp4", + "tMs": 522995 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142610_8cc58099-d3b3-4d81-acc8-66e1e1544662.mp4", + "tMs": 523001 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142620_d97d49f4-fe36-49b9-9cf0-dc8af58d9312.mp4", + "tMs": 532983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142620_c2bcd3fd-5b9a-4051-bd5f-fd7561f48cb6.mp4", + "tMs": 532987 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142620_a7cc2ec8-02dd-4d9d-9196-7d2392ca5842.mp4", + "tMs": 532989 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142620_f944263f-03b1-4a46-a89c-eab02e145895.mp4", + "tMs": 532990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142630_8c618694-bee4-4f65-b1a8-dfd9a184ecaa.mp4", + "tMs": 542982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142630_eb324bbe-5672-4dec-9cce-62107cd99b6f.mp4", + "tMs": 542990 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142630_6e60be34-6b2f-4ef0-83aa-ae0dafd50f17.mp4", + "tMs": 542990 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142630_f3ddc926-52cd-406f-8db9-6e94f4722f9d.mp4", + "tMs": 542990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142640_f62cb652-2250-4e34-9fce-ad9dda3182f3.mp4", + "tMs": 552982 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142640_3843a5c3-0522-4fb2-9049-dfa8f3ddd346.mp4", + "tMs": 552987 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142640_e9adbdcd-4a0a-469d-8ca8-4a7d06a0e286.mp4", + "tMs": 552989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142640_c9a86a47-c6ca-4276-826c-428e34e84229.mp4", + "tMs": 552993 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142650_4642f5a4-ee95-4b08-85d0-514cb1da1955.mp4", + "tMs": 562982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142650_aa0a1c62-ef76-4677-8848-01e1d0dc2d66.mp4", + "tMs": 562990 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142650_80d5e37f-82a6-4276-aa7c-055c4acd4616.mp4", + "tMs": 562993 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142650_21ec47c4-33f9-46ce-a412-93e859d4e197.mp4", + "tMs": 563005 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142700_9bf7ffe1-b96e-42ab-9c9c-875bf7f74f60.mp4", + "tMs": 572983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142700_91272482-616a-4a40-a046-ab65f12aea68.mp4", + "tMs": 572989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142700_72f1f9ff-aa53-491e-9a6a-0a1656b9d985.mp4", + "tMs": 572989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142700_f9624f87-62c0-4393-a4ad-956f631106db.mp4", + "tMs": 572989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142710_5808eb04-caac-4f24-ba72-c09cb33af505.mp4", + "tMs": 582984 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142710_da13692c-f60e-401d-8d17-45c851a7751a.mp4", + "tMs": 582988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142710_b937a8a7-1ae8-48c7-a24c-74e5e5bba883.mp4", + "tMs": 582989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142710_bd4feb4d-d31c-477f-9a21-56adc5153c40.mp4", + "tMs": 582989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142720_bd0dfbff-c42b-4273-ac1b-722dbd092267.mp4", + "tMs": 592982 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142720_59e7d949-618d-4a9c-bce8-870a92c638e3.mp4", + "tMs": 592988 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142720_119989f7-04ca-46d0-9377-7cde29bfd030.mp4", + "tMs": 592988 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142720_182a4b1f-bea3-4499-93fd-7ecaaf5cb0aa.mp4", + "tMs": 592989 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_142730_1cd3a951-8b0f-4f6d-ab19-48ab85a112f7.mp4", + "tMs": 602982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_142730_73cb7224-6a12-470a-86d2-dac2407219eb.mp4", + "tMs": 602988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_142730_a6814d8b-ff0a-4cb3-8efa-f21c3ddff480.mp4", + "tMs": 602989 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_142730_ed54fd23-796a-4a0c-bf9f-90c02c35589e.mp4", + "tMs": 602989 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 2532, + "assets": { + "by_state_reason": { + "ready": 1124, + "unavailable:queue_full": 1408 + }, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 141364145, + "ready_asset_bytes_max": 126889, + "detections_total": 2532, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2532 + } + }, + "finished_at": "2026-11-08T19:27:35.852Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-enabled-saturated2.json b/tests/performance/observations/results/2026-11-08-x2-enabled-saturated2.json new file mode 100644 index 000000000..d3696fc26 --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-enabled-saturated2.json @@ -0,0 +1,8697 @@ +{ + "schema_version": 1, + "workload_name": "int01-observations-v1", + "mode": "enabled", + "status": "completed", + "started_at": "2026-11-08T20:00:51.874Z", + "host": { + "hostname_sha256": "e39d1e55f8ec5588", + "cpu_model": "AMD Ryzen 9 7950X 16-Core Processor", + "logical_cores": 32, + "cpu_governor": "powersave", + "ram_bytes": 131701190656, + "kernel": "7.0.0-38-generic", + "kernel_version": "#38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026", + "arch": "x64", + "os_release": "Ubuntu 26.04.1 LTS", + "loadavg_at_start": [ + 3.89, + 3.64, + 4.39 + ], + "storage": { + "path": "/home/matteius/opensensor/lightNVR/build/perf-observations/work", + "device": "/dev/nvme1n1p3", + "fstype": "ext4", + "mount_options": "rw,relatime", + "device_model": "Samsung SSD 990 PRO with Heatsink 4TB", + "rotational": false + }, + "ffmpeg": "ffmpeg version 8.0.1-3ubuntu2 Copyright (c) 2000-2025 the FFmpeg developers", + "go2rtc": "go2rtc version 1.9.14+dev.33ef787 (33ef787) linux/amd64", + "node": "v24.22.1" + }, + "build": { + "revision": "9ed02bb471b2934ccea3a796a1d8bb009389a255", + "branch": "feat/int01-event-evidence", + "tracked_diff_sha256": "405055d8456e5a351ce4dc41ae15057c8885b2ab150071a9a27c201cf6e8e4d9", + "binary": "build-int01-X2/bin/lightnvr", + "binary_sha256": "12ad35981afec5811e810f322c9fe86a79e60db9b7cb5680d74cdf79b90e82a6", + "harness_sha256": "fc6725afe7f49cdd1fd216af1462b76af47a58307b51b29b7ad90a5c682e4da2", + "metrics_sha256": "bc2e2d22463863616cf5394016924014fb877c51324a140d206fe3863f560769" + }, + "workload": { + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 600, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "live_viewers": 0, + "audio": "none (record_audio = false)", + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "interval_seconds": 1, + "threshold_percent": 50, + "model_sha256": "2f2f0af3c8915093d43843311ddc34f3b7838da9e9b42171c1adbba15834f196" + }, + "source": "user clip (see source_sha256)", + "source_sha256": "5bf26bbc3d0b1ada66aad39a80153b7b43917f6bad0d6208fecbfca50f48dcf6", + "source_probe": { + "programs": [], + "stream_groups": [], + "streams": [ + { + "codec_name": "h264", + "width": 1280, + "height": 720, + "pix_fmt": "yuv420p", + "r_frame_rate": "15/1" + } + ], + "format": { + "duration": "30.000000" + } + } + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "feature": { + "stream_extra": { + "observation_evidence_enabled": true, + "observation_max_bytes": 67108864 + }, + "config_extra": null, + "system_settings": { + "observation_global_queue_bytes": "4194304" + } + }, + "definitions": { + "latency.segment_interval_ms": "Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.", + "latency.segment_interval_excess_ms": "segment_interval_ms minus nominal.", + "latency.db_probe_ms": "Round trip of GET /api/recordings?limit=1 with a session cookie every 250 ms; contends on the same global DB mutex as recording-path catalog writes.", + "gaps.metrics_recording_gaps_total": "Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).", + "gaps.catalog_gaps_total": "Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).", + "gaps.segment_interval_gaps": "Segment intervals longer than nominal + 5 s inside the window." + }, + "limitations": [ + "Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).", + "No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.", + "Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary." + ], + "definitions_x2": { + "feature_metrics": "L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.", + "feature_metrics.submit_latency": "Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).", + "feature_db": "Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows." + }, + "fixture_directory": "/home/matteius/opensensor/lightNVR/build/perf-observations/work/lightnvr-contract-VtdlCj", + "detector": { + "inferences_in_window": 2404, + "inferences_per_stream_per_second": 1.002, + "expected_per_stream_per_second": 1, + "detections_in_window": 2404, + "source": "count of \"sod_cnn_predict returned\" info lines in server.log during the window" + }, + "log_errors_in_window": 0, + "feature_metrics": { + "accepted": 1073, + "dropped": { + "queue_full": 1331, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "ready": 1073, + "failures": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "unavailable_submitted": 0, + "bytes_written": 134910689, + "high_water_items": 1, + "high_water_bytes": 2771712, + "camera_high_water_items": 1, + "limits": { + "items": 64, + "camera_items": 8, + "bytes": 4194304, + "camera_bytes": 4194304, + "discard_bytes": 33554432 + }, + "discards": { + "deferred": 1331, + "inline": 0 + }, + "discard_pending_bytes_max": 5529600, + "backlog_items_max": 1, + "backlog_bytes_max": 2771712, + "drop_ring_depth_max": 2, + "budget_exhausted_cameras_max": 0, + "relay_lag_seconds_max": 0, + "queue_allocated_end": 1, + "worker_running_end": 1, + "retained_bytes_by_camera_end": { + "5f37b549-c8db-493a-8a96-0a78a5eedf48": 17734895, + "85d34c92-a4b8-4bb6-871b-be12f7f9d4d3": 16681390, + "8b4a20c7-0359-4f24-b9cc-299b4fccb9ef": 22914964, + "ac62fb73-0b50-4464-b785-98d2c8e2f82d": 23995953 + }, + "budget_bytes_by_camera_end": { + "5f37b549-c8db-493a-8a96-0a78a5eedf48": 67108864, + "85d34c92-a4b8-4bb6-871b-be12f7f9d4d3": 67108864, + "8b4a20c7-0359-4f24-b9cc-299b4fccb9ef": 67108864, + "ac62fb73-0b50-4464-b785-98d2c8e2f82d": 67108864 + }, + "history_observations_end": 2513, + "publications_end": { + "pending": 0, + "failed": 0, + "expired": 0 + }, + "submit_latency": { + "count": 2404, + "sum_seconds": 0.010672585, + "mean_seconds": 0.00000444, + "max_seconds": 0.00036462, + "p50_upper_seconds": 0.000005, + "p99_upper_seconds": 0.00005, + "p999_upper_seconds": 0.00005, + "buckets": [ + { + "le": 0.000001, + "count": 0 + }, + { + "le": 0.000005, + "count": 1487 + }, + { + "le": 0.00001, + "count": 2362 + }, + { + "le": 0.00005, + "count": 2404 + }, + { + "le": 0.0001, + "count": 2404 + }, + { + "le": 0.00025, + "count": 2404 + }, + { + "le": 0.0005, + "count": 2404 + }, + { + "le": 0.001, + "count": 2404 + }, + { + "le": 0.01, + "count": 2404 + }, + { + "le": "+Inf", + "count": 2404 + } + ], + "max_note": "max_seconds is the per-process-run maximum (includes warmup); buckets are the window delta" + } + }, + "evidence_init_log": "Observation evidence initialized (worker starts on first submission; queue 64 items / 4194304 bytes, 8 per camera)", + "window_unix": { + "start": 1794168094, + "end": 1794168696 + }, + "latency": { + "segment_interval_ms": { + "count": 240, + "min": 9977.07, + "p50": 10000.277, + "p95": 10009.342, + "p99": 10018.892, + "max": 10020.092, + "mean": 10000.058 + }, + "segment_interval_excess_ms": { + "count": 240, + "min": -22.93, + "p50": 0.277, + "p95": 9.342, + "p99": 18.892, + "max": 20.092, + "mean": 0.058 + }, + "db_probe_ms": { + "count": 2318, + "min": 6.97, + "p50": 8.116, + "p95": 13.702, + "p99": 18.806, + "max": 29.333, + "mean": 8.905 + }, + "db_probe_failures": 0, + "per_stream_segment_interval_ms": { + "Load0": { + "count": 60, + "min": 9979.486, + "p50": 10000.296, + "p95": 10004.784, + "p99": 10014.204, + "max": 10018.691, + "mean": 9999.954 + }, + "Load1": { + "count": 60, + "min": 9978.829, + "p50": 10000.579, + "p95": 10008.026, + "p99": 10015.47, + "max": 10020.092, + "mean": 10000.083 + }, + "Load2": { + "count": 60, + "min": 9981.385, + "p50": 10000.317, + "p95": 10009.44, + "p99": 10016.442, + "max": 10019.02, + "mean": 10000.112 + }, + "Load3": { + "count": 60, + "min": 9977.07, + "p50": 10000.095, + "p95": 10009.934, + "p99": 10015.492, + "max": 10019.208, + "mean": 10000.084 + } + } + }, + "gaps": { + "metrics_recording_gaps_total": 0, + "metrics_recording_gaps_by_stream_end": { + "Load0": 0, + "Load1": 0, + "Load2": 0, + "Load3": 0 + }, + "catalog_gaps_total": 0, + "catalog_gaps": [], + "segment_interval_gaps": 0 + }, + "recording": { + "segments_created_in_window": 240, + "expected_segments_in_window": 240, + "metrics_segments_delta": 240, + "metrics_bytes_written_delta": 1032278160, + "frames_dropped_delta": 0, + "reconnects_delta": 0, + "catalog_rows_after_warmup": 244, + "fps_samples": { + "count": 480, + "min": 15, + "p50": 15.1, + "p95": 15.2, + "p99": 15.2, + "max": 15.2, + "mean": 15.102 + }, + "streams_up_min": 4 + }, + "resources": { + "lightnvr": { + "window_seconds": 599.319, + "cpu_percent_one_core": { + "count": 520, + "min": 114.945, + "p50": 140.444, + "p95": 193.316, + "p99": 200.986, + "max": 207.657, + "mean": 150.674 + }, + "cpu_percent_one_core_avg": 151.031, + "rss_kib": { + "count": 521, + "min": 899020, + "p50": 904412, + "p95": 928868, + "p99": 929128, + "max": 929304, + "mean": 910811.516 + }, + "rss_high_water_kib": 929304, + "disk_write_bytes": 1718915072, + "disk_write_bytes_per_sec": 2868114.244, + "threads_max": 35, + "fds_max": 38 + }, + "fixture_go2rtc_and_publishers": { + "cpu_percent_one_core_avg": 2.098, + "processes": 5 + }, + "storage_bytes_end": 1229471499, + "loadavg_at_end": [ + 4.75, + 3.29, + 3.64 + ] + }, + "window": { + "start": "2026-11-08T20:01:34.779Z", + "end": "2026-11-08T20:11:35.958Z", + "seconds": 601.18 + }, + "samples": { + "proc": [ + { + "tMs": 13, + "cpuTicks": 4160, + "rssKiB": 921240, + "peakRssKiB": 921240, + "threads": 35, + "fds": 36, + "writeBytes": 73736192 + }, + { + "tMs": 1228, + "cpuTicks": 4393, + "rssKiB": 922728, + "peakRssKiB": 922728, + "threads": 35, + "fds": 36, + "writeBytes": 76275712 + }, + { + "tMs": 2410, + "cpuTicks": 4575, + "rssKiB": 899020, + "peakRssKiB": 919272, + "threads": 35, + "fds": 36, + "writeBytes": 79720448 + }, + { + "tMs": 3561, + "cpuTicks": 4727, + "rssKiB": 899208, + "peakRssKiB": 919436, + "threads": 35, + "fds": 36, + "writeBytes": 84340736 + }, + { + "tMs": 4711, + "cpuTicks": 4870, + "rssKiB": 899388, + "peakRssKiB": 919436, + "threads": 35, + "fds": 36, + "writeBytes": 86908928 + }, + { + "tMs": 5847, + "cpuTicks": 5015, + "rssKiB": 899768, + "peakRssKiB": 919912, + "threads": 35, + "fds": 36, + "writeBytes": 89452544 + }, + { + "tMs": 7022, + "cpuTicks": 5192, + "rssKiB": 924012, + "peakRssKiB": 924012, + "threads": 35, + "fds": 36, + "writeBytes": 93458432 + }, + { + "tMs": 8210, + "cpuTicks": 5413, + "rssKiB": 924092, + "peakRssKiB": 924092, + "threads": 35, + "fds": 36, + "writeBytes": 95825920 + }, + { + "tMs": 9361, + "cpuTicks": 5620, + "rssKiB": 923640, + "peakRssKiB": 923640, + "threads": 35, + "fds": 37, + "writeBytes": 98623488 + }, + { + "tMs": 10513, + "cpuTicks": 5768, + "rssKiB": 899580, + "peakRssKiB": 920024, + "threads": 35, + "fds": 36, + "writeBytes": 101752832 + }, + { + "tMs": 11636, + "cpuTicks": 5915, + "rssKiB": 899608, + "peakRssKiB": 920024, + "threads": 35, + "fds": 36, + "writeBytes": 104284160 + }, + { + "tMs": 12751, + "cpuTicks": 6064, + "rssKiB": 899636, + "peakRssKiB": 920024, + "threads": 35, + "fds": 36, + "writeBytes": 106811392 + }, + { + "tMs": 13883, + "cpuTicks": 6226, + "rssKiB": 899640, + "peakRssKiB": 920024, + "threads": 35, + "fds": 36, + "writeBytes": 111132672 + }, + { + "tMs": 15050, + "cpuTicks": 6401, + "rssKiB": 924136, + "peakRssKiB": 924136, + "threads": 35, + "fds": 36, + "writeBytes": 115036160 + }, + { + "tMs": 16202, + "cpuTicks": 6608, + "rssKiB": 924144, + "peakRssKiB": 924144, + "threads": 35, + "fds": 36, + "writeBytes": 117932032 + }, + { + "tMs": 17359, + "cpuTicks": 6794, + "rssKiB": 900004, + "peakRssKiB": 920024, + "threads": 35, + "fds": 36, + "writeBytes": 121155584 + }, + { + "tMs": 18503, + "cpuTicks": 6933, + "rssKiB": 900016, + "peakRssKiB": 920024, + "threads": 35, + "fds": 36, + "writeBytes": 123994112 + }, + { + "tMs": 19645, + "cpuTicks": 7089, + "rssKiB": 899696, + "peakRssKiB": 920052, + "threads": 35, + "fds": 36, + "writeBytes": 126578688 + }, + { + "tMs": 20789, + "cpuTicks": 7227, + "rssKiB": 899544, + "peakRssKiB": 920052, + "threads": 35, + "fds": 36, + "writeBytes": 129183744 + }, + { + "tMs": 21915, + "cpuTicks": 7382, + "rssKiB": 899872, + "peakRssKiB": 920052, + "threads": 35, + "fds": 36, + "writeBytes": 132055040 + }, + { + "tMs": 23114, + "cpuTicks": 7614, + "rssKiB": 924052, + "peakRssKiB": 924052, + "threads": 35, + "fds": 36, + "writeBytes": 137699328 + }, + { + "tMs": 24276, + "cpuTicks": 7825, + "rssKiB": 924056, + "peakRssKiB": 924056, + "threads": 35, + "fds": 36, + "writeBytes": 140197888 + }, + { + "tMs": 25418, + "cpuTicks": 7978, + "rssKiB": 900240, + "peakRssKiB": 920264, + "threads": 35, + "fds": 36, + "writeBytes": 143687680 + }, + { + "tMs": 26529, + "cpuTicks": 8115, + "rssKiB": 900244, + "peakRssKiB": 920512, + "threads": 35, + "fds": 36, + "writeBytes": 146214912 + }, + { + "tMs": 27641, + "cpuTicks": 8281, + "rssKiB": 900248, + "peakRssKiB": 920512, + "threads": 35, + "fds": 36, + "writeBytes": 149176320 + }, + { + "tMs": 28765, + "cpuTicks": 8432, + "rssKiB": 900260, + "peakRssKiB": 920512, + "threads": 35, + "fds": 36, + "writeBytes": 151711744 + }, + { + "tMs": 29883, + "cpuTicks": 8584, + "rssKiB": 901284, + "peakRssKiB": 920512, + "threads": 35, + "fds": 36, + "writeBytes": 154251264 + }, + { + "tMs": 31052, + "cpuTicks": 8782, + "rssKiB": 925524, + "peakRssKiB": 925524, + "threads": 35, + "fds": 36, + "writeBytes": 157982720 + }, + { + "tMs": 32215, + "cpuTicks": 8999, + "rssKiB": 925528, + "peakRssKiB": 925528, + "threads": 35, + "fds": 36, + "writeBytes": 160612352 + }, + { + "tMs": 33382, + "cpuTicks": 9181, + "rssKiB": 901392, + "peakRssKiB": 921576, + "threads": 35, + "fds": 36, + "writeBytes": 166207488 + }, + { + "tMs": 34531, + "cpuTicks": 9313, + "rssKiB": 901396, + "peakRssKiB": 921576, + "threads": 35, + "fds": 36, + "writeBytes": 168587264 + }, + { + "tMs": 35676, + "cpuTicks": 9471, + "rssKiB": 901096, + "peakRssKiB": 921576, + "threads": 35, + "fds": 36, + "writeBytes": 171216896 + }, + { + "tMs": 36809, + "cpuTicks": 9622, + "rssKiB": 901424, + "peakRssKiB": 921896, + "threads": 35, + "fds": 36, + "writeBytes": 173588480 + }, + { + "tMs": 37936, + "cpuTicks": 9763, + "rssKiB": 901440, + "peakRssKiB": 922100, + "threads": 35, + "fds": 36, + "writeBytes": 176177152 + }, + { + "tMs": 39104, + "cpuTicks": 9961, + "rssKiB": 925952, + "peakRssKiB": 925952, + "threads": 35, + "fds": 36, + "writeBytes": 180207616 + }, + { + "tMs": 40263, + "cpuTicks": 10172, + "rssKiB": 925992, + "peakRssKiB": 925992, + "threads": 35, + "fds": 36, + "writeBytes": 183160832 + }, + { + "tMs": 41425, + "cpuTicks": 10330, + "rssKiB": 901828, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 186650624 + }, + { + "tMs": 42558, + "cpuTicks": 10474, + "rssKiB": 901848, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 189071360 + }, + { + "tMs": 43680, + "cpuTicks": 10624, + "rssKiB": 901880, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 193437696 + }, + { + "tMs": 44841, + "cpuTicks": 10767, + "rssKiB": 901880, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 196640768 + }, + { + "tMs": 45967, + "cpuTicks": 10916, + "rssKiB": 926060, + "peakRssKiB": 926060, + "threads": 35, + "fds": 36, + "writeBytes": 200548352 + }, + { + "tMs": 47153, + "cpuTicks": 11129, + "rssKiB": 926076, + "peakRssKiB": 926076, + "threads": 35, + "fds": 36, + "writeBytes": 202420224 + }, + { + "tMs": 48290, + "cpuTicks": 11324, + "rssKiB": 926088, + "peakRssKiB": 926088, + "threads": 35, + "fds": 38, + "writeBytes": 205881344 + }, + { + "tMs": 49431, + "cpuTicks": 11488, + "rssKiB": 901564, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 209076224 + }, + { + "tMs": 50563, + "cpuTicks": 11628, + "rssKiB": 901752, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 211726336 + }, + { + "tMs": 51702, + "cpuTicks": 11768, + "rssKiB": 901764, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 214507520 + }, + { + "tMs": 52818, + "cpuTicks": 11908, + "rssKiB": 902088, + "peakRssKiB": 923256, + "threads": 35, + "fds": 36, + "writeBytes": 217055232 + }, + { + "tMs": 53934, + "cpuTicks": 12057, + "rssKiB": 902416, + "peakRssKiB": 923344, + "threads": 35, + "fds": 36, + "writeBytes": 221212672 + }, + { + "tMs": 55137, + "cpuTicks": 12283, + "rssKiB": 926592, + "peakRssKiB": 926592, + "threads": 35, + "fds": 37, + "writeBytes": 225157120 + }, + { + "tMs": 56282, + "cpuTicks": 12499, + "rssKiB": 926628, + "peakRssKiB": 926628, + "threads": 35, + "fds": 36, + "writeBytes": 227717120 + }, + { + "tMs": 57441, + "cpuTicks": 12660, + "rssKiB": 902464, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 231714816 + }, + { + "tMs": 58597, + "cpuTicks": 12807, + "rssKiB": 902468, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 234094592 + }, + { + "tMs": 59737, + "cpuTicks": 12954, + "rssKiB": 902472, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 236683264 + }, + { + "tMs": 60881, + "cpuTicks": 13092, + "rssKiB": 902480, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 239316992 + }, + { + "tMs": 62085, + "cpuTicks": 13298, + "rssKiB": 926652, + "peakRssKiB": 926652, + "threads": 35, + "fds": 36, + "writeBytes": 243228672 + }, + { + "tMs": 63235, + "cpuTicks": 13497, + "rssKiB": 926656, + "peakRssKiB": 926656, + "threads": 35, + "fds": 36, + "writeBytes": 248066048 + }, + { + "tMs": 64383, + "cpuTicks": 13670, + "rssKiB": 902168, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 251498496 + }, + { + "tMs": 65520, + "cpuTicks": 13818, + "rssKiB": 902184, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 254062592 + }, + { + "tMs": 66661, + "cpuTicks": 13957, + "rssKiB": 902188, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 256602112 + }, + { + "tMs": 67802, + "cpuTicks": 14099, + "rssKiB": 902192, + "peakRssKiB": 923900, + "threads": 35, + "fds": 37, + "writeBytes": 259158016 + }, + { + "tMs": 68949, + "cpuTicks": 14235, + "rssKiB": 902196, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 262283264 + }, + { + "tMs": 70181, + "cpuTicks": 14486, + "rssKiB": 926392, + "peakRssKiB": 926392, + "threads": 35, + "fds": 36, + "writeBytes": 266186752 + }, + { + "tMs": 71357, + "cpuTicks": 14704, + "rssKiB": 902224, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 269545472 + }, + { + "tMs": 72503, + "cpuTicks": 14856, + "rssKiB": 902228, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 272211968 + }, + { + "tMs": 73616, + "cpuTicks": 15022, + "rssKiB": 901908, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 276598784 + }, + { + "tMs": 74746, + "cpuTicks": 15162, + "rssKiB": 902036, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 279691264 + }, + { + "tMs": 75875, + "cpuTicks": 15307, + "rssKiB": 902036, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 282390528 + }, + { + "tMs": 77040, + "cpuTicks": 15487, + "rssKiB": 926212, + "peakRssKiB": 926212, + "threads": 35, + "fds": 36, + "writeBytes": 285315072 + }, + { + "tMs": 78207, + "cpuTicks": 15703, + "rssKiB": 926212, + "peakRssKiB": 926212, + "threads": 35, + "fds": 36, + "writeBytes": 288690176 + }, + { + "tMs": 79368, + "cpuTicks": 15904, + "rssKiB": 902052, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 291971072 + }, + { + "tMs": 80496, + "cpuTicks": 16051, + "rssKiB": 902060, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 295211008 + }, + { + "tMs": 81634, + "cpuTicks": 16203, + "rssKiB": 902064, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 298029056 + }, + { + "tMs": 82757, + "cpuTicks": 16356, + "rssKiB": 902068, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 300417024 + }, + { + "tMs": 83923, + "cpuTicks": 16505, + "rssKiB": 901748, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 304889856 + }, + { + "tMs": 85111, + "cpuTicks": 16709, + "rssKiB": 926244, + "peakRssKiB": 926244, + "threads": 35, + "fds": 36, + "writeBytes": 308555776 + }, + { + "tMs": 86271, + "cpuTicks": 16910, + "rssKiB": 926252, + "peakRssKiB": 926252, + "threads": 35, + "fds": 36, + "writeBytes": 312037376 + }, + { + "tMs": 87398, + "cpuTicks": 17068, + "rssKiB": 902084, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 315211776 + }, + { + "tMs": 88541, + "cpuTicks": 17211, + "rssKiB": 902088, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 317657088 + }, + { + "tMs": 89666, + "cpuTicks": 17349, + "rssKiB": 902092, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 320053248 + }, + { + "tMs": 90787, + "cpuTicks": 17494, + "rssKiB": 902148, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 322920448 + }, + { + "tMs": 91909, + "cpuTicks": 17641, + "rssKiB": 902152, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 325902336 + }, + { + "tMs": 93099, + "cpuTicks": 17841, + "rssKiB": 926660, + "peakRssKiB": 926660, + "threads": 35, + "fds": 36, + "writeBytes": 331571200 + }, + { + "tMs": 94269, + "cpuTicks": 18065, + "rssKiB": 926660, + "peakRssKiB": 926660, + "threads": 35, + "fds": 36, + "writeBytes": 334163968 + }, + { + "tMs": 95427, + "cpuTicks": 18228, + "rssKiB": 902500, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 337719296 + }, + { + "tMs": 96561, + "cpuTicks": 18370, + "rssKiB": 902500, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 340234240 + }, + { + "tMs": 97690, + "cpuTicks": 18525, + "rssKiB": 902500, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 343367680 + }, + { + "tMs": 98825, + "cpuTicks": 18677, + "rssKiB": 902500, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 345993216 + }, + { + "tMs": 99955, + "cpuTicks": 18835, + "rssKiB": 902524, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 348721152 + }, + { + "tMs": 101159, + "cpuTicks": 19067, + "rssKiB": 926716, + "peakRssKiB": 926716, + "threads": 35, + "fds": 36, + "writeBytes": 352124928 + }, + { + "tMs": 102321, + "cpuTicks": 19289, + "rssKiB": 926740, + "peakRssKiB": 926740, + "threads": 35, + "fds": 36, + "writeBytes": 354578432 + }, + { + "tMs": 103456, + "cpuTicks": 19461, + "rssKiB": 902248, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 360341504 + }, + { + "tMs": 104590, + "cpuTicks": 19625, + "rssKiB": 902576, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 362954752 + }, + { + "tMs": 105709, + "cpuTicks": 19771, + "rssKiB": 902588, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 365604864 + }, + { + "tMs": 106830, + "cpuTicks": 19911, + "rssKiB": 902816, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 368168960 + }, + { + "tMs": 107965, + "cpuTicks": 20064, + "rssKiB": 926760, + "peakRssKiB": 926760, + "threads": 35, + "fds": 36, + "writeBytes": 371617792 + }, + { + "tMs": 109168, + "cpuTicks": 20286, + "rssKiB": 926880, + "peakRssKiB": 926880, + "threads": 35, + "fds": 36, + "writeBytes": 374550528 + }, + { + "tMs": 110332, + "cpuTicks": 20480, + "rssKiB": 902724, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 378560512 + }, + { + "tMs": 111483, + "cpuTicks": 20629, + "rssKiB": 902724, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 381177856 + }, + { + "tMs": 112630, + "cpuTicks": 20770, + "rssKiB": 902724, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 383713280 + }, + { + "tMs": 113762, + "cpuTicks": 20928, + "rssKiB": 902724, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 387854336 + }, + { + "tMs": 114899, + "cpuTicks": 21080, + "rssKiB": 902400, + "peakRssKiB": 923900, + "threads": 35, + "fds": 37, + "writeBytes": 391208960 + }, + { + "tMs": 116082, + "cpuTicks": 21281, + "rssKiB": 926900, + "peakRssKiB": 926900, + "threads": 35, + "fds": 36, + "writeBytes": 394997760 + }, + { + "tMs": 117263, + "cpuTicks": 21522, + "rssKiB": 926900, + "peakRssKiB": 926900, + "threads": 35, + "fds": 36, + "writeBytes": 397541376 + }, + { + "tMs": 118398, + "cpuTicks": 21690, + "rssKiB": 903052, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 401113088 + }, + { + "tMs": 119543, + "cpuTicks": 21856, + "rssKiB": 903376, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 403689472 + }, + { + "tMs": 120661, + "cpuTicks": 21995, + "rssKiB": 903388, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 406827008 + }, + { + "tMs": 121776, + "cpuTicks": 22139, + "rssKiB": 903388, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 409444352 + }, + { + "tMs": 122904, + "cpuTicks": 22291, + "rssKiB": 903388, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 412237824 + }, + { + "tMs": 124069, + "cpuTicks": 22485, + "rssKiB": 927560, + "peakRssKiB": 927560, + "threads": 35, + "fds": 36, + "writeBytes": 417935360 + }, + { + "tMs": 125231, + "cpuTicks": 22705, + "rssKiB": 927560, + "peakRssKiB": 927560, + "threads": 35, + "fds": 36, + "writeBytes": 420593664 + }, + { + "tMs": 126371, + "cpuTicks": 22894, + "rssKiB": 903400, + "peakRssKiB": 923900, + "threads": 35, + "fds": 37, + "writeBytes": 424837120 + }, + { + "tMs": 127506, + "cpuTicks": 23036, + "rssKiB": 903400, + "peakRssKiB": 923900, + "threads": 35, + "fds": 37, + "writeBytes": 427499520 + }, + { + "tMs": 128621, + "cpuTicks": 23187, + "rssKiB": 903400, + "peakRssKiB": 923900, + "threads": 35, + "fds": 38, + "writeBytes": 429912064 + }, + { + "tMs": 129742, + "cpuTicks": 23330, + "rssKiB": 902428, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 432492544 + }, + { + "tMs": 130876, + "cpuTicks": 23494, + "rssKiB": 902428, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 435154944 + }, + { + "tMs": 132028, + "cpuTicks": 23674, + "rssKiB": 926628, + "peakRssKiB": 926628, + "threads": 35, + "fds": 36, + "writeBytes": 439758848 + }, + { + "tMs": 133217, + "cpuTicks": 23917, + "rssKiB": 926624, + "peakRssKiB": 926624, + "threads": 35, + "fds": 36, + "writeBytes": 444186624 + }, + { + "tMs": 134377, + "cpuTicks": 24104, + "rssKiB": 902476, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 447950848 + }, + { + "tMs": 135521, + "cpuTicks": 24254, + "rssKiB": 902508, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 450445312 + }, + { + "tMs": 136682, + "cpuTicks": 24418, + "rssKiB": 902508, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 453738496 + }, + { + "tMs": 137842, + "cpuTicks": 24579, + "rssKiB": 902508, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 456167424 + }, + { + "tMs": 139015, + "cpuTicks": 24766, + "rssKiB": 926800, + "peakRssKiB": 926800, + "threads": 35, + "fds": 36, + "writeBytes": 460017664 + }, + { + "tMs": 140205, + "cpuTicks": 25001, + "rssKiB": 926800, + "peakRssKiB": 926800, + "threads": 35, + "fds": 36, + "writeBytes": 462815232 + }, + { + "tMs": 141356, + "cpuTicks": 25206, + "rssKiB": 926808, + "peakRssKiB": 926808, + "threads": 35, + "fds": 37, + "writeBytes": 466223104 + }, + { + "tMs": 142496, + "cpuTicks": 25364, + "rssKiB": 902948, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 469643264 + }, + { + "tMs": 143623, + "cpuTicks": 25530, + "rssKiB": 902948, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 473878528 + }, + { + "tMs": 144748, + "cpuTicks": 25686, + "rssKiB": 902948, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 476340224 + }, + { + "tMs": 145875, + "cpuTicks": 25834, + "rssKiB": 902948, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 478949376 + }, + { + "tMs": 147044, + "cpuTicks": 26010, + "rssKiB": 927120, + "peakRssKiB": 927120, + "threads": 35, + "fds": 36, + "writeBytes": 482795520 + }, + { + "tMs": 148213, + "cpuTicks": 26236, + "rssKiB": 927440, + "peakRssKiB": 927440, + "threads": 35, + "fds": 36, + "writeBytes": 486162432 + }, + { + "tMs": 149369, + "cpuTicks": 26420, + "rssKiB": 903592, + "peakRssKiB": 923900, + "threads": 35, + "fds": 36, + "writeBytes": 489758720 + }, + { + "tMs": 150495, + "cpuTicks": 26565, + "rssKiB": 903620, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 492421120 + }, + { + "tMs": 151623, + "cpuTicks": 26703, + "rssKiB": 903620, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 495071232 + }, + { + "tMs": 152780, + "cpuTicks": 26845, + "rssKiB": 903620, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 497704960 + }, + { + "tMs": 153928, + "cpuTicks": 26998, + "rssKiB": 903620, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 502624256 + }, + { + "tMs": 155116, + "cpuTicks": 27200, + "rssKiB": 927792, + "peakRssKiB": 927792, + "threads": 35, + "fds": 36, + "writeBytes": 506548224 + }, + { + "tMs": 156287, + "cpuTicks": 27419, + "rssKiB": 927796, + "peakRssKiB": 927796, + "threads": 35, + "fds": 38, + "writeBytes": 509419520 + }, + { + "tMs": 157421, + "cpuTicks": 27579, + "rssKiB": 903624, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 512638976 + }, + { + "tMs": 158560, + "cpuTicks": 27732, + "rssKiB": 903636, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 515244032 + }, + { + "tMs": 159687, + "cpuTicks": 27876, + "rssKiB": 902680, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 518463488 + }, + { + "tMs": 160821, + "cpuTicks": 28023, + "rssKiB": 902704, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 521121792 + }, + { + "tMs": 161964, + "cpuTicks": 28177, + "rssKiB": 925040, + "peakRssKiB": 925040, + "threads": 35, + "fds": 36, + "writeBytes": 524787712 + }, + { + "tMs": 163160, + "cpuTicks": 28402, + "rssKiB": 926880, + "peakRssKiB": 926880, + "threads": 35, + "fds": 36, + "writeBytes": 529465344 + }, + { + "tMs": 164332, + "cpuTicks": 28621, + "rssKiB": 902832, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 532611072 + }, + { + "tMs": 165474, + "cpuTicks": 28764, + "rssKiB": 902832, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 536453120 + }, + { + "tMs": 166631, + "cpuTicks": 28922, + "rssKiB": 902832, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 538894336 + }, + { + "tMs": 167789, + "cpuTicks": 29059, + "rssKiB": 902832, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 541544448 + }, + { + "tMs": 168915, + "cpuTicks": 29206, + "rssKiB": 902832, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 543928320 + }, + { + "tMs": 170103, + "cpuTicks": 29415, + "rssKiB": 927004, + "peakRssKiB": 927004, + "threads": 35, + "fds": 36, + "writeBytes": 547647488 + }, + { + "tMs": 171254, + "cpuTicks": 29627, + "rssKiB": 927008, + "peakRssKiB": 927008, + "threads": 35, + "fds": 36, + "writeBytes": 550830080 + }, + { + "tMs": 172405, + "cpuTicks": 29803, + "rssKiB": 903160, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 554369024 + }, + { + "tMs": 173539, + "cpuTicks": 29944, + "rssKiB": 903160, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 558645248 + }, + { + "tMs": 174673, + "cpuTicks": 30103, + "rssKiB": 903160, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 561041408 + }, + { + "tMs": 175806, + "cpuTicks": 30251, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 563593216 + }, + { + "tMs": 176940, + "cpuTicks": 30395, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 566800384 + }, + { + "tMs": 178146, + "cpuTicks": 30622, + "rssKiB": 927656, + "peakRssKiB": 927656, + "threads": 35, + "fds": 36, + "writeBytes": 570531840 + }, + { + "tMs": 179302, + "cpuTicks": 30822, + "rssKiB": 927656, + "peakRssKiB": 927656, + "threads": 35, + "fds": 36, + "writeBytes": 572989440 + }, + { + "tMs": 180451, + "cpuTicks": 30988, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 576434176 + }, + { + "tMs": 181590, + "cpuTicks": 31132, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 579096576 + }, + { + "tMs": 182736, + "cpuTicks": 31278, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 582529024 + }, + { + "tMs": 183879, + "cpuTicks": 31430, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 586997760 + }, + { + "tMs": 185072, + "cpuTicks": 31623, + "rssKiB": 927656, + "peakRssKiB": 927656, + "threads": 35, + "fds": 36, + "writeBytes": 590921728 + }, + { + "tMs": 186210, + "cpuTicks": 31832, + "rssKiB": 927332, + "peakRssKiB": 927332, + "threads": 35, + "fds": 36, + "writeBytes": 593575936 + }, + { + "tMs": 187355, + "cpuTicks": 32018, + "rssKiB": 903160, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 597970944 + }, + { + "tMs": 188484, + "cpuTicks": 32171, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 600440832 + }, + { + "tMs": 189633, + "cpuTicks": 32324, + "rssKiB": 903484, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 603017216 + }, + { + "tMs": 190790, + "cpuTicks": 32470, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 605868032 + }, + { + "tMs": 191932, + "cpuTicks": 32626, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 608559104 + }, + { + "tMs": 193117, + "cpuTicks": 32838, + "rssKiB": 927664, + "peakRssKiB": 927664, + "threads": 35, + "fds": 36, + "writeBytes": 614989824 + }, + { + "tMs": 194287, + "cpuTicks": 33039, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 37, + "writeBytes": 617832448 + }, + { + "tMs": 195424, + "cpuTicks": 33202, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 621223936 + }, + { + "tMs": 196570, + "cpuTicks": 33344, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 623837184 + }, + { + "tMs": 197714, + "cpuTicks": 33491, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 626331648 + }, + { + "tMs": 198847, + "cpuTicks": 33643, + "rssKiB": 903168, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 629886976 + }, + { + "tMs": 199994, + "cpuTicks": 33801, + "rssKiB": 927340, + "peakRssKiB": 927340, + "threads": 35, + "fds": 36, + "writeBytes": 633782272 + }, + { + "tMs": 201180, + "cpuTicks": 34023, + "rssKiB": 927340, + "peakRssKiB": 927340, + "threads": 35, + "fds": 36, + "writeBytes": 636432384 + }, + { + "tMs": 202351, + "cpuTicks": 34226, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 639795200 + }, + { + "tMs": 203493, + "cpuTicks": 34388, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 645074944 + }, + { + "tMs": 204624, + "cpuTicks": 34554, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 647512064 + }, + { + "tMs": 205774, + "cpuTicks": 34706, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 650100736 + }, + { + "tMs": 206913, + "cpuTicks": 34871, + "rssKiB": 903492, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 652500992 + }, + { + "tMs": 208119, + "cpuTicks": 35099, + "rssKiB": 927664, + "peakRssKiB": 927664, + "threads": 35, + "fds": 36, + "writeBytes": 656388096 + }, + { + "tMs": 209258, + "cpuTicks": 35315, + "rssKiB": 927664, + "peakRssKiB": 927664, + "threads": 35, + "fds": 36, + "writeBytes": 658964480 + }, + { + "tMs": 210398, + "cpuTicks": 35500, + "rssKiB": 903524, + "peakRssKiB": 924028, + "threads": 35, + "fds": 37, + "writeBytes": 663252992 + }, + { + "tMs": 211517, + "cpuTicks": 35662, + "rssKiB": 903536, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 665706496 + }, + { + "tMs": 212644, + "cpuTicks": 35824, + "rssKiB": 903212, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 668291072 + }, + { + "tMs": 213778, + "cpuTicks": 35985, + "rssKiB": 903212, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 672653312 + }, + { + "tMs": 214910, + "cpuTicks": 36138, + "rssKiB": 903216, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 675987456 + }, + { + "tMs": 216123, + "cpuTicks": 36360, + "rssKiB": 927388, + "peakRssKiB": 927388, + "threads": 35, + "fds": 36, + "writeBytes": 679907328 + }, + { + "tMs": 217291, + "cpuTicks": 36586, + "rssKiB": 927388, + "peakRssKiB": 927388, + "threads": 35, + "fds": 36, + "writeBytes": 682643456 + }, + { + "tMs": 218445, + "cpuTicks": 36763, + "rssKiB": 903216, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 686145536 + }, + { + "tMs": 219593, + "cpuTicks": 36925, + "rssKiB": 903540, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 688959488 + }, + { + "tMs": 220714, + "cpuTicks": 37072, + "rssKiB": 903540, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 692523008 + }, + { + "tMs": 221827, + "cpuTicks": 37221, + "rssKiB": 903540, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 695201792 + }, + { + "tMs": 222958, + "cpuTicks": 37384, + "rssKiB": 909096, + "peakRssKiB": 924028, + "threads": 35, + "fds": 35, + "writeBytes": 697954304 + }, + { + "tMs": 224177, + "cpuTicks": 37637, + "rssKiB": 927712, + "peakRssKiB": 927712, + "threads": 35, + "fds": 36, + "writeBytes": 703385600 + }, + { + "tMs": 225346, + "cpuTicks": 37872, + "rssKiB": 927712, + "peakRssKiB": 927712, + "threads": 35, + "fds": 36, + "writeBytes": 705859584 + }, + { + "tMs": 226495, + "cpuTicks": 38017, + "rssKiB": 903540, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 710467584 + }, + { + "tMs": 227644, + "cpuTicks": 38174, + "rssKiB": 903540, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 713015296 + }, + { + "tMs": 228802, + "cpuTicks": 38331, + "rssKiB": 903540, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 715616256 + }, + { + "tMs": 229938, + "cpuTicks": 38475, + "rssKiB": 903216, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 718180352 + }, + { + "tMs": 231149, + "cpuTicks": 38713, + "rssKiB": 927400, + "peakRssKiB": 927400, + "threads": 35, + "fds": 36, + "writeBytes": 722051072 + }, + { + "tMs": 232308, + "cpuTicks": 38922, + "rssKiB": 927396, + "peakRssKiB": 927396, + "threads": 35, + "fds": 37, + "writeBytes": 725643264 + }, + { + "tMs": 233444, + "cpuTicks": 39101, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 730705920 + }, + { + "tMs": 234586, + "cpuTicks": 39255, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 733270016 + }, + { + "tMs": 235704, + "cpuTicks": 39415, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 735657984 + }, + { + "tMs": 236821, + "cpuTicks": 39565, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 739041280 + }, + { + "tMs": 237936, + "cpuTicks": 39708, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 741720064 + }, + { + "tMs": 239146, + "cpuTicks": 39951, + "rssKiB": 927072, + "peakRssKiB": 927072, + "threads": 35, + "fds": 37, + "writeBytes": 745586688 + }, + { + "tMs": 240309, + "cpuTicks": 40177, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 37, + "writeBytes": 748670976 + }, + { + "tMs": 241462, + "cpuTicks": 40324, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 37, + "writeBytes": 751493120 + }, + { + "tMs": 242601, + "cpuTicks": 40473, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 753893376 + }, + { + "tMs": 243742, + "cpuTicks": 40630, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 759062528 + }, + { + "tMs": 244878, + "cpuTicks": 40776, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 761741312 + }, + { + "tMs": 246056, + "cpuTicks": 40963, + "rssKiB": 927396, + "peakRssKiB": 927396, + "threads": 35, + "fds": 36, + "writeBytes": 765861888 + }, + { + "tMs": 247227, + "cpuTicks": 41182, + "rssKiB": 927396, + "peakRssKiB": 927396, + "threads": 35, + "fds": 36, + "writeBytes": 768442368 + }, + { + "tMs": 248393, + "cpuTicks": 41385, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 772947968 + }, + { + "tMs": 249523, + "cpuTicks": 41531, + "rssKiB": 903224, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 775639040 + }, + { + "tMs": 250670, + "cpuTicks": 41674, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 778342400 + }, + { + "tMs": 251802, + "cpuTicks": 41821, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 780972032 + }, + { + "tMs": 252949, + "cpuTicks": 41975, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 783417344 + }, + { + "tMs": 254157, + "cpuTicks": 42201, + "rssKiB": 927072, + "peakRssKiB": 927072, + "threads": 35, + "fds": 36, + "writeBytes": 790036480 + }, + { + "tMs": 255331, + "cpuTicks": 42422, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 37, + "writeBytes": 793317376 + }, + { + "tMs": 256488, + "cpuTicks": 42576, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 796078080 + }, + { + "tMs": 257623, + "cpuTicks": 42719, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 798539776 + }, + { + "tMs": 258756, + "cpuTicks": 42873, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 801886208 + }, + { + "tMs": 259890, + "cpuTicks": 43027, + "rssKiB": 902900, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 804331520 + }, + { + "tMs": 261071, + "cpuTicks": 43221, + "rssKiB": 927396, + "peakRssKiB": 927396, + "threads": 35, + "fds": 36, + "writeBytes": 808542208 + }, + { + "tMs": 262233, + "cpuTicks": 43431, + "rssKiB": 927396, + "peakRssKiB": 927396, + "threads": 35, + "fds": 36, + "writeBytes": 811147264 + }, + { + "tMs": 263372, + "cpuTicks": 43624, + "rssKiB": 903548, + "peakRssKiB": 924028, + "threads": 35, + "fds": 36, + "writeBytes": 816201728 + }, + { + "tMs": 264515, + "cpuTicks": 43769, + "rssKiB": 903548, + "peakRssKiB": 924088, + "threads": 35, + "fds": 36, + "writeBytes": 819679232 + }, + { + "tMs": 265648, + "cpuTicks": 43911, + "rssKiB": 903576, + "peakRssKiB": 924088, + "threads": 35, + "fds": 36, + "writeBytes": 822247424 + }, + { + "tMs": 266774, + "cpuTicks": 44063, + "rssKiB": 903576, + "peakRssKiB": 924196, + "threads": 35, + "fds": 36, + "writeBytes": 824594432 + }, + { + "tMs": 267932, + "cpuTicks": 44205, + "rssKiB": 903576, + "peakRssKiB": 924196, + "threads": 35, + "fds": 36, + "writeBytes": 827219968 + }, + { + "tMs": 269143, + "cpuTicks": 44433, + "rssKiB": 927748, + "peakRssKiB": 927748, + "threads": 35, + "fds": 36, + "writeBytes": 831139840 + }, + { + "tMs": 270306, + "cpuTicks": 44664, + "rssKiB": 927892, + "peakRssKiB": 927892, + "threads": 35, + "fds": 36, + "writeBytes": 833658880 + }, + { + "tMs": 271455, + "cpuTicks": 44822, + "rssKiB": 903720, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 837955584 + }, + { + "tMs": 272600, + "cpuTicks": 44983, + "rssKiB": 903720, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 840425472 + }, + { + "tMs": 273738, + "cpuTicks": 45145, + "rssKiB": 903396, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 844791808 + }, + { + "tMs": 274867, + "cpuTicks": 45303, + "rssKiB": 903396, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 847441920 + }, + { + "tMs": 276054, + "cpuTicks": 45489, + "rssKiB": 927584, + "peakRssKiB": 927584, + "threads": 35, + "fds": 36, + "writeBytes": 852344832 + }, + { + "tMs": 277241, + "cpuTicks": 45724, + "rssKiB": 927584, + "peakRssKiB": 927584, + "threads": 35, + "fds": 36, + "writeBytes": 854720512 + }, + { + "tMs": 278397, + "cpuTicks": 45921, + "rssKiB": 903412, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 858116096 + }, + { + "tMs": 279522, + "cpuTicks": 46067, + "rssKiB": 903736, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 860672000 + }, + { + "tMs": 280633, + "cpuTicks": 46227, + "rssKiB": 903740, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 863096832 + }, + { + "tMs": 281749, + "cpuTicks": 46367, + "rssKiB": 903740, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 866672640 + }, + { + "tMs": 282867, + "cpuTicks": 46525, + "rssKiB": 903740, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 869085184 + }, + { + "tMs": 284039, + "cpuTicks": 46706, + "rssKiB": 927912, + "peakRssKiB": 927912, + "threads": 35, + "fds": 36, + "writeBytes": 874774528 + }, + { + "tMs": 285208, + "cpuTicks": 46916, + "rssKiB": 927912, + "peakRssKiB": 927912, + "threads": 35, + "fds": 36, + "writeBytes": 877182976 + }, + { + "tMs": 286371, + "cpuTicks": 47114, + "rssKiB": 903740, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 880533504 + }, + { + "tMs": 287525, + "cpuTicks": 47269, + "rssKiB": 903740, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 883896320 + }, + { + "tMs": 288669, + "cpuTicks": 47427, + "rssKiB": 903740, + "peakRssKiB": 924304, + "threads": 35, + "fds": 36, + "writeBytes": 886484992 + }, + { + "tMs": 289797, + "cpuTicks": 47585, + "rssKiB": 903416, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 888954880 + }, + { + "tMs": 290923, + "cpuTicks": 47739, + "rssKiB": 903416, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 891596800 + }, + { + "tMs": 292116, + "cpuTicks": 47948, + "rssKiB": 927588, + "peakRssKiB": 927588, + "threads": 35, + "fds": 36, + "writeBytes": 895479808 + }, + { + "tMs": 293279, + "cpuTicks": 48164, + "rssKiB": 927588, + "peakRssKiB": 927588, + "threads": 35, + "fds": 37, + "writeBytes": 901115904 + }, + { + "tMs": 294418, + "cpuTicks": 48331, + "rssKiB": 903092, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 904282112 + }, + { + "tMs": 295555, + "cpuTicks": 48472, + "rssKiB": 903092, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 907108352 + }, + { + "tMs": 296703, + "cpuTicks": 48633, + "rssKiB": 903104, + "peakRssKiB": 924436, + "threads": 35, + "fds": 37, + "writeBytes": 909594624 + }, + { + "tMs": 297833, + "cpuTicks": 48788, + "rssKiB": 903104, + "peakRssKiB": 924436, + "threads": 35, + "fds": 37, + "writeBytes": 912257024 + }, + { + "tMs": 298973, + "cpuTicks": 48950, + "rssKiB": 927276, + "peakRssKiB": 927276, + "threads": 35, + "fds": 37, + "writeBytes": 917114880 + }, + { + "tMs": 300177, + "cpuTicks": 49181, + "rssKiB": 927600, + "peakRssKiB": 927600, + "threads": 35, + "fds": 36, + "writeBytes": 919891968 + }, + { + "tMs": 301342, + "cpuTicks": 49381, + "rssKiB": 903428, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 923394048 + }, + { + "tMs": 302480, + "cpuTicks": 49535, + "rssKiB": 903428, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 925900800 + }, + { + "tMs": 303612, + "cpuTicks": 49694, + "rssKiB": 903428, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 931295232 + }, + { + "tMs": 304740, + "cpuTicks": 49854, + "rssKiB": 903428, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 933924864 + }, + { + "tMs": 305866, + "cpuTicks": 50026, + "rssKiB": 903432, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 936529920 + }, + { + "tMs": 307027, + "cpuTicks": 50218, + "rssKiB": 927604, + "peakRssKiB": 927604, + "threads": 35, + "fds": 36, + "writeBytes": 940388352 + }, + { + "tMs": 308201, + "cpuTicks": 50438, + "rssKiB": 927604, + "peakRssKiB": 927604, + "threads": 35, + "fds": 36, + "writeBytes": 942997504 + }, + { + "tMs": 309377, + "cpuTicks": 50645, + "rssKiB": 903432, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 947482624 + }, + { + "tMs": 310535, + "cpuTicks": 50790, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 950022144 + }, + { + "tMs": 311692, + "cpuTicks": 50931, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 37, + "writeBytes": 952725504 + }, + { + "tMs": 312845, + "cpuTicks": 51087, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 37, + "writeBytes": 955363328 + }, + { + "tMs": 314041, + "cpuTicks": 51295, + "rssKiB": 927280, + "peakRssKiB": 927280, + "threads": 35, + "fds": 37, + "writeBytes": 961101824 + }, + { + "tMs": 315215, + "cpuTicks": 51517, + "rssKiB": 927280, + "peakRssKiB": 927280, + "threads": 35, + "fds": 36, + "writeBytes": 964448256 + }, + { + "tMs": 316375, + "cpuTicks": 51706, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 967888896 + }, + { + "tMs": 317527, + "cpuTicks": 51872, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 970416128 + }, + { + "tMs": 318661, + "cpuTicks": 52033, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 973021184 + }, + { + "tMs": 319792, + "cpuTicks": 52186, + "rssKiB": 903108, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 976535552 + }, + { + "tMs": 320935, + "cpuTicks": 52352, + "rssKiB": 903436, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 979070976 + }, + { + "tMs": 322117, + "cpuTicks": 52563, + "rssKiB": 927608, + "peakRssKiB": 927608, + "threads": 35, + "fds": 36, + "writeBytes": 983027712 + }, + { + "tMs": 323281, + "cpuTicks": 52781, + "rssKiB": 927608, + "peakRssKiB": 927608, + "threads": 35, + "fds": 36, + "writeBytes": 987475968 + }, + { + "tMs": 324437, + "cpuTicks": 52949, + "rssKiB": 903436, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 990859264 + }, + { + "tMs": 325578, + "cpuTicks": 53102, + "rssKiB": 903436, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 994238464 + }, + { + "tMs": 326721, + "cpuTicks": 53242, + "rssKiB": 903436, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 996843520 + }, + { + "tMs": 327852, + "cpuTicks": 53379, + "rssKiB": 903436, + "peakRssKiB": 924436, + "threads": 35, + "fds": 36, + "writeBytes": 999297024 + }, + { + "tMs": 329029, + "cpuTicks": 53540, + "rssKiB": 927932, + "peakRssKiB": 927932, + "threads": 35, + "fds": 36, + "writeBytes": 1003327488 + }, + { + "tMs": 330196, + "cpuTicks": 53745, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1006006272 + }, + { + "tMs": 331350, + "cpuTicks": 53920, + "rssKiB": 904696, + "peakRssKiB": 924836, + "threads": 35, + "fds": 36, + "writeBytes": 1010188288 + }, + { + "tMs": 332483, + "cpuTicks": 54061, + "rssKiB": 904696, + "peakRssKiB": 924960, + "threads": 35, + "fds": 36, + "writeBytes": 1012625408 + }, + { + "tMs": 333633, + "cpuTicks": 54219, + "rssKiB": 904696, + "peakRssKiB": 924960, + "threads": 35, + "fds": 36, + "writeBytes": 1016872960 + }, + { + "tMs": 334798, + "cpuTicks": 54370, + "rssKiB": 904696, + "peakRssKiB": 925124, + "threads": 35, + "fds": 36, + "writeBytes": 1019498496 + }, + { + "tMs": 336012, + "cpuTicks": 54559, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1023758336 + }, + { + "tMs": 337216, + "cpuTicks": 54800, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1027100672 + }, + { + "tMs": 338364, + "cpuTicks": 54996, + "rssKiB": 904696, + "peakRssKiB": 925152, + "threads": 35, + "fds": 36, + "writeBytes": 1030680576 + }, + { + "tMs": 339497, + "cpuTicks": 55156, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1033277440 + }, + { + "tMs": 340617, + "cpuTicks": 55318, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1035681792 + }, + { + "tMs": 341751, + "cpuTicks": 55463, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1038176256 + }, + { + "tMs": 342883, + "cpuTicks": 55620, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1041707008 + }, + { + "tMs": 344076, + "cpuTicks": 55829, + "rssKiB": 928544, + "peakRssKiB": 928544, + "threads": 35, + "fds": 36, + "writeBytes": 1047384064 + }, + { + "tMs": 345242, + "cpuTicks": 56055, + "rssKiB": 928544, + "peakRssKiB": 928544, + "threads": 35, + "fds": 36, + "writeBytes": 1049903104 + }, + { + "tMs": 346405, + "cpuTicks": 56247, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1053454336 + }, + { + "tMs": 347560, + "cpuTicks": 56398, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1056890880 + }, + { + "tMs": 348719, + "cpuTicks": 56557, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1059504128 + }, + { + "tMs": 349845, + "cpuTicks": 56716, + "rssKiB": 904372, + "peakRssKiB": 925192, + "threads": 35, + "fds": 36, + "writeBytes": 1062105088 + }, + { + "tMs": 351013, + "cpuTicks": 56878, + "rssKiB": 928544, + "peakRssKiB": 928544, + "threads": 35, + "fds": 36, + "writeBytes": 1066115072 + }, + { + "tMs": 352207, + "cpuTicks": 57107, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1068793856 + }, + { + "tMs": 353379, + "cpuTicks": 57304, + "rssKiB": 904696, + "peakRssKiB": 925264, + "threads": 35, + "fds": 36, + "writeBytes": 1074937856 + }, + { + "tMs": 354520, + "cpuTicks": 57448, + "rssKiB": 905016, + "peakRssKiB": 925412, + "threads": 35, + "fds": 36, + "writeBytes": 1077510144 + }, + { + "tMs": 355636, + "cpuTicks": 57583, + "rssKiB": 905016, + "peakRssKiB": 925412, + "threads": 35, + "fds": 36, + "writeBytes": 1080078336 + }, + { + "tMs": 356759, + "cpuTicks": 57742, + "rssKiB": 905016, + "peakRssKiB": 925552, + "threads": 35, + "fds": 36, + "writeBytes": 1082474496 + }, + { + "tMs": 357883, + "cpuTicks": 57898, + "rssKiB": 905016, + "peakRssKiB": 925552, + "threads": 35, + "fds": 36, + "writeBytes": 1085083648 + }, + { + "tMs": 359060, + "cpuTicks": 58083, + "rssKiB": 929188, + "peakRssKiB": 929188, + "threads": 35, + "fds": 36, + "writeBytes": 1089921024 + }, + { + "tMs": 360220, + "cpuTicks": 58299, + "rssKiB": 929188, + "peakRssKiB": 929188, + "threads": 35, + "fds": 36, + "writeBytes": 1092296704 + }, + { + "tMs": 361367, + "cpuTicks": 58497, + "rssKiB": 905016, + "peakRssKiB": 925836, + "threads": 35, + "fds": 36, + "writeBytes": 1095618560 + }, + { + "tMs": 362509, + "cpuTicks": 58649, + "rssKiB": 905016, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1098235904 + }, + { + "tMs": 363630, + "cpuTicks": 58811, + "rssKiB": 905016, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1102422016 + }, + { + "tMs": 364760, + "cpuTicks": 58966, + "rssKiB": 904696, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1106087936 + }, + { + "tMs": 365887, + "cpuTicks": 59130, + "rssKiB": 904696, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1108537344 + }, + { + "tMs": 367068, + "cpuTicks": 59337, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1112281088 + }, + { + "tMs": 368247, + "cpuTicks": 59572, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1114914816 + }, + { + "tMs": 369416, + "cpuTicks": 59778, + "rssKiB": 904696, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1118416896 + }, + { + "tMs": 370554, + "cpuTicks": 59946, + "rssKiB": 904696, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1121837056 + }, + { + "tMs": 371707, + "cpuTicks": 60092, + "rssKiB": 904696, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1124564992 + }, + { + "tMs": 372845, + "cpuTicks": 60249, + "rssKiB": 904696, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1127084032 + }, + { + "tMs": 374024, + "cpuTicks": 60434, + "rssKiB": 928868, + "peakRssKiB": 928868, + "threads": 35, + "fds": 36, + "writeBytes": 1132875776 + }, + { + "tMs": 375185, + "cpuTicks": 60644, + "rssKiB": 928220, + "peakRssKiB": 928220, + "threads": 35, + "fds": 36, + "writeBytes": 1135521792 + }, + { + "tMs": 376330, + "cpuTicks": 60858, + "rssKiB": 928220, + "peakRssKiB": 928220, + "threads": 35, + "fds": 36, + "writeBytes": 1139376128 + }, + { + "tMs": 377484, + "cpuTicks": 61002, + "rssKiB": 904048, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1142591488 + }, + { + "tMs": 378619, + "cpuTicks": 61146, + "rssKiB": 904048, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1145331712 + }, + { + "tMs": 379764, + "cpuTicks": 61299, + "rssKiB": 904048, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1148059648 + }, + { + "tMs": 380900, + "cpuTicks": 61457, + "rssKiB": 904048, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1151758336 + }, + { + "tMs": 382104, + "cpuTicks": 61662, + "rssKiB": 928220, + "peakRssKiB": 928220, + "threads": 35, + "fds": 36, + "writeBytes": 1155735552 + }, + { + "tMs": 383273, + "cpuTicks": 61882, + "rssKiB": 928220, + "peakRssKiB": 928220, + "threads": 35, + "fds": 37, + "writeBytes": 1160396800 + }, + { + "tMs": 384422, + "cpuTicks": 62032, + "rssKiB": 904048, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1163747328 + }, + { + "tMs": 385563, + "cpuTicks": 62171, + "rssKiB": 904372, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1167400960 + }, + { + "tMs": 386717, + "cpuTicks": 62322, + "rssKiB": 904372, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1169821696 + }, + { + "tMs": 387851, + "cpuTicks": 62469, + "rssKiB": 904372, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1172471808 + }, + { + "tMs": 389040, + "cpuTicks": 62649, + "rssKiB": 928544, + "peakRssKiB": 928544, + "threads": 35, + "fds": 36, + "writeBytes": 1176375296 + }, + { + "tMs": 390219, + "cpuTicks": 62871, + "rssKiB": 928568, + "peakRssKiB": 928568, + "threads": 35, + "fds": 36, + "writeBytes": 1179066368 + }, + { + "tMs": 391392, + "cpuTicks": 63070, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1183563776 + }, + { + "tMs": 392541, + "cpuTicks": 63223, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1186095104 + }, + { + "tMs": 393672, + "cpuTicks": 63372, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1190350848 + }, + { + "tMs": 394807, + "cpuTicks": 63513, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1192992768 + }, + { + "tMs": 395967, + "cpuTicks": 63663, + "rssKiB": 928256, + "peakRssKiB": 928256, + "threads": 35, + "fds": 36, + "writeBytes": 1197023232 + }, + { + "tMs": 397184, + "cpuTicks": 63900, + "rssKiB": 928256, + "peakRssKiB": 928256, + "threads": 35, + "fds": 36, + "writeBytes": 1200615424 + }, + { + "tMs": 398362, + "cpuTicks": 64112, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1204244480 + }, + { + "tMs": 399526, + "cpuTicks": 64252, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1206865920 + }, + { + "tMs": 400673, + "cpuTicks": 64395, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1209507840 + }, + { + "tMs": 401820, + "cpuTicks": 64536, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1213333504 + }, + { + "tMs": 402999, + "cpuTicks": 64708, + "rssKiB": 928256, + "peakRssKiB": 928256, + "threads": 35, + "fds": 36, + "writeBytes": 1219088384 + }, + { + "tMs": 404174, + "cpuTicks": 64920, + "rssKiB": 928256, + "peakRssKiB": 928256, + "threads": 35, + "fds": 36, + "writeBytes": 1221758976 + }, + { + "tMs": 405333, + "cpuTicks": 65108, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1225498624 + }, + { + "tMs": 406470, + "cpuTicks": 65260, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1229119488 + }, + { + "tMs": 407613, + "cpuTicks": 65417, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1231650816 + }, + { + "tMs": 408769, + "cpuTicks": 65560, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1234489344 + }, + { + "tMs": 409913, + "cpuTicks": 65702, + "rssKiB": 904084, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1237159936 + }, + { + "tMs": 411094, + "cpuTicks": 65894, + "rssKiB": 928256, + "peakRssKiB": 928256, + "threads": 35, + "fds": 36, + "writeBytes": 1241116672 + }, + { + "tMs": 412255, + "cpuTicks": 66114, + "rssKiB": 928256, + "peakRssKiB": 928256, + "threads": 35, + "fds": 36, + "writeBytes": 1243586560 + }, + { + "tMs": 413406, + "cpuTicks": 66290, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1249869824 + }, + { + "tMs": 414551, + "cpuTicks": 66432, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1252462592 + }, + { + "tMs": 415683, + "cpuTicks": 66580, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1255006208 + }, + { + "tMs": 416814, + "cpuTicks": 66732, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1257463808 + }, + { + "tMs": 417937, + "cpuTicks": 66875, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1261342720 + }, + { + "tMs": 419140, + "cpuTicks": 67102, + "rssKiB": 928904, + "peakRssKiB": 928904, + "threads": 35, + "fds": 36, + "writeBytes": 1265176576 + }, + { + "tMs": 420302, + "cpuTicks": 67319, + "rssKiB": 928904, + "peakRssKiB": 928904, + "threads": 35, + "fds": 37, + "writeBytes": 1267982336 + }, + { + "tMs": 421469, + "cpuTicks": 67473, + "rssKiB": 904732, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1271468032 + }, + { + "tMs": 422620, + "cpuTicks": 67623, + "rssKiB": 904732, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1274081280 + }, + { + "tMs": 423770, + "cpuTicks": 67782, + "rssKiB": 904732, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1279406080 + }, + { + "tMs": 424921, + "cpuTicks": 67930, + "rssKiB": 904732, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1282052096 + }, + { + "tMs": 426121, + "cpuTicks": 68134, + "rssKiB": 928904, + "peakRssKiB": 928904, + "threads": 35, + "fds": 36, + "writeBytes": 1286000640 + }, + { + "tMs": 427270, + "cpuTicks": 68338, + "rssKiB": 928904, + "peakRssKiB": 928904, + "threads": 35, + "fds": 36, + "writeBytes": 1288531968 + }, + { + "tMs": 428430, + "cpuTicks": 68539, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 38, + "writeBytes": 1292128256 + }, + { + "tMs": 429589, + "cpuTicks": 68677, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1295589376 + }, + { + "tMs": 430738, + "cpuTicks": 68819, + "rssKiB": 904408, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1298083840 + }, + { + "tMs": 431877, + "cpuTicks": 68968, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1300750336 + }, + { + "tMs": 433053, + "cpuTicks": 69150, + "rssKiB": 928584, + "peakRssKiB": 928584, + "threads": 35, + "fds": 36, + "writeBytes": 1306505216 + }, + { + "tMs": 434233, + "cpuTicks": 69371, + "rssKiB": 928584, + "peakRssKiB": 928584, + "threads": 35, + "fds": 36, + "writeBytes": 1308958720 + }, + { + "tMs": 435393, + "cpuTicks": 69563, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1313415168 + }, + { + "tMs": 436528, + "cpuTicks": 69721, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1315876864 + }, + { + "tMs": 437671, + "cpuTicks": 69865, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1318334464 + }, + { + "tMs": 438800, + "cpuTicks": 70014, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1321132032 + }, + { + "tMs": 439946, + "cpuTicks": 70169, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1323769856 + }, + { + "tMs": 441151, + "cpuTicks": 70395, + "rssKiB": 928584, + "peakRssKiB": 928584, + "threads": 35, + "fds": 36, + "writeBytes": 1328553984 + }, + { + "tMs": 442309, + "cpuTicks": 70608, + "rssKiB": 904412, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1331920896 + }, + { + "tMs": 443438, + "cpuTicks": 70767, + "rssKiB": 904736, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1336467456 + }, + { + "tMs": 444572, + "cpuTicks": 70924, + "rssKiB": 904736, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1339002880 + }, + { + "tMs": 445697, + "cpuTicks": 71075, + "rssKiB": 904736, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1342636032 + }, + { + "tMs": 446820, + "cpuTicks": 71228, + "rssKiB": 904736, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1345216512 + }, + { + "tMs": 447948, + "cpuTicks": 71381, + "rssKiB": 904736, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1347915776 + }, + { + "tMs": 449154, + "cpuTicks": 71619, + "rssKiB": 928908, + "peakRssKiB": 928908, + "threads": 35, + "fds": 36, + "writeBytes": 1351671808 + }, + { + "tMs": 450306, + "cpuTicks": 71840, + "rssKiB": 928924, + "peakRssKiB": 928924, + "threads": 35, + "fds": 37, + "writeBytes": 1354416128 + }, + { + "tMs": 451438, + "cpuTicks": 72006, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1358675968 + }, + { + "tMs": 452567, + "cpuTicks": 72172, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1361145856 + }, + { + "tMs": 453696, + "cpuTicks": 72327, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1365643264 + }, + { + "tMs": 454840, + "cpuTicks": 72483, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1368346624 + }, + { + "tMs": 456006, + "cpuTicks": 72655, + "rssKiB": 928824, + "peakRssKiB": 928824, + "threads": 35, + "fds": 36, + "writeBytes": 1373499392 + }, + { + "tMs": 457208, + "cpuTicks": 72888, + "rssKiB": 928600, + "peakRssKiB": 928600, + "threads": 35, + "fds": 36, + "writeBytes": 1376079872 + }, + { + "tMs": 458371, + "cpuTicks": 73088, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1379749888 + }, + { + "tMs": 459516, + "cpuTicks": 73245, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1382408192 + }, + { + "tMs": 460663, + "cpuTicks": 73403, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1385054208 + }, + { + "tMs": 461808, + "cpuTicks": 73548, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1389002752 + }, + { + "tMs": 462991, + "cpuTicks": 73714, + "rssKiB": 928924, + "peakRssKiB": 928924, + "threads": 35, + "fds": 36, + "writeBytes": 1394683904 + }, + { + "tMs": 464195, + "cpuTicks": 73948, + "rssKiB": 928924, + "peakRssKiB": 928924, + "threads": 35, + "fds": 36, + "writeBytes": 1397325824 + }, + { + "tMs": 465368, + "cpuTicks": 74151, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1400983552 + }, + { + "tMs": 466497, + "cpuTicks": 74308, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1404440576 + }, + { + "tMs": 467650, + "cpuTicks": 74463, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1406808064 + }, + { + "tMs": 468782, + "cpuTicks": 74612, + "rssKiB": 904752, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1409548288 + }, + { + "tMs": 469918, + "cpuTicks": 74766, + "rssKiB": 904104, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1412194304 + }, + { + "tMs": 471125, + "cpuTicks": 74985, + "rssKiB": 928276, + "peakRssKiB": 928276, + "threads": 35, + "fds": 36, + "writeBytes": 1416212480 + }, + { + "tMs": 472288, + "cpuTicks": 75200, + "rssKiB": 928276, + "peakRssKiB": 928276, + "threads": 35, + "fds": 36, + "writeBytes": 1418862592 + }, + { + "tMs": 473423, + "cpuTicks": 75368, + "rssKiB": 904104, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1425416192 + }, + { + "tMs": 474546, + "cpuTicks": 75519, + "rssKiB": 904104, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1427816448 + }, + { + "tMs": 475675, + "cpuTicks": 75658, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1430491136 + }, + { + "tMs": 476795, + "cpuTicks": 75808, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1433104384 + }, + { + "tMs": 477930, + "cpuTicks": 75958, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1436864512 + }, + { + "tMs": 479125, + "cpuTicks": 76166, + "rssKiB": 928600, + "peakRssKiB": 928600, + "threads": 35, + "fds": 36, + "writeBytes": 1440604160 + }, + { + "tMs": 480294, + "cpuTicks": 76389, + "rssKiB": 928600, + "peakRssKiB": 928600, + "threads": 35, + "fds": 36, + "writeBytes": 1443323904 + }, + { + "tMs": 481448, + "cpuTicks": 76566, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1446760448 + }, + { + "tMs": 482577, + "cpuTicks": 76719, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1449365504 + }, + { + "tMs": 483702, + "cpuTicks": 76885, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1454579712 + }, + { + "tMs": 484830, + "cpuTicks": 77035, + "rssKiB": 904428, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1457217536 + }, + { + "tMs": 485963, + "cpuTicks": 77189, + "rssKiB": 923604, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1460871168 + }, + { + "tMs": 487159, + "cpuTicks": 77412, + "rssKiB": 928276, + "peakRssKiB": 928276, + "threads": 35, + "fds": 37, + "writeBytes": 1463758848 + }, + { + "tMs": 488302, + "cpuTicks": 77623, + "rssKiB": 904104, + "peakRssKiB": 925876, + "threads": 35, + "fds": 38, + "writeBytes": 1466507264 + }, + { + "tMs": 489454, + "cpuTicks": 77785, + "rssKiB": 904104, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1470701568 + }, + { + "tMs": 490584, + "cpuTicks": 77942, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1473318912 + }, + { + "tMs": 491736, + "cpuTicks": 78090, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1476214784 + }, + { + "tMs": 492891, + "cpuTicks": 78248, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1478848512 + }, + { + "tMs": 494080, + "cpuTicks": 78461, + "rssKiB": 928388, + "peakRssKiB": 928388, + "threads": 35, + "fds": 36, + "writeBytes": 1485471744 + }, + { + "tMs": 495236, + "cpuTicks": 78688, + "rssKiB": 928388, + "peakRssKiB": 928388, + "threads": 35, + "fds": 36, + "writeBytes": 1488166912 + }, + { + "tMs": 496396, + "cpuTicks": 78879, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1491820544 + }, + { + "tMs": 497535, + "cpuTicks": 79033, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1494278144 + }, + { + "tMs": 498667, + "cpuTicks": 79187, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1496981504 + }, + { + "tMs": 499801, + "cpuTicks": 79343, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1500655616 + }, + { + "tMs": 500919, + "cpuTicks": 79498, + "rssKiB": 904216, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1503363072 + }, + { + "tMs": 502110, + "cpuTicks": 79708, + "rssKiB": 928388, + "peakRssKiB": 928388, + "threads": 35, + "fds": 37, + "writeBytes": 1507241984 + }, + { + "tMs": 503269, + "cpuTicks": 79931, + "rssKiB": 928712, + "peakRssKiB": 928712, + "threads": 35, + "fds": 37, + "writeBytes": 1511477248 + }, + { + "tMs": 504433, + "cpuTicks": 80108, + "rssKiB": 904540, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1515753472 + }, + { + "tMs": 505561, + "cpuTicks": 80274, + "rssKiB": 904540, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1518235648 + }, + { + "tMs": 506707, + "cpuTicks": 80425, + "rssKiB": 904540, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1520730112 + }, + { + "tMs": 507850, + "cpuTicks": 80578, + "rssKiB": 904540, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1523179520 + }, + { + "tMs": 509022, + "cpuTicks": 80761, + "rssKiB": 928936, + "peakRssKiB": 928936, + "threads": 35, + "fds": 36, + "writeBytes": 1527017472 + }, + { + "tMs": 510216, + "cpuTicks": 81005, + "rssKiB": 928728, + "peakRssKiB": 928728, + "threads": 35, + "fds": 36, + "writeBytes": 1529499648 + }, + { + "tMs": 511375, + "cpuTicks": 81202, + "rssKiB": 904568, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1534287872 + }, + { + "tMs": 512518, + "cpuTicks": 81353, + "rssKiB": 904568, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1536958464 + }, + { + "tMs": 513648, + "cpuTicks": 81497, + "rssKiB": 904244, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1541304320 + }, + { + "tMs": 514797, + "cpuTicks": 81638, + "rssKiB": 904244, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1544011776 + }, + { + "tMs": 515958, + "cpuTicks": 81785, + "rssKiB": 907740, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1548283904 + }, + { + "tMs": 517173, + "cpuTicks": 82023, + "rssKiB": 928420, + "peakRssKiB": 928420, + "threads": 35, + "fds": 36, + "writeBytes": 1551949824 + }, + { + "tMs": 518334, + "cpuTicks": 82227, + "rssKiB": 904572, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1555742720 + }, + { + "tMs": 519465, + "cpuTicks": 82381, + "rssKiB": 904572, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1558380544 + }, + { + "tMs": 520589, + "cpuTicks": 82537, + "rssKiB": 904572, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1561866240 + }, + { + "tMs": 521718, + "cpuTicks": 82693, + "rssKiB": 904580, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1564372992 + }, + { + "tMs": 522837, + "cpuTicks": 82848, + "rssKiB": 904580, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1566961664 + }, + { + "tMs": 523975, + "cpuTicks": 83015, + "rssKiB": 928752, + "peakRssKiB": 928752, + "threads": 35, + "fds": 36, + "writeBytes": 1572745216 + }, + { + "tMs": 525159, + "cpuTicks": 83234, + "rssKiB": 928752, + "peakRssKiB": 928752, + "threads": 35, + "fds": 36, + "writeBytes": 1575358464 + }, + { + "tMs": 526355, + "cpuTicks": 83469, + "rssKiB": 928756, + "peakRssKiB": 928756, + "threads": 35, + "fds": 36, + "writeBytes": 1577996288 + }, + { + "tMs": 527522, + "cpuTicks": 83637, + "rssKiB": 904584, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1582301184 + }, + { + "tMs": 528690, + "cpuTicks": 83787, + "rssKiB": 904260, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1584971776 + }, + { + "tMs": 529842, + "cpuTicks": 83929, + "rssKiB": 904260, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1587712000 + }, + { + "tMs": 531000, + "cpuTicks": 84085, + "rssKiB": 928660, + "peakRssKiB": 928660, + "threads": 35, + "fds": 36, + "writeBytes": 1591570432 + }, + { + "tMs": 532191, + "cpuTicks": 84311, + "rssKiB": 928980, + "peakRssKiB": 928980, + "threads": 35, + "fds": 36, + "writeBytes": 1595236352 + }, + { + "tMs": 533396, + "cpuTicks": 84536, + "rssKiB": 905132, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1600716800 + }, + { + "tMs": 534549, + "cpuTicks": 84684, + "rssKiB": 905132, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1603297280 + }, + { + "tMs": 535688, + "cpuTicks": 84835, + "rssKiB": 905132, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1605730304 + }, + { + "tMs": 536833, + "cpuTicks": 84983, + "rssKiB": 905132, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1609449472 + }, + { + "tMs": 538001, + "cpuTicks": 85153, + "rssKiB": 929304, + "peakRssKiB": 929304, + "threads": 35, + "fds": 36, + "writeBytes": 1613373440 + }, + { + "tMs": 539186, + "cpuTicks": 85374, + "rssKiB": 929304, + "peakRssKiB": 929304, + "threads": 35, + "fds": 36, + "writeBytes": 1615953920 + }, + { + "tMs": 540349, + "cpuTicks": 85591, + "rssKiB": 905132, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1619320832 + }, + { + "tMs": 541490, + "cpuTicks": 85749, + "rssKiB": 905136, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1621938176 + }, + { + "tMs": 542624, + "cpuTicks": 85906, + "rssKiB": 905136, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1625624576 + }, + { + "tMs": 543767, + "cpuTicks": 86068, + "rssKiB": 904492, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1630060544 + }, + { + "tMs": 544889, + "cpuTicks": 86223, + "rssKiB": 904492, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1632509952 + }, + { + "tMs": 546088, + "cpuTicks": 86434, + "rssKiB": 928984, + "peakRssKiB": 928984, + "threads": 35, + "fds": 36, + "writeBytes": 1636483072 + }, + { + "tMs": 547273, + "cpuTicks": 86663, + "rssKiB": 928984, + "peakRssKiB": 928984, + "threads": 35, + "fds": 36, + "writeBytes": 1639075840 + }, + { + "tMs": 548436, + "cpuTicks": 86832, + "rssKiB": 904812, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1643843584 + }, + { + "tMs": 549568, + "cpuTicks": 86989, + "rssKiB": 904812, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1646477312 + }, + { + "tMs": 550698, + "cpuTicks": 87145, + "rssKiB": 904812, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1649082368 + }, + { + "tMs": 551820, + "cpuTicks": 87298, + "rssKiB": 904824, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1651757056 + }, + { + "tMs": 552951, + "cpuTicks": 87442, + "rssKiB": 904824, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1654411264 + }, + { + "tMs": 554151, + "cpuTicks": 87679, + "rssKiB": 928996, + "peakRssKiB": 928996, + "threads": 35, + "fds": 36, + "writeBytes": 1661280256 + }, + { + "tMs": 555344, + "cpuTicks": 87901, + "rssKiB": 904276, + "peakRssKiB": 925876, + "threads": 35, + "fds": 37, + "writeBytes": 1665118208 + }, + { + "tMs": 556498, + "cpuTicks": 88060, + "rssKiB": 904600, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1667760128 + }, + { + "tMs": 557645, + "cpuTicks": 88234, + "rssKiB": 904600, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1670111232 + }, + { + "tMs": 558782, + "cpuTicks": 88389, + "rssKiB": 904600, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1673760768 + }, + { + "tMs": 559921, + "cpuTicks": 88540, + "rssKiB": 904600, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1676369920 + }, + { + "tMs": 561108, + "cpuTicks": 88761, + "rssKiB": 928772, + "peakRssKiB": 928772, + "threads": 35, + "fds": 36, + "writeBytes": 1680351232 + }, + { + "tMs": 562272, + "cpuTicks": 88979, + "rssKiB": 928772, + "peakRssKiB": 928772, + "threads": 35, + "fds": 36, + "writeBytes": 1682944000 + }, + { + "tMs": 563448, + "cpuTicks": 89171, + "rssKiB": 904924, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1689468928 + }, + { + "tMs": 564596, + "cpuTicks": 89317, + "rssKiB": 904924, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1692127232 + }, + { + "tMs": 565738, + "cpuTicks": 89477, + "rssKiB": 904924, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1694756864 + }, + { + "tMs": 566872, + "cpuTicks": 89639, + "rssKiB": 904928, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1697202176 + }, + { + "tMs": 568048, + "cpuTicks": 89831, + "rssKiB": 929100, + "peakRssKiB": 929100, + "threads": 35, + "fds": 37, + "writeBytes": 1701146624 + }, + { + "tMs": 569221, + "cpuTicks": 90051, + "rssKiB": 929100, + "peakRssKiB": 929100, + "threads": 35, + "fds": 36, + "writeBytes": 1703682048 + }, + { + "tMs": 570379, + "cpuTicks": 90259, + "rssKiB": 904944, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1707995136 + }, + { + "tMs": 571519, + "cpuTicks": 90403, + "rssKiB": 904944, + "peakRssKiB": 925876, + "threads": 35, + "fds": 36, + "writeBytes": 1710817280 + }, + { + "tMs": 572646, + "cpuTicks": 90561, + "rssKiB": 904944, + "peakRssKiB": 925920, + "threads": 35, + "fds": 36, + "writeBytes": 1713238016 + }, + { + "tMs": 573783, + "cpuTicks": 90719, + "rssKiB": 904620, + "peakRssKiB": 925920, + "threads": 35, + "fds": 36, + "writeBytes": 1717653504 + }, + { + "tMs": 574925, + "cpuTicks": 90875, + "rssKiB": 904620, + "peakRssKiB": 925920, + "threads": 35, + "fds": 36, + "writeBytes": 1720213504 + }, + { + "tMs": 576127, + "cpuTicks": 91093, + "rssKiB": 929116, + "peakRssKiB": 929116, + "threads": 35, + "fds": 36, + "writeBytes": 1725100032 + }, + { + "tMs": 577310, + "cpuTicks": 91316, + "rssKiB": 929116, + "peakRssKiB": 929116, + "threads": 35, + "fds": 37, + "writeBytes": 1728053248 + }, + { + "tMs": 578478, + "cpuTicks": 91488, + "rssKiB": 904944, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1731342336 + }, + { + "tMs": 579607, + "cpuTicks": 91638, + "rssKiB": 904944, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1733943296 + }, + { + "tMs": 580737, + "cpuTicks": 91789, + "rssKiB": 904944, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1737633792 + }, + { + "tMs": 581887, + "cpuTicks": 91943, + "rssKiB": 904956, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1740328960 + }, + { + "tMs": 583077, + "cpuTicks": 92140, + "rssKiB": 929128, + "peakRssKiB": 929128, + "threads": 35, + "fds": 36, + "writeBytes": 1746042880 + }, + { + "tMs": 584253, + "cpuTicks": 92367, + "rssKiB": 929128, + "peakRssKiB": 929128, + "threads": 35, + "fds": 36, + "writeBytes": 1748795392 + }, + { + "tMs": 585418, + "cpuTicks": 92557, + "rssKiB": 904536, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1753292800 + }, + { + "tMs": 586561, + "cpuTicks": 92697, + "rssKiB": 904544, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1755918336 + }, + { + "tMs": 587694, + "cpuTicks": 92859, + "rssKiB": 904316, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1758457856 + }, + { + "tMs": 588834, + "cpuTicks": 93026, + "rssKiB": 904768, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1760874496 + }, + { + "tMs": 590001, + "cpuTicks": 93181, + "rssKiB": 928716, + "peakRssKiB": 928716, + "threads": 35, + "fds": 36, + "writeBytes": 1764855808 + }, + { + "tMs": 591178, + "cpuTicks": 93403, + "rssKiB": 928504, + "peakRssKiB": 928504, + "threads": 35, + "fds": 36, + "writeBytes": 1767489536 + }, + { + "tMs": 592337, + "cpuTicks": 93621, + "rssKiB": 904656, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1771962368 + }, + { + "tMs": 593504, + "cpuTicks": 93770, + "rssKiB": 904656, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1776504832 + }, + { + "tMs": 594655, + "cpuTicks": 93928, + "rssKiB": 904656, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1779093504 + }, + { + "tMs": 595811, + "cpuTicks": 94072, + "rssKiB": 904656, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1781612544 + }, + { + "tMs": 596961, + "cpuTicks": 94234, + "rssKiB": 916748, + "peakRssKiB": 925932, + "threads": 35, + "fds": 36, + "writeBytes": 1786040320 + }, + { + "tMs": 598165, + "cpuTicks": 94474, + "rssKiB": 929156, + "peakRssKiB": 929156, + "threads": 35, + "fds": 36, + "writeBytes": 1789296640 + }, + { + "tMs": 599332, + "cpuTicks": 94676, + "rssKiB": 904984, + "peakRssKiB": 926012, + "threads": 35, + "fds": 36, + "writeBytes": 1792651264 + } + ], + "db_probe_ms": [ + 11.066, + 9.794, + 7.448, + 7.399, + 8.61, + 10.706, + 8.518, + 7.491, + 9.357, + 7.911, + 8.544, + 7.437, + 10.153, + 7.825, + 7.467, + 8.003, + 7.69, + 7.308, + 7.266, + 11.204, + 7.83, + 7.647, + 7.65, + 10.643, + 14.934, + 7.595, + 7.297, + 10.88, + 9.131, + 8.14, + 7.731, + 8.853, + 8.296, + 7.62, + 7.38, + 8.762, + 8.681, + 7.844, + 7.483, + 9.805, + 7.215, + 8.54, + 7.75, + 10.149, + 8.442, + 8.39, + 7.859, + 12.622, + 8.374, + 7.434, + 8.789, + 12.518, + 7.419, + 7.337, + 11.187, + 8.642, + 8.516, + 7.422, + 8.643, + 8.905, + 10.63, + 7.22, + 9.588, + 21.612, + 8.103, + 7.038, + 8.074, + 8.009, + 8.342, + 7.847, + 7.471, + 9.484, + 7.981, + 7.537, + 9.473, + 7.877, + 7.841, + 8.135, + 8.375, + 7.528, + 7.616, + 13.995, + 8.333, + 7.315, + 9.187, + 12.234, + 10.505, + 7.258, + 8.193, + 12.333, + 12.17, + 7.46, + 7.312, + 12.84, + 7.528, + 7.641, + 7.099, + 7.762, + 7.196, + 8.205, + 7.346, + 7.728, + 7.446, + 8.047, + 7.761, + 8.131, + 7.21, + 7.482, + 9.749, + 8.06, + 7.317, + 7.257, + 11.665, + 8.692, + 7.391, + 7.714, + 13.1, + 8.44, + 7.791, + 7.662, + 8.282, + 14.879, + 7.892, + 7.234, + 8.351, + 9.43, + 7.639, + 8.253, + 10.845, + 8.287, + 7.901, + 7.754, + 7.91, + 7.689, + 8.105, + 8.696, + 9.95, + 7.781, + 7.701, + 8.574, + 8.13, + 7.54, + 7.784, + 10.497, + 11.586, + 7.315, + 7.11, + 10.128, + 7.515, + 7.495, + 7.206, + 8.268, + 7.979, + 10.419, + 7.156, + 10.773, + 8.972, + 7.885, + 7.244, + 7.905, + 7.443, + 8.785, + 7.863, + 7.618, + 8.417, + 8.036, + 13.771, + 9.296, + 7.819, + 9.93, + 10.547, + 12.482, + 8.1, + 7.552, + 9.252, + 15.661, + 7.943, + 7.754, + 8.997, + 8.048, + 7.605, + 7.826, + 8.159, + 8.648, + 7.417, + 7.352, + 7.955, + 8.835, + 8.076, + 7.187, + 8.014, + 7.398, + 8.287, + 7.744, + 7.864, + 7.316, + 8.386, + 9.77, + 8.36, + 7.325, + 10.484, + 9.038, + 10.993, + 10.288, + 7.676, + 13.731, + 7.401, + 7.221, + 7.829, + 12.052, + 7.815, + 8.03, + 7.348, + 8.017, + 7.978, + 7.963, + 7.768, + 7.868, + 7.33, + 8.208, + 7.561, + 7.737, + 8.458, + 8.509, + 12.674, + 8.477, + 7.684, + 7.827, + 8.889, + 7.94, + 7.405, + 9.669, + 8.37, + 8.235, + 7.489, + 7.671, + 10.799, + 16.939, + 8.099, + 7.669, + 7.822, + 8.451, + 7.492, + 7.188, + 8.91, + 8.877, + 8.051, + 7.331, + 7.758, + 11.021, + 7.819, + 7.306, + 8.119, + 10.278, + 7.841, + 10.111, + 7.624, + 7.898, + 8.238, + 10.057, + 8.729, + 7.421, + 9.059, + 10.625, + 7.596, + 7.069, + 7.087, + 8.518, + 12.865, + 7.728, + 7.909, + 8.621, + 8.864, + 7.499, + 7.224, + 7.754, + 8.947, + 8.344, + 7.339, + 7.693, + 8.308, + 8.331, + 14.236, + 8.926, + 7.607, + 8.138, + 11.097, + 16.324, + 7.467, + 8.101, + 10.273, + 13.471, + 7.959, + 7.38, + 9.399, + 7.598, + 8.034, + 7.336, + 10.306, + 7.677, + 7.264, + 8.14, + 11.305, + 8.075, + 7.522, + 8.659, + 10.548, + 9.281, + 8.069, + 10.911, + 8.344, + 7.31, + 8.327, + 8.554, + 10.211, + 7.045, + 9.642, + 8.317, + 8.127, + 7.414, + 7.653, + 11.012, + 10.94, + 7.318, + 8.37, + 8.918, + 7.509, + 7.895, + 7.614, + 10.123, + 8.742, + 7.362, + 8.231, + 9.037, + 9.812, + 7.514, + 7.785, + 8.004, + 8.361, + 8.408, + 9.994, + 8.045, + 7.189, + 7.849, + 8.337, + 8.153, + 8.352, + 7.771, + 9.061, + 7.928, + 7.44, + 7.297, + 10.708, + 7.476, + 7.175, + 7.4, + 9.393, + 8.379, + 7.344, + 9.963, + 12.145, + 9.017, + 7.669, + 7.294, + 8.977, + 10.668, + 9.061, + 8.013, + 10.027, + 7.343, + 7.865, + 9.112, + 13.423, + 7.766, + 8.519, + 8.716, + 10.109, + 7.662, + 10.455, + 8.682, + 11.322, + 8.612, + 9.148, + 9.327, + 7.509, + 7.304, + 7.48, + 8.102, + 7.628, + 8.032, + 7.527, + 8.414, + 8.11, + 7.355, + 8.652, + 7.738, + 9.671, + 9.167, + 15.346, + 9.348, + 8.704, + 7.972, + 11.715, + 9.093, + 7.312, + 8.863, + 8.315, + 8.894, + 7.838, + 8.597, + 7.755, + 7.674, + 7.912, + 10.204, + 11.118, + 7.538, + 8.51, + 7.362, + 12.187, + 7.21, + 7.694, + 7.311, + 14.027, + 8.895, + 7.377, + 12.396, + 7.614, + 8.801, + 7.355, + 12.7, + 8.566, + 7.297, + 8.036, + 10.257, + 12.893, + 7.863, + 8.044, + 11.889, + 7.485, + 7.136, + 7.679, + 12.688, + 7.566, + 7.522, + 7.27, + 8.505, + 7.603, + 7.352, + 7.462, + 8.644, + 7.957, + 7.595, + 8.709, + 7.766, + 8.837, + 7.593, + 13.041, + 8.245, + 10.069, + 8.054, + 12.44, + 7.911, + 9.251, + 7.88, + 8.657, + 7.454, + 7.485, + 8.202, + 8.573, + 7.768, + 7.196, + 8.562, + 14.36, + 8.465, + 7.77, + 8.154, + 13.762, + 7.428, + 7.407, + 7.185, + 10.461, + 7.512, + 7.106, + 10.474, + 10.406, + 7.488, + 7.72, + 14.966, + 7.655, + 8.969, + 7.441, + 12.477, + 7.541, + 10.426, + 7.801, + 8.2, + 7.34, + 7.71, + 8.848, + 7.974, + 7.625, + 7.342, + 9.247, + 7.82, + 8.166, + 7.478, + 7.168, + 8.67, + 7.322, + 7.395, + 13.609, + 9.718, + 7.459, + 7.382, + 9.717, + 11.808, + 7.934, + 7.511, + 13.99, + 8.785, + 9.029, + 7.044, + 8.042, + 7.299, + 11.024, + 7.684, + 10.356, + 9.047, + 8.168, + 8.995, + 8.507, + 7.416, + 7.162, + 8.703, + 8.06, + 7.126, + 7.345, + 8.719, + 8.51, + 7.687, + 7.334, + 8.497, + 8.067, + 8.06, + 7.948, + 9.356, + 8.379, + 8.233, + 7.854, + 9.041, + 10.273, + 8.651, + 8.594, + 9.713, + 13.754, + 7.445, + 8.913, + 7.817, + 7.447, + 7.338, + 9, + 7.863, + 7.296, + 7.316, + 8.741, + 9.728, + 7.51, + 7.935, + 13.596, + 7.928, + 7.242, + 7.445, + 8.211, + 9.174, + 7.402, + 7.314, + 8.67, + 10.571, + 9.076, + 7.96, + 10.677, + 7.273, + 8.451, + 8.78, + 7.571, + 7.427, + 7.966, + 9.112, + 7.938, + 8.146, + 7.374, + 7.57, + 12.424, + 7.268, + 7.647, + 7.979, + 8.957, + 7.212, + 7.54, + 12.88, + 8.758, + 7.717, + 7.127, + 9.094, + 12.525, + 8.254, + 7.207, + 11.151, + 17.104, + 9.232, + 7.257, + 7.982, + 9.445, + 8.041, + 8.159, + 7.91, + 7.346, + 7.303, + 8.298, + 13.867, + 8.411, + 7.442, + 8.527, + 9.264, + 7.516, + 7.177, + 12.692, + 11.868, + 7.099, + 8.579, + 14.912, + 11.306, + 8.561, + 7.379, + 13.204, + 18.801, + 8, + 7.368, + 11.089, + 7.084, + 8.433, + 8.587, + 8.088, + 7.291, + 9.708, + 7.574, + 8.033, + 7.259, + 7.409, + 7.312, + 7.539, + 7.752, + 7.381, + 19.63, + 8.496, + 7.66, + 7.861, + 10.039, + 8.744, + 7.187, + 7.69, + 7.891, + 12.705, + 8.662, + 7.106, + 8.547, + 10.342, + 8.053, + 7.556, + 8.814, + 7.238, + 8.232, + 8.408, + 8.26, + 7.307, + 7.485, + 7.869, + 8.908, + 7.197, + 7.46, + 13.699, + 8.169, + 7.429, + 7.405, + 11.3, + 9.62, + 7.732, + 7.206, + 7.859, + 19.988, + 7.394, + 7.119, + 9.651, + 18.855, + 7.876, + 7.44, + 8.268, + 7.064, + 8.285, + 7.152, + 7.883, + 7.336, + 7.572, + 7.727, + 7.879, + 8.375, + 7.729, + 8.789, + 10.343, + 9.895, + 7.374, + 9.573, + 11.544, + 7.552, + 7.63, + 8.093, + 10.089, + 9.345, + 7.85, + 7.874, + 8.11, + 8.236, + 7.467, + 14.645, + 8.005, + 7.848, + 7.477, + 8.573, + 7.811, + 8.29, + 7.474, + 7.661, + 7.029, + 11.318, + 11.852, + 7.684, + 7.314, + 7.409, + 15.44, + 8.168, + 7.421, + 7.245, + 15.386, + 10.356, + 7.204, + 7.253, + 9.241, + 8.121, + 6.97, + 7.772, + 8.029, + 7.805, + 8.51, + 8.669, + 8.913, + 7.471, + 8.78, + 7.404, + 7.808, + 7.287, + 8.401, + 7.904, + 8.604, + 7.234, + 7.708, + 11.598, + 10.997, + 7.027, + 7.097, + 9.071, + 7.662, + 7.361, + 7.566, + 10.521, + 18.608, + 7.539, + 7.801, + 8.648, + 8.325, + 7.857, + 7.116, + 11.709, + 10.928, + 7.88, + 7.341, + 9.307, + 10.047, + 7.907, + 7.778, + 9.375, + 7.945, + 8.407, + 7.618, + 10.056, + 8.12, + 7.474, + 13.643, + 9.045, + 8.349, + 7.525, + 15.726, + 9.174, + 8.094, + 7.317, + 8.789, + 9.243, + 7.205, + 7.365, + 8.958, + 9.749, + 7.537, + 8.036, + 7.808, + 8.554, + 7.979, + 7.782, + 8.18, + 7.308, + 9.196, + 7.258, + 8.076, + 9.345, + 8.422, + 19.117, + 8.925, + 7.504, + 7.271, + 12.781, + 7.975, + 7.415, + 7.821, + 10.093, + 9.833, + 7.346, + 7.351, + 8.321, + 9.655, + 7.561, + 7.959, + 8.196, + 11.275, + 7.851, + 7.963, + 8.868, + 7.789, + 8.452, + 7.53, + 10.586, + 7.545, + 8.586, + 10.636, + 8.415, + 7.377, + 9.199, + 11.003, + 9.468, + 7.391, + 7.555, + 10.2, + 11.745, + 8.237, + 7.7, + 11.328, + 8.519, + 9.364, + 8.138, + 8.699, + 13.054, + 7.609, + 9.219, + 8.662, + 10.123, + 8.687, + 7.707, + 7.748, + 7.957, + 9.854, + 10.059, + 8.925, + 8.542, + 7.937, + 13.317, + 8.18, + 8.212, + 7.156, + 9.961, + 8.298, + 8.091, + 10.062, + 10.552, + 21.732, + 7.598, + 8.78, + 8.907, + 7.934, + 7.029, + 7.448, + 7.816, + 9.359, + 8.212, + 7.713, + 10.198, + 7.335, + 8.265, + 9.186, + 8.187, + 7.778, + 9.31, + 11.057, + 8.266, + 7.188, + 8.459, + 9.982, + 11.063, + 7.584, + 7.218, + 12.218, + 9.113, + 11.141, + 7.319, + 12.122, + 7.777, + 8.142, + 7.154, + 8.637, + 8.25, + 7.431, + 7.071, + 8.823, + 8.056, + 8.851, + 7.351, + 8.132, + 8.668, + 8.249, + 11.822, + 9.708, + 7.073, + 8.867, + 11.75, + 10.724, + 7.432, + 8.634, + 9.195, + 8.06, + 7.498, + 7.453, + 12.61, + 7.548, + 8.248, + 7.386, + 12.978, + 7.519, + 7.482, + 7.276, + 8.708, + 8.455, + 7.631, + 7.535, + 7.766, + 7.239, + 8.848, + 13.159, + 7.749, + 7.342, + 8.748, + 8.082, + 10.528, + 7.126, + 7.884, + 9.541, + 8.221, + 7.476, + 7.467, + 9.65, + 8.329, + 7.221, + 7.364, + 9.589, + 9.131, + 7.203, + 7.479, + 8.657, + 7.713, + 11.178, + 7.07, + 12.719, + 7.785, + 7.088, + 7.662, + 7.766, + 7.497, + 7.371, + 9.398, + 7.975, + 7.344, + 8.037, + 8.76, + 8.739, + 8.673, + 8.766, + 9.153, + 10.016, + 7.188, + 7.345, + 11.379, + 7.499, + 7.078, + 7.961, + 8.102, + 7.938, + 8.688, + 7.358, + 11.969, + 9.684, + 8.136, + 7.503, + 9.792, + 10.554, + 7.908, + 17.14, + 8.343, + 7.312, + 8.27, + 9.523, + 8.673, + 8.043, + 8.324, + 9.415, + 14.127, + 7.563, + 7.721, + 10.139, + 7.436, + 7.42, + 7.976, + 8.669, + 8.014, + 7.162, + 8.695, + 9.624, + 8.356, + 7.71, + 7.587, + 7.718, + 7.853, + 7.817, + 7.251, + 8.01, + 7.42, + 7.979, + 19.974, + 9.926, + 7.044, + 8.022, + 16.606, + 10.371, + 7.252, + 7.428, + 12.922, + 9.108, + 7.855, + 7.338, + 14.821, + 7.8, + 7.31, + 7.515, + 9.987, + 8.137, + 7.378, + 7.5, + 9.448, + 8.089, + 7.471, + 10.622, + 7.664, + 9.288, + 7.824, + 9.831, + 10.142, + 7.181, + 8.151, + 9.07, + 15.748, + 7.155, + 8.508, + 7.986, + 18.793, + 7.46, + 8.045, + 8.775, + 8.785, + 7.35, + 10.881, + 13.553, + 7.267, + 7.415, + 7.296, + 10.416, + 9.494, + 7.551, + 8.207, + 7.94, + 8.741, + 7.119, + 8.826, + 8.814, + 8.36, + 7.062, + 9.515, + 9.198, + 7.449, + 7.502, + 13.565, + 7.511, + 7.309, + 8.115, + 10.603, + 19.07, + 7.71, + 7.561, + 8.618, + 7.516, + 7.728, + 7.418, + 8.506, + 7.653, + 7.439, + 10.259, + 8.038, + 9.242, + 7.193, + 18.806, + 9.314, + 8.542, + 7.223, + 9.577, + 8.6, + 7.86, + 7.737, + 8.76, + 11.599, + 8.725, + 7.34, + 9.937, + 7.849, + 7.409, + 9.582, + 8.303, + 8.057, + 7.148, + 7.571, + 8.066, + 8.002, + 7.485, + 7.969, + 8.866, + 7.497, + 7.322, + 10.693, + 8.505, + 8.251, + 7.497, + 12.041, + 8.384, + 7.913, + 7.646, + 8.394, + 9.383, + 7.183, + 8.063, + 9.544, + 20.063, + 8.044, + 8.174, + 8.291, + 9.019, + 7.412, + 8.563, + 8.756, + 7.7, + 8.925, + 7.826, + 14.543, + 8.124, + 7.703, + 9.709, + 15.819, + 7.597, + 7.735, + 8.472, + 9.885, + 7.89, + 8.336, + 11.29, + 9.771, + 7.802, + 10.504, + 11.324, + 7.474, + 9, + 7.863, + 9.093, + 8.013, + 7.111, + 8.346, + 8.348, + 8.34, + 7.846, + 8.488, + 11.465, + 7.328, + 7.853, + 7.875, + 9.201, + 7.447, + 7.467, + 8.577, + 8.094, + 8.983, + 7.691, + 10.028, + 15.417, + 9.328, + 7.407, + 9.085, + 15.516, + 8.553, + 8.182, + 9.188, + 7.972, + 7.47, + 8.317, + 8.297, + 7.522, + 7.436, + 8.627, + 11.221, + 7.723, + 7.289, + 8.117, + 8.727, + 7.579, + 7.603, + 12.969, + 10.053, + 8.393, + 7.393, + 9.966, + 7.916, + 8.395, + 7.185, + 8.509, + 7.715, + 7.749, + 7.844, + 7.993, + 8.279, + 8.643, + 7.86, + 8.223, + 7.074, + 7.402, + 8.371, + 7.926, + 7.157, + 7.345, + 9.093, + 9.023, + 7.393, + 7.437, + 7.474, + 9.837, + 7.051, + 8.045, + 10.689, + 8.996, + 9.172, + 7.316, + 9.207, + 8.063, + 9.09, + 7.375, + 10.843, + 8.415, + 9.3, + 7.199, + 14.11, + 7.683, + 7.993, + 9.236, + 8.261, + 7.204, + 7.511, + 9.699, + 11.5, + 9.795, + 7.613, + 7.74, + 9.587, + 7.547, + 7.069, + 10.652, + 9.345, + 7.489, + 7.324, + 8.522, + 8.777, + 8.022, + 7.571, + 9.149, + 8.291, + 9.423, + 8.185, + 14.906, + 10.053, + 7.638, + 8.076, + 8.307, + 7.659, + 7.894, + 8.666, + 12.785, + 9.248, + 7.642, + 7.393, + 9.934, + 7.996, + 7.748, + 10.004, + 10.853, + 7.355, + 7.846, + 8.767, + 9, + 8.089, + 7.546, + 8.691, + 7.867, + 8.01, + 8.208, + 8.273, + 12.718, + 10.843, + 7.833, + 8.561, + 14.853, + 7.183, + 7.817, + 7.931, + 8.12, + 7.587, + 8.143, + 9.637, + 7.72, + 7.477, + 7.362, + 10.442, + 7.084, + 7.571, + 18.891, + 9.633, + 7.767, + 7.254, + 14.118, + 11.681, + 8.648, + 8.093, + 7.727, + 14.856, + 7.168, + 8.185, + 8.348, + 7.84, + 7.44, + 7.629, + 13.495, + 8.015, + 7.589, + 9.632, + 15.108, + 7.487, + 7.089, + 7.946, + 15.78, + 7.63, + 7.133, + 10.547, + 8.429, + 7.68, + 7.292, + 12.531, + 9.516, + 8.976, + 7.316, + 12.814, + 15.467, + 8.978, + 7.139, + 10.503, + 8.306, + 9.678, + 7.523, + 10.252, + 7.276, + 7.421, + 7.343, + 9.966, + 7.204, + 8.485, + 8.841, + 7.711, + 8.401, + 7.16, + 16.655, + 17.664, + 7.352, + 7.542, + 9.613, + 11.971, + 7.594, + 7.693, + 14.647, + 14.33, + 8.208, + 7.88, + 8.857, + 11.463, + 9.317, + 8.033, + 9.042, + 8.07, + 8.824, + 7.511, + 7.755, + 7.583, + 7.871, + 8.365, + 8.162, + 8.503, + 7.57, + 17.181, + 9.867, + 7.661, + 7.453, + 11.96, + 10.903, + 7.792, + 7.838, + 9.73, + 10.971, + 7.781, + 8.521, + 8.15, + 7.739, + 8.841, + 7.532, + 8.071, + 7.728, + 8.221, + 7.511, + 8.272, + 7.438, + 9.216, + 7.433, + 7.994, + 7.577, + 7.934, + 15.658, + 7.933, + 7.116, + 7.11, + 9.82, + 14.107, + 7.671, + 7.595, + 9.725, + 10.091, + 7.469, + 7.139, + 13.719, + 8.023, + 8.96, + 7.164, + 8.306, + 7.693, + 9.21, + 8.018, + 8.422, + 7.192, + 7.92, + 7.196, + 9.518, + 7.753, + 7.369, + 13.527, + 8.472, + 7.341, + 7.285, + 17.392, + 14.47, + 7.526, + 7.395, + 10.057, + 8.564, + 7.306, + 7.121, + 15.419, + 24.295, + 9.261, + 8.008, + 7.836, + 7.651, + 9.511, + 7.928, + 7.452, + 9.258, + 8.198, + 8.306, + 9.066, + 7.13, + 7.557, + 17.984, + 8.199, + 7.314, + 7.514, + 13.29, + 13.001, + 7.028, + 7.081, + 9.812, + 15.979, + 7.112, + 7.349, + 9.435, + 18.58, + 7.143, + 7.692, + 7.945, + 9.203, + 9.706, + 7.642, + 8.302, + 7.417, + 8.376, + 7.494, + 7.9, + 7.878, + 8.047, + 9.486, + 8.2, + 8.225, + 8.116, + 10.194, + 11.215, + 7.697, + 7.946, + 10.989, + 7.713, + 7.436, + 7.074, + 7.871, + 8.159, + 7.913, + 7.533, + 7.914, + 8.289, + 7.522, + 7.433, + 7.649, + 7.42, + 12.132, + 7.849, + 8.29, + 7.521, + 8.227, + 7.962, + 7.823, + 7.708, + 9.743, + 11.169, + 9.233, + 7.863, + 8.791, + 13.332, + 8.856, + 7.627, + 7.29, + 8.487, + 15.166, + 7.435, + 7.385, + 8.272, + 8.637, + 8.275, + 8.779, + 11.835, + 7.83, + 8.779, + 7.787, + 7.759, + 7.307, + 7.957, + 7.255, + 8.548, + 7.223, + 9.221, + 15.192, + 8.375, + 7.881, + 7.346, + 14.172, + 15.355, + 7.671, + 7.419, + 13.275, + 11.63, + 7.919, + 7.135, + 10.025, + 8.719, + 7.174, + 7.838, + 7.995, + 10.041, + 7.642, + 7.733, + 8.156, + 7.377, + 8.25, + 7.704, + 8.705, + 9.92, + 7.746, + 23.055, + 7.703, + 7.237, + 7.306, + 14.009, + 15.774, + 7.264, + 7.598, + 9.156, + 8.436, + 7.506, + 7.264, + 7.794, + 7.589, + 7.335, + 7.589, + 8.212, + 7.338, + 8.228, + 7.327, + 8.424, + 20.358, + 8.411, + 8.796, + 7.803, + 7.427, + 7.875, + 13.131, + 7.861, + 7.652, + 8.943, + 12.251, + 8.386, + 7.238, + 7.824, + 8.904, + 9.12, + 7.492, + 8.066, + 10.029, + 8.212, + 8.04, + 7.214, + 10.639, + 9.852, + 8.07, + 7.121, + 8.85, + 8.439, + 8.358, + 7.875, + 8.118, + 7.616, + 8.484, + 13.626, + 8.025, + 7.518, + 9.127, + 8.678, + 8.059, + 8.025, + 7.704, + 9.859, + 8.39, + 7.209, + 7.263, + 10.545, + 15.003, + 8.007, + 7.389, + 7.906, + 8.624, + 7.452, + 7.61, + 8.361, + 9.05, + 8.602, + 8.079, + 9.665, + 7.37, + 8.883, + 7.658, + 14.295, + 7.305, + 8.242, + 9.077, + 7.52, + 7.281, + 7.986, + 13.811, + 19.886, + 7.882, + 8.019, + 13.865, + 7.697, + 7.526, + 7.468, + 9.886, + 7.334, + 7.202, + 9.886, + 8, + 7.908, + 7.235, + 7.085, + 7.63, + 9.645, + 7.394, + 7.277, + 7.971, + 7.937, + 9.003, + 10.694, + 9.716, + 8.385, + 8.61, + 13.517, + 9.483, + 7.348, + 9.977, + 11.997, + 11.622, + 7.168, + 9.529, + 9.292, + 8.522, + 7.314, + 8.036, + 10.204, + 8.095, + 7.424, + 7.074, + 7.862, + 8.141, + 7.235, + 7.884, + 9.672, + 9.007, + 7.287, + 19.95, + 8.191, + 7.97, + 7.832, + 13.653, + 8.161, + 7.474, + 9.884, + 9.205, + 15.902, + 7.651, + 7.473, + 10.566, + 8.636, + 8.113, + 7.205, + 11.576, + 9.288, + 7.217, + 7.513, + 7.938, + 8.35, + 7.424, + 7.821, + 9.697, + 7.906, + 8.44, + 12.129, + 9.735, + 7.256, + 8.83, + 9.528, + 9.016, + 7.413, + 9.541, + 9.423, + 7.943, + 7.462, + 8.424, + 14.416, + 7.588, + 7.106, + 7.272, + 8.319, + 7.455, + 7.636, + 7.426, + 13.051, + 8.457, + 7.589, + 7.709, + 8.044, + 9.219, + 7.591, + 11.871, + 9.243, + 8.655, + 8.288, + 9.301, + 9.793, + 7.836, + 10.255, + 8.315, + 9.577, + 8.11, + 8.749, + 8.965, + 12.581, + 7.765, + 9.102, + 8.696, + 7.198, + 7.452, + 7.425, + 9.055, + 7.577, + 7.233, + 7.318, + 9.568, + 10.831, + 7.803, + 8.166, + 7.885, + 8.341, + 7.522, + 8.737, + 7.903, + 8.32, + 7.691, + 8.545, + 9.264, + 8.287, + 8.768, + 10.317, + 10.544, + 7.193, + 7.612, + 7.642, + 7.69, + 9.021, + 8.205, + 8.126, + 7.348, + 7.858, + 21.707, + 7.891, + 7.201, + 7.736, + 7.469, + 9.561, + 7.39, + 7.264, + 12.536, + 7.959, + 8.586, + 7.691, + 8.397, + 7.927, + 7.563, + 8.265, + 8.957, + 9.7, + 10.472, + 8.806, + 8.471, + 8.548, + 7.557, + 8.124, + 14.144, + 9.64, + 7.596, + 7.079, + 12.938, + 7.618, + 7.441, + 7.398, + 9.99, + 7.725, + 8.211, + 7.366, + 7.934, + 7.682, + 7.488, + 10.206, + 7.48, + 11.602, + 7.971, + 11.258, + 7.831, + 8.165, + 8.785, + 11.048, + 15.311, + 7.406, + 8.41, + 8.334, + 18.852, + 7.068, + 8.186, + 13.261, + 7.801, + 7.431, + 9.51, + 10.475, + 10.528, + 7.531, + 11.058, + 13.215, + 7.524, + 7.807, + 11.485, + 8.73, + 7.79, + 7.3, + 7.623, + 8.525, + 7.169, + 7.538, + 11.502, + 16.065, + 10.109, + 7.576, + 8.143, + 7.408, + 7.266, + 8.341, + 7.757, + 7.322, + 9.812, + 9.248, + 8.3, + 7.254, + 7.602, + 7.305, + 9.027, + 8.049, + 9.107, + 10.671, + 10.13, + 10.07, + 7.32, + 9.297, + 14.052, + 8.865, + 7.223, + 8.872, + 20.984, + 9.07, + 8.073, + 9.045, + 7.489, + 7.258, + 8.689, + 8.755, + 7.275, + 7.485, + 9.994, + 9.049, + 7.346, + 7.256, + 7.276, + 15.376, + 8.104, + 7.809, + 8.53, + 8.48, + 8.807, + 7.06, + 8.077, + 23.646, + 8.454, + 7.212, + 9.352, + 15.56, + 7.941, + 7.373, + 9.394, + 9.159, + 8.649, + 7.639, + 8.361, + 7.453, + 7.333, + 7.634, + 11.79, + 8.235, + 8.139, + 8.266, + 9.638, + 7.106, + 7.563, + 16.345, + 8.464, + 7.05, + 7.227, + 12.909, + 12.054, + 10.684, + 7.887, + 8.709, + 9.56, + 8.565, + 7.934, + 17.675, + 7.57, + 8.387, + 8.416, + 7.749, + 7.33, + 7.42, + 8.773, + 7.486, + 7.462, + 7.49, + 9.396, + 8.384, + 7.447, + 7.691, + 15.783, + 8.671, + 7.654, + 10.067, + 9.786, + 17.421, + 7.672, + 7.92, + 11.268, + 9.064, + 8.057, + 7.615, + 8.137, + 7.019, + 8.105, + 8.672, + 7.996, + 7.177, + 7.319, + 8.364, + 8.463, + 7.588, + 7.167, + 7.838, + 9.006, + 7.461, + 7.773, + 10.956, + 9.028, + 7.573, + 7.214, + 8.845, + 9.782, + 7.732, + 7.296, + 10.395, + 8.373, + 9.241, + 7.591, + 8.755, + 14.111, + 9.371, + 8.262, + 8.366, + 11.489, + 8.448, + 9.841, + 9.4, + 7.434, + 7.822, + 9.363, + 8.23, + 7.112, + 7.522, + 9.838, + 8.233, + 8.271, + 7.314, + 14.577, + 13.104, + 7.682, + 7.644, + 9.358, + 13.441, + 8.229, + 7.856, + 10.506, + 7.588, + 10.402, + 7.333, + 8.571, + 9.999, + 11.537, + 8.857, + 11.941, + 8.763, + 7.392, + 8.747, + 8.194, + 7.445, + 7.346, + 11.379, + 8.067, + 8.613, + 7.932, + 18.628, + 11.602, + 7.298, + 7.48, + 10.291, + 23.532, + 7.285, + 7.217, + 8.078, + 7.519, + 8.602, + 7.388, + 9.28, + 10.376, + 7.741, + 8.505, + 10.664, + 11.705, + 9.894, + 9.469, + 7.775, + 8.673, + 7.532, + 17.018, + 8.226, + 7.74, + 7.905, + 10.418, + 9.236, + 7.381, + 7.372, + 9.395, + 15.768, + 7.577, + 7.207, + 11.439, + 11.737, + 8.002, + 7.405, + 10.426, + 7.462, + 7.731, + 9.407, + 7.614, + 7.793, + 8.522, + 7.971, + 8.475, + 7.99, + 8.3, + 16.119, + 9.185, + 7.947, + 7.138, + 14.286, + 8.987, + 7.835, + 7.682, + 8.938, + 13.523, + 7.206, + 7.292, + 7.514, + 29.319, + 7.428, + 7.151, + 9.71, + 7.982, + 9.714, + 7.977, + 8.145, + 7.301, + 8.342, + 7.322, + 7.476, + 8.481, + 7.756, + 29.333, + 9.746, + 7.239, + 7.269, + 11.712, + 8.121, + 7.656, + 10.34, + 8.894, + 7.461, + 7.106, + 7.677, + 10.307, + 9.262, + 7.263, + 7.784, + 8.235, + 8.737, + 10.151, + 10.166, + 8.715, + 7.657, + 8.166, + 7.774, + 13.792, + 7.266, + 9.097, + 12.657, + 8.268, + 7.886, + 7.983, + 14.57, + 8.951, + 8.48, + 7.569, + 12.125, + 8.716, + 8.051, + 7.299, + 9.93, + 8.858, + 7.573, + 7.414, + 7.918, + 8.067, + 7.532, + 7.387, + 7.866, + 8.321, + 9.529, + 9.06, + 12.198, + 7.269, + 9.983, + 15.62, + 8.789, + 7.368, + 9.683, + 9.338, + 9.278, + 9.156, + 7.795, + 10.165, + 12.354, + 7.314, + 7.911, + 10.088, + 7.309, + 8.759, + 7.606, + 8.912, + 8.02, + 8.768, + 7.352, + 7.823, + 10.469, + 7.751, + 7.651, + 8.984, + 7.696, + 7.988, + 8.223, + 8.061, + 7.732, + 7.891, + 14.657, + 14.681, + 7.332, + 7.695, + 9.717, + 17.266, + 8.097, + 7.553, + 9.169, + 20.656, + 7.407, + 7.589, + 8.367, + 8.111, + 7.624, + 8.121, + 7.527, + 8.352, + 7.595, + 7.487 + ], + "segment_events": [ + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150104_c1248242-3c5c-40c7-a36a-4a98446a6e7d.mp4", + "tMs": -27248 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150104_00c876a3-5437-47f8-a681-7faececc5f99.mp4", + "tMs": -27241 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150104_cb4abd35-071b-436f-aaf0-6b4b188e6925.mp4", + "tMs": -27238 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150104_58e1b70d-59cb-4090-b137-7fce88a40604.mp4", + "tMs": -27234 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150117_83ec16a0-faea-43da-9077-8c9faaaf6e9a.mp4", + "tMs": -17038 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150117_bb7f9082-1169-41ae-84ce-3f92bed061f9.mp4", + "tMs": -17031 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150117_8c102c81-e579-489d-9b39-b16640701f1c.mp4", + "tMs": -17025 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150117_32ee0e54-fea1-4cbd-8a0e-055b75ac7fbd.mp4", + "tMs": -17020 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150127_144def8f-942b-42b5-885a-e4db3ad8610e.mp4", + "tMs": -7037 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150127_c3e39309-7124-4ead-ba47-77999542b7af.mp4", + "tMs": -7030 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150127_1e494a36-4f65-4583-a4eb-1d30a4b0f2f1.mp4", + "tMs": -7026 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150127_be42fedc-32f0-4330-b981-a9a0c497612d.mp4", + "tMs": -7020 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150137_489eda29-f967-4ef1-b666-d125f49c5f84.mp4", + "tMs": 2961 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150137_2eb1bfd5-3b68-408f-b60d-6f0d838ec5f4.mp4", + "tMs": 2977 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150137_aa60f062-152c-4d2e-b4d5-a2a21442941f.mp4", + "tMs": 2977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150137_f042dce8-9cd7-499a-8e19-6ba3a2acbfc1.mp4", + "tMs": 2981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150147_0c3fedf6-ab50-4917-bee5-1e328e51e794.mp4", + "tMs": 12963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150147_e2d92988-2352-400e-a3a1-70497d023d29.mp4", + "tMs": 12970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150147_be149d18-f73d-43c6-88cd-b0e8fa068e90.mp4", + "tMs": 12978 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150147_72fdb9fd-4061-4661-90e0-b593bedd92e0.mp4", + "tMs": 12983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150157_5550e777-aae0-4b13-8282-6b161b53d24d.mp4", + "tMs": 22962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150157_209d2aac-8803-407c-874f-c35e4ef6dae8.mp4", + "tMs": 22969 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150157_beeb297c-3f59-4e3a-ae18-03561444f735.mp4", + "tMs": 22986 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150157_d0e68cbd-7b33-4bb6-910a-429a28e587fa.mp4", + "tMs": 22988 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150207_6b3e4530-fe8b-436e-8a3b-9688dbcdc31b.mp4", + "tMs": 32962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150207_c939dcce-af54-4489-95cd-c9040a190cf8.mp4", + "tMs": 32969 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150207_fcc74639-4cc2-473e-8851-7bf0a400593d.mp4", + "tMs": 32977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150207_703f810e-2439-4637-b57d-602c619eb654.mp4", + "tMs": 32981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150217_83730d51-f5f3-4d8a-a0dd-f475e1e486f2.mp4", + "tMs": 42960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150217_1e4a49da-a7cb-4666-ae41-6f8ea5a0223e.mp4", + "tMs": 42973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150217_e82e9586-95b4-46b6-b8c9-112b6fab4a0d.mp4", + "tMs": 42977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150217_81a8e152-2f7d-489d-99f3-24b08d3a3972.mp4", + "tMs": 42980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150227_498c6790-0cae-4237-82ae-bd6494b8ad20.mp4", + "tMs": 52960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150227_542b7b4d-ecc7-4241-8eb2-2dd747363391.mp4", + "tMs": 52971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150227_4872e076-0e1f-45a4-a1a5-d1ef7869dde6.mp4", + "tMs": 52976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150227_b36bf721-ca5e-4c47-876f-aee9f32f0cd9.mp4", + "tMs": 52986 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150237_7b73f57f-8c22-4dcf-b6c4-7e016a29b287.mp4", + "tMs": 62960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150237_88d91684-5c0b-4754-bc79-c745db880d21.mp4", + "tMs": 62970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150237_93e22df1-14c0-4914-b1c9-ead75b358233.mp4", + "tMs": 62975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150237_d6e70ad9-bfc6-47cd-8664-619502913f44.mp4", + "tMs": 62981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150247_e24555d6-e33b-4ba0-ab8a-6a6bb65ed124.mp4", + "tMs": 72960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150247_d574fe6f-5cab-4df8-826e-3c17c8ea078e.mp4", + "tMs": 72973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150247_48a54f7d-a8e3-4874-bb04-3541902555a8.mp4", + "tMs": 72974 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150247_65640012-4d19-4554-b768-852e17d25655.mp4", + "tMs": 72984 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150257_831b6709-6e85-43ad-a041-ac14c2a9c621.mp4", + "tMs": 82962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150257_b3721aa2-e7e4-438d-a379-3ab9f476f5c0.mp4", + "tMs": 82973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150257_99040167-8ae9-4aae-a1d4-40140912be4e.mp4", + "tMs": 82975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150257_d7df8593-fba3-43f9-9036-a392b8edfac5.mp4", + "tMs": 82979 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150307_17c39e27-17a8-4233-b3a9-c52ccb731afd.mp4", + "tMs": 92962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150307_39c705da-f452-434f-b3ec-6d4cf1b8039d.mp4", + "tMs": 92969 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150307_a0183d07-f35a-4443-92ba-1f18dfb0fe88.mp4", + "tMs": 92974 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150307_d90e1f6a-590b-429b-ad28-a6359582cd06.mp4", + "tMs": 92979 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150317_a44d9481-8c8d-4b1c-839c-9d351cc2e7d3.mp4", + "tMs": 102962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150317_3661c9f2-aba4-4a50-9217-9bfceb5a8f58.mp4", + "tMs": 102973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150317_1e1bea76-8bc3-4506-9d8e-e2e2c74675cc.mp4", + "tMs": 102985 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150317_7b49060e-f2b9-4a10-83dd-6bf2d1f75610.mp4", + "tMs": 102989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150327_a07b312a-6e9b-4728-9990-3c2d3b2b84d7.mp4", + "tMs": 112961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150327_2614858d-5694-46e2-a4de-89de8f8e6561.mp4", + "tMs": 112971 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150327_aabdc194-303c-4e7a-bf20-cb8d2552a815.mp4", + "tMs": 112982 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150327_9635ce28-c6a3-42ed-a471-a54bad3296a7.mp4", + "tMs": 112994 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150337_a997f80c-dc97-4a79-8b70-4515ce823628.mp4", + "tMs": 122962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150337_48379be8-e35d-4f74-8d20-9b3563674ea9.mp4", + "tMs": 122975 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150337_c459dcfb-0b36-4a9a-ad3a-bff0b9c80c66.mp4", + "tMs": 122976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150337_9840118f-7b03-4af3-86f0-e26be08f1f99.mp4", + "tMs": 122981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150347_2f3312d6-b66f-40e1-b4f1-bb296b3d07c0.mp4", + "tMs": 132965 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150347_3d527f95-03d0-4ad2-bd32-42cfbd1d023a.mp4", + "tMs": 132970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150347_d2ab81ac-59b7-42bd-bbfa-1efc59a12f47.mp4", + "tMs": 132977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150347_7cdff178-8c74-4993-a853-2f7b8a879d20.mp4", + "tMs": 132980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150357_a0a1fd85-9940-41fc-9718-77fe03822578.mp4", + "tMs": 142962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150357_b01b38df-84fc-4b3f-b66f-1160e2a3b9df.mp4", + "tMs": 142971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150357_f3da4af6-7680-4d95-9117-f7cf5289a42a.mp4", + "tMs": 142977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150357_8f289eb0-84ab-4c43-8a4d-bc99c6efb81a.mp4", + "tMs": 142980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150407_7676934c-8583-426b-a461-ba06dae402c4.mp4", + "tMs": 152962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150407_4fcff08e-454a-4cba-bf56-dc70461b341c.mp4", + "tMs": 152972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150407_8af41437-86c0-46a9-8938-b8c4f74bdca2.mp4", + "tMs": 152976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150407_ca81e886-3147-4356-9903-04b5c99a0346.mp4", + "tMs": 152981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150417_0d8aee4a-8431-40d1-97d0-36cfbccbed6f.mp4", + "tMs": 162963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150417_3e213792-7f26-4da0-a48e-045547fb8c9b.mp4", + "tMs": 162972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150417_ecf6747d-7480-4d8c-a055-a7315cf01ada.mp4", + "tMs": 162977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150417_80e273c3-61b7-4c6c-9415-b718802ad784.mp4", + "tMs": 162985 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150427_74042617-8ccb-432b-9ec6-973d09fac47a.mp4", + "tMs": 172963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150427_d9ce4929-7b24-42bb-bf26-78c1061e2cdf.mp4", + "tMs": 172974 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150427_2c09ff58-ac36-4ee5-bcd2-acfbdc60f1e5.mp4", + "tMs": 172979 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150427_4a778f58-9375-476b-acbf-f948b8093d52.mp4", + "tMs": 172984 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150437_742597c2-36f7-4642-ac86-c4a6444ac5ab.mp4", + "tMs": 182960 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150437_85cd0468-ed77-461d-bacf-b26008ab4c60.mp4", + "tMs": 182980 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150437_9298c535-61e8-41f6-8976-b4b3e4c41719.mp4", + "tMs": 182980 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150437_81da7d7a-97f1-4130-af1e-aa0f8021d918.mp4", + "tMs": 182982 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150447_c72b74ac-55b5-4d9f-849b-060f822e9b46.mp4", + "tMs": 192963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150447_05ca0046-f9af-471f-afe3-58a70bb206d5.mp4", + "tMs": 192970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150447_3b4c3229-70cb-4f65-8bdc-97aab493f6f9.mp4", + "tMs": 192975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150447_d4db8477-8f93-4bfc-baba-e9839f5b11d2.mp4", + "tMs": 192980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150457_78340a9b-ffa9-4d17-bd67-8aef46065409.mp4", + "tMs": 202964 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150457_cf29b7fa-304e-46c5-a53f-d875fcebcfd3.mp4", + "tMs": 202973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150457_1850ab78-b678-4165-a008-5be4a7207d42.mp4", + "tMs": 202976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150457_f9236579-60c2-457f-983c-ce7dffd98076.mp4", + "tMs": 202981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150507_11f42b35-45f3-4994-a655-f2f01fd1126b.mp4", + "tMs": 212961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150507_679a7095-963c-4147-9765-886ae9c9230c.mp4", + "tMs": 212979 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150507_86f6a514-424a-453e-b652-6895cad48939.mp4", + "tMs": 212981 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150507_01254016-dd5c-40c6-a955-91394dcb778e.mp4", + "tMs": 212985 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150517_9bbd6310-e928-485b-96a5-fb7bb3075abf.mp4", + "tMs": 222965 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150517_275e2cc5-26a3-4b9b-bd30-12051a3e22f6.mp4", + "tMs": 222974 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150517_7e42aafe-80f8-473d-96e9-1c997583963f.mp4", + "tMs": 222976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150517_2ac41746-1fc6-424f-b24f-61ae20e96430.mp4", + "tMs": 222987 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150527_433ba827-dd66-49e4-a923-93381efd2fc3.mp4", + "tMs": 232967 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150527_862f6b95-3ba0-43d5-9232-30caa33fdbfb.mp4", + "tMs": 232977 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150527_0c97451c-4d73-47fc-a6e4-48121b62813c.mp4", + "tMs": 232982 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150527_8d3eec87-8c68-42e9-8bf5-3b821a5c771f.mp4", + "tMs": 232985 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150537_8369e36f-fd21-4953-b9ca-87e73e6c8b89.mp4", + "tMs": 242978 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150537_3bb98975-64d2-4b72-87d0-311493e5a6a4.mp4", + "tMs": 242986 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150537_1243c2cb-d194-4908-abb3-9264a7fc3a09.mp4", + "tMs": 242986 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150537_1ce085e9-fb82-47a1-b4e3-976e1af735a9.mp4", + "tMs": 242987 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150547_a45ef5d9-e09d-4af4-9261-dc25e5a68ea8.mp4", + "tMs": 252962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150547_0f04dcca-a66e-4b2b-ab80-097715e36d72.mp4", + "tMs": 252973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150547_e70ba65f-3572-43a4-9ed6-98d4b3c07e07.mp4", + "tMs": 252977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150547_534b613a-02e7-4059-9dbe-4e11472cbe36.mp4", + "tMs": 253006 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150557_113bdc42-3e39-4312-88aa-af58a8f1d1c7.mp4", + "tMs": 262959 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150557_960d9935-57fc-4851-9339-dcd3d3bb3073.mp4", + "tMs": 262970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150557_7c301577-104a-42bd-8125-4ecd074de52d.mp4", + "tMs": 262975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150557_8aa42658-565e-4981-bc84-b9c395497ebd.mp4", + "tMs": 262983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150607_660bbcc0-51be-410f-965a-71d797cca91a.mp4", + "tMs": 272960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150607_78f9d502-dd70-403b-8d56-c51ffe02e18b.mp4", + "tMs": 272973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150607_32a309c8-c65f-4bfd-b597-447ade6969a8.mp4", + "tMs": 272975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150607_da49f542-9548-429f-bef1-6761f5537dab.mp4", + "tMs": 272981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150617_bea1a118-dcfb-4085-90c0-ad305b294a49.mp4", + "tMs": 282961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150617_de121b6d-15a0-4939-a187-1c28569266e8.mp4", + "tMs": 282980 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150617_3b14d72a-82c2-42c3-93d3-d099bfa3a5b8.mp4", + "tMs": 282981 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150617_dc17507f-47c6-480a-a7c6-1cf65162416b.mp4", + "tMs": 282982 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150627_491d48a1-fff2-4f4e-b181-3569c737ccd6.mp4", + "tMs": 292966 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150627_cab89caf-91fe-41c8-aa03-d7ab982101d7.mp4", + "tMs": 292983 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150627_7ec946fe-853f-4642-ab08-d3200be061d2.mp4", + "tMs": 292983 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150627_5ef720f4-9a9b-4565-b0fd-12ebfb3cf369.mp4", + "tMs": 292984 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150637_52e1fb11-8aba-4cd7-8769-abf73a7601bb.mp4", + "tMs": 302960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150637_881c3b15-3bdc-4c5f-b719-02bced81eb37.mp4", + "tMs": 302971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150637_c545b793-7de1-4894-8294-6b10c26326f1.mp4", + "tMs": 302975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150637_48e9fc14-51c3-4029-9f85-c7e12bffa94b.mp4", + "tMs": 302983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150647_9ee70d4d-c36a-4258-9246-7ae3caaf2471.mp4", + "tMs": 312961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150647_09860def-1dd3-481c-b2eb-09196410522c.mp4", + "tMs": 312970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150647_02619a78-145e-431e-b7b8-97a0342086c9.mp4", + "tMs": 312975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150647_ff3b65ea-4470-4360-91cb-024080a76604.mp4", + "tMs": 312981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150657_1c1a8a67-f1df-441e-8ef7-ac4c6426395d.mp4", + "tMs": 322963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150657_567484f1-a26e-4b3e-b931-10f2bde08f0d.mp4", + "tMs": 322971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150657_e6fd8b45-e16e-46d7-afe8-5a6fdd5ffe93.mp4", + "tMs": 322976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150657_bffc81c8-f2c7-4575-b3cd-2f2877e522ac.mp4", + "tMs": 322983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150707_c469a343-234a-4c6d-8bdc-6a90b0bb7767.mp4", + "tMs": 332982 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150707_aa7e6e67-4e8a-461c-8b2d-fea5f758b8fc.mp4", + "tMs": 332984 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150707_64f42a72-6dac-4e95-a573-0ba528140f9d.mp4", + "tMs": 332991 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150707_8be3b1d1-0905-47a0-8e79-04ffe0ec49f0.mp4", + "tMs": 332993 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150717_8c6df2ef-4668-495a-aa0d-c9273da0a853.mp4", + "tMs": 342961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150717_54500544-a1e5-4e72-ba77-0e6eb5be9f4d.mp4", + "tMs": 342970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150717_642ed981-abb6-4ea5-b0f7-05cc4314c5bd.mp4", + "tMs": 342983 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150717_3cec765e-1d54-45cb-b590-f08d236bcba5.mp4", + "tMs": 342990 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150727_19594730-38f9-4855-86b0-23b79231e2b4.mp4", + "tMs": 352963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150727_266c6cc3-0f1c-4aed-a59c-dcafaa9a9b62.mp4", + "tMs": 352973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150727_63353fe8-97a1-4713-a582-084f20691d42.mp4", + "tMs": 352975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150727_a690df72-db5a-4b0a-8df4-166ac91efeec.mp4", + "tMs": 352989 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150737_7d882dd4-e683-475c-bad4-00e507e2fa5e.mp4", + "tMs": 362961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150737_777f0112-31f1-4c55-8ad7-91ebae5a16f4.mp4", + "tMs": 362970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150737_09c60827-8d16-4e04-9c49-07615e2be90a.mp4", + "tMs": 362977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150737_fae229a1-a2b6-4d2e-932b-03cab004cd78.mp4", + "tMs": 362983 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150747_29cd54a4-762a-4176-85c3-fc7e4653210b.mp4", + "tMs": 372962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150747_700e09a0-8ec9-4613-a648-83a54abb0714.mp4", + "tMs": 372972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150747_7b69b9f5-0c80-46cd-a9a6-f3e1d9849866.mp4", + "tMs": 372975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150747_d83419dc-e2e7-4848-9c3b-f6d3b168e1b7.mp4", + "tMs": 372980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150757_6286cd29-4176-4796-a224-871e7442bddf.mp4", + "tMs": 382964 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150757_5cae6690-3905-434a-95c3-ed32a11836e2.mp4", + "tMs": 382970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150757_037dbb07-faa5-4e17-9bd0-e927f8d43ce9.mp4", + "tMs": 382977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150757_549907a8-164d-4bae-93bd-f6ffe022ebce.mp4", + "tMs": 382987 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150807_64586067-c9cb-4c69-93c3-2bc7687780f5.mp4", + "tMs": 392962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150807_93f6a25b-09ec-44c9-97f9-f0ac04876ac2.mp4", + "tMs": 392968 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150807_9d257898-895c-4329-b7d9-afa2cce9ff6c.mp4", + "tMs": 392977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150807_0d661cb1-683a-4e80-aee3-0450446a3426.mp4", + "tMs": 392980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150817_a15bf6a2-e752-45d8-8b90-1db272b541d9.mp4", + "tMs": 402960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150817_6e8c0f4f-7beb-4771-8574-5385ed434c36.mp4", + "tMs": 402971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150817_ca9ab27d-7ab4-4f56-ac82-9522faf1283c.mp4", + "tMs": 402975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150817_c0d1e187-e766-40b3-bee5-7ff20c5c06ab.mp4", + "tMs": 402982 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150827_3e28d717-532a-4e9f-8fe8-fe14b9590d0f.mp4", + "tMs": 412962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150827_5fac849d-9f11-416e-8cf6-9b3743112058.mp4", + "tMs": 412972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150827_61327aed-4b04-45a2-8c6e-b879c6c72a4d.mp4", + "tMs": 412975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150827_6623271f-c3bd-4a0a-9538-8db9f786090d.mp4", + "tMs": 412981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150837_cec7901e-7d32-4b37-afde-387de5c2b93a.mp4", + "tMs": 422961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150837_47e95c44-fcc8-4f0e-b3e6-3132e65c7b40.mp4", + "tMs": 422972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150837_ad554903-5786-4808-81f6-f6f264a80f6d.mp4", + "tMs": 422979 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150837_650832ee-cbea-4b95-91df-3403606a7714.mp4", + "tMs": 422982 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150847_c8820996-dd24-45a7-aeec-80688cfc706e.mp4", + "tMs": 432963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150847_f163c57f-3297-42bc-a838-501900a700c5.mp4", + "tMs": 432971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150847_e7db99df-eaed-416e-8c92-0f452a1dc842.mp4", + "tMs": 432976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150847_46e1e44f-b960-46c3-b3d7-451423cde0a3.mp4", + "tMs": 432981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150857_073fdc00-947b-4c6d-b071-678a823264df.mp4", + "tMs": 442960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150857_61869948-73c2-40a9-b566-d709ca246d59.mp4", + "tMs": 442972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150857_be852651-fc9e-4237-bb13-e0b42d170a74.mp4", + "tMs": 442977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150857_27078d37-70b9-47d6-aaca-5832b112a228.mp4", + "tMs": 442980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150907_ec6f32e6-5c93-400b-8429-c69022b2a88d.mp4", + "tMs": 452961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150907_a1a25a44-252a-4edb-991c-cfab6b6d7238.mp4", + "tMs": 452972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150907_12a142c1-2b22-4ff0-9d1f-1e89e2dab98e.mp4", + "tMs": 452976 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150907_54bf0855-a776-4990-8147-b01ca7587b59.mp4", + "tMs": 452980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150917_14af50e5-26c5-423d-a26c-76165a5db009.mp4", + "tMs": 462960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150917_a818b467-67f6-43df-b5c8-0459f1f65264.mp4", + "tMs": 462972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150917_00c0fd96-b60e-43b8-8ff9-d3bfb96f8b25.mp4", + "tMs": 462977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150917_bd8916af-1538-40f0-af85-78237a22af6a.mp4", + "tMs": 462980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150927_637a3e07-6ff7-4478-98e4-ca02a57f6f9d.mp4", + "tMs": 472962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150927_5a2b558b-b0ac-4086-a1b1-947b62b6db61.mp4", + "tMs": 472970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150927_1275674f-c621-492e-a42e-86cb0e7f86f1.mp4", + "tMs": 472975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150927_bdfe5fa5-d43d-4e78-b573-0e1403968910.mp4", + "tMs": 472980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150937_a4702765-7eed-40d0-8abe-39010bd94ef1.mp4", + "tMs": 482962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150937_3b63aaab-535b-4832-a5b5-ccc8902052f4.mp4", + "tMs": 482990 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150937_af80efa6-dd1e-4026-b027-2069445ff996.mp4", + "tMs": 482993 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150937_8f2bf3ba-0227-46ef-b521-d21a8a6f2fc9.mp4", + "tMs": 482994 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150947_1147f757-596b-4040-859c-9aa3c65850b3.mp4", + "tMs": 492967 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150947_5103cc3c-aebe-4b76-bec3-63c5b26ddb11.mp4", + "tMs": 492969 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150947_4c70f403-7931-4c63-9fbd-3d8c3350157b.mp4", + "tMs": 492978 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150947_6b1cd2b3-3086-413b-866d-78d09c5c15e4.mp4", + "tMs": 492981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_150957_a8884030-43ae-4517-a17c-42ed6247b57a.mp4", + "tMs": 502963 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_150957_278c2c70-7e89-4198-be1d-8d6586b2dcf6.mp4", + "tMs": 502972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_150957_43a3075f-dfe4-4640-8656-f89018ddbc2a.mp4", + "tMs": 502977 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_150957_18d540c7-3ae6-4043-9784-7167f40371f1.mp4", + "tMs": 502980 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151007_63bea137-97bb-48f0-abb1-d3acd958739b.mp4", + "tMs": 512961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151007_cd7277da-d1dc-4348-af83-18aeb3d3069c.mp4", + "tMs": 512970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151007_00e8cbbc-8bc1-4063-ab39-f5c739ce6db2.mp4", + "tMs": 512979 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151007_a8560034-af21-4fbe-809f-f313af568e27.mp4", + "tMs": 512981 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151017_b5d0c6b5-f7a0-4879-b4a4-d550eee414c2.mp4", + "tMs": 522964 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151017_24db685a-ae2e-44be-96d8-63ccb574e1ec.mp4", + "tMs": 522970 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151017_5eb084ff-6fce-49cd-b1b5-8923410d4a73.mp4", + "tMs": 522974 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151017_73d4a1d0-2218-4cdf-8ba7-bcebbbbdcfe4.mp4", + "tMs": 522986 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151027_e01f21c0-bf63-4641-a887-8c21b530c135.mp4", + "tMs": 532962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151027_a041a606-8023-4ec6-b0ac-8feeddfe59cf.mp4", + "tMs": 532973 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151027_69c411de-c6eb-4588-b5f6-42af1080367b.mp4", + "tMs": 532975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151027_43e1da2b-0b9d-4e77-a6ab-6afc642dde62.mp4", + "tMs": 532999 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151037_0433ff7c-5ec5-493d-855c-cac0e0be796e.mp4", + "tMs": 542961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151037_8a0cf3f7-8676-43e6-9e29-1881d4addba8.mp4", + "tMs": 542971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151037_2667bc4b-3764-4c47-baff-4780f90f3038.mp4", + "tMs": 542975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151037_1a1169b7-5333-4f02-96d2-52ebce535ed5.mp4", + "tMs": 542982 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151047_9fddd12b-ba00-432f-b766-6e1d9f8305da.mp4", + "tMs": 552961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151047_4f2c5927-a777-4d18-9bed-0fd893f20fe7.mp4", + "tMs": 552969 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151047_5604b5c7-e8d5-4095-9e67-2507ba3396b7.mp4", + "tMs": 552978 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151047_cd2627cb-585d-429f-b775-e695a6a6a4b8.mp4", + "tMs": 552986 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151057_7c7a7256-a773-4dc7-aaa7-d8115dec928c.mp4", + "tMs": 562962 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151057_0ccc832b-e728-4e2e-bf4b-91452f41fcb1.mp4", + "tMs": 562971 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151057_9b02b3b6-5a16-47d2-b5cd-b6ad960be840.mp4", + "tMs": 562978 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151057_48a1a04b-ce21-4dac-830b-51062fb920df.mp4", + "tMs": 562991 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151107_86184c4e-77c8-4351-8ab4-3b8f3e99d3c5.mp4", + "tMs": 572961 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151107_f51f3abb-af44-4404-b693-f027f0fa11ca.mp4", + "tMs": 572979 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151107_3271671d-9f3a-454f-bf61-9a7cf08c9249.mp4", + "tMs": 572979 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151107_632f9b91-2608-42b4-8d38-7fcd66c9fba1.mp4", + "tMs": 572984 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151117_24ae0cb1-08c0-4d80-a0be-03c90b2356d6.mp4", + "tMs": 582960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151117_9a9155fc-0c6e-4c54-bef3-619fae9f70fc.mp4", + "tMs": 582972 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151117_9d581e4d-5fdb-4134-87ab-c33ca633fa4c.mp4", + "tMs": 582978 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151117_46d0f8b4-d302-43b5-bc0e-d8528e1b73b7.mp4", + "tMs": 582986 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151127_3b50427a-44b7-4bdd-8e0d-a11b566da07a.mp4", + "tMs": 592961 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151127_57125a0f-1f29-40dc-a095-e8d46e403a20.mp4", + "tMs": 592975 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151127_be55ccf7-e2ce-485a-87d8-17e4a33cea54.mp4", + "tMs": 592981 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151127_43dd4d18-01ef-4925-84c2-3be8acb4cac1.mp4", + "tMs": 592985 + }, + { + "stream": "Load0", + "file": "mp4/Load0/2026/11/08/recording_20261108_151137_eb93721f-0a51-48c0-a39b-6600ec9df2f0.mp4", + "tMs": 602960 + }, + { + "stream": "Load1", + "file": "mp4/Load1/2026/11/08/recording_20261108_151137_6b2b47ab-0ff5-42cb-abd7-1419b01d7af5.mp4", + "tMs": 602969 + }, + { + "stream": "Load2", + "file": "mp4/Load2/2026/11/08/recording_20261108_151137_f73362d4-49f4-4778-87af-59d8f91248b2.mp4", + "tMs": 602975 + }, + { + "stream": "Load3", + "file": "mp4/Load3/2026/11/08/recording_20261108_151137_ed9e6d07-1a3a-4fcb-ad08-514e4336e485.mp4", + "tMs": 602980 + } + ] + }, + "problems": [], + "feature_db": { + "observations": 2529, + "assets": { + "by_state_reason": { + "ready": 1134, + "unavailable:queue_full": 1395 + }, + "unavailable_or_expired_without_reason": 0, + "unknown_reasons": [] + }, + "ready_asset_bytes": 142588998, + "ready_asset_bytes_max": 126889, + "detections_total": 2529, + "detections_in_window": 2412, + "detection_labels": { + "bird": 2529 + } + }, + "finished_at": "2026-11-08T20:11:43.352Z" +} diff --git a/tests/performance/observations/results/2026-11-08-x2-gate.json b/tests/performance/observations/results/2026-11-08-x2-gate.json new file mode 100644 index 000000000..4624ff59e --- /dev/null +++ b/tests/performance/observations/results/2026-11-08-x2-gate.json @@ -0,0 +1,900 @@ +{ + "schema_version": 1, + "task": "INT_01 X2 (A11)", + "generated_at": "2026-11-08T20:13:56.366Z", + "binary": "build-int01-X2/bin/lightnvr from 9ed02bb4 (Release, LiteRT on, apt FFmpeg 8.0.1 headers)", + "rule": "Gating: segment_interval_ms p95 +<=5%, no additional gaps (3 sources), same workload and binary, and the evidence checks (metrics.mjs evidenceGate). db_probe_ms p95 is advisory under the C4 rule because it fails its own 2% repeatability (disabled runs below); it is reported both per pair and pooled.", + "gates": [ + { + "baseline": "disabled-run1", + "candidate": "enabled-defaults", + "args": [ + "--advisory", + "latency.db_probe_ms.p95" + ], + "exit": 0, + "result": { + "max_increase_pct": 5, + "pass": true, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "baseline": 10008.926, + "candidate": 10008.535, + "increase_pct": -0.004, + "pass": true + }, + { + "key": "gaps.metrics_recording_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.catalog_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.segment_interval_gaps", + "baseline": 0, + "candidate": 0, + "pass": true + } + ], + "advisory": [ + { + "key": "latency.db_probe_ms.p95", + "baseline": 13.081, + "candidate": 14.834, + "increase_pct": 13.401, + "pass": false, + "gating": false + } + ], + "same_workload": true, + "same_binary": true, + "evidence": { + "require_saturation": false, + "max_submit_seconds": 0.001, + "pass": true, + "checks": [ + { + "key": "baseline.feature_inactive", + "value": { + "accepted": 0, + "queue_allocated": 0 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.detections", + "value": 2408, + "limit": null, + "pass": true + }, + { + "key": "evidence.ready", + "value": { + "accepted": 2408, + "ready": 2408 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.db_observations", + "value": 2524, + "limit": null, + "pass": true + }, + { + "key": "limits.items", + "value": { + "high_water": 4, + "backlog_max": 3 + }, + "limit": 64, + "pass": true + }, + { + "key": "limits.bytes", + "value": { + "high_water": 11086848, + "backlog_max": 8315136 + }, + "limit": 33554432, + "pass": true + }, + { + "key": "limits.camera_items", + "value": 1, + "limit": 8, + "pass": true + }, + { + "key": "drops.untracked", + "value": 0, + "limit": 0, + "pass": true + }, + { + "key": "drops.reasons_recorded", + "value": { + "window_drops": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "db": { + "ready": 2524 + }, + "without_reason": 0, + "unknown": [] + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.failures", + "value": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "limit": 0, + "pass": true + }, + { + "key": "submit.count", + "value": 2408, + "limit": null, + "pass": true + }, + { + "key": "submit.max_seconds", + "value": 0.000340905, + "limit": 0.001, + "pass": true + } + ] + }, + "verdict": "pass" + } + }, + { + "baseline": "disabled-run1", + "candidate": "enabled-saturated", + "args": [ + "--advisory", + "latency.db_probe_ms.p95", + "--require-saturation" + ], + "exit": 0, + "result": { + "max_increase_pct": 5, + "pass": true, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "baseline": 10008.926, + "candidate": 10012.879, + "increase_pct": 0.039, + "pass": true + }, + { + "key": "gaps.metrics_recording_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.catalog_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.segment_interval_gaps", + "baseline": 0, + "candidate": 0, + "pass": true + } + ], + "advisory": [ + { + "key": "latency.db_probe_ms.p95", + "baseline": 13.081, + "candidate": 14.23, + "increase_pct": 8.784, + "pass": false, + "gating": false + } + ], + "same_workload": true, + "same_binary": true, + "evidence": { + "require_saturation": true, + "max_submit_seconds": 0.001, + "pass": true, + "checks": [ + { + "key": "baseline.feature_inactive", + "value": { + "accepted": 0, + "queue_allocated": 0 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.detections", + "value": 2404, + "limit": null, + "pass": true + }, + { + "key": "evidence.ready", + "value": { + "accepted": 1063, + "ready": 1063 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.db_observations", + "value": 2532, + "limit": null, + "pass": true + }, + { + "key": "limits.items", + "value": { + "high_water": 1, + "backlog_max": 1 + }, + "limit": 64, + "pass": true + }, + { + "key": "limits.bytes", + "value": { + "high_water": 2771712, + "backlog_max": 2771712 + }, + "limit": 4194304, + "pass": true + }, + { + "key": "limits.camera_items", + "value": 1, + "limit": 8, + "pass": true + }, + { + "key": "drops.untracked", + "value": 0, + "limit": 0, + "pass": true + }, + { + "key": "drops.reasons_recorded", + "value": { + "window_drops": { + "queue_full": 1341, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "db": { + "ready": 1124, + "unavailable:queue_full": 1408 + }, + "without_reason": 0, + "unknown": [] + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.failures", + "value": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "limit": 0, + "pass": true + }, + { + "key": "submit.count", + "value": 2404, + "limit": null, + "pass": true + }, + { + "key": "submit.max_seconds", + "value": 0.000404625, + "limit": 0.001, + "pass": true + }, + { + "key": "saturation.queue_full_drops", + "value": 1341, + "limit": null, + "pass": true + } + ] + }, + "verdict": "pass" + } + }, + { + "baseline": "disabled-run3", + "candidate": "enabled-defaults2", + "args": [ + "--advisory", + "latency.db_probe_ms.p95" + ], + "exit": 0, + "result": { + "max_increase_pct": 5, + "pass": true, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "baseline": 10016.068, + "candidate": 10013.473, + "increase_pct": -0.026, + "pass": true + }, + { + "key": "gaps.metrics_recording_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.catalog_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.segment_interval_gaps", + "baseline": 0, + "candidate": 0, + "pass": true + } + ], + "advisory": [ + { + "key": "latency.db_probe_ms.p95", + "baseline": 13.401, + "candidate": 13.991, + "increase_pct": 4.403, + "pass": true, + "gating": false + } + ], + "same_workload": true, + "same_binary": true, + "evidence": { + "require_saturation": false, + "max_submit_seconds": 0.001, + "pass": true, + "checks": [ + { + "key": "baseline.feature_inactive", + "value": { + "accepted": 0, + "queue_allocated": 0 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.detections", + "value": 2407, + "limit": null, + "pass": true + }, + { + "key": "evidence.ready", + "value": { + "accepted": 2407, + "ready": 2408 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.db_observations", + "value": 2532, + "limit": null, + "pass": true + }, + { + "key": "limits.items", + "value": { + "high_water": 4, + "backlog_max": 3 + }, + "limit": 64, + "pass": true + }, + { + "key": "limits.bytes", + "value": { + "high_water": 11086848, + "backlog_max": 8315136 + }, + "limit": 33554432, + "pass": true + }, + { + "key": "limits.camera_items", + "value": 1, + "limit": 8, + "pass": true + }, + { + "key": "drops.untracked", + "value": 0, + "limit": 0, + "pass": true + }, + { + "key": "drops.reasons_recorded", + "value": { + "window_drops": { + "queue_full": 0, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "db": { + "ready": 2532 + }, + "without_reason": 0, + "unknown": [] + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.failures", + "value": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "limit": 0, + "pass": true + }, + { + "key": "submit.count", + "value": 2407, + "limit": null, + "pass": true + }, + { + "key": "submit.max_seconds", + "value": 0.000396048, + "limit": 0.001, + "pass": true + } + ] + }, + "verdict": "pass" + } + }, + { + "baseline": "disabled-run3", + "candidate": "enabled-saturated2", + "args": [ + "--advisory", + "latency.db_probe_ms.p95", + "--require-saturation" + ], + "exit": 0, + "result": { + "max_increase_pct": 5, + "pass": true, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "baseline": 10016.068, + "candidate": 10009.342, + "increase_pct": -0.067, + "pass": true + }, + { + "key": "gaps.metrics_recording_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.catalog_gaps_total", + "baseline": 0, + "candidate": 0, + "pass": true + }, + { + "key": "gaps.segment_interval_gaps", + "baseline": 0, + "candidate": 0, + "pass": true + } + ], + "advisory": [ + { + "key": "latency.db_probe_ms.p95", + "baseline": 13.401, + "candidate": 13.702, + "increase_pct": 2.246, + "pass": true, + "gating": false + } + ], + "same_workload": true, + "same_binary": true, + "evidence": { + "require_saturation": true, + "max_submit_seconds": 0.001, + "pass": true, + "checks": [ + { + "key": "baseline.feature_inactive", + "value": { + "accepted": 0, + "queue_allocated": 0 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.detections", + "value": 2404, + "limit": null, + "pass": true + }, + { + "key": "evidence.ready", + "value": { + "accepted": 1073, + "ready": 1073 + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.db_observations", + "value": 2529, + "limit": null, + "pass": true + }, + { + "key": "limits.items", + "value": { + "high_water": 1, + "backlog_max": 1 + }, + "limit": 64, + "pass": true + }, + { + "key": "limits.bytes", + "value": { + "high_water": 2771712, + "backlog_max": 2771712 + }, + "limit": 4194304, + "pass": true + }, + { + "key": "limits.camera_items", + "value": 1, + "limit": 8, + "pass": true + }, + { + "key": "drops.untracked", + "value": 0, + "limit": 0, + "pass": true + }, + { + "key": "drops.reasons_recorded", + "value": { + "window_drops": { + "queue_full": 1331, + "budget_exhausted": 0, + "disabled": 0, + "untracked": 0 + }, + "db": { + "ready": 1134, + "unavailable:queue_full": 1395 + }, + "without_reason": 0, + "unknown": [] + }, + "limit": null, + "pass": true + }, + { + "key": "evidence.failures", + "value": { + "encode_failed": 0, + "write_failed": 0, + "db_error": 0, + "disabled": 0, + "interrupted": 0 + }, + "limit": 0, + "pass": true + }, + { + "key": "submit.count", + "value": 2404, + "limit": null, + "pass": true + }, + { + "key": "submit.max_seconds", + "value": 0.00036462, + "limit": 0.001, + "pass": true + }, + { + "key": "saturation.queue_full_drops", + "value": 1331, + "limit": null, + "pass": true + } + ] + }, + "verdict": "pass" + } + } + ], + "strict_gates_without_advisory": [ + { + "baseline": "disabled-run1", + "candidate": "enabled-defaults", + "exit": 1, + "db_probe": { + "key": "latency.db_probe_ms.p95", + "baseline": 13.081, + "candidate": 14.834, + "increase_pct": 13.401, + "pass": false + } + }, + { + "baseline": "disabled-run1", + "candidate": "enabled-saturated", + "exit": 1, + "db_probe": { + "key": "latency.db_probe_ms.p95", + "baseline": 13.081, + "candidate": 14.23, + "increase_pct": 8.784, + "pass": false + } + }, + { + "baseline": "disabled-run3", + "candidate": "enabled-defaults2", + "exit": 0, + "db_probe": { + "key": "latency.db_probe_ms.p95", + "baseline": 13.401, + "candidate": 13.991, + "increase_pct": 4.403, + "pass": true + } + }, + { + "baseline": "disabled-run3", + "candidate": "enabled-saturated2", + "exit": 0, + "db_probe": { + "key": "latency.db_probe_ms.p95", + "baseline": 13.401, + "candidate": 13.702, + "increase_pct": 2.246, + "pass": true + } + } + ], + "pooled_db_probe_p95": { + "defaults": { + "exit": 1, + "series": "db_probe_ms", + "percentile": 95, + "max_increase_pct": 5, + "baseline": { + "runs": 3, + "count": 6955, + "min": 6.803, + "p50": 8.17, + "p95": 13.131, + "p99": 17.032, + "max": 38.949, + "mean": 8.867 + }, + "candidate": { + "runs": 2, + "count": 4630, + "min": 6.885, + "p50": 8.275, + "p95": 14.432, + "p99": 19.815, + "max": 36.451, + "mean": 9.194 + }, + "baseline_p95": 13.130799999999995, + "candidate_p95": 14.43155, + "increase_pct": 9.906, + "pass": false, + "baseline_files": [ + "tests/performance/observations/results/2026-11-08-x2-disabled-run1.json", + "tests/performance/observations/results/2026-11-08-x2-disabled-run2.json", + "tests/performance/observations/results/2026-11-08-x2-disabled-run3.json" + ], + "candidate_files": [ + "tests/performance/observations/results/2026-11-08-x2-enabled-defaults.json", + "tests/performance/observations/results/2026-11-08-x2-enabled-defaults2.json" + ], + "same_workload": true, + "same_binary": true + }, + "saturated": { + "exit": 1, + "series": "db_probe_ms", + "percentile": 95, + "max_increase_pct": 5, + "baseline": { + "runs": 3, + "count": 6955, + "min": 6.803, + "p50": 8.17, + "p95": 13.131, + "p99": 17.032, + "max": 38.949, + "mean": 8.867 + }, + "candidate": { + "runs": 2, + "count": 4635, + "min": 6.97, + "p50": 8.143, + "p95": 13.984, + "p99": 18.798, + "max": 32.759, + "mean": 8.96 + }, + "baseline_p95": 13.130799999999995, + "candidate_p95": 13.983700000000002, + "increase_pct": 6.495, + "pass": false, + "baseline_files": [ + "tests/performance/observations/results/2026-11-08-x2-disabled-run1.json", + "tests/performance/observations/results/2026-11-08-x2-disabled-run2.json", + "tests/performance/observations/results/2026-11-08-x2-disabled-run3.json" + ], + "candidate_files": [ + "tests/performance/observations/results/2026-11-08-x2-enabled-saturated.json", + "tests/performance/observations/results/2026-11-08-x2-enabled-saturated2.json" + ], + "same_workload": true, + "same_binary": true + } + }, + "disabled_repeatability": [ + { + "a": "disabled-run1", + "b": "disabled-run2", + "exit": 1, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "a": 10008.926, + "b": 10009.659, + "variance_pct": 0.007, + "pass": true + }, + { + "key": "latency.db_probe_ms.p95", + "a": 13.081, + "b": 12.744, + "variance_pct": 2.61, + "pass": false + } + ] + }, + { + "a": "disabled-run2", + "b": "disabled-run3", + "exit": 1, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "a": 10009.659, + "b": 10016.068, + "variance_pct": 0.064, + "pass": true + }, + { + "key": "latency.db_probe_ms.p95", + "a": 12.744, + "b": 13.401, + "variance_pct": 5.026, + "pass": false + } + ] + }, + { + "a": "disabled-run1", + "b": "disabled-run3", + "exit": 1, + "checks": [ + { + "key": "latency.segment_interval_ms.p95", + "a": 10008.926, + "b": 10016.068, + "variance_pct": 0.071, + "pass": true + }, + { + "key": "latency.db_probe_ms.p95", + "a": 13.081, + "b": 13.401, + "variance_pct": 2.417, + "pass": false + } + ] + } + ], + "gate_can_fail": { + "args": [ + "gate", + "disabled-run3", + "enabled-defaults2", + "--max-pct", + "-1" + ], + "exit": 1, + "verdict": "fail" + }, + "evidence_gate_can_fail": { + "args": [ + "gate", + "disabled-run3", + "enabled-defaults2", + "--advisory", + "latency.db_probe_ms.p95", + "--require-saturation", + "--max-submit-seconds", + "0.00001" + ], + "exit": 1, + "failing": [ + "submit.max_seconds", + "saturation.queue_full_drops" + ] + } +} diff --git a/tests/performance/observations/run-workload.mjs b/tests/performance/observations/run-workload.mjs new file mode 100644 index 000000000..8dd01c023 --- /dev/null +++ b/tests/performance/observations/run-workload.mjs @@ -0,0 +1,455 @@ +#!/usr/bin/env node +/** + * INT_01 observation performance workload (plan tasks C4 and X2). + * + * node tests/performance/observations/run-workload.mjs \ + * --binary build/int01-c4-release/bin/lightnvr --model /path/to/tiny20.sod \ + * --output build/perf-observations/disabled-run1 --mode disabled + * + * Starts a standalone go2rtc serving N looped RTSP fixture streams, an isolated + * lightNVR (tests/contracts/fixture-server.mjs) with continuous recording and + * a local SOD detector on every stream, then samples a fixed window and writes + * /results.json. See README.md for every measured field. + */ +import {spawn, execFileSync} from 'node:child_process'; +import {createHash} from 'node:crypto'; +import {watch} from 'node:fs'; +import {mkdir, readFile, readdir, stat, writeFile, copyFile, open, rm} from 'node:fs/promises'; +import {DatabaseSync} from 'node:sqlite'; +import os from 'node:os'; +import path from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {reservePort} from '../../contracts/fixture-server.mjs'; +import {trackedDiffSha256} from '../git-provenance.mjs'; +import { + summarize, parsePrometheus, sumMetric, metricByLabel, countIntervalGaps, + catalogGaps, resourceSummary, observationFeatureSummary, assetReasonSummary, +} from './metrics.mjs'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const repoRoot = path.resolve(here, '../../..'); +const workload = JSON.parse(await readFile(path.join(here, 'workload.json'), 'utf8')); +const GAP_THRESHOLD_SEC = 5; // RECORDING_GAP_THRESHOLD_SEC in include/telemetry/stream_metrics.h + +// ---------- arguments ---------- +const args = process.argv.slice(2); +const opt = (name, fallback) => { const i = args.indexOf(name); return i < 0 ? fallback : args[i + 1]; }; +const flag = name => args.includes(name); +const optAll = name => args.flatMap((a, i) => (a === name && i + 1 < args.length ? [args[i + 1]] : [])); +if (flag('--help')) { + console.log(`Options (defaults from workload.json): + --binary PATH lightNVR binary (default $LIGHTNVR_CONTRACT_BIN or build/int01-c4-release/bin/lightnvr) + --model PATH SOD model file (required unless --no-detector) + --output DIR new results directory (must not exist) + --mode NAME label: disabled | enabled (default disabled) + --streams N --seconds S --warmup S --width W --height H --fps F --gop G --segment S + --detection-interval S + --source FILE use this H.264 clip instead of the synthetic testsrc2 clip + --stream-extra FILE JSON merged into every POST /api/streams body (X2: evidence opt-in) + --config-extra FILE INI text appended to the generated lightnvr.ini + --system-setting K=V (repeatable) system_settings row written before streams are added; lightNVR is + restarted once so startup-only settings apply (X2: observation_global_queue_bytes) + --work-dir DIR where fixture storage lives (default build/perf-observations/work; must be a real disk, not tmpfs) + --go2rtc PATH go2rtc binary (default: go2rtc on PATH) + --no-detector omit the detector (diagnostic only; not a C4/X2 workload) + --keep keep the fixture directory`); + process.exit(0); +} +const int = (name, fallback, lo, hi) => { + const v = Number(opt(name, fallback)); + if (!Number.isInteger(v) || v < lo || v > hi) throw new Error(`${name} must be an integer ${lo}..${hi}`); + return v; +}; +const binary = path.resolve(opt('--binary', process.env.LIGHTNVR_CONTRACT_BIN || 'build/int01-c4-release/bin/lightnvr')); +const output = path.resolve(opt('--output', `build/perf-observations/run-${Date.now()}`)); +const mode = opt('--mode', 'disabled'); +const streams = int('--streams', workload.streams, 1, 32); +const seconds = int('--seconds', workload.duration_seconds, 10, 7200); +const warmup = int('--warmup', workload.warmup_seconds, 0, 600); +const width = int('--width', workload.width, 64, 3840); +const height = int('--height', workload.height, 64, 2160); +const fps = int('--fps', workload.fps, 1, 60); +const gop = int('--gop', workload.gop, 1, 600); +const segment = int('--segment', workload.segment_seconds, 5, 900); +const detectionInterval = int('--detection-interval', workload.detector.interval_seconds, 1, 3600); +const useDetector = !flag('--no-detector'); +const modelPath = opt('--model', process.env.LIGHTNVR_PERF_SOD_MODEL); +if (useDetector && !modelPath) throw new Error('--model PATH (SOD model, e.g. tiny20.sod) is required; see README.md'); +const workDir = path.resolve(opt('--work-dir', 'build/perf-observations/work')); +const go2rtcBin = opt('--go2rtc', 'go2rtc'); +const streamExtra = opt('--stream-extra') ? JSON.parse(await readFile(opt('--stream-extra'), 'utf8')) : {}; +const configExtra = opt('--config-extra') ? await readFile(opt('--config-extra'), 'utf8') : ''; +const systemSettings = optAll('--system-setting').map(kv => { + const at = kv.indexOf('='); + if (at < 1) throw new Error(`--system-setting expects KEY=VALUE, got ${kv}`); + return [kv.slice(0, at), kv.slice(at + 1)]; +}); + +// ---------- helpers ---------- +const sha256 = buf => createHash('sha256').update(buf).digest('hex'); +const sh = (cmd, argv) => { try { return execFileSync(cmd, argv, {encoding: 'utf8', timeout: 10000, stdio: ['ignore', 'pipe', 'pipe']}).trim(); } catch { return null; } }; +const pause = ms => new Promise(r => setTimeout(r, ms)); +const now = () => performance.now(); +const clkTck = Number(sh('getconf', ['CLK_TCK'])) || 100; + +async function procSample(pid) { + try { + const [status, statText, io, fds] = await Promise.all([ + readFile(`/proc/${pid}/status`, 'utf8'), readFile(`/proc/${pid}/stat`, 'utf8'), + readFile(`/proc/${pid}/io`, 'utf8').catch(() => ''), readdir(`/proc/${pid}/fd`).catch(() => []), + ]); + const f = statText.slice(statText.lastIndexOf(')') + 2).split(' '); + return { + tMs: now(), cpuTicks: Number(f[11]) + Number(f[12]), + rssKiB: Number(status.match(/^VmRSS:\s+(\d+)/m)?.[1]), peakRssKiB: Number(status.match(/^VmHWM:\s+(\d+)/m)?.[1]), + threads: Number(status.match(/^Threads:\s+(\d+)/m)?.[1]), fds: fds.length, + writeBytes: Number(io.match(/^write_bytes:\s+(\d+)/m)?.[1] ?? NaN), + }; + } catch { return null; } +} + +/** Sum CPU ticks of a process and all its descendants. */ +async function treeCpuTicks(rootPid) { + const children = new Map(); + for (const entry of await readdir('/proc')) { + if (!/^\d+$/.test(entry)) continue; + try { + const s = await readFile(`/proc/${entry}/stat`, 'utf8'); + const f = s.slice(s.lastIndexOf(')') + 2).split(' '); + const ppid = Number(f[1]); + if (!children.has(ppid)) children.set(ppid, []); + children.get(ppid).push({pid: Number(entry), ticks: Number(f[11]) + Number(f[12])}); + } catch {} + } + let total = 0, count = 0; + const walk = pid => { for (const c of children.get(pid) || []) { total += c.ticks; count++; walk(c.pid); } }; + try { + const s = await readFile(`/proc/${rootPid}/stat`, 'utf8'); + const f = s.slice(s.lastIndexOf(')') + 2).split(' '); + total += Number(f[11]) + Number(f[12]); count++; + } catch { return null; } + walk(rootPid); + return {tMs: now(), ticks: total, processes: count}; +} + +async function describeHost(storageDir) { + const cpus = os.cpus(); + const mount = sh('findmnt', ['-n', '-T', storageDir, '-o', 'SOURCE,FSTYPE,OPTIONS'])?.split(/\s+/) || []; + let deviceModel = null, rotational = null; + if (mount[0]?.startsWith('/dev/')) { + const parent = sh('lsblk', ['-no', 'PKNAME', mount[0]])?.split('\n')[0] || path.basename(mount[0]); + deviceModel = sh('lsblk', ['-dno', 'MODEL', `/dev/${parent}`]); + rotational = sh('lsblk', ['-dno', 'ROTA', `/dev/${parent}`]) === '1'; + } + return { + hostname_sha256: sha256(os.hostname()).slice(0, 16), + cpu_model: cpus[0]?.model, logical_cores: cpus.length, + cpu_governor: (await readFile('/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor', 'utf8').catch(() => '')).trim() || null, + ram_bytes: os.totalmem(), kernel: os.release(), kernel_version: os.version(), arch: os.arch(), + os_release: (await readFile('/etc/os-release', 'utf8').catch(() => '')).match(/^PRETTY_NAME="?([^"\n]*)/m)?.[1] || null, + loadavg_at_start: os.loadavg(), + storage: {path: storageDir, device: mount[0] || null, fstype: mount[1] || null, mount_options: mount[2] || null, + device_model: deviceModel, rotational}, + ffmpeg: sh('ffmpeg', ['-version'])?.split('\n')[0] || null, + go2rtc: sh(go2rtcBin, ['--version']) || sh(go2rtcBin, ['-version']), + node: process.version, + }; +} + +// ---------- setup ---------- +await mkdir(output, {recursive: false}); +await mkdir(workDir, {recursive: true}); +const tmpfsLike = sh('findmnt', ['-n', '-T', workDir, '-o', 'FSTYPE']); +if (['tmpfs', 'ramfs'].includes(tmpfsLike)) throw new Error(`--work-dir ${workDir} is ${tmpfsLike}; disk write rate would be meaningless`); +process.env.TMPDIR = workDir; // fixture-server uses os.tmpdir() for its private directory + +const git = (...a) => sh('git', ['-C', repoRoot, ...a]); +const report = { + schema_version: 1, workload_name: workload.name, mode, status: 'running', started_at: new Date().toISOString(), + host: await describeHost(workDir), + build: {revision: git('rev-parse', 'HEAD'), branch: git('rev-parse', '--abbrev-ref', 'HEAD'), + tracked_diff_sha256: await trackedDiffSha256(repoRoot), binary: path.relative(repoRoot, binary), + binary_sha256: sha256(await readFile(binary)), harness_sha256: sha256(await readFile(fileURLToPath(import.meta.url))), + metrics_sha256: sha256(await readFile(path.join(here, 'metrics.mjs')))}, + workload: {streams, width, height, fps, gop, codec: workload.codec, segment_seconds: segment, warmup_seconds: warmup, + duration_seconds: seconds, transport: workload.transport, live_viewers: 0, audio: workload.audio, + detector: useDetector ? {kind: workload.detector.kind, model_file: path.basename(modelPath), interval_seconds: detectionInterval, + threshold_percent: workload.detector.threshold_percent} : null}, + retention: workload.retention, + feature: {stream_extra: streamExtra, config_extra: configExtra || null, system_settings: Object.fromEntries(systemSettings)}, + definitions: { + 'latency.segment_interval_ms': 'Per stream, wall-clock interval between first sight (inotify) of consecutive new segment files; nominal = segment_seconds*1000. A recording-path stall (writer, rotation, DB insert under the global DB mutex) lengthens it.', + 'latency.segment_interval_excess_ms': 'segment_interval_ms minus nominal.', + 'latency.db_probe_ms': `Round trip of GET /api/recordings?limit=1 with a session cookie every ${workload.sampling.db_probe_interval_ms} ms; contends on the same global DB mutex as recording-path catalog writes.`, + 'gaps.metrics_recording_gaps_total': 'Delta of lightnvr_recording_gaps_total over the window (in-process detector, threshold 5 s).', + 'gaps.catalog_gaps_total': 'Consecutive catalog rows per stream with start - previous end > 5 s (whole run, after warmup).', + 'gaps.segment_interval_gaps': 'Segment intervals longer than nominal + 5 s inside the window.', + }, + limitations: [ + 'Workstation measurement; go2rtc and the ffmpeg publishers share the host (their CPU is reported separately under resources.fixture_go2rtc_and_publishers).', + 'No in-process packet-write latency histogram exists in lightNVR today; recording-path latency is the black-box segment-interval proxy plus the DB-mutex probe.', + 'Synthetic testsrc2 content produces no VOC detections: the detector runs inference at its cadence but creates no detections. X2 needs a fixture that yields detections and must rerun disabled mode on that same source and binary.', + ], + definitions_x2: { + feature_metrics: 'L10 lightnvr_observation_* families: counters are window deltas (start..end scrape), high_water_* / camera_high_water_items / submit max are per-process-run maxima, *_max fields are maxima over every scrape, limits are the configured admission bounds.', + 'feature_metrics.submit_latency': 'Producer time inside observation_evidence_submit*/admission (lightnvr_observation_submit_latency_seconds histogram, window delta). p*_upper_seconds are bucket upper bounds (conservative).', + feature_db: 'Read from the fixture DB after shutdown (the worker drains pending drop records at shutdown): observation rows, assets by state:reason, detection rows.', + }, +}; +if (useDetector) report.workload.detector.model_sha256 = sha256(await readFile(modelPath)); +const save = () => writeFile(path.join(output, 'results.json'), JSON.stringify(report, null, 2) + '\n'); + +// Source clip: deterministic synthetic H.264 (or a user clip). +let source = opt('--source'); +if (source) source = path.resolve(source); +else { + source = path.join(output, 'source.mp4'); + execFileSync('ffmpeg', ['-nostdin', '-hide_banner', '-loglevel', 'error', '-f', 'lavfi', '-i', `testsrc2=size=${width}x${height}:rate=${fps}`, + '-t', String(workload.source_clip_seconds), '-an', '-c:v', 'libx264', '-threads', '1', '-preset', 'ultrafast', '-pix_fmt', 'yuv420p', + '-g', String(gop), '-keyint_min', String(gop), '-sc_threshold', '0', '-bf', '0', '-movflags', '+faststart', source], {timeout: 120000}); +} +report.workload.source = opt('--source') ? 'user clip (see source_sha256)' : workload.source; +report.workload.source_sha256 = sha256(await readFile(source)); +report.workload.source_probe = JSON.parse(execFileSync('ffprobe', ['-v', 'error', '-select_streams', 'v:0', '-show_entries', + 'stream=codec_name,width,height,r_frame_rate,pix_fmt:format=duration', '-of', 'json', source], {encoding: 'utf8'})); + +// ---------- go2rtc fixture ---------- +const rtspPort = await reservePort(), apiPort = await reservePort(); +const ports = {rtsp: rtspPort.port, api: apiPort.port}; +await rtspPort.release(); await apiPort.release(); +const go2rtcDir = path.join(output, 'go2rtc'); +await mkdir(go2rtcDir); +// Streams are declared without a source; one always-on ffmpeg per stream +// publishes the looped clip into go2rtc, so the producer never depends on +// consumer timing (go2rtc exec: sources start lazily and raced the first +// recorder/detector connect in a smoke run). +const yaml = [`api:\n listen: "127.0.0.1:${ports.api}"`, `rtsp:\n listen: "127.0.0.1:${ports.rtsp}"`, + 'webrtc:\n listen: ""', 'srtp:\n listen: ""', 'log:\n level: warn', 'streams:', + ...Array.from({length: streams}, (_, i) => ` cam${i}:`), +].join('\n') + '\n'; +await writeFile(path.join(go2rtcDir, 'go2rtc.yaml'), yaml); +const go2rtcLog = await open(path.join(go2rtcDir, 'go2rtc.log'), 'w'); +const go2rtc = spawn(go2rtcBin, ['-config', path.join(go2rtcDir, 'go2rtc.yaml')], {stdio: ['ignore', go2rtcLog.fd, go2rtcLog.fd], detached: true}); +const go2rtcExit = new Promise(r => go2rtc.once('close', r)); +for (let i = 0; ; i++) { + try { if ((await fetch(`http://127.0.0.1:${ports.api}/api/streams`, {signal: AbortSignal.timeout(500)})).ok) break; } catch {} + if (i > 50 || go2rtc.exitCode !== null) throw new Error('go2rtc did not start; see go2rtc/go2rtc.log'); + await pause(100); +} +let stopping = false; +const sourceErrors = []; +const publishers = Array.from({length: streams}, (_, i) => { + const child = spawn('ffmpeg', ['-hide_banner', '-loglevel', 'error', '-nostdin', '-re', '-stream_loop', '-1', '-i', source, + '-c', 'copy', '-rtsp_transport', 'tcp', '-f', 'rtsp', `rtsp://127.0.0.1:${ports.rtsp}/cam${i}`], {stdio: ['ignore', 'ignore', 'pipe'], detached: true}); + let stderr = ''; + child.stderr.on('data', c => { stderr = (stderr + c).slice(-4096); }); + const exit = new Promise(r => child.once('close', (code, signal) => { if (!stopping) sourceErrors.push({stream: `cam${i}`, code, signal, stderr}); r(); })); + return {child, exit}; +}); +// ---------- lightNVR fixture ---------- +const {startFixture} = await import('../../contracts/fixture-server.mjs'); +let server, watcher, cookie; +const segEvents = [], seenFiles = new Set(); +const proc = [], tree = [], probes = [], prom = []; +try { + for (let i = 0; i < streams; i++) { + for (let attempt = 0; ; attempt++) { + const ok = sh('ffprobe', ['-v', 'error', '-rtsp_transport', 'tcp', '-select_streams', 'v:0', '-show_entries', 'stream=codec_name', '-of', 'csv=p=0', `rtsp://127.0.0.1:${ports.rtsp}/cam${i}`]); + if (ok === 'h264') break; + if (attempt > 20) throw new Error(`fixture stream cam${i} never became readable`); + await pause(250); + } + } + server = await startFixture({binary, maxStreams: streams, seed: false, retain: true, extraConfig: configExtra}); + report.fixture_directory = server.directory; + await server.json('/api/setup/status', {method: 'POST', body: {complete: true}}); + if (systemSettings.length) { + // Startup-only settings (main.c reads observation_global_queue_bytes once): + // write them while stopped, then start the next process generation. + await server.kill('SIGTERM'); + const db = new DatabaseSync(path.join(server.directory, 'lightnvr.db')); + const upsert = db.prepare("INSERT INTO system_settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = strftime('%s', 'now')"); + for (const [key, value] of systemSettings) upsert.run(key, value); + db.close(); + await server.relaunch(); + } + const login = await server.request('/api/auth/login', {principal: 'anonymous', method: 'POST', body: server.credentials.admin}); + cookie = (login.headers.getSetCookie?.() || []).map(c => c.split(';')[0]).join('; '); + if (!login.ok || !cookie) throw new Error(`session login failed (${login.status})`); + const sessionFetch = route => fetch(`${server.baseURL}${route}`, {headers: {Cookie: cookie}, signal: AbortSignal.timeout(10000)}); + + let modelInFixture = null; + if (useDetector) { + modelInFixture = path.join(server.directory, 'models', path.basename(modelPath)); + await copyFile(modelPath, modelInFixture); + } + const recordingsDir = path.join(server.directory, 'recordings'); + const streamNames = Array.from({length: streams}, (_, i) => `Load${i}`); + watcher = watch(recordingsDir, {recursive: true}, (event, file) => { + if (!file || !/\.mp4$/i.test(file) || seenFiles.has(file)) return; + const stream = file.split(path.sep).find(part => streamNames.includes(part)) || streamNames.find(n => file.includes(n)); + seenFiles.add(file); + segEvents.push({stream, file, tMs: now(), wallMs: Date.now()}); + }); + for (const [i, name] of streamNames.entries()) { + await server.json('/api/streams', {method: 'POST', body: { + name, url: `rtsp://127.0.0.1:${ports.rtsp}/cam${i}`, enabled: true, streaming_enabled: false, record: true, + record_audio: false, segment_duration: segment, fps, protocol: 0, + detection_based_recording: useDetector, detection_model: modelInFixture || '', detection_threshold: workload.detector.threshold_percent, + detection_interval: detectionInterval, pre_detection_buffer: 0, post_detection_buffer: 0, ...streamExtra, + }}); + } + report.phase = 'warmup'; await save(); + await pause(warmup * 1000); + + // ---------- measurement window ---------- + const logPath = path.join(server.directory, 'server.log'); + const logStart = (await stat(logPath)).size; + const windowStart = now(), windowStartWall = Date.now(); + const promAt = async () => { const r = await sessionFetch('/api/metrics'); return {tMs: now(), parsed: parsePrometheus(await r.text())}; }; + const promStart = await promAt(); + report.phase = 'measuring'; await save(); + const deadline = windowStart + seconds * 1000; + const loops = [ + (async () => { while (now() < deadline) { const s = await procSample(server.pid); if (s) proc.push(s); const parts = (await Promise.all([go2rtc.pid, ...publishers.map(p => p.child.pid)].map(treeCpuTicks))).filter(Boolean); if (parts.length) tree.push({tMs: now(), ticks: parts.reduce((a, b) => a + b.ticks, 0), processes: parts.reduce((a, b) => a + b.processes, 0)}); await pause(workload.sampling.proc_interval_ms); } })(), + (async () => { while (now() < deadline) { const t0 = now(); const r = await sessionFetch('/api/recordings?limit=1&page=1'); await r.arrayBuffer(); probes.push({tMs: t0, ms: now() - t0, status: r.status}); await pause(workload.sampling.db_probe_interval_ms); } })(), + (async () => { while (now() < deadline) { await pause(workload.sampling.prometheus_interval_ms); prom.push(await promAt()); } })(), + ]; + await Promise.all(loops); + const promEnd = await promAt(); + const windowEnd = now(), windowEndWall = Date.now(); + const logEnd = (await stat(logPath)).size; + + // ---------- analysis ---------- + const fh = await open(logPath, 'r'); + const buf = Buffer.alloc(logEnd - logStart); + await fh.read(buf, 0, buf.length, logStart); await fh.close(); + const logWindow = buf.toString('utf8'); + const inferences = (logWindow.match(/sod_cnn_predict returned/g) || []).length; + report.detector = useDetector ? { + inferences_in_window: inferences, + inferences_per_stream_per_second: Math.round(inferences / streams / seconds * 1000) / 1000, + expected_per_stream_per_second: Math.round(1 / detectionInterval * 1000) / 1000, + detections_in_window: (logWindow.match(/Valid detection \d+:/g) || []).length, + source: 'count of "sod_cnn_predict returned" info lines in server.log during the window', + } : null; + report.log_errors_in_window = (logWindow.match(/\[ERROR\]/g) || []).length; + report.feature_metrics = observationFeatureSummary(promStart.parsed, promEnd.parsed, prom.map(p => p.parsed)); + report.evidence_init_log = (logWindow.match(/Observation evidence initialized[^\n]*/) || (await readFile(logPath, 'utf8')).match(/Observation evidence initialized[^\n]*/g)?.slice(-1) || [null])[0]; + report.window_unix = {start: Math.floor(windowStartWall / 1000), end: Math.ceil(windowEndWall / 1000)}; + + // Intervals whose closing segment appears inside the window (predecessor may be in warmup). + const endTimes = {}; + for (const e of segEvents) (endTimes[e.stream] ||= []).push(e.tMs); + const all = []; + for (const [stream, list] of Object.entries(endTimes)) { + list.sort((a, b) => a - b); + for (let i = 1; i < list.length; i++) if (list[i] >= windowStart && list[i] <= windowEnd) all.push({stream, ms: list[i] - list[i - 1]}); + } + const nominal = segment * 1000; + const intervalValues = all.map(x => x.ms); + const okProbes = probes.filter(p => p.status === 200).map(p => p.ms); + report.latency = { + segment_interval_ms: summarize(intervalValues), + segment_interval_excess_ms: summarize(intervalValues.map(v => v - nominal)), + db_probe_ms: summarize(okProbes), + db_probe_failures: probes.length - okProbes.length, + per_stream_segment_interval_ms: Object.fromEntries(Object.keys(endTimes).sort().map(s => [s, summarize(all.filter(x => x.stream === s).map(x => x.ms))])), + }; + + const gapsStart = sumMetric(promStart.parsed, 'lightnvr_recording_gaps_total') ?? NaN; + const gapsEnd = sumMetric(promEnd.parsed, 'lightnvr_recording_gaps_total') ?? NaN; + const delta = name => (sumMetric(promEnd.parsed, name) ?? NaN) - (sumMetric(promStart.parsed, name) ?? NaN); + + // Stop recording before reading the catalog so the last segments finalize. + for (const name of streamNames) await server.json(`/api/streams/${name}/recording`, {method: 'POST', body: {action: 'stop'}}).catch(() => {}); + await pause(3000); + const rows = []; + for (let page = 1; ; page++) { + const r = await server.json(`/api/recordings?limit=1000&page=${page}&sort=id&order=asc`); + rows.push(...r.recordings); + if (r.recordings.length < 1000) break; + } + const toSec = v => (typeof v === 'number' ? v : Math.floor(Date.parse(v) / 1000)); + const catalogRows = rows.map(r => ({id: r.id, stream: r.stream, start_time: toSec(r.start_time), end_time: toSec(r.end_time)})) + .filter(r => Number.isFinite(r.start_time) && Number.isFinite(r.end_time) && r.end_time > 0 + && r.start_time * 1000 >= windowStartWall - segment * 1000); + const catalog = catalogGaps(catalogRows, GAP_THRESHOLD_SEC); + report.gaps = { + metrics_recording_gaps_total: gapsEnd - gapsStart, + metrics_recording_gaps_by_stream_end: metricByLabel(promEnd.parsed, 'lightnvr_recording_gaps_total'), + catalog_gaps_total: catalog.total, catalog_gaps: catalog.gaps, + segment_interval_gaps: countIntervalGaps(intervalValues, nominal, GAP_THRESHOLD_SEC * 1000), + }; + report.recording = { + segments_created_in_window: segEvents.filter(e => e.tMs >= windowStart && e.tMs <= windowEnd).length, + expected_segments_in_window: Math.floor(seconds / segment) * streams, + metrics_segments_delta: delta('lightnvr_recording_segments_total'), + metrics_bytes_written_delta: delta('lightnvr_recording_bytes_written'), + frames_dropped_delta: delta('lightnvr_stream_frames_dropped'), + reconnects_delta: delta('lightnvr_stream_reconnects_total'), + catalog_rows_after_warmup: catalogRows.length, + fps_samples: summarize(prom.flatMap(p => Object.values(metricByLabel(p.parsed, 'lightnvr_stream_fps')))), + streams_up_min: Math.min(...prom.map(p => sumMetric(p.parsed, 'lightnvr_instance_streams_up') ?? NaN)), + }; + const treeSec = tree.length > 1 ? (tree.at(-1).tMs - tree[0].tMs) / 1000 : NaN; + report.resources = { + lightnvr: resourceSummary(proc, clkTck), + fixture_go2rtc_and_publishers: tree.length > 1 ? {cpu_percent_one_core_avg: Math.round((tree.at(-1).ticks - tree[0].ticks) / clkTck / treeSec * 100 * 1000) / 1000, + processes: tree.at(-1).processes} : null, + storage_bytes_end: Number(sh('du', ['-sb', recordingsDir])?.split(/\s+/)[0]), + loadavg_at_end: os.loadavg(), + }; + report.window = {start: new Date(windowStartWall).toISOString(), end: new Date(windowEndWall).toISOString(), seconds: Math.round((windowEnd - windowStart) / 10) / 100}; + report.samples = {proc: proc.map(s => ({...s, tMs: Math.round(s.tMs - windowStart)})), + db_probe_ms: probes.map(p => Math.round(p.ms * 1000) / 1000), + segment_events: segEvents.map(e => ({stream: e.stream, file: e.file, tMs: Math.round(e.tMs - windowStart)}))}; + + const problems = []; + if (report.recording.streams_up_min < streams) problems.push(`streams_up dropped to ${report.recording.streams_up_min}`); + if (useDetector && report.detector.inferences_per_stream_per_second < 0.5 / detectionInterval) problems.push('detector ran below half its configured cadence'); + if (intervalValues.length < streams) problems.push('too few segment intervals measured'); + if (report.latency.db_probe_failures > 0) problems.push(`${report.latency.db_probe_failures} DB probe failures`); + if (sourceErrors.length) problems.push(`fixture publisher exited: ${JSON.stringify(sourceErrors)}`); + report.problems = problems; + report.status = problems.length ? 'invalid' : 'completed'; + if (problems.length) process.exitCode = 1; +} catch (error) { + report.status = 'failed'; report.error = String(error?.stack || error); process.exitCode = 1; +} finally { + watcher?.close(); + try { await server?.stop({failed: report.status !== 'completed' || flag('--keep')}); } catch (e) { report.cleanup_error = String(e); } + if (server && report.window_unix) { + try { + const db = new DatabaseSync(path.join(server.directory, 'lightnvr.db'), {readOnly: true}); + const one = sql => Object.values(db.prepare(sql).get())[0]; + const {start, end} = report.window_unix; + report.feature_db = { + observations: one('SELECT count(*) FROM object_observations'), + assets: assetReasonSummary(db.prepare('SELECT state, reason, count(*) AS n FROM observation_assets GROUP BY state, reason').all()), + ready_asset_bytes: one("SELECT coalesce(sum(bytes), 0) FROM observation_assets WHERE state = 'ready'"), + ready_asset_bytes_max: one("SELECT coalesce(max(bytes), 0) FROM observation_assets WHERE state = 'ready'"), + detections_total: one('SELECT count(*) FROM detections'), + detections_in_window: one(`SELECT count(*) FROM detections WHERE timestamp >= ${start} AND timestamp <= ${end}`), + detection_labels: Object.fromEntries(db.prepare('SELECT label, count(*) AS n FROM detections GROUP BY label').all().map(r => [r.label, r.n])), + }; + db.close(); + } catch (e) { report.feature_db = {error: String(e)}; } + if (!flag('--keep') && report.status === 'completed') await rm(server.directory, {recursive: true, force: true}).catch(() => {}); + } + stopping = true; + for (const p of publishers) { try { process.kill(-p.child.pid, 'SIGTERM'); } catch {} } + await Promise.race([Promise.all(publishers.map(p => p.exit)), pause(3000)]); + for (const p of publishers) { try { process.kill(-p.child.pid, 'SIGKILL'); } catch {} } + try { process.kill(-go2rtc.pid, 'SIGTERM'); } catch {} + const t = setTimeout(() => { try { process.kill(-go2rtc.pid, 'SIGKILL'); } catch {} }, 3000); + await go2rtcExit; clearTimeout(t); await go2rtcLog.close(); + report.finished_at = new Date().toISOString(); + delete report.phase; + await save(); +} +console.log(JSON.stringify({status: report.status, output: path.relative(repoRoot, output), problems: report.problems, error: report.error, + segment_interval_p95_ms: report.latency?.segment_interval_ms?.p95, db_probe_p95_ms: report.latency?.db_probe_ms?.p95, + gaps: report.gaps && {metrics: report.gaps.metrics_recording_gaps_total, catalog: report.gaps.catalog_gaps_total, interval: report.gaps.segment_interval_gaps}, + detections_in_window: report.detector?.detections_in_window, accepted: report.feature_metrics?.accepted, dropped: report.feature_metrics?.dropped, + observations: report.feature_db?.observations})); diff --git a/tests/performance/observations/workload.json b/tests/performance/observations/workload.json new file mode 100644 index 000000000..af87f1ce2 --- /dev/null +++ b/tests/performance/observations/workload.json @@ -0,0 +1,35 @@ +{ + "schema_version": 1, + "name": "int01-observations-v1", + "description": "INT_01 performance fixture (plan task C4). N looped H.264 RTSP streams served by a standalone go2rtc, continuous recording plus a local SOD detector at a fixed interval on every stream. X2 reruns the same workload with evidence enabled.", + "streams": 4, + "width": 1280, + "height": 720, + "fps": 15, + "gop": 15, + "codec": "H.264 (libx264 ultrafast, yuv420p, no B-frames)", + "source": "synthetic: ffmpeg lavfi testsrc2, 30 s clip, published into go2rtc by one always-on ffmpeg -re -stream_loop -1 -c copy per stream", + "source_clip_seconds": 30, + "transport": "RTSP over TCP from standalone go2rtc on 127.0.0.1 (lightNVR's own go2rtc integration disabled, as in the contract fixture)", + "segment_seconds": 10, + "warmup_seconds": 30, + "duration_seconds": 300, + "detector": { + "kind": "local SOD CNN (unified detection thread, annotation-only because continuous recording is on)", + "model_file": "tiny20.sod", + "model_note": "SOD tiny YOLO VOC-20 model; not vendored (63 MB). Pass --model; its sha256 is recorded.", + "interval_seconds": 1, + "threshold_percent": 50 + }, + "sampling": { + "proc_interval_ms": 1000, + "db_probe_interval_ms": 250, + "prometheus_interval_ms": 5000 + }, + "retention": { + "storage_max_size": 0, + "note": "Recording retention/quota is unlimited in the fixture (storage.max_size = 0) so retention sweeps do not perturb the window. Evidence byte/queue budgets do not exist in disabled mode; X2 records the C1 budgets it runs with in results.feature." + }, + "live_viewers": 0, + "audio": "none (record_audio = false)" +} diff --git a/tests/reliability/README.md b/tests/reliability/README.md index 2c57f76cc..c83f85617 100644 --- a/tests/reliability/README.md +++ b/tests/reliability/README.md @@ -277,3 +277,27 @@ preparer/upgrade evidence is recorded in `docs/api/contracts/upgrade-evidence.md The [2026-11-07 qualification](../../docs/validation/recovery-qualification-2026-11-07.md) repeats all four scenarios on one current Release binary and extends lifecycle to ten cycles per phase. Named hardware and long-duration acceptance remain open. + +## Observation evidence crash boundaries (INT_01 L11, A6) + +`tests/unit/test_observation_crash_boundaries.c` forks a process that runs the +real evidence worker, relay, router and outbox on a migrated database, SIGKILLs +it at one persistence boundary (after the batch insert, after the tmp write, +after rename before commit, after commit before relay, after the outbox step +before the publication mark, and after the mark before delivery), restarts the +`main.c` sequence (reconcile -> evidence -> relay) in a second process and +asserts that no ready asset points at an unfinished file and that every +committed change reaches the outbox exactly once. The kill points are stage +hooks (`observation_evidence_set_stage_hook`, `observation_relay_set_stage_hook`) +that only linked test code can install; no configuration, environment variable +or API reaches them. + +```sh +make -C test_observation_crash_boundaries && /bin/test_observation_crash_boundaries +``` + +Binary-level restarts (SIGKILL with committed-but-unrelayed changes, relayed but +unacknowledged MQTT delivery, DB restore, broker outage with 100 observations) +are Playwright contracts in `tests/contracts/observationsReliability.api.spec.ts`; +`tests/contracts/observationsPipeline.api.spec.ts` drives a real detection +stream into evidence and MQTT. Both need `LIGHTNVR_CONTRACT_MQTT=1`. diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index a66676cf9..3733a8cd7 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -151,6 +151,25 @@ target_link_options(test_db_backup_publication PRIVATE -Wl,--wrap=rename -Wl,--w set_tests_properties(test_db_backup_publication PROPERTIES TIMEOUT 30) add_layer2_test(test_sqlite_migration_boundaries CI_UNIT CI_SANITIZER) set_tests_properties(test_sqlite_migration_boundaries PROPERTIES TIMEOUT 20) +add_layer2_test(test_observation_migration CI_UNIT CI_SANITIZER) +set_tests_properties(test_observation_migration PROPERTIES TIMEOUT 30) +add_layer2_test(test_observation_codec CI_UNIT CI_SANITIZER) +add_layer2_test(test_db_observations CI_UNIT CI_SANITIZER) +set_tests_properties(test_db_observations PROPERTIES TIMEOUT 60) +add_layer2_test(test_db_observation_classifications CI_UNIT CI_SANITIZER) +set_tests_properties(test_db_observation_classifications PROPERTIES TIMEOUT 60) +add_layer2_test_with_ffmpeg(test_observation_evidence CI_UNIT CI_SANITIZER) +set_tests_properties(test_observation_evidence PROPERTIES TIMEOUT 120) +add_layer2_test(test_observation_relay CI_UNIT CI_SANITIZER) +target_compile_definitions(test_observation_relay PRIVATE LIGHTNVR_SOURCE_DIR="${CMAKE_SOURCE_DIR}") +set_tests_properties(test_observation_relay PROPERTIES TIMEOUT 60) +# INT_01 L11 / A6: SIGKILL at each observation persistence boundary, then restart. +add_layer2_test(test_observation_crash_boundaries CI_UNIT CI_SANITIZER) +set_tests_properties(test_observation_crash_boundaries PROPERTIES TIMEOUT 180) +add_layer2_test(test_observation_retention CI_UNIT CI_SANITIZER) +set_tests_properties(test_observation_retention PROPERTIES TIMEOUT 60) +add_layer2_test_with_ffmpeg(test_observation_diagnostics CI_UNIT CI_SANITIZER) +set_tests_properties(test_observation_diagnostics PROPERTIES TIMEOUT 60) add_layer2_test(test_logger CI_UNIT CI_SANITIZER) add_layer2_test(test_strings CI_UNIT CI_SANITIZER) add_layer2_test(test_interruptible_sleep) @@ -190,6 +209,8 @@ target_link_options(test_go2rtc_override_status PRIVATE add_layer3_test(test_onvif_transport CI_UNIT CI_SANITIZER) target_link_options(test_onvif_transport PRIVATE "-Wl,--wrap=_log_message_ctx" "-Wl,--wrap=send") set_tests_properties(test_onvif_transport PROPERTIES TIMEOUT 20) +add_layer3_test(test_onvif_device_test CI_UNIT CI_SANITIZER) +set_tests_properties(test_onvif_device_test PROPERTIES TIMEOUT 60) target_link_options(test_onvif_poll_recovery PRIVATE "-Wl,--wrap=detect_motion_onvif" "-Wl,--wrap=av_usleep" ) @@ -199,6 +220,8 @@ target_compile_definitions(test_onvif_lpr_event PRIVATE TEST_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}" ) add_layer2_test_with_ffmpeg(test_api_detection) +target_compile_definitions(test_api_detection PRIVATE + OBS_FIXTURE_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures/integrations/observations") add_layer2_test_with_ffmpeg(test_mp4_failed_open_memory CI_UNIT) add_layer2_test_with_ffmpeg(test_ffmpeg_rgb24_padding) if(TARGET lightnvr-ffmpeg-guard) @@ -239,6 +262,17 @@ add_layer2_test(test_db_live_saved_layouts) add_layer2_test(test_api_handlers_live_layouts) add_layer2_test(test_db_camera_favorites CI_UNIT) add_layer2_test(test_api_handlers_camera_favorites CI_UNIT) +add_layer2_test(test_api_handlers_observations CI_UNIT CI_SANITIZER) +target_link_options(test_api_handlers_observations PRIVATE + "-Wl,--wrap=httpd_check_action_access" + "-Wl,--wrap=httpd_evaluate_camera_identity_action" + "-Wl,--wrap=authorization_filter_cameras" + "-Wl,--wrap=http_serve_file" + "-Wl,--wrap=audit_log_append" + "-Wl,--wrap=audit_log_operation") +target_compile_definitions(test_api_handlers_observations PRIVATE + OBS_FIXTURE_DIR="${CMAKE_SOURCE_DIR}/tests/fixtures/integrations/observations") +set_tests_properties(test_api_handlers_observations PROPERTIES TIMEOUT 60) add_layer2_test(test_db_operator_floor_plans) add_layer2_test(test_api_handlers_operator_floor_plans) add_layer2_test(test_floor_plan_background_publication CI_UNIT CI_SANITIZER) @@ -343,6 +377,7 @@ add_layer2_test(test_api_handlers_users CI_UNIT) add_layer2_test(test_audit_log CI_SANITIZER) add_layer2_test(test_audit_summary CI_SANITIZER) add_layer2_test(test_event_envelope) +target_compile_definitions(test_event_envelope PRIVATE LIGHTNVR_SOURCE_DIR="${CMAKE_SOURCE_DIR}") add_layer2_test(test_event_bus) add_layer2_test(test_event_detection_adapter) add_layer2_test(test_event_outbox) @@ -580,7 +615,7 @@ target_compile_definitions(test_detection_current_frame PRIVATE ) target_link_options(test_detection_current_frame PRIVATE "-Wl,--wrap=detect_objects_api_snapshot_with_options" - "-Wl,--wrap=detect_objects_api_with_options" + "-Wl,--wrap=detect_objects_api_with_evidence" "-Wl,--wrap=filter_detections_by_zones" "-Wl,--wrap=filter_detections_by_stream_objects" ) @@ -605,6 +640,23 @@ target_compile_definitions(test_udt_recording PRIVATE TEST_RECORDING_PATH="${CMAKE_SOURCE_DIR}/tests/integration/fixtures/recording.mp4" ) set_tests_properties(test_udt_recording PROPERTIES TIMEOUT 60) +# INT_01 L6: local detector evidence (pure batch logic + UDT integration). +add_layer2_test_with_ffmpeg(test_detection_evidence CI_UNIT CI_SANITIZER) +add_layer3_test(test_detection_observation_evidence CI_UNIT CI_SANITIZER) +target_link_options(test_detection_observation_evidence PRIVATE + "-Wl,--wrap=detect_objects" + "-Wl,--wrap=detect_motion" + "-Wl,--wrap=detection_evidence_model_geometry" + "-Wl,--wrap=filter_detections_by_zones" + "-Wl,--wrap=filter_detections_by_stream_objects" +) +set_tests_properties(test_detection_observation_evidence PROPERTIES TIMEOUT 60) +add_layer3_test(test_api_detection_observation_evidence CI_UNIT CI_SANITIZER) +target_link_options(test_api_detection_observation_evidence PRIVATE + "-Wl,--wrap=filter_detections_by_zones" + "-Wl,--wrap=filter_detections_by_stream_objects" +) +set_tests_properties(test_api_detection_observation_evidence PROPERTIES TIMEOUT 60) # Health tasks register their tests in independent fragments so parallel work # never needs to modify this shared list. @@ -627,6 +679,9 @@ target_link_options(test_recording_file PRIVATE "-Wl,--wrap=write" "-Wl,--wrap=c add_layer1_test(test_uuid CI_UNIT CI_SANITIZER) target_sources(test_uuid PRIVATE ${CMAKE_SOURCE_DIR}/src/utils/uuid.c) +# UUIDv5 (observation message IDs) hashes with mbedTLS SHA-1. +target_include_directories(test_uuid PRIVATE ${SSL_INCLUDE_DIRS}) +target_link_libraries(test_uuid ${SSL_LIBRARIES}) target_link_options(test_uuid PRIVATE "-Wl,--wrap=getrandom" "-Wl,--wrap=fread") # Compile the MQTT producer even in the isolated no-MQTT API variant, when the diff --git a/tests/unit/test_api_detection.c b/tests/unit/test_api_detection.c index fc5617c1a..e5b396a93 100644 --- a/tests/unit/test_api_detection.c +++ b/tests/unit/test_api_detection.c @@ -15,6 +15,7 @@ #include "unity.h" #include "core/config.h" #include "core/logger.h" +#include "core/observation_evidence.h" #include "video/api_detection.h" void setUp(void) { @@ -357,6 +358,171 @@ void test_parse_response_caps_at_max_detections(void) { TEST_ASSERT_EQUAL_INT(MAX_DETECTIONS, result.count); } +/* ---- INT_01 L7: response geometry and the submitted image -------------- */ + +#ifndef OBS_FIXTURE_DIR +#define OBS_FIXTURE_DIR "tests/fixtures/integrations/observations" +#endif + +static char *read_text(const char *path, size_t *size_out) { + FILE *f = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, path); + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + char *buf = malloc((size_t)n + 1); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL_size_t((size_t)n, fread(buf, 1, (size_t)n, f)); + buf[n] = '\0'; + fclose(f); + if (size_out) *size_out = (size_t)n; + return buf; +} + +static cJSON *load_json(const char *relative) { + char path[512]; + snprintf(path, sizeof(path), "%s/%s", OBS_FIXTURE_DIR, relative); + char *text = read_text(path, NULL); + cJSON *root = cJSON_Parse(text); + free(text); + TEST_ASSERT_NOT_NULL_MESSAGE(root, relative); + return root; +} + +static double jnum(const cJSON *object, const char *key) { + const cJSON *item = cJSON_GetObjectItemCaseSensitive(object, key); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsNumber(item), key); + return item->valuedouble; +} + +/* C3 fixture D: the raw inference-space box from manifest.json, sent the way a + * letterboxing server reports it, lands on the box stored in + * observations/http-letterbox.json with the same transform. */ +static void assert_letterbox_fixture(bool pixel_units, bool declare_scale_and_pad) { + cJSON *manifest = load_json("manifest.json"); + const cJSON *ibox = NULL; + const cJSON *file; + cJSON_ArrayForEach(file, cJSON_GetObjectItemCaseSensitive(manifest, "fixtures")) { + const cJSON *name = cJSON_GetObjectItemCaseSensitive(file, "file"); + if (cJSON_IsString(name) && strcmp(name->valuestring, "observations/http-letterbox.json") == 0) + ibox = cJSON_GetObjectItemCaseSensitive(file, "inference_box"); + } + TEST_ASSERT_NOT_NULL_MESSAGE(ibox, "manifest inference_box for fixture D"); + cJSON *obs = load_json("observations/http-letterbox.json"); + const cJSON *evidence = cJSON_GetObjectItemCaseSensitive(obs, "evidence"); + const cJSON *tf = cJSON_GetObjectItemCaseSensitive(evidence, "transform"); + const cJSON *box = cJSON_GetObjectItemCaseSensitive(obs, "box"); + const int iw = (int)jnum(tf, "inference_width"), ih = (int)jnum(tf, "inference_height"); + const int sw = (int)jnum(evidence, "width"), sh = (int)jnum(evidence, "height"); + + double u = jnum(ibox, "u"), v = jnum(ibox, "v"); + double bw = jnum(ibox, "width"), bh = jnum(ibox, "height"); + if (!pixel_units) { u /= iw; bw /= iw; v /= ih; bh /= ih; } + char lb[256]; + if (declare_scale_and_pad) { + snprintf(lb, sizeof(lb), "{\"width\":%d,\"height\":%d,\"scale\":%.6f,\"pad_x\":%.1f," + "\"pad_y\":%.1f,\"box_units\":\"%s\"}", iw, ih, jnum(tf, "scale_x"), + jnum(tf, "pad_x"), jnum(tf, "pad_y"), pixel_units ? "pixels" : "normalized"); + } else { + snprintf(lb, sizeof(lb), "{\"width\":%d,\"height\":%d%s}", iw, ih, + pixel_units ? ",\"box_units\":\"pixels\"" : ""); + } + char json[1024]; + snprintf(json, sizeof(json), + "{\"letterbox\":%s,\"model\":\"yolov8n\",\"model_version\":\"8.3.0\"," + "\"detections\":[{\"label\":\"bird\",\"confidence\":0.84,\"track_id\":17," + "\"x_min\":%.9f,\"y_min\":%.9f,\"x_max\":%.9f,\"y_max\":%.9f}]}", + lb, u, v, u + bw, v + bh); + + api_detection_options_t options = make_options(API_DETECTION_FORMAT_LIGHT_OBJECT_DETECT, "onnx", NULL); + detection_result_t result; + api_detection_response_meta_t meta; + TEST_ASSERT_EQUAL_INT(0, api_detection_parse_response_meta(json, &options, &result, &meta)); + TEST_ASSERT_EQUAL_INT(1, result.count); + TEST_ASSERT_TRUE(meta.letterbox); + TEST_ASSERT_EQUAL_STRING("yolov8n", meta.model); + TEST_ASSERT_EQUAL_STRING("8.3.0", meta.model_version); + + observation_evidence_transform_t t; + TEST_ASSERT_EQUAL_INT(0, api_detection_apply_response_geometry( + &meta, sw, sh, (int)jnum(tf, "source_width"), (int)jnum(tf, "source_height"), &result, &t)); + TEST_ASSERT_FLOAT_WITHIN(1e-5f, (float)jnum(box, "x"), result.detections[0].x); + TEST_ASSERT_FLOAT_WITHIN(1e-5f, (float)jnum(box, "y"), result.detections[0].y); + TEST_ASSERT_FLOAT_WITHIN(1e-5f, (float)jnum(box, "width"), result.detections[0].width); + TEST_ASSERT_FLOAT_WITHIN(1e-5f, (float)jnum(box, "height"), result.detections[0].height); + TEST_ASSERT_EQUAL_INT(OBS_TRANSFORM_LETTERBOX, t.mode); + TEST_ASSERT_EQUAL_INT((int)jnum(tf, "source_width"), t.source_width); + TEST_ASSERT_EQUAL_INT((int)jnum(tf, "source_height"), t.source_height); + TEST_ASSERT_EQUAL_INT(iw, t.inference_width); + TEST_ASSERT_EQUAL_INT(ih, t.inference_height); + TEST_ASSERT_FLOAT_WITHIN(1e-6f, (float)jnum(tf, "scale_x"), (float)t.scale_x); + TEST_ASSERT_FLOAT_WITHIN(1e-6f, (float)jnum(tf, "scale_y"), (float)t.scale_y); + TEST_ASSERT_FLOAT_WITHIN(1e-6f, (float)jnum(tf, "pad_x"), (float)t.pad_x); + TEST_ASSERT_FLOAT_WITHIN(1e-6f, (float)jnum(tf, "pad_y"), (float)t.pad_y); + + /* The serialized transform is the fixture's (contract section 4.4). */ + char tjson[512]; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_transform_json(&t, sw, sh, tjson, sizeof(tjson))); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(tjson, "\"mode\":\"letterbox\""), tjson); + cJSON_Delete(obs); + cJSON_Delete(manifest); +} + +void test_letterbox_fixture_pixel_box_maps_to_stored_image(void) { + assert_letterbox_fixture(true, true); +} + +void test_letterbox_fixture_normalized_box_maps_to_stored_image(void) { + assert_letterbox_fixture(false, true); +} + +void test_letterbox_scale_and_padding_are_derived_when_omitted(void) { + assert_letterbox_fixture(true, false); + assert_letterbox_fixture(false, false); +} + +void test_no_letterbox_leaves_boxes_and_records_mode_none(void) { + const char *json = "{\"detections\":[{\"label\":\"bird\",\"confidence\":0.9," + "\"x_min\":0.1,\"y_min\":0.2,\"x_max\":0.4,\"y_max\":0.8}]," + "\"letterbox\":{\"width\":0}}"; + api_detection_options_t options = make_options(API_DETECTION_FORMAT_LIGHT_OBJECT_DETECT, "onnx", NULL); + detection_result_t result; + api_detection_response_meta_t meta; + TEST_ASSERT_EQUAL_INT(0, api_detection_parse_response_meta(json, &options, &result, &meta)); + TEST_ASSERT_FALSE(meta.letterbox); /* invalid declaration is ignored */ + TEST_ASSERT_EQUAL_STRING("", meta.model); + + observation_evidence_transform_t t; + TEST_ASSERT_EQUAL_INT(0, api_detection_apply_response_geometry(&meta, 640, 480, 0, 0, &result, &t)); + TEST_ASSERT_FLOAT_WITHIN(1e-6f, 0.1f, result.detections[0].x); + TEST_ASSERT_FLOAT_WITHIN(1e-6f, 0.6f, result.detections[0].height); + TEST_ASSERT_EQUAL_INT(OBS_TRANSFORM_NONE, t.mode); + TEST_ASSERT_EQUAL_INT(640, t.source_width); /* the JPEG is its own source */ + TEST_ASSERT_EQUAL_INT(640, t.inference_width); + TEST_ASSERT_EQUAL_INT(480, t.inference_height); + TEST_ASSERT_EQUAL_INT(-1, api_detection_apply_response_geometry(&meta, 0, 480, 0, 0, &result, &t)); + TEST_ASSERT_EQUAL_INT(0, api_detection_apply_response_geometry(NULL, 64, 48, 320, 240, &result, &t)); + TEST_ASSERT_EQUAL_INT(320, t.source_width); + TEST_ASSERT_EQUAL_INT(64, t.inference_width); +} + +void test_jpeg_dimensions_reads_sof_and_rejects_garbage(void) { + char path[512]; + snprintf(path, sizeof(path), "%s/media/two-birds-160x80.jpg", OBS_FIXTURE_DIR); + size_t size = 0; + char *jpeg = read_text(path, &size); + int w = 0, h = 0; + TEST_ASSERT_EQUAL_INT(0, api_detection_jpeg_dimensions((const uint8_t *)jpeg, size, &w, &h)); + TEST_ASSERT_EQUAL_INT(160, w); + TEST_ASSERT_EQUAL_INT(80, h); + TEST_ASSERT_EQUAL_INT(-1, api_detection_jpeg_dimensions((const uint8_t *)jpeg, 64, &w, &h)); + free(jpeg); + + const uint8_t not_jpeg[] = {0x89, 'P', 'N', 'G', 0, 0, 0, 0}; + TEST_ASSERT_EQUAL_INT(-1, api_detection_jpeg_dimensions(not_jpeg, sizeof(not_jpeg), &w, &h)); + TEST_ASSERT_EQUAL_INT(-1, api_detection_jpeg_dimensions(NULL, 0, &w, &h)); +} + int main(void) { init_logger(); UNITY_BEGIN(); @@ -382,5 +548,10 @@ int main(void) { RUN_TEST(test_parse_response_doods2_error_field_fails_request); RUN_TEST(test_parse_response_handles_null_missing_and_garbage); RUN_TEST(test_parse_response_caps_at_max_detections); + RUN_TEST(test_letterbox_fixture_pixel_box_maps_to_stored_image); + RUN_TEST(test_letterbox_fixture_normalized_box_maps_to_stored_image); + RUN_TEST(test_letterbox_scale_and_padding_are_derived_when_omitted); + RUN_TEST(test_no_letterbox_leaves_boxes_and_records_mode_none); + RUN_TEST(test_jpeg_dimensions_reads_sof_and_rejects_garbage); return UNITY_END(); } diff --git a/tests/unit/test_api_detection_observation_evidence.c b/tests/unit/test_api_detection_observation_evidence.c new file mode 100644 index 000000000..0741453a5 --- /dev/null +++ b/tests/unit/test_api_detection_observation_evidence.c @@ -0,0 +1,656 @@ +/* INT_01 L7: HTTP API detector and ONVIF observation evidence. + * + * A synthetic decoded frame goes through the production + * detect_on_decoded_frame() API dispatch to a real detect_objects_api_*() + * request against a local fake detection server. The stored evidence must be + * byte-identical to the JPEG in the request body (origin submitted_image, no + * re-encode, no second fetch), a server-declared letterbox (C3 fixture D) is + * mapped onto the stored image, and nothing is queued when evidence is off. + * ONVIF object-class events become unavailable(no_matching_media) + * observations; motion-only events never do. Real migrated DB + real L3 + * worker. */ +#include "../../src/video/unified_detection_thread.c" + +#include +#include +#include +#include + +#include "unity.h" +#include "core/event_identity.h" +#include "core/observation_evidence.h" +#include "database/db_core.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "video/api_detection.h" +#include "video/detection_evidence.h" + +static char db_path[256]; +static char storage_dir[256]; +static char cam_uuid[OBS_UUID_SIZE]; +static unified_detection_ctx_t *ctx; + +int __wrap_filter_detections_by_zones(const char *name, detection_result_t *result) { + (void)name; (void)result; + return 0; +} +int __wrap_filter_detections_by_stream_objects(const char *name, detection_result_t *result) { + (void)name; (void)result; + return 0; +} + +/* ---- fake detection server ---------------------------------------------- */ + +static struct { + int listen_fd; + int port; + pthread_t thread; + atomic_bool stop; + pthread_mutex_t lock; + char response[4096]; + uint8_t *last_jpeg; /* image extracted from the last request body */ + size_t last_jpeg_size; + int requests; +} srv; + +static uint8_t *memfind(uint8_t *hay, size_t hay_len, const char *needle, size_t needle_len) { + if (needle_len == 0 || hay_len < needle_len) return NULL; + for (size_t i = 0; i + needle_len <= hay_len; ++i) + if (memcmp(hay + i, needle, needle_len) == 0) return hay + i; + return NULL; +} + +static int b64_value(char c) { + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return -1; +} + +/* Standard padded base64 -> bytes; returns the length or -1. */ +static int b64_decode(const char *in, uint8_t *out) { + int n = 0, bits = 0; + unsigned acc = 0; + for (const char *p = in; *p && *p != '='; ++p) { + int v = b64_value(*p); + if (v < 0) return -1; + acc = (acc << 6) | (unsigned)v; + bits += 6; + if (bits >= 8) { + bits -= 8; + out[n++] = (uint8_t)((acc >> bits) & 0xFF); + } + } + return n; +} + +/* light-object-detect multipart "file" part, or DOODS2 JSON "data". */ +static void capture_image(uint8_t *body, size_t len, const char *headers) { + uint8_t *img = NULL; + size_t img_len = 0; + if (strcasestr(headers, "Content-Type: application/json")) { + body[len] = '\0'; + cJSON *root = cJSON_Parse((const char *)body); + const cJSON *data = root ? cJSON_GetObjectItem(root, "data") : NULL; + if (cJSON_IsString(data)) { + size_t cap = strlen(data->valuestring); + img = malloc(cap + 1); + int n = img ? b64_decode(data->valuestring, img) : -1; + if (n > 0) img_len = (size_t)n; else { free(img); img = NULL; } + } + cJSON_Delete(root); + } else { + uint8_t *part = memfind(body, len, "Content-Type: image/jpeg", 24); + uint8_t *start = part ? memfind(part, len - (size_t)(part - body), "\r\n\r\n", 4) : NULL; + if (start) { + start += 4; + /* the part ends at the last CRLF + boundary marker */ + uint8_t *end = NULL; + for (uint8_t *p = body + len - 4; p > start; --p) + if (p[0] == '\r' && p[1] == '\n' && p[2] == '-' && p[3] == '-') { end = p; break; } + if (end) { + img_len = (size_t)(end - start); + img = malloc(img_len); + memcpy(img, start, img_len); + } + } + } + pthread_mutex_lock(&srv.lock); + free(srv.last_jpeg); + srv.last_jpeg = img; + srv.last_jpeg_size = img_len; + srv.requests++; + pthread_mutex_unlock(&srv.lock); +} + +static void serve_client(int fd) { + size_t cap = 1 << 22, len = 0; + uint8_t *buf = malloc(cap + 1); + if (!buf) { close(fd); return; } + size_t header_end = 0; + long content_length = -1; + bool continued = false; + for (;;) { + ssize_t n = recv(fd, buf + len, cap - len, 0); + if (n <= 0) break; + len += (size_t)n; + buf[len] = '\0'; + if (!header_end) { + uint8_t *e = memfind(buf, len, "\r\n\r\n", 4); + if (!e) continue; + header_end = (size_t)(e - buf) + 4; + const char *cl = strcasestr((const char *)buf, "Content-Length:"); + if (cl) content_length = strtol(cl + 15, NULL, 10); + if (!continued && strcasestr((const char *)buf, "Expect: 100-continue")) { + const char *cont = "HTTP/1.1 100 Continue\r\n\r\n"; + (void)!send(fd, cont, strlen(cont), 0); + continued = true; + } + } + if (header_end && content_length >= 0 && len >= header_end + (size_t)content_length) break; + } + if (header_end && content_length >= 0) { + char headers[4096]; + size_t hl = header_end < sizeof(headers) - 1 ? header_end : sizeof(headers) - 1; + memcpy(headers, buf, hl); + headers[hl] = '\0'; + capture_image(buf + header_end, (size_t)content_length, headers); + } + char out[8192]; + pthread_mutex_lock(&srv.lock); + int body_len = (int)strlen(srv.response); + int n = snprintf(out, sizeof(out), + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n" + "Content-Length: %d\r\nConnection: close\r\n\r\n%s", + body_len, srv.response); + pthread_mutex_unlock(&srv.lock); + (void)!send(fd, out, (size_t)n, 0); + free(buf); + close(fd); +} + +static void *server_main(void *arg) { + (void)arg; + while (!atomic_load(&srv.stop)) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(srv.listen_fd, &fds); + struct timeval tv = {0, 100000}; + if (select(srv.listen_fd + 1, &fds, NULL, NULL, &tv) <= 0) continue; + int fd = accept(srv.listen_fd, NULL, NULL); + if (fd >= 0) serve_client(fd); + } + return NULL; +} + +static int server_start(void) { + pthread_mutex_init(&srv.lock, NULL); + srv.listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (srv.listen_fd < 0) return -1; + int one = 1; + setsockopt(srv.listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + if (bind(srv.listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) return -1; + if (listen(srv.listen_fd, 4) != 0) return -1; + socklen_t alen = sizeof(addr); + if (getsockname(srv.listen_fd, (struct sockaddr *)&addr, &alen) != 0) return -1; + srv.port = ntohs(addr.sin_port); + return pthread_create(&srv.thread, NULL, server_main, NULL); +} + +static void server_stop(void) { + atomic_store(&srv.stop, true); + pthread_join(srv.thread, NULL); + close(srv.listen_fd); + free(srv.last_jpeg); + srv.last_jpeg = NULL; +} + +static void set_response(const char *json) { + pthread_mutex_lock(&srv.lock); + snprintf(srv.response, sizeof(srv.response), "%s", json); + pthread_mutex_unlock(&srv.lock); +} + +/* ---- helpers -------------------------------------------------------------- */ + +static int scalar(const char *sql) { + sqlite3_stmt *stmt = NULL; + int value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, value, sql); + return value; +} + +static double scalar_double(const char *sql) { + sqlite3_stmt *stmt = NULL; + double value = -999.0; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_double(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_TRUE_MESSAGE(value != -999.0, sql); + return value; +} + +static void text_column(const char *sql, char *out, size_t size) { + sqlite3_stmt *stmt = NULL; + out[0] = '\0'; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW && sqlite3_column_text(stmt, 0)) + snprintf(out, size, "%s", (const char *)sqlite3_column_text(stmt, 0)); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); +} + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static void wait_idle(void) { + for (int i = 0; i < 1000; ++i) { + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + if (s.backlog_items == 0 && s.drop_ring_depth == 0) { + usleep(20000); + observation_evidence_get_stats(&s); + if (s.backlog_items == 0 && s.drop_ring_depth == 0) return; + } + usleep(10000); + } + TEST_FAIL_MESSAGE("evidence worker did not go idle"); +} + +static AVFrame *make_frame(int w, int h, int bird_x) { + AVFrame *f = av_frame_alloc(); + TEST_ASSERT_NOT_NULL(f); + f->format = AV_PIX_FMT_YUV420P; + f->width = w; + f->height = h; + TEST_ASSERT_EQUAL_INT(0, av_frame_get_buffer(f, 0)); + for (int y = 0; y < h; ++y) + for (int x = 0; x < w; ++x) { + bool bird = x >= bird_x && x < bird_x + 40 && y >= h / 2 && y < h / 2 + 40; + f->data[0][y * f->linesize[0] + x] = bird ? 235 : (uint8_t)(32 + (x + y) / 8); + } + for (int y = 0; y < h / 2; ++y) + for (int x = 0; x < w / 2; ++x) { + f->data[1][y * f->linesize[1] + x] = (uint8_t)(96 + x / 8); + f->data[2][y * f->linesize[2] + x] = (uint8_t)(160 - y / 8); + } + return f; +} + +static uint8_t *read_file(const char *path, size_t *size) { + FILE *f = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, path); + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + uint8_t *buf = malloc((size_t)n); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL_size_t((size_t)n, fread(buf, 1, (size_t)n, f)); + fclose(f); + *size = (size_t)n; + return buf; +} + +static void ready_asset_path(char *out, size_t size) { + char rel[512]; + text_column("SELECT path FROM observation_assets WHERE state = 'ready'", rel, sizeof(rel)); + TEST_ASSERT_TRUE_MESSAGE(rel[0], "no ready asset"); + snprintf(out, size, "%s/%s", storage_dir, rel); +} + +static void use_api_engine(const char *format) { + snprintf(ctx->model_path, sizeof(ctx->model_path), "http://127.0.0.1:%d/detect", srv.port); + snprintf(ctx->engines[0].engine_key, sizeof(ctx->engines[0].engine_key), "http-yolo"); + snprintf(ctx->engines[0].engine_type, sizeof(ctx->engines[0].engine_type), "api"); + snprintf(ctx->engines[0].model_path, sizeof(ctx->engines[0].model_path), "%s", ctx->model_path); + snprintf(g_config.api_detection_format, sizeof(g_config.api_detection_format), "%s", format); +} + +/* ---- fixtures ------------------------------------------------------------- */ + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets; " + "DELETE FROM detections;"); + observation_evidence_config_t cfg = { .storage_path = storage_dir }; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_init(&cfg)); + TEST_ASSERT_EQUAL_INT(0, init_api_detection_system()); + snprintf(g_config.api_detection_backend, sizeof(g_config.api_detection_backend), "onnx"); + snprintf(g_config.api_detection_detector_name, sizeof(g_config.api_detection_detector_name), + "default"); + + ctx = calloc(1, sizeof(*ctx)); + TEST_ASSERT_NOT_NULL(ctx); + pthread_mutex_init(&ctx->mutex, NULL); + snprintf(ctx->stream_name, sizeof(ctx->stream_name), "feeder-east"); + snprintf(ctx->camera_uuid, sizeof(ctx->camera_uuid), "%s", cam_uuid); + ctx->detection_threshold = 0.5f; + ctx->engine_count = 1; + ctx->engines[0].enabled = true; + ctx->engines[0].threshold = 0.5f; + use_api_engine("light-object-detect"); + atomic_store(&ctx->observation_evidence_enabled, true); + ctx->observation_labels[0] = '\0'; + pthread_mutex_lock(&srv.lock); + srv.requests = 0; + pthread_mutex_unlock(&srv.lock); +} + +void tearDown(void) { + observation_evidence_shutdown(2000); + shutdown_api_detection_system(); + pthread_mutex_destroy(&ctx->mutex); + free(ctx); + ctx = NULL; +} + +/* ---- HTTP API ------------------------------------------------------------------ */ + +static void assert_asset_is_request_image(const char *format) { + char path[1024]; + ready_asset_path(path, sizeof(path)); + size_t stored_size = 0; + uint8_t *stored = read_file(path, &stored_size); + /* Snapshot under the lock; assert outside it (a failed assertion + * longjmps and must not leave the server thread blocked). */ + pthread_mutex_lock(&srv.lock); + int requests = srv.requests; + size_t sent_size = srv.last_jpeg_size; + uint8_t *sent = sent_size ? malloc(sent_size) : NULL; + if (sent) memcpy(sent, srv.last_jpeg, sent_size); + pthread_mutex_unlock(&srv.lock); + char msg[128]; + snprintf(msg, sizeof(msg), "%s: sent %zu bytes, stored %zu", format, sent_size, stored_size); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, requests, "exactly one request, no second fetch"); + TEST_ASSERT_NOT_NULL_MESSAGE(sent, msg); + TEST_ASSERT_EQUAL_size_t_MESSAGE(sent_size, stored_size, msg); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(sent, stored, stored_size, msg); + free(sent); + free(stored); +} + +static void test_asset_bytes_identical_to_request_body_image(void) { + const char *formats[] = {"light-object-detect", "doods2"}; + for (int f = 0; f < 2; ++f) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets;"); + pthread_mutex_lock(&srv.lock); + srv.requests = 0; + pthread_mutex_unlock(&srv.lock); + use_api_engine(formats[f]); + if (f == 0) { + set_response("{\"detections\":[" + "{\"label\":\"bird\",\"confidence\":0.91,\"x_min\":0.1,\"y_min\":0.5," + "\"x_max\":0.25,\"y_max\":0.7,\"track_id\":17}," + "{\"label\":\"bird\",\"confidence\":0.42,\"x_min\":0.5,\"y_min\":0.5," + "\"x_max\":0.6,\"y_max\":0.6}," + "{\"label\":\"squirrel\",\"confidence\":0.8,\"x_min\":0.6,\"y_min\":0.1," + "\"x_max\":0.9,\"y_max\":0.4}]}"); + } else { + set_response("{\"id\":\"feeder-east\",\"detections\":[" + "{\"top\":0.5,\"left\":0.1,\"bottom\":0.7,\"right\":0.25," + "\"label\":\"bird\",\"confidence\":91.0}," + "{\"top\":0.5,\"left\":0.5,\"bottom\":0.6,\"right\":0.6," + "\"label\":\"bird\",\"confidence\":42.0}," + "{\"top\":0.1,\"left\":0.6,\"bottom\":0.4,\"right\":0.9," + "\"label\":\"squirrel\",\"confidence\":80.0}],\"error\":\"\"}"); + } + + AVFrame *frame = make_frame(320, 240, 40); + time_t now = time(NULL); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, now, &result)); + av_frame_free(&frame); + TEST_ASSERT_EQUAL_INT(2, result.count); /* 0.42 < threshold */ + wait_idle(); + + assert_asset_is_request_image(formats[f]); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(DISTINCT asset_uuid) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations WHERE " + "engine_type = 'api' AND engine_key = 'http-yolo' " + "AND confidence >= 0.5")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_assets WHERE " + "state = 'ready' AND origin = 'submitted_image' AND " + "media_type = 'image/jpeg' AND width = 320 AND height = 240")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_assets WHERE " + "transform_json LIKE '%\"mode\":\"none\"%' AND " + "transform_json LIKE '%\"source_width\":320%' AND " + "transform_json LIKE '%\"inference_width\":320%'")); + char sql[256]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM object_observations WHERE " + "observed_at_ms / 1000 BETWEEN %lld AND %lld", (long long)now, (long long)now + 1); + TEST_ASSERT_EQUAL_INT(2, scalar(sql)); + /* Detections are stored against the capture-time camera identity. */ + snprintf(sql, sizeof(sql), "SELECT count(*) FROM detections WHERE camera_uuid = '%s'", + cam_uuid); + TEST_ASSERT_TRUE(scalar(sql) >= 2); + } + /* Only the DOODS2 batch remains; DOODS2 reports no track IDs. */ + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations WHERE track_id IS NULL")); +} + +static void test_track_and_model_from_response(void) { + set_response("{\"model\":\"yolov8n\",\"model_version\":\"8.3.0\",\"detections\":[" + "{\"label\":\"bird\",\"confidence\":0.9,\"x_min\":0.1,\"y_min\":0.1," + "\"x_max\":0.2,\"y_max\":0.2,\"track_id\":17}]}"); + AVFrame *frame = make_frame(320, 240, 40); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + av_frame_free(&frame); + wait_idle(); + char sql[384]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM object_observations WHERE track_id = '17' " + "AND track_namespace = 'http-yolo' AND track_session = '%s' AND " + "model = 'yolov8n' AND model_version = '8.3.0'", detection_evidence_boot_session()); + TEST_ASSERT_EQUAL_INT(1, scalar(sql)); +} + +/* C3 fixture D: 1280x720 submitted, letterboxed server-side to 640x640 + * (scale 0.5, pad_y 140); the inference-space box u=256 v=185 128x72 is + * stored as x 0.4 y 0.125 0.2x0.2 on the submitted image. */ +static void test_letterboxed_response_maps_box_to_stored_image(void) { + set_response("{\"letterbox\":{\"width\":640,\"height\":640,\"scale\":0.5,\"pad_x\":0," + "\"pad_y\":140,\"box_units\":\"pixels\"},\"detections\":[" + "{\"label\":\"bird\",\"confidence\":0.84,\"x_min\":256,\"y_min\":185," + "\"x_max\":384,\"y_max\":257,\"track_id\":17}]}"); + AVFrame *frame = make_frame(1280, 720, 512); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + av_frame_free(&frame); + TEST_ASSERT_EQUAL_INT(1, result.count); + /* The mapped box is what every consumer (detections table, MQTT) sees. */ + TEST_ASSERT_FLOAT_WITHIN(1e-5f, 0.4f, result.detections[0].x); + TEST_ASSERT_FLOAT_WITHIN(1e-5f, 0.125f, result.detections[0].y); + wait_idle(); + + assert_asset_is_request_image("letterbox"); + TEST_ASSERT_FLOAT_WITHIN(1e-6, 0.4, scalar_double("SELECT box_x FROM object_observations")); + TEST_ASSERT_FLOAT_WITHIN(1e-6, 0.125, scalar_double("SELECT box_y FROM object_observations")); + TEST_ASSERT_FLOAT_WITHIN(1e-6, 0.2, scalar_double("SELECT box_width FROM object_observations")); + TEST_ASSERT_FLOAT_WITHIN(1e-6, 0.2, scalar_double("SELECT box_height FROM object_observations")); + char tjson[512]; + text_column("SELECT transform_json FROM observation_assets", tjson, sizeof(tjson)); + const char *expect[] = {"\"mode\":\"letterbox\"", "\"source_width\":1280", + "\"source_height\":720", "\"inference_width\":640", + "\"inference_height\":640", "\"pad_y\":140"}; + for (size_t i = 0; i < sizeof(expect) / sizeof(expect[0]); ++i) + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(tjson, expect[i]), tjson); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_assets WHERE " + "width = 1280 AND height = 720")); +} + +static void test_disabled_or_ineligible_submits_nothing(void) { + set_response("{\"detections\":[{\"label\":\"bird\",\"confidence\":0.9," + "\"x_min\":0.1,\"y_min\":0.1,\"x_max\":0.2,\"y_max\":0.2}]}"); + atomic_store(&ctx->observation_evidence_enabled, false); + AVFrame *frame = make_frame(320, 240, 40); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + + atomic_store(&ctx->observation_evidence_enabled, true); + snprintf(ctx->observation_labels, sizeof(ctx->observation_labels), "cat, dog"); + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + av_frame_free(&frame); + + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_FALSE(s.worker_running); + TEST_ASSERT_EQUAL_UINT64(0, s.accepted); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM observation_assets")); + /* Legacy detection storage is unaffected. */ + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM detections")); +} + +/* ---- ONVIF -------------------------------------------------------------------- */ + +static detection_result_t onvif_result(const char *label) { + detection_result_t r; + memset(&r, 0, sizeof(r)); + r.count = 1; + snprintf(r.detections[0].label, sizeof(r.detections[0].label), "%s", label); + r.detections[0].confidence = 1.0f; + r.detections[0].width = r.detections[0].height = 1.0f; + r.detections[0].track_id = -1; + return r; +} + +static void use_onvif_engine(void) { + snprintf(ctx->model_path, sizeof(ctx->model_path), "onvif"); + snprintf(ctx->engines[0].engine_key, sizeof(ctx->engines[0].engine_key), "onvif-events"); + snprintf(ctx->engines[0].engine_type, sizeof(ctx->engines[0].engine_type), "onvif"); +} + +static void test_onvif_object_event_records_no_matching_media(void) { + use_onvif_engine(); + detection_result_t r = onvif_result("animal"); + int64_t observed = detection_evidence_observed_at_ms(0); + udt_record_onvif_observations(ctx, &r, observed); + wait_idle(); + + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM object_observations WHERE " + "label = 'animal' AND engine_type = 'onvif' AND " + "engine_key = 'onvif-events' AND box_x IS NULL AND " + "track_id IS NULL AND model IS NULL AND confidence = 1.0")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_assets WHERE " + "state = 'unavailable' AND reason = 'no_matching_media' AND " + "origin IS NULL AND path IS NULL AND transform_json IS NULL")); + char sql[256]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM object_observations WHERE observed_at_ms = %lld", + (long long)observed); + TEST_ASSERT_EQUAL_INT(1, scalar(sql)); +} + +static void test_onvif_motion_only_and_disabled_record_nothing(void) { + use_onvif_engine(); + detection_result_t motion = onvif_result("motion"); + udt_record_onvif_observations(ctx, &motion, detection_evidence_observed_at_ms(0)); + detection_result_t upper = onvif_result("Motion"); + udt_record_onvif_observations(ctx, &upper, detection_evidence_observed_at_ms(0)); + + atomic_store(&ctx->observation_evidence_enabled, false); + detection_result_t person = onvif_result("person"); + udt_record_onvif_observations(ctx, &person, detection_evidence_observed_at_ms(0)); + + atomic_store(&ctx->observation_evidence_enabled, true); + snprintf(ctx->observation_labels, sizeof(ctx->observation_labels), "bird"); + udt_record_onvif_observations(ctx, &person, detection_evidence_observed_at_ms(0)); + + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_EQUAL_UINT64(0, s.unavailable_submitted); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM observation_assets")); +} + +static void test_no_media_helper_rejects_wrong_engine_types(void) { + detection_result_t r = onvif_result("person"); + detection_evidence_camera_t camera = { .camera_uuid = cam_uuid, .stream_name = "feeder-east", + .evidence_enabled = true }; + detection_evidence_engine_t object = { .engine_key = "k", .engine_type = "object" }; + detection_evidence_engine_t api = { .engine_key = "k", .engine_type = "api" }; + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_OBJECT_ENGINE, + detection_evidence_record_no_media(&camera, &object, r.detections, 1, + detection_evidence_observed_at_ms(0), false, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_OBJECT_ENGINE, + detection_evidence_record_no_media(&camera, &api, r.detections, 1, + detection_evidence_observed_at_ms(0), false, NULL, NULL)); + /* The JPEG path accepts only api engines and always consumes the bytes. */ + uint8_t *jpeg = malloc(16); + TEST_ASSERT_NOT_NULL(jpeg); + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_OBJECT_ENGINE, + detection_evidence_capture_jpeg(&camera, &object, r.detections, 1, + detection_evidence_observed_at_ms(0), jpeg, 16, 4, 4, + NULL, NULL, NULL)); + TEST_ASSERT_TRUE(detection_evidence_is_motion_label("MOTION")); + TEST_ASSERT_FALSE(detection_evidence_is_motion_label("person")); +} + +int main(void) { + init_logger(); + set_log_level(LOG_LEVEL_ERROR); + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_api_evidence_%ld.db", (long)getpid()); + snprintf(storage_dir, sizeof(storage_dir), "/tmp/lightnvr_api_evidence_%ld", (long)getpid()); + unlink(db_path); + mkdir(storage_dir, 0750); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0) return 1; + if (server_start() != 0) return 1; + + stream_config_t *s = calloc(1, sizeof(*s)); + if (!s) return 1; + safe_strcpy(s->name, "feeder-east", sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + s->observation_evidence_enabled = true; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + if (add_stream_config(s) <= 0) return 1; + memset(s, 0, sizeof(*s)); + if (get_stream_config_by_name("feeder-east", s) != 0) return 1; + safe_strcpy(cam_uuid, s->camera_uuid, sizeof(cam_uuid), 0); + free(s); + + UNITY_BEGIN(); + RUN_TEST(test_disabled_or_ineligible_submits_nothing); + RUN_TEST(test_asset_bytes_identical_to_request_body_image); + RUN_TEST(test_track_and_model_from_response); + RUN_TEST(test_letterboxed_response_maps_box_to_stored_image); + RUN_TEST(test_onvif_motion_only_and_disabled_record_nothing); + RUN_TEST(test_onvif_object_event_records_no_matching_media); + RUN_TEST(test_no_media_helper_rejects_wrong_engine_types); + int failures = UNITY_END(); + + server_stop(); + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char cmd[600]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", storage_dir); + if (system(cmd) != 0) fprintf(stderr, "cleanup failed: %s\n", storage_dir); + return failures; +} diff --git a/tests/unit/test_api_handlers_event_routes.c b/tests/unit/test_api_handlers_event_routes.c index 8fae3b6fd..ecb4b5d5d 100644 --- a/tests/unit/test_api_handlers_event_routes.c +++ b/tests/unit/test_api_handlers_event_routes.c @@ -171,8 +171,10 @@ void tearDown(void) { void test_catalog_and_route_crud_are_versioned_and_audited(void) { cJSON *json = call(handle_get_event_catalog, HTTP_METHOD_GET, "/api/events/catalog", NULL, NULL, NULL, 200); - TEST_ASSERT_EQUAL_INT(13, + TEST_ASSERT_EQUAL_INT(14, cJSON_GetObjectItemCaseSensitive(json, "count")->valueint); + TEST_ASSERT_NOT_NULL(catalog_type( + json, "io.lightnvr.detection.observation_changed.v1")); cJSON *health = catalog_type(json, HEALTH_ALERT_TYPE); TEST_ASSERT_NOT_NULL(health); TEST_ASSERT_TRUE(cJSON_IsTrue( diff --git a/tests/unit/test_api_handlers_observations.c b/tests/unit/test_api_handlers_observations.c new file mode 100644 index 000000000..b183de773 --- /dev/null +++ b/tests/unit/test_api_handlers_observations.c @@ -0,0 +1,1957 @@ +/** + * @file test_api_handlers_observations.c + * @brief INT_01 observation API handlers (contract + * docs/integrations/OBSERVATIONS_CONTRACT.md sections 7-9, 11, 12). + * + * Runs the GET handlers and the P2 classification PUT (L14) against a real + * migrated database populated through the L2/L13 DB layer. Authentication, the per-camera authorization decision, + * list filtering, file serving and audit appends are wrapped so each test + * controls the principal's camera grants and observes what was served or + * audited. Response bodies are compared with the shared C3 fixtures in + * tests/fixtures/integrations/observations. + * + * Set OBS_RESPONSE_DUMP_DIR to also write every JSON body the handlers + * produce (for an ad-hoc check against web/generated/api/validators.js). + */ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "unity.h" +#include "core/authorization.h" +#include "core/config.h" +#include "core/event_identity.h" +#include "database/db_auth.h" +#include "core/event_envelope.h" +#include "core/observation_relay.h" +#include "database/db_core.h" +#include "database/db_observation_classifications.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" +#include "web/api_handlers_observations.h" +#include "web/request_response.h" + +#ifndef OBS_FIXTURE_DIR +#error "OBS_FIXTURE_DIR must point at tests/fixtures/integrations/observations" +#endif + +/* ---- wrapped collaborators ------------------------------------------- */ + +typedef struct { + char camera[OBS_UUID_SIZE]; + bool live_view; + bool snapshot; + bool annotate; +} grant_t; + +static bool authenticated = true; +static char principal_name[64] = "YaWamf"; +static int principal_id = 42; +static grant_t grants[4]; +static int grant_count; +static bool evaluation_fails; + +static int serve_calls; +static char served_path[MAX_PATH_LENGTH]; +static char served_type[64]; +static char served_headers[512]; + +#define MAX_AUDITS 256 +typedef struct { + char action[64]; + char target_type[32]; + char target_uuid[OBS_UUID_SIZE]; + char outcome[16]; + char details[1024]; + char operation[48]; +} audit_record_t; +static audit_record_t audits[MAX_AUDITS]; +static int audit_count; + +int __wrap_httpd_check_action_access(const http_request_t *req, user_t *user) { + (void)req; + if (user) { + memset(user, 0, sizeof(*user)); + user->id = principal_id; + safe_strcpy(user->username, principal_name, sizeof(user->username), 0); + } + return authenticated ? 1 : 0; +} + +static const grant_t *find_grant(const char *camera) { + for (int i = 0; camera && i < grant_count; i++) + if (strcmp(grants[i].camera, camera) == 0) return &grants[i]; + return NULL; +} + +static bool granted(const char *camera, authorization_action_t action) { + const grant_t *grant = find_grant(camera); + if (!grant) return false; + if (action == AUTHZ_LIVE_VIEW) return grant->live_view; + if (action == AUTHZ_SNAPSHOT_CREATE) return grant->snapshot; + if (action == AUTHZ_OBSERVATIONS_ANNOTATE) return grant->annotate; + return false; +} + +int __wrap_httpd_evaluate_camera_identity_action(const user_t *user, + authorization_action_t action, const char *camera_uuid, + const char *legacy_stream_name, authorization_evaluation_t *evaluation) { + (void)user; (void)legacy_stream_name; + memset(evaluation, 0, sizeof(*evaluation)); + if (evaluation_fails) return -1; + evaluation->decision = granted(camera_uuid, action) ? AUTHZ_DECISION_ALLOW + : AUTHZ_DECISION_DENY; + return 0; +} + +int __wrap_authorization_filter_cameras(const user_t *user, authorization_action_t action, + fleet_camera_t *cameras, int *count) { + (void)user; + if (evaluation_fails) return -1; + int kept = 0; + for (int i = 0; i < *count; i++) + if (granted(cameras[i].camera_uuid, action)) cameras[kept++] = cameras[i]; + *count = kept; + return 0; +} + +int __wrap_http_serve_file(const http_request_t *req, const http_response_t *res, + const char *file_path, const char *content_type, + const char *extra_headers) { + (void)req; (void)res; + serve_calls++; + safe_strcpy(served_path, file_path, sizeof(served_path), 0); + safe_strcpy(served_type, content_type ? content_type : "", sizeof(served_type), 0); + safe_strcpy(served_headers, extra_headers ? extra_headers : "", sizeof(served_headers), 0); + return 0; +} + +void __wrap_audit_log_append(const http_request_t *req, const user_t *user, + const char *action, const char *target_type, + const char *target_uuid, const char *outcome, + const cJSON *details) { + (void)req; (void)user; + if (audit_count >= MAX_AUDITS) return; + audit_record_t *record = &audits[audit_count++]; + memset(record, 0, sizeof(*record)); + safe_strcpy(record->action, action ? action : "", sizeof(record->action), 0); + safe_strcpy(record->target_type, target_type ? target_type : "", sizeof(record->target_type), 0); + safe_strcpy(record->target_uuid, target_uuid ? target_uuid : "", sizeof(record->target_uuid), 0); + safe_strcpy(record->outcome, outcome ? outcome : "", sizeof(record->outcome), 0); + char *text = details ? cJSON_PrintUnformatted(details) : NULL; + if (text) safe_strcpy(record->details, text, sizeof(record->details), 0); + cJSON_free(text); +} + +/* Calls from the handler file are intercepted; the context is kept as the + * record's details so tests can read the section 12.2 fields. */ +void __wrap_audit_log_operation(const http_request_t *req, const user_t *user, + const char *action, const char *target_type, + const char *target_uuid, const char *operation, + const char *outcome, const cJSON *context) { + int before = audit_count; + __wrap_audit_log_append(req, user, action, target_type, target_uuid, outcome, context); + if (audit_count > before) + safe_strcpy(audits[before].operation, operation ? operation : "", + sizeof(audits[before].operation), 0); +} + +/* ---- fixture and request helpers -------------------------------------- */ + +static char db_path[256]; +static char storage_dir[256]; +static char source[128]; +static char cam_a[OBS_UUID_SIZE]; +static char cam_b[OBS_UUID_SIZE]; +static char cam_c[OBS_UUID_SIZE]; + +static cJSON *load_fixture(const char *relative) { + char path[512]; + snprintf(path, sizeof(path), "%s/%s", OBS_FIXTURE_DIR, relative); + FILE *file = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(file, path); + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + char *text = calloc(1, (size_t)size + 1); + TEST_ASSERT_NOT_NULL(text); + TEST_ASSERT_EQUAL_size_t((size_t)size, fread(text, 1, (size_t)size, file)); + fclose(file); + cJSON *json = cJSON_Parse(text); + free(text); + TEST_ASSERT_NOT_NULL_MESSAGE(json, path); + return json; +} + +static void assert_json_equal(const cJSON *expected, const cJSON *actual, const char *what) { + if (!cJSON_Compare(expected, actual, true)) { + char *a = cJSON_Print(expected); + char *b = cJSON_Print(actual); + fprintf(stderr, "%s mismatch\nexpected: %s\nactual: %s\n", what, a, b); + cJSON_free(a); + cJSON_free(b); + TEST_FAIL_MESSAGE(what); + } +} + +/* Same keys (recursively, for objects) and compatible JSON types; nullable + * fields may be null on either side. Detects fields the handler invents or + * drops relative to a fixture. */ +static void assert_same_shape(const cJSON *fixture, const cJSON *actual, const char *where) { + char message[256]; + if (cJSON_IsNull(fixture) || cJSON_IsNull(actual)) return; + snprintf(message, sizeof(message), "type at %s", where); + if (cJSON_IsBool(fixture)) TEST_ASSERT_TRUE_MESSAGE(cJSON_IsBool(actual), message); + else TEST_ASSERT_EQUAL_INT_MESSAGE(fixture->type & 0xFF, actual->type & 0xFF, message); + if (!cJSON_IsObject(fixture)) return; + const cJSON *item; + cJSON_ArrayForEach(item, fixture) { + snprintf(message, sizeof(message), "missing %s.%s", where, item->string); + const cJSON *other = cJSON_GetObjectItemCaseSensitive(actual, item->string); + TEST_ASSERT_NOT_NULL_MESSAGE(other, message); + char child[200]; + snprintf(child, sizeof(child), "%s.%s", where, item->string); + assert_same_shape(item, other, child); + } + cJSON_ArrayForEach(item, actual) { + snprintf(message, sizeof(message), "unexpected %s.%s", where, item->string); + TEST_ASSERT_NOT_NULL_MESSAGE(cJSON_GetObjectItemCaseSensitive(fixture, item->string), + message); + } +} + +typedef void (*handler_fn)(const http_request_t *, http_response_t *); + +typedef struct { + http_response_t response; + cJSON *json; +} result_t; + +static int dump_seq; + +static result_t call_with_body(handler_fn handler, http_method_t method, const char *path, + const char *query, const char *if_none_match, + const char *body) { + http_request_t request; + result_t result; + memset(&result, 0, sizeof(result)); + http_request_init(&request); + http_response_init(&result.response); + request.method = method; + safe_strcpy(request.method_str, method == HTTP_METHOD_PUT ? "PUT" : "GET", + sizeof(request.method_str), 0); + safe_strcpy(request.path, path, sizeof(request.path), 0); + if (query) safe_strcpy(request.query_string, query, sizeof(request.query_string), 0); + if (if_none_match) { + safe_strcpy(request.headers[0].name, "If-None-Match", sizeof(request.headers[0].name), 0); + safe_strcpy(request.headers[0].value, if_none_match, sizeof(request.headers[0].value), 0); + request.num_headers = 1; + } + if (body) { + request.body = (void *)body; + request.body_len = strlen(body); + } + handler(&request, &result.response); + if (result.response.body && result.response.body_length > 0) { + result.json = cJSON_ParseWithLength((const char *)result.response.body, + result.response.body_length); + const char *dump = getenv("OBS_RESPONSE_DUMP_DIR"); + if (dump && result.json) { + char file[512]; + snprintf(file, sizeof(file), "%s/%03d-%d.json", dump, ++dump_seq, + result.response.status_code); + FILE *out = fopen(file, "w"); + if (out) { + fwrite(result.response.body, 1, result.response.body_length, out); + fclose(out); + } + } + } + return result; +} + +static result_t call(handler_fn handler, http_method_t method, const char *path, + const char *query, const char *if_none_match) { + return call_with_body(handler, method, path, query, if_none_match, NULL); +} + +static void release(result_t *result) { + cJSON_Delete(result->json); + http_response_free(&result->response); +} + +static const char *header(const http_response_t *response, const char *name) { + for (int i = 0; i < response->num_headers; i++) + if (strcasecmp(response->headers[i].name, name) == 0) return response->headers[i].value; + return NULL; +} + +static const char *str(const cJSON *json, const char *key) { + const cJSON *item = cJSON_GetObjectItemCaseSensitive(json, key); + return cJSON_IsString(item) ? item->valuestring : NULL; +} + +static void expect_error(result_t *result, int status, const char *code) { + char message[128]; + snprintf(message, sizeof(message), "status for %s", code ? code : "(none)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(status, result->response.status_code, message); + TEST_ASSERT_NOT_NULL(result->json); + TEST_ASSERT_NOT_NULL(str(result->json, "error")); + if (code) TEST_ASSERT_EQUAL_STRING(code, str(result->json, "code")); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + s->observation_evidence_enabled = strcmp(name, "feeder-east") == 0; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static int64_t base_time_ms = 1793000000000LL; + +typedef struct { + char observation[OBS_UUID_SIZE]; + char asset[OBS_UUID_SIZE]; + char relative_path[MAX_PATH_LENGTH]; +} seeded_t; + +static seeded_t seed(const char *camera, const char *label, observation_asset_state_t state) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, camera, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = base_time_ms++; + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + safe_strcpy(batch.model, "ssd_mobilenet_v2.sod", sizeof(batch.model), 0); + batch.recording_id = 0; + batch.object_count = 1; + observation_object_input_t *object = &batch.objects[0]; + safe_strcpy(object->label, label, sizeof(object->label), 0); + object->confidence = 0.87; + object->has_box = true; + object->box_x = 0.412; object->box_y = 0.288; + object->box_width = 0.12; object->box_height = 0.165; + + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + if (state == OBS_ASSET_UNAVAILABLE) { + asset.state = OBS_ASSET_UNAVAILABLE; + safe_strcpy(asset.reason, "queue_full", sizeof(asset.reason), 0); + } else { + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.origin, "inference_input", sizeof(asset.origin), 0); + safe_strcpy(asset.media_type, "image/jpeg", sizeof(asset.media_type), 0); + safe_strcpy(asset.transform_json, + "{\"mode\":\"none\",\"source_width\":640,\"source_height\":360," + "\"inference_width\":640,\"inference_height\":360,\"scale_x\":1," + "\"scale_y\":1,\"pad_x\":0,\"pad_y\":0}", + sizeof(asset.transform_json), 0); + } + observation_insert_result_t inserted; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &inserted)); + seeded_t out; + memset(&out, 0, sizeof(out)); + safe_strcpy(out.observation, inserted.observation_uuids[0], sizeof(out.observation), 0); + safe_strcpy(out.asset, inserted.asset_uuid, sizeof(out.asset), 0); + if (state == OBS_ASSET_READY || state == OBS_ASSET_EXPIRED) { + char dir[512], file[1024]; + snprintf(out.relative_path, sizeof(out.relative_path), "observations/%s/2026-11-08/%s.jpg", + camera, out.asset); + snprintf(dir, sizeof(dir), "%s/observations", storage_dir); + mkdir(dir, 0700); + snprintf(dir, sizeof(dir), "%s/observations/%s", storage_dir, camera); + mkdir(dir, 0700); + snprintf(dir, sizeof(dir), "%s/observations/%s/2026-11-08", storage_dir, camera); + mkdir(dir, 0700); + snprintf(file, sizeof(file), "%s/%s", storage_dir, out.relative_path); + FILE *jpeg = fopen(file, "wb"); + TEST_ASSERT_NOT_NULL(jpeg); + fputs("\xff\xd8 not really a jpeg \xff\xd9", jpeg); + fclose(jpeg); + observation_asset_media_t media = { + .path = out.relative_path, .bytes = 23, .width = 640, .height = 360, + .expires_at_ms = base_time_ms + 7LL * 86400000LL, + }; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + out.asset, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &media, NULL)); + } + if (state == OBS_ASSET_EXPIRED) { + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + out.asset, OBS_ASSET_READY, OBS_ASSET_EXPIRED, "retention_expired", NULL, NULL)); + } + return out; +} + +static void grant_all(const char *camera, bool live_view, bool snapshot, bool annotate) { + TEST_ASSERT_LESS_THAN(4, grant_count); + safe_strcpy(grants[grant_count].camera, camera, OBS_UUID_SIZE, 0); + grants[grant_count].live_view = live_view; + grants[grant_count].snapshot = snapshot; + grants[grant_count].annotate = annotate; + grant_count++; +} + +static void grant(const char *camera, bool live_view, bool snapshot) { + grant_all(camera, live_view, snapshot, false); +} + +static void obs_path(char *out, size_t size, const char *id, const char *suffix) { + snprintf(out, size, "/api/observations/%s%s", id, suffix ? suffix : ""); +} + +/* ---- setup ------------------------------------------------------------ */ + +void setUp(void) { + authenticated = true; + safe_strcpy(principal_name, "YaWamf", sizeof(principal_name), 0); + principal_id = 42; + grant_count = 0; + memset(grants, 0, sizeof(grants)); + evaluation_fails = false; + serve_calls = 0; + served_path[0] = served_type[0] = served_headers[0] = '\0'; + audit_count = 0; + sqlite3_exec(get_db_handle(), + "DELETE FROM object_observations; DELETE FROM observation_assets;", + NULL, NULL, NULL); +} + +void tearDown(void) {} + +/* ---- authentication ---------------------------------------------------- */ + +static void test_every_route_requires_authentication(void) { + authenticated = false; + handler_fn handlers[] = {handle_get_integration_capabilities, handle_get_observations, + handle_get_observation, handle_get_observation_snapshot, + handle_put_observation_classification}; + const char *paths[] = {"/api/integrations/capabilities", "/api/observations", + "/api/observations/55555555-5555-4555-8555-555555555555", + "/api/observations/55555555-5555-4555-8555-555555555555/snapshot", + "/api/observations/55555555-5555-4555-8555-555555555555/" + "classifications/yawamf/species"}; + for (size_t i = 0; i < sizeof(handlers) / sizeof(handlers[0]); i++) { + result_t result = call(handlers[i], i == 4 ? HTTP_METHOD_PUT : HTTP_METHOD_GET, + paths[i], NULL, NULL); + expect_error(&result, 401, NULL); + release(&result); + } +} + +/* ---- serialization conformance with the C3 fixtures -------------------- */ + +static void time_ms(const char *text, int64_t *ms) { + TEST_ASSERT_EQUAL_INT_MESSAGE(0, observation_parse_time(text, ms), text); +} + +static void copy_str(char *out, size_t size, const cJSON *json, const char *key) { + const char *value = str(json, key); + safe_strcpy(out, value ? value : "", size, 0); +} + +/* Build the DB record the fixture represents, as db_observation_get would + * return it (including a path, which must never be serialized). */ +static void record_from_fixture(const cJSON *fixture, observation_record_t *record) { + memset(record, 0, sizeof(*record)); + copy_str(record->uuid, sizeof(record->uuid), fixture, "id"); + copy_str(record->camera_uuid, sizeof(record->camera_uuid), fixture, "camera_uuid"); + copy_str(record->stream_name_at_capture, sizeof(record->stream_name_at_capture), fixture, + "stream_name_at_capture"); + copy_str(record->batch_id, sizeof(record->batch_id), fixture, "batch_id"); + record->object_index = cJSON_GetObjectItem(fixture, "object_index")->valueint; + time_ms(str(fixture, "observed_at"), &record->observed_at_ms); + time_ms(str(fixture, "created_at"), &record->created_at_ms); + const cJSON *detector = cJSON_GetObjectItem(fixture, "detector"); + copy_str(record->engine_key, sizeof(record->engine_key), detector, "engine_key"); + copy_str(record->engine_type, sizeof(record->engine_type), detector, "engine_type"); + copy_str(record->model, sizeof(record->model), detector, "model"); + copy_str(record->model_version, sizeof(record->model_version), detector, "model_version"); + copy_str(record->label, sizeof(record->label), fixture, "label"); + record->confidence = cJSON_GetObjectItem(fixture, "confidence")->valuedouble; + const cJSON *box = cJSON_GetObjectItem(fixture, "box"); + record->has_box = cJSON_IsObject(box); + if (record->has_box) { + record->box_x = cJSON_GetObjectItem(box, "x")->valuedouble; + record->box_y = cJSON_GetObjectItem(box, "y")->valuedouble; + record->box_width = cJSON_GetObjectItem(box, "width")->valuedouble; + record->box_height = cJSON_GetObjectItem(box, "height")->valuedouble; + } + const cJSON *track = cJSON_GetObjectItem(fixture, "track"); + record->has_track = cJSON_IsObject(track); + if (record->has_track) { + copy_str(record->track_id, sizeof(record->track_id), track, "id"); + copy_str(record->track_namespace, sizeof(record->track_namespace), track, "namespace"); + copy_str(record->track_session, sizeof(record->track_session), track, "session"); + } + const cJSON *recording = cJSON_GetObjectItem(fixture, "recording_reference"); + if (cJSON_IsObject(recording)) + record->recording_id = (int64_t)cJSON_GetObjectItem(recording, "recording_id")->valuedouble; + record->revision = cJSON_GetObjectItem(fixture, "revision")->valueint; + + const cJSON *evidence = cJSON_GetObjectItem(fixture, "evidence"); + observation_asset_record_t *asset = &record->asset; + copy_str(asset->uuid, sizeof(asset->uuid), evidence, "asset_id"); + safe_strcpy(asset->camera_uuid, record->camera_uuid, sizeof(asset->camera_uuid), 0); + TEST_ASSERT_EQUAL_INT(0, observation_asset_state_parse(str(evidence, "state"), &asset->state)); + copy_str(asset->reason, sizeof(asset->reason), evidence, "reason"); + copy_str(asset->origin, sizeof(asset->origin), evidence, "origin"); + copy_str(asset->media_type, sizeof(asset->media_type), evidence, "media_type"); + safe_strcpy(asset->path, "observations/secret/never-serialized.jpg", sizeof(asset->path), 0); + const cJSON *bytes = cJSON_GetObjectItem(evidence, "bytes"); + asset->has_bytes = cJSON_IsNumber(bytes); + if (asset->has_bytes) asset->bytes = (int64_t)bytes->valuedouble; + const cJSON *width = cJSON_GetObjectItem(evidence, "width"); + const cJSON *height = cJSON_GetObjectItem(evidence, "height"); + if (cJSON_IsNumber(width)) asset->width = width->valueint; + if (cJSON_IsNumber(height)) asset->height = height->valueint; + if (str(evidence, "captured_at")) time_ms(str(evidence, "captured_at"), &asset->captured_at_ms); + else asset->captured_at_ms = record->observed_at_ms; /* L2 defaults it; must stay hidden */ + if (str(evidence, "expires_at")) time_ms(str(evidence, "expires_at"), &asset->expires_at_ms); + const cJSON *transform = cJSON_GetObjectItem(evidence, "transform"); + if (cJSON_IsObject(transform)) { + char *text = cJSON_PrintUnformatted(transform); + safe_strcpy(asset->transform_json, text, sizeof(asset->transform_json), 0); + cJSON_free(text); + } + asset->created_at_ms = record->created_at_ms; + asset->updated_at_ms = record->created_at_ms; +} + +/* The DB row an annotation object (contract section 10.3) represents. */ +static void classification_from_json(const cJSON *json, observation_classification_t *out) { + memset(out, 0, sizeof(*out)); + copy_str(out->key, sizeof(out->key), json, "key"); + copy_str(out->writer_namespace, sizeof(out->writer_namespace), json, "writer_namespace"); + out->writer_user_id = 42; + copy_str(out->classifier, sizeof(out->classifier), json, "classifier"); + copy_str(out->model_version, sizeof(out->model_version), json, "model_version"); + copy_str(out->label, sizeof(out->label), json, "label"); + copy_str(out->taxonomy_id, sizeof(out->taxonomy_id), json, "taxonomy_id"); + out->confidence = cJSON_GetObjectItem(json, "confidence")->valuedouble; + time_ms(str(json, "classified_at"), &out->classified_at_ms); + copy_str(out->input_asset_uuid, sizeof(out->input_asset_uuid), json, "input_asset_id"); + out->input_revision = cJSON_GetObjectItem(json, "input_revision")->valueint; + TEST_ASSERT_EQUAL_INT(0, observation_classification_kind_parse(str(json, "kind"), &out->kind)); + out->annotation_revision = cJSON_GetObjectItem(json, "annotation_revision")->valueint; + time_ms(str(json, "created_at"), &out->created_at_ms); + time_ms(str(json, "updated_at"), &out->updated_at_ms); +} + +static void set_from_fixture(const cJSON *fixture, observation_classification_set_t *set) { + memset(set, 0, sizeof(*set)); + const cJSON *lists = cJSON_GetObjectItem(fixture, "classifications"); + const cJSON *item; + cJSON_ArrayForEach(item, cJSON_GetObjectItem(lists, "automated")) + classification_from_json(item, &set->automated[set->automated_count++]); + cJSON_ArrayForEach(item, cJSON_GetObjectItem(lists, "owner_confirmed")) + classification_from_json(item, &set->owner_confirmed[set->owner_confirmed_count++]); +} + +static void test_serializer_reproduces_every_observation_fixture(void) { + const char *files[] = { + "observations/single-pending.json", "observations/single-ready.json", + "observations/single-unavailable.json", "observations/single-expired.json", + "observations/two-birds-object-0.json", "observations/two-birds-object-1.json", + "observations/http-letterbox.json", "observations/onvif-no-matching-media.json", + "observations/single-ready-classified.json", "observations/single-owner-confirmed.json", + }; + for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); i++) { + cJSON *fixture = load_fixture(files[i]); + observation_record_t record; + record_from_fixture(fixture, &record); + observation_classification_set_t *set = calloc(1, sizeof(*set)); + TEST_ASSERT_NOT_NULL(set); + set_from_fixture(fixture, set); + cJSON *actual = observation_record_to_json(&record, str(fixture, "source"), set); + free(set); + TEST_ASSERT_NOT_NULL(actual); + char *text = cJSON_PrintUnformatted(actual); + TEST_ASSERT_NULL_MESSAGE(strstr(text, "never-serialized"), "asset path leaked"); + TEST_ASSERT_NULL_MESSAGE(strstr(text, "\"path\""), "path key leaked"); + TEST_ASSERT_NULL_MESSAGE(strstr(text, "writer_user_id"), "writer user id leaked"); + cJSON_free(text); + assert_json_equal(fixture, actual, files[i]); + cJSON_Delete(actual); + cJSON_Delete(fixture); + } + /* NULL classifications: both lists present and empty. */ + cJSON *fixture = load_fixture("observations/single-ready.json"); + observation_record_t record; + record_from_fixture(fixture, &record); + cJSON *actual = observation_record_to_json(&record, str(fixture, "source"), NULL); + assert_json_equal(fixture, actual, "single-ready without annotations"); + cJSON_Delete(actual); + cJSON_Delete(fixture); +} + +static void test_time_and_namespace_helpers(void) { + char text[OBS_TIME_TEXT_SIZE]; + int64_t ms = 0; + TEST_ASSERT_EQUAL_INT(0, observation_parse_time("2026-11-08T14:03:12.481Z", &ms)); + TEST_ASSERT_EQUAL_INT(0, observation_format_time(ms, text, sizeof(text))); + TEST_ASSERT_EQUAL_STRING("2026-11-08T14:03:12.481Z", text); + int64_t offset = 0; + TEST_ASSERT_EQUAL_INT(0, observation_parse_time("2026-11-08T16:03:12.481+02:00", &offset)); + TEST_ASSERT_EQUAL_INT64(ms, offset); + TEST_ASSERT_EQUAL_INT(0, observation_parse_time("2026-11-08T14:03:12Z", &offset)); + TEST_ASSERT_EQUAL_INT64(ms - 481, offset); + TEST_ASSERT_NOT_EQUAL(0, observation_parse_time("2026-11-08 14:03:12Z", &offset)); + TEST_ASSERT_NOT_EQUAL(0, observation_parse_time("2026-13-08T14:03:12Z", &offset)); + TEST_ASSERT_NOT_EQUAL(0, observation_parse_time("2026-11-08T14:03:12", &offset)); + TEST_ASSERT_NOT_EQUAL(0, observation_parse_time("yesterday", &offset)); + + char ns[OBS_WRITER_NAMESPACE_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_writer_namespace("YaWamf", ns)); + TEST_ASSERT_EQUAL_STRING("yawamf", ns); + TEST_ASSERT_EQUAL_INT(0, observation_writer_namespace("Bird Watcher@home", ns)); + TEST_ASSERT_EQUAL_STRING("bird-watcher-home", ns); + TEST_ASSERT_EQUAL_INT(-2, observation_writer_namespace("System", ns)); + TEST_ASSERT_EQUAL_STRING("", ns); + TEST_ASSERT_EQUAL_INT(-1, observation_writer_namespace("-leading", ns)); + TEST_ASSERT_EQUAL_INT(-1, observation_writer_namespace( + "a-username-that-is-much-longer-than-32", ns)); + TEST_ASSERT_EQUAL_INT(-1, observation_writer_namespace("", ns)); +} + +/* ---- GET /api/observations/{id} --------------------------------------- */ + +static void test_get_returns_db_rows_in_fixture_shape(void) { + grant(cam_a, true, false); + struct { observation_asset_state_t state; const char *fixture; } cases[] = { + {OBS_ASSET_PENDING, "observations/single-pending.json"}, + {OBS_ASSET_READY, "observations/single-ready.json"}, + {OBS_ASSET_UNAVAILABLE, "observations/single-unavailable.json"}, + {OBS_ASSET_EXPIRED, "observations/single-expired.json"}, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + seeded_t seeded = seed(cam_a, "bird", cases[i].state); + char path[128]; + obs_path(path, sizeof(path), seeded.observation, NULL); + result_t result = call(handle_get_observation, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, result.response.status_code); + TEST_ASSERT_NOT_NULL(result.json); + TEST_ASSERT_NULL(strstr((const char *)result.response.body, seeded.relative_path[0] + ? seeded.relative_path : "\"path\"")); + cJSON *fixture = load_fixture(cases[i].fixture); + cJSON_ReplaceItemInObject(cJSON_GetObjectItem(fixture, "classifications"), + "owner_confirmed", cJSON_CreateArray()); + /* recording_reference is null in the DB rows (no recording). */ + cJSON_ReplaceItemInObject(fixture, "recording_reference", cJSON_CreateNull()); + assert_same_shape(fixture, result.json, cases[i].fixture); + TEST_ASSERT_EQUAL_STRING(seeded.observation, str(result.json, "id")); + TEST_ASSERT_EQUAL_STRING(source, str(result.json, "source")); + TEST_ASSERT_EQUAL_STRING(cam_a, str(result.json, "camera_uuid")); + const cJSON *evidence = cJSON_GetObjectItem(result.json, "evidence"); + TEST_ASSERT_EQUAL_STRING(seeded.asset, str(evidence, "asset_id")); + if (cases[i].state == OBS_ASSET_UNAVAILABLE) { + /* Never had media: every media field null, even captured_at. */ + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItem(evidence, "captured_at"))); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItem(evidence, "origin"))); + } + cJSON_Delete(fixture); + release(&result); + } +} + +static void test_get_unknown_unauthorized_and_malformed(void) { + seeded_t other = seed(cam_b, "bird", OBS_ASSET_READY); + grant(cam_a, true, true); + cJSON *not_found = load_fixture("errors/observation-not-found.json"); + + char path[128]; + obs_path(path, sizeof(path), other.observation, NULL); + result_t unauthorized = call(handle_get_observation, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT(404, unauthorized.response.status_code); + assert_json_equal(not_found, unauthorized.json, "unauthorized get body"); + + result_t unknown = call(handle_get_observation, HTTP_METHOD_GET, + "/api/observations/00000000-0000-4000-8000-000000000000", NULL, NULL); + TEST_ASSERT_EQUAL_INT(404, unknown.response.status_code); + /* Unknown and unauthorized are byte-identical. */ + TEST_ASSERT_EQUAL_size_t(unknown.response.body_length, unauthorized.response.body_length); + TEST_ASSERT_EQUAL_MEMORY(unknown.response.body, unauthorized.response.body, + unknown.response.body_length); + + result_t malformed = call(handle_get_observation, HTTP_METHOD_GET, + "/api/observations/not-a-uuid", NULL, NULL); + expect_error(&malformed, 400, "invalid_request"); + + evaluation_fails = true; + result_t failure = call(handle_get_observation, HTTP_METHOD_GET, path, NULL, NULL); + expect_error(&failure, 500, "internal_error"); + + cJSON_Delete(not_found); + release(&unauthorized); + release(&unknown); + release(&malformed); + release(&failure); +} + +/* ---- GET /api/observations ------------------------------------------- */ + +static int item_count(const result_t *result) { + return cJSON_GetArraySize(cJSON_GetObjectItem(result->json, "items")); +} + +static bool page_contains(const result_t *result, const char *id) { + const cJSON *item; + cJSON_ArrayForEach(item, cJSON_GetObjectItem(result->json, "items")) + if (strcmp(str(item, "id"), id) == 0) return true; + return false; +} + +static void test_list_intersects_with_authorized_cameras(void) { + seeded_t a = seed(cam_a, "bird", OBS_ASSET_PENDING); + seeded_t b = seed(cam_b, "bird", OBS_ASSET_PENDING); + seeded_t c = seed(cam_c, "squirrel", OBS_ASSET_UNAVAILABLE); + grant(cam_a, true, false); + grant(cam_c, true, false); + grant(cam_b, false, true); /* snapshot without live.view: no metadata */ + + result_t all = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, all.response.status_code); + TEST_ASSERT_EQUAL_INT(2, item_count(&all)); + TEST_ASSERT_TRUE(page_contains(&all, a.observation)); + TEST_ASSERT_TRUE(page_contains(&all, c.observation)); + TEST_ASSERT_FALSE(page_contains(&all, b.observation)); + TEST_ASSERT_NOT_NULL(str(all.json, "next_cursor")); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(all.json, "has_more"))); + cJSON *page_fixture = load_fixture("pages/page-1.json"); + cJSON *fixture_items = cJSON_DetachItemFromObject(page_fixture, "items"); + cJSON *all_items = cJSON_DetachItemFromObject(all.json, "items"); + assert_same_shape(page_fixture, all.json, "page"); + cJSON_AddItemToObject(all.json, "items", all_items); + cJSON_Delete(fixture_items); + cJSON_Delete(page_fixture); + + char query[256]; + snprintf(query, sizeof(query), "camera_uuid=%s", cam_c); + result_t filtered = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + TEST_ASSERT_EQUAL_INT(200, filtered.response.status_code); + TEST_ASSERT_EQUAL_INT(1, item_count(&filtered)); + TEST_ASSERT_TRUE(page_contains(&filtered, c.observation)); + + result_t labelled = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + "label=BIRD,owl", NULL); + TEST_ASSERT_EQUAL_INT(200, labelled.response.status_code); + TEST_ASSERT_EQUAL_INT(1, item_count(&labelled)); + TEST_ASSERT_TRUE(page_contains(&labelled, a.observation)); + + /* An explicit camera without live.view is 403, even alongside allowed ones. */ + snprintf(query, sizeof(query), "camera_uuid=%s,%s", cam_a, cam_b); + result_t forbidden = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + expect_error(&forbidden, 403, "forbidden"); + /* An unknown camera UUID is treated as unauthorized. */ + result_t unknown = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + "camera_uuid=00000000-0000-4000-8000-000000000000", NULL); + expect_error(&unknown, 403, "forbidden"); + /* Uppercase input is normalized. */ + char upper[OBS_UUID_SIZE]; + safe_strcpy(upper, cam_a, sizeof(upper), 0); + for (char *p = upper; *p; p++) if (*p >= 'a' && *p <= 'f') *p = (char)(*p - 32); + snprintf(query, sizeof(query), "camera_uuid=%s", upper); + result_t uppercase = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + TEST_ASSERT_EQUAL_INT(200, uppercase.response.status_code); + TEST_ASSERT_EQUAL_INT(1, item_count(&uppercase)); + + release(&all); + release(&filtered); + release(&labelled); + release(&forbidden); + release(&unknown); + release(&uppercase); +} + +static void test_list_without_any_grant_is_empty(void) { + seed(cam_a, "bird", OBS_ASSET_PENDING); + result_t result = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, result.response.status_code); + TEST_ASSERT_EQUAL_INT(0, item_count(&result)); + release(&result); +} + +static void test_list_rejects_invalid_parameters(void) { + grant(cam_a, true, false); + const char *bad[] = { + "limit=0", "limit=501", "limit=abc", "limit=", "limit=10x", + "camera_uuid=not-a-uuid", "camera_uuid=", "camera_uuid=,", + "label=", "label=a,,b", "label=a,b,c,d,e,f,g,h,i", + "label=abcdefghijabcdefghijabcdefghijab", + "since=yesterday", "until=2026-11-08", "cursor=", + "since=2026-11-08T14:00:00Z&until=2026-11-08T14:00:00Z", + "since=2026-11-08T15:00:00Z&until=2026-11-08T14:00:00Z", + }; + for (size_t i = 0; i < sizeof(bad) / sizeof(bad[0]); i++) { + result_t result = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + bad[i], NULL); + char message[160]; + snprintf(message, sizeof(message), "query %s", bad[i]); + TEST_ASSERT_EQUAL_INT_MESSAGE(400, result.response.status_code, message); + TEST_ASSERT_EQUAL_STRING_MESSAGE("invalid_request", str(result.json, "code"), message); + release(&result); + } + char query[512] = "camera_uuid="; + for (int i = 0; i < 11; i++) { + if (i) strcat(query, ","); + strcat(query, cam_a); + } + result_t too_many = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + expect_error(&too_many, 400, "invalid_request"); + release(&too_many); +} + +static void test_list_filters_time_and_limit_and_pages(void) { + grant(cam_a, true, false); + seeded_t first = seed(cam_a, "bird", OBS_ASSET_PENDING); + seeded_t second = seed(cam_a, "bird", OBS_ASSET_PENDING); + seeded_t third = seed(cam_a, "bird", OBS_ASSET_PENDING); + (void)first; + + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(second.observation, &record)); + char since[OBS_TIME_TEXT_SIZE], query[512]; + observation_format_time(record.observed_at_ms, since, sizeof(since)); + snprintf(query, sizeof(query), "since=%s", since); + result_t window = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + TEST_ASSERT_EQUAL_INT(200, window.response.status_code); + TEST_ASSERT_EQUAL_INT(2, item_count(&window)); + TEST_ASSERT_TRUE(page_contains(&window, second.observation)); + TEST_ASSERT_TRUE(page_contains(&window, third.observation)); + + result_t page1 = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + "limit=2", NULL); + TEST_ASSERT_EQUAL_INT(200, page1.response.status_code); + TEST_ASSERT_EQUAL_INT(2, item_count(&page1)); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(page1.json, "has_more"))); + snprintf(query, sizeof(query), "limit=2&cursor=%s", str(page1.json, "next_cursor")); + result_t page2 = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + TEST_ASSERT_EQUAL_INT(200, page2.response.status_code); + TEST_ASSERT_EQUAL_INT(1, item_count(&page2)); + TEST_ASSERT_TRUE(page_contains(&page2, third.observation)); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(page2.json, "has_more"))); + + result_t head = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + "cursor=head", NULL); + TEST_ASSERT_EQUAL_INT(200, head.response.status_code); + TEST_ASSERT_EQUAL_INT(0, item_count(&head)); + TEST_ASSERT_EQUAL_STRING(str(page2.json, "next_cursor"), str(head.json, "next_cursor")); + + release(&window); + release(&page1); + release(&page2); + release(&head); +} + +static void test_list_cursor_errors(void) { + grant(cam_a, true, false); + seed(cam_a, "bird", OBS_ASSET_PENDING); + cJSON *invalid_fixture = load_fixture("errors/invalid-cursor.json"); + result_t invalid = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + "cursor=not-a-cursor!", NULL); + TEST_ASSERT_EQUAL_INT(400, invalid.response.status_code); + assert_json_equal(invalid_fixture, invalid.json, "invalid cursor body"); + + /* A cursor from another DB generation (epoch mismatch) has expired. */ + char cursor[OBS_CURSOR_SIZE], query[512]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(1, "0f0f0f0f-0f0f-4f0f-8f0f-0f0f0f0f0f0f", + cursor, sizeof(cursor))); + snprintf(query, sizeof(query), "cursor=%s", cursor); + result_t expired = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + expect_error(&expired, 410, "cursor_expired"); + cJSON *expired_fixture = load_fixture("errors/cursor-expired.json"); + assert_same_shape(expired_fixture, expired.json, "cursor_expired"); + TEST_ASSERT_EQUAL_STRING(str(expired_fixture, "error"), str(expired.json, "error")); + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + char oldest[OBS_CURSOR_SIZE]; + observation_cursor_encode(meta.oldest_seq - 1, meta.epoch_uuid, oldest, sizeof(oldest)); + TEST_ASSERT_EQUAL_STRING(oldest, str(expired.json, "oldest_cursor")); + TEST_ASSERT_NOT_NULL(str(expired.json, "oldest_observed_at")); + + /* The oldest cursor resumes history. */ + snprintf(query, sizeof(query), "cursor=%s", oldest); + result_t resumed = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", + query, NULL); + TEST_ASSERT_EQUAL_INT(200, resumed.response.status_code); + TEST_ASSERT_EQUAL_INT(1, item_count(&resumed)); + + cJSON_Delete(invalid_fixture); + cJSON_Delete(expired_fixture); + release(&invalid); + release(&expired); + release(&resumed); +} + +/* ---- GET /api/observations/{id}/snapshot ------------------------------ */ + +static bool audited(const char *target, const char *outcome) { + for (int i = 0; i < audit_count; i++) + if (strcmp(audits[i].action, "snapshot.create") == 0 && + strcmp(audits[i].target_type, "observation") == 0 && + strcmp(audits[i].target_uuid, target) == 0 && + strcmp(audits[i].outcome, outcome) == 0) + return true; + return false; +} + +static void test_snapshot_ready_streams_the_exact_asset(void) { + grant(cam_a, true, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + char path[128], expected_file[1024], etag[64]; + obs_path(path, sizeof(path), seeded.observation, "/snapshot"); + snprintf(expected_file, sizeof(expected_file), "%s/%s", storage_dir, seeded.relative_path); + snprintf(etag, sizeof(etag), "\"%s\"", seeded.asset); + + result_t result = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT(1, serve_calls); + TEST_ASSERT_EQUAL_STRING(expected_file, served_path); + TEST_ASSERT_EQUAL_STRING("image/jpeg", served_type); + TEST_ASSERT_NOT_NULL(strstr(served_headers, "Cache-Control: private, max-age=300\r\n")); + char etag_header[96]; + snprintf(etag_header, sizeof(etag_header), "ETag: %s\r\n", etag); + TEST_ASSERT_NOT_NULL(strstr(served_headers, etag_header)); + TEST_ASSERT_TRUE(audited(seeded.observation, "allowed")); + for (int i = 0; i < audit_count; i++) { + TEST_ASSERT_NULL_MESSAGE(strstr(audits[i].details, seeded.relative_path), "audit path"); + if (strcmp(audits[i].target_type, "observation") == 0) { + TEST_ASSERT_NOT_NULL(strstr(audits[i].details, seeded.asset)); + TEST_ASSERT_NOT_NULL(strstr(audits[i].details, cam_a)); + } + } + release(&result); + + /* Conditional request: 304 without serving the file. */ + result_t cached = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, etag); + TEST_ASSERT_EQUAL_INT(304, cached.response.status_code); + TEST_ASSERT_EQUAL_INT(1, serve_calls); + TEST_ASSERT_EQUAL_STRING(etag, header(&cached.response, "ETag")); + TEST_ASSERT_EQUAL_STRING("private, max-age=300", header(&cached.response, "Cache-Control")); + release(&cached); + + /* A different ETag is served normally. */ + result_t stale = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, + "\"00000000-0000-4000-8000-000000000000\""); + TEST_ASSERT_EQUAL_INT(2, serve_calls); + release(&stale); +} + +static void test_snapshot_ready_with_missing_file_never_substitutes(void) { + grant(cam_a, true, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + char file[1024], path[128]; + snprintf(file, sizeof(file), "%s/%s", storage_dir, seeded.relative_path); + TEST_ASSERT_EQUAL_INT(0, unlink(file)); + obs_path(path, sizeof(path), seeded.observation, "/snapshot"); + result_t result = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + expect_error(&result, 404, "evidence_unavailable"); + TEST_ASSERT_EQUAL_STRING("write_failed", str(result.json, "reason")); + TEST_ASSERT_EQUAL_INT(0, serve_calls); + release(&result); +} + +static void test_snapshot_rejects_paths_outside_observation_storage(void) { + grant(cam_a, true, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + const char *paths[] = {"../etc/passwd", "observations/../../etc/passwd", "/etc/passwd", + "snapshots/cam/x.jpg"}; + for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); i++) { + char sql[512]; + snprintf(sql, sizeof(sql), "UPDATE observation_assets SET path = '%s' WHERE uuid = '%s';", + paths[i], seeded.asset); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(get_db_handle(), sql, NULL, NULL, NULL)); + char path[128]; + obs_path(path, sizeof(path), seeded.observation, "/snapshot"); + result_t result = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(500, result.response.status_code, paths[i]); + TEST_ASSERT_EQUAL_INT(0, serve_calls); + release(&result); + } +} + +static void test_snapshot_pending_unavailable_expired(void) { + grant(cam_a, true, true); + seeded_t pending = seed(cam_a, "bird", OBS_ASSET_PENDING); + seeded_t unavailable = seed(cam_a, "bird", OBS_ASSET_UNAVAILABLE); + seeded_t expired = seed(cam_a, "bird", OBS_ASSET_EXPIRED); + char path[128]; + + obs_path(path, sizeof(path), pending.observation, "/snapshot"); + result_t r_pending = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + expect_error(&r_pending, 202, "evidence_pending"); + const char *retry = header(&r_pending.response, "Retry-After"); + TEST_ASSERT_NOT_NULL(retry); + int seconds = atoi(retry); + TEST_ASSERT_TRUE(seconds >= 1 && seconds <= 30); + TEST_ASSERT_EQUAL_INT(seconds, + cJSON_GetObjectItem(r_pending.json, "retry_after_seconds")->valueint); + cJSON *pending_fixture = load_fixture("errors/evidence-pending.json"); + assert_same_shape(pending_fixture, r_pending.json, "evidence_pending"); + TEST_ASSERT_EQUAL_STRING(str(pending_fixture, "error"), str(r_pending.json, "error")); + + obs_path(path, sizeof(path), unavailable.observation, "/snapshot"); + result_t r_unavailable = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, + NULL); + TEST_ASSERT_EQUAL_INT(404, r_unavailable.response.status_code); + cJSON *unavailable_fixture = load_fixture("errors/evidence-unavailable.json"); + assert_json_equal(unavailable_fixture, r_unavailable.json, "evidence_unavailable body"); + + obs_path(path, sizeof(path), expired.observation, "/snapshot"); + result_t r_expired = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + expect_error(&r_expired, 410, "evidence_expired"); + TEST_ASSERT_EQUAL_STRING("retention_expired", str(r_expired.json, "reason")); + cJSON *expired_fixture = load_fixture("errors/evidence-expired.json"); + assert_same_shape(expired_fixture, r_expired.json, "evidence_expired"); + TEST_ASSERT_EQUAL_STRING(str(expired_fixture, "error"), str(r_expired.json, "error")); + int64_t expired_at = 0; + TEST_ASSERT_EQUAL_INT(0, observation_parse_time(str(r_expired.json, "expired_at"), &expired_at)); + + TEST_ASSERT_EQUAL_INT(0, serve_calls); + cJSON_Delete(pending_fixture); + cJSON_Delete(unavailable_fixture); + cJSON_Delete(expired_fixture); + release(&r_pending); + release(&r_unavailable); + release(&r_expired); +} + +static void test_snapshot_authorization_d4(void) { + seeded_t viewable = seed(cam_a, "bird", OBS_ASSET_READY); + seeded_t hidden = seed(cam_b, "bird", OBS_ASSET_READY); + grant(cam_a, true, false); /* live.view only */ + grant(cam_b, false, false); + char path[128]; + + obs_path(path, sizeof(path), viewable.observation, "/snapshot"); + result_t forbidden = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT(403, forbidden.response.status_code); + cJSON *forbidden_fixture = load_fixture("errors/snapshot-forbidden.json"); + assert_json_equal(forbidden_fixture, forbidden.json, "snapshot forbidden body"); + TEST_ASSERT_TRUE(audited(viewable.observation, "denied")); + + obs_path(path, sizeof(path), hidden.observation, "/snapshot"); + result_t hidden_result = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, + NULL); + TEST_ASSERT_EQUAL_INT(404, hidden_result.response.status_code); + cJSON *not_found = load_fixture("errors/observation-not-found.json"); + assert_json_equal(not_found, hidden_result.json, "hidden snapshot body"); + + result_t unknown = call(handle_get_observation_snapshot, HTTP_METHOD_GET, + "/api/observations/00000000-0000-4000-8000-000000000000/snapshot", + NULL, NULL); + TEST_ASSERT_EQUAL_INT(404, unknown.response.status_code); + TEST_ASSERT_EQUAL_MEMORY(unknown.response.body, hidden_result.response.body, + unknown.response.body_length); + + /* snapshot.create alone (without live.view) still serves the image. */ + grant_count = 0; + grant(cam_b, false, true); + result_t allowed = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT(1, serve_calls); + + evaluation_fails = true; + result_t failure = call(handle_get_observation_snapshot, HTTP_METHOD_GET, path, NULL, NULL); + expect_error(&failure, 500, "internal_error"); + + TEST_ASSERT_EQUAL_INT(1, serve_calls); + cJSON_Delete(forbidden_fixture); + cJSON_Delete(not_found); + release(&forbidden); + release(&hidden_result); + release(&unknown); + release(&allowed); + release(&failure); +} + +/* ---- GET /api/integrations/capabilities ------------------------------ */ + +static const cJSON *operation(const cJSON *json, const char *name) { + return cJSON_GetObjectItem(cJSON_GetObjectItem(json, "operations"), name); +} + +static void test_capabilities_document(void) { + grant_all(cam_a, true, true, true); + grant(cam_b, true, false); + grant_all(cam_c, false, false, true); /* annotate alone still lists the camera */ + seed(cam_a, "bird", OBS_ASSET_PENDING); + result_t result = call(handle_get_integration_capabilities, HTTP_METHOD_GET, + "/api/integrations/capabilities", NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, result.response.status_code); + /* P2 (L14): observations.annotate is supported and advertised per camera. */ + cJSON *fixture = load_fixture("capabilities/p2.json"); + /* cameras is an array whose items are compared separately. */ + cJSON *fixture_cameras = cJSON_DetachItemFromObject(fixture, "cameras"); + cJSON *cameras = cJSON_DetachItemFromObject(result.json, "cameras"); + assert_same_shape(fixture, result.json, "capabilities"); + assert_json_equal(cJSON_GetObjectItem(fixture, "contract_versions"), + cJSON_GetObjectItem(result.json, "contract_versions"), "contract_versions"); + assert_json_equal(cJSON_GetObjectItem(fixture, "limits"), + cJSON_GetObjectItem(result.json, "limits"), "limits"); + const char *unsupported[] = {"observations.annotate", "track_lifecycle", "recorded_frame", + "event_clip", "range_clip"}; + for (size_t i = 0; i < sizeof(unsupported) / sizeof(unsupported[0]); i++) + assert_json_equal(operation(fixture, unsupported[i]), + operation(result.json, unsupported[i]), unsupported[i]); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(operation(result.json, + "observations.history"), + "authorized"))); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(operation(result.json, + "observations.snapshot"), + "authorized"))); + + const cJSON *src = cJSON_GetObjectItem(result.json, "source"); + TEST_ASSERT_EQUAL_STRING(source, str(src, "id")); + TEST_ASSERT_EQUAL_STRING("lightnvr", str(src, "kind")); + TEST_ASSERT_EQUAL_STRING(source + strlen("urn:lightnvr:"), str(src, "installation_uuid")); + TEST_ASSERT_EQUAL_STRING("yawamf", + str(cJSON_GetObjectItem(result.json, "principal"), "writer_namespace")); + const cJSON *history = cJSON_GetObjectItem(result.json, "history"); + TEST_ASSERT_EQUAL_INT(30, cJSON_GetObjectItem(history, "retention_days")->valueint); + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + char oldest[OBS_CURSOR_SIZE]; + observation_cursor_encode(meta.oldest_seq - 1, meta.epoch_uuid, oldest, sizeof(oldest)); + TEST_ASSERT_EQUAL_STRING(oldest, str(history, "oldest_cursor")); + TEST_ASSERT_NOT_NULL(str(history, "oldest_observed_at")); + const cJSON *delivery = cJSON_GetObjectItem(result.json, "delivery"); + TEST_ASSERT_EQUAL_STRING( + "lightnvr/v1/events/io.lightnvr.detection.observation_changed.v1/{camera_uuid}", + str(delivery, "topic")); + + /* Only cameras with an observation action, with the actions held. */ + TEST_ASSERT_EQUAL_INT(3, cJSON_GetArraySize(cameras)); + const cJSON *camera; + cJSON_ArrayForEach(camera, cameras) { + assert_same_shape(cJSON_GetArrayItem(fixture_cameras, 0), camera, "cameras[]"); + const cJSON *actions = cJSON_GetObjectItem(camera, "actions"); + if (strcmp(str(camera, "camera_uuid"), cam_a) == 0) { + assert_json_equal(cJSON_GetObjectItem(cJSON_GetArrayItem(fixture_cameras, 0), + "actions"), actions, "cam_a actions"); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(camera, "evidence_enabled"))); + } else if (strcmp(str(camera, "camera_uuid"), cam_c) == 0) { + TEST_ASSERT_EQUAL_INT(1, cJSON_GetArraySize(actions)); + TEST_ASSERT_EQUAL_STRING("observations.annotate", + cJSON_GetArrayItem(actions, 0)->valuestring); + } else { + TEST_ASSERT_EQUAL_STRING(cam_b, str(camera, "camera_uuid")); + TEST_ASSERT_EQUAL_INT(1, cJSON_GetArraySize(actions)); + TEST_ASSERT_EQUAL_STRING("live.view", cJSON_GetArrayItem(actions, 0)->valuestring); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(camera, "evidence_enabled"))); + } + } + cJSON_Delete(cameras); + cJSON_Delete(fixture_cameras); + cJSON_Delete(fixture); + release(&result); +} + +static void test_capabilities_scope_without_grants_or_namespace(void) { + safe_strcpy(principal_name, "system", sizeof(principal_name), 0); + result_t result = call(handle_get_integration_capabilities, HTTP_METHOD_GET, + "/api/integrations/capabilities", NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, result.response.status_code); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItem(cJSON_GetObjectItem(result.json, + "principal"), + "writer_namespace"))); + const char *ops[] = {"observations.history", "observations.snapshot"}; + for (int i = 0; i < 2; i++) { + const cJSON *op = operation(result.json, ops[i]); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(op, "supported"))); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(op, "authorized"))); + TEST_ASSERT_EQUAL_STRING("not_authorized", str(op, "reason")); + } + /* annotate is supported, but a reserved username has no writer namespace. */ + const cJSON *annotate = operation(result.json, "observations.annotate"); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(annotate, "supported"))); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(annotate, "authorized"))); + TEST_ASSERT_EQUAL_STRING("reserved_writer_namespace", str(annotate, "reason")); + TEST_ASSERT_EQUAL_INT(0, cJSON_GetArraySize(cJSON_GetObjectItem(result.json, "cameras"))); + release(&result); + + /* A valid namespace without the action anywhere: not_authorized. */ + safe_strcpy(principal_name, "yawamf", sizeof(principal_name), 0); + grant(cam_a, true, true); + result = call(handle_get_integration_capabilities, HTTP_METHOD_GET, + "/api/integrations/capabilities", NULL, NULL); + annotate = operation(result.json, "observations.annotate"); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(annotate, "authorized"))); + TEST_ASSERT_EQUAL_STRING("not_authorized", str(annotate, "reason")); + release(&result); + + /* The action held with an invalid username: invalid_writer_namespace. */ + safe_strcpy(principal_name, "-bad", sizeof(principal_name), 0); + grant_count = 0; + grant_all(cam_a, true, true, true); + result = call(handle_get_integration_capabilities, HTTP_METHOD_GET, + "/api/integrations/capabilities", NULL, NULL); + annotate = operation(result.json, "observations.annotate"); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(annotate, "authorized"))); + TEST_ASSERT_EQUAL_STRING("invalid_writer_namespace", str(annotate, "reason")); + release(&result); + grant_count = 0; + safe_strcpy(principal_name, "system", sizeof(principal_name), 0); + result = call(handle_get_integration_capabilities, HTTP_METHOD_GET, + "/api/integrations/capabilities", NULL, NULL); + /* Empty history still has the oldest cursor; oldest_observed_at is null. */ + const cJSON *history = cJSON_GetObjectItem(result.json, "history"); + TEST_ASSERT_NOT_NULL(str(history, "oldest_cursor")); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItem(history, "oldest_observed_at"))); + release(&result); +} + +/* ---- PUT /api/observations/{id}/classifications/{ns}/{name} (L14) ------ */ + +#define FIXTURE_OBSERVATION "55555555-5555-4555-8555-555555555555" +#define FIXTURE_ASSET "88888888-8888-4888-8888-888888888888" + +static void put_path(char *out, size_t size, const char *id, const char *ns, const char *name) { + snprintf(out, size, "/api/observations/%s/classifications/%s/%s", id, ns, name); +} + +static result_t put(const char *path, const char *body) { + return call_with_body(handle_put_observation_classification, HTTP_METHOD_PUT, path, NULL, + NULL, body); +} + +static int count_for(const char *sql, const char *uuid) { + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL)); + sqlite3_bind_text(stmt, 1, uuid, -1, SQLITE_TRANSIENT); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(stmt)); + int count = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + return count; +} + +static int classification_rows(const char *observation) { + return count_for("SELECT count(*) FROM observation_classifications WHERE observation_uuid = ?;", + observation); +} + +static int publication_rows(const char *observation) { + return count_for("SELECT count(*) FROM observation_publications p JOIN observation_changes c " + "ON c.seq = p.change_seq WHERE c.observation_uuid = ?;", observation); +} + +static int revision_of(const char *observation) { + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(observation, &record)); + return record.revision; +} + +/* Replace every string equal to from (recursively). */ +static void substitute(cJSON *json, const char *from, const char *to) { + cJSON *item; + cJSON_ArrayForEach(item, json) { + if (cJSON_IsString(item) && strcmp(item->valuestring, from) == 0) + cJSON_SetValuestring(item, to); + else if (cJSON_IsObject(item) || cJSON_IsArray(item)) + substitute(item, from, to); + } +} + +/* Run a C3 classification exchange against a seeded observation: the + * fixture's observation and asset IDs are mapped to the seeded ones in the + * path, body and expected response. Returns the response; *expected is the + * mapped fixture response body. */ +static result_t put_exchange(const char *file, const seeded_t *seeded, cJSON **expected, + int *status) { + cJSON *exchange = load_fixture(file); + const cJSON *request = cJSON_GetObjectItem(exchange, "request"); + const cJSON *principal = cJSON_GetObjectItem(request, "principal"); + safe_strcpy(principal_name, str(principal, "username"), sizeof(principal_name), 0); + char path[256]; + const char *fixture_path = str(request, "path"); + const char *at = strstr(fixture_path, FIXTURE_OBSERVATION); + TEST_ASSERT_NOT_NULL(at); + snprintf(path, sizeof(path), "%.*s%s%s", (int)(at - fixture_path), fixture_path, + seeded->observation, at + strlen(FIXTURE_OBSERVATION)); + cJSON *body = cJSON_Duplicate(cJSON_GetObjectItem(request, "body"), true); + substitute(body, FIXTURE_ASSET, seeded->asset); + char *text = cJSON_PrintUnformatted(body); + cJSON_Delete(body); + result_t result = put(path, text); + cJSON_free(text); + const cJSON *response = cJSON_GetObjectItem(exchange, "response"); + *status = cJSON_GetObjectItem(response, "status")->valueint; + *expected = cJSON_Duplicate(cJSON_GetObjectItem(response, "body"), true); + substitute(*expected, FIXTURE_ASSET, seeded->asset); + substitute(*expected, FIXTURE_OBSERVATION, seeded->observation); + cJSON_Delete(exchange); + return result; +} + +/* created_at/updated_at are server times: check them, then align them with + * the fixture so the rest compares exactly. Returns a copy of the actual + * annotation (before alignment), or NULL when the body has none. */ +static cJSON *align_annotation_times(cJSON *expected, cJSON *actual) { + const char *keys[] = {"annotation", "current"}; + for (int k = 0; k < 2; k++) { + cJSON *want = cJSON_GetObjectItem(expected, keys[k]); + cJSON *got = cJSON_GetObjectItem(actual, keys[k]); + if (!want) { + TEST_ASSERT_NULL_MESSAGE(got, keys[k]); + continue; + } + TEST_ASSERT_NOT_NULL_MESSAGE(got, keys[k]); + cJSON *copy = cJSON_Duplicate(got, true); + int64_t created = 0, updated = 0; + time_ms(str(got, "created_at"), &created); + time_ms(str(got, "updated_at"), &updated); + TEST_ASSERT_TRUE(updated >= created); + cJSON_ReplaceItemInObject(got, "created_at", + cJSON_CreateString(str(want, "created_at"))); + cJSON_ReplaceItemInObject(got, "updated_at", + cJSON_CreateString(str(want, "updated_at"))); + return copy; + } + return NULL; +} + +static cJSON *run_exchange(const char *file, const seeded_t *seeded) { + cJSON *expected = NULL; + int status = 0; + result_t result = put_exchange(file, seeded, &expected, &status); + TEST_ASSERT_EQUAL_INT_MESSAGE(status, result.response.status_code, file); + TEST_ASSERT_NOT_NULL_MESSAGE(result.json, file); + cJSON *annotation = align_annotation_times(expected, result.json); + assert_json_equal(expected, result.json, file); + cJSON_Delete(expected); + release(&result); + return annotation; +} + +static const audit_record_t *last_annotate_audit(void) { + for (int i = audit_count - 1; i >= 0; i--) + if (strcmp(audits[i].action, "observations.annotate") == 0) return &audits[i]; + return NULL; +} + +static void expect_annotate_audit(const char *observation, const char *outcome, + const char *result_name) { + const audit_record_t *record = last_annotate_audit(); + TEST_ASSERT_NOT_NULL_MESSAGE(record, result_name); + TEST_ASSERT_EQUAL_STRING("observation", record->target_type); + TEST_ASSERT_EQUAL_STRING(observation, record->target_uuid); + TEST_ASSERT_EQUAL_STRING_MESSAGE(outcome, record->outcome, result_name); + cJSON *details = cJSON_Parse(record->details); + TEST_ASSERT_NOT_NULL(details); + TEST_ASSERT_EQUAL_STRING_MESSAGE(result_name, str(details, "result"), record->details); + cJSON_Delete(details); +} + +/* Fixtures 01-05 are one history: create, retry, stale write, owner + * correction and the automated re-run it locks out. */ +static void test_put_fixture_chain_create_retry_conflict_owner(void) { + grant_all(cam_a, true, false, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + TEST_ASSERT_EQUAL_INT(2, revision_of(seeded.observation)); + + cJSON *created = run_exchange("classifications/01-create-automated.json", &seeded); + TEST_ASSERT_EQUAL_STRING(str(created, "created_at"), str(created, "updated_at")); + TEST_ASSERT_EQUAL_INT(3, revision_of(seeded.observation)); + TEST_ASSERT_EQUAL_INT(1, classification_rows(seeded.observation)); + expect_annotate_audit(seeded.observation, "success", "created"); + int publications = publication_rows(seeded.observation); + + /* 02: identical retry, nothing written and the revision is not bumped. */ + cJSON *retried = run_exchange("classifications/02-retry-noop.json", &seeded); + assert_json_equal(created, retried, "retry returns the stored annotation"); + TEST_ASSERT_EQUAL_INT(3, revision_of(seeded.observation)); + TEST_ASSERT_EQUAL_INT(publications, publication_rows(seeded.observation)); + expect_annotate_audit(seeded.observation, "success", "noop"); + + /* 03: a different result with a stale expected_revision. */ + cJSON *conflict = run_exchange("classifications/03-revision-conflict.json", &seeded); + assert_json_equal(created, conflict, "409 current is the stored annotation"); + TEST_ASSERT_EQUAL_INT(3, revision_of(seeded.observation)); + expect_annotate_audit(seeded.observation, "failure", "revision_conflict"); + + /* 04: the owner correction replaces it. */ + cJSON *owner = run_exchange("classifications/04-owner-confirm.json", &seeded); + TEST_ASSERT_EQUAL_STRING(str(created, "created_at"), str(owner, "created_at")); + TEST_ASSERT_EQUAL_INT(4, revision_of(seeded.observation)); + TEST_ASSERT_EQUAL_INT(1, classification_rows(seeded.observation)); + TEST_ASSERT_EQUAL_INT(publications + 1, publication_rows(seeded.observation)); + expect_annotate_audit(seeded.observation, "success", "updated"); + + /* 05: an automated re-run never overrides the owner decision. */ + cJSON *locked = run_exchange("classifications/05-owner-decision-locked.json", &seeded); + assert_json_equal(owner, locked, "409 current is the owner decision"); + TEST_ASSERT_EQUAL_INT(4, revision_of(seeded.observation)); + expect_annotate_audit(seeded.observation, "failure", "owner_decision_locked"); + + /* The detector fields are untouched. */ + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(seeded.observation, &record)); + TEST_ASSERT_EQUAL_STRING("bird", record.label); + TEST_ASSERT_EQUAL_FLOAT(0.87f, (float)record.confidence); + + cJSON_Delete(created); + cJSON_Delete(retried); + cJSON_Delete(conflict); + cJSON_Delete(owner); + cJSON_Delete(locked); +} + +static void test_put_fixture_rejections_write_nothing(void) { + grant_all(cam_a, true, false, true); + struct { const char *file; observation_asset_state_t state; const char *outcome; + const char *result; } cases[] = { + {"classifications/06-evidence-mismatch-asset.json", OBS_ASSET_READY, "failure", + "evidence_mismatch"}, + {"classifications/07-evidence-mismatch-pending.json", OBS_ASSET_PENDING, "failure", + "evidence_mismatch"}, + {"classifications/08-namespace-forbidden.json", OBS_ASSET_READY, "denied", + "namespace_forbidden"}, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + seeded_t seeded = seed(cam_a, "bird", cases[i].state); + int revision = revision_of(seeded.observation); + int publications = publication_rows(seeded.observation); + cJSON *annotation = run_exchange(cases[i].file, &seeded); + TEST_ASSERT_NULL(annotation); + TEST_ASSERT_EQUAL_INT(0, classification_rows(seeded.observation)); + TEST_ASSERT_EQUAL_INT(revision, revision_of(seeded.observation)); + TEST_ASSERT_EQUAL_INT(publications, publication_rows(seeded.observation)); + expect_annotate_audit(seeded.observation, cases[i].outcome, cases[i].result); + } +} + +static const char *body_for(const char *asset, const char *kind, int expected_revision, + const char *label, const char *extra) { + static char buffer[2048]; + snprintf(buffer, sizeof(buffer), + "{\"classifier\":\"yawamf\",\"model_version\":\"m1\",\"label\":\"%s\"," + "\"taxonomy_id\":null,\"confidence\":0.5," + "\"classified_at\":\"2026-11-08T14:03:15.002Z\",\"input_asset_id\":\"%s\"," + "\"input_revision\":2,\"kind\":\"%s\",\"expected_revision\":%d%s}", + label, asset, kind, expected_revision, extra ? extra : ""); + return buffer; +} + +/* A7-annotate: a camera the caller cannot view looks like an unknown ID; a + * viewable camera without the action is a 403; nothing is written on denial. */ +static void test_put_authorization_on_capture_camera(void) { + seeded_t viewable = seed(cam_a, "bird", OBS_ASSET_READY); + seeded_t hidden = seed(cam_b, "bird", OBS_ASSET_READY); + seeded_t writable = seed(cam_c, "bird", OBS_ASSET_READY); + grant_all(cam_a, true, true, false); /* live.view + snapshot, no annotate */ + grant_all(cam_c, false, false, true); /* annotate alone is sufficient */ + char path[256]; + + put_path(path, sizeof(path), viewable.observation, "yawamf", "species"); + result_t forbidden = put(path, body_for(viewable.asset, "automated", 0, "Robin", NULL)); + expect_error(&forbidden, 403, "forbidden"); + TEST_ASSERT_NULL(cJSON_GetObjectItem(forbidden.json, "current")); + expect_annotate_audit(viewable.observation, "denied", "forbidden"); + + cJSON *not_found = load_fixture("errors/observation-not-found.json"); + put_path(path, sizeof(path), hidden.observation, "yawamf", "species"); + result_t hidden_result = put(path, body_for(hidden.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_EQUAL_INT(404, hidden_result.response.status_code); + assert_json_equal(not_found, hidden_result.json, "hidden put body"); + expect_annotate_audit(hidden.observation, "denied", "observation_not_found"); + /* Authorization precedes namespace and body checks, so they leak nothing. */ + put_path(path, sizeof(path), hidden.observation, "someone-else", "species"); + result_t hidden_bad = put(path, "{not json"); + TEST_ASSERT_EQUAL_INT(404, hidden_bad.response.status_code); + TEST_ASSERT_EQUAL_MEMORY(hidden_result.response.body, hidden_bad.response.body, + hidden_result.response.body_length); + + put_path(path, sizeof(path), "00000000-0000-4000-8000-000000000000", "yawamf", "species"); + result_t unknown = put(path, body_for(hidden.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_EQUAL_INT(404, unknown.response.status_code); + TEST_ASSERT_EQUAL_size_t(unknown.response.body_length, hidden_result.response.body_length); + TEST_ASSERT_EQUAL_MEMORY(unknown.response.body, hidden_result.response.body, + unknown.response.body_length); + + TEST_ASSERT_EQUAL_INT(0, classification_rows(viewable.observation)); + TEST_ASSERT_EQUAL_INT(0, classification_rows(hidden.observation)); + TEST_ASSERT_EQUAL_INT(2, revision_of(viewable.observation)); + TEST_ASSERT_EQUAL_INT(2, revision_of(hidden.observation)); + + put_path(path, sizeof(path), writable.observation, "yawamf", "species"); + result_t allowed = put(path, body_for(writable.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_EQUAL_INT(201, allowed.response.status_code); + TEST_ASSERT_EQUAL_INT(1, classification_rows(writable.observation)); + + evaluation_fails = true; + put_path(path, sizeof(path), writable.observation, "yawamf", "other"); + result_t failure = put(path, body_for(writable.asset, "automated", 0, "Robin", NULL)); + expect_error(&failure, 500, "internal_error"); + TEST_ASSERT_EQUAL_INT(1, classification_rows(writable.observation)); + expect_annotate_audit(writable.observation, "error", "internal_error"); + + cJSON_Delete(not_found); + release(&forbidden); + release(&hidden_result); + release(&hidden_bad); + release(&unknown); + release(&allowed); + release(&failure); +} + +static void test_put_namespace_rules(void) { + grant_all(cam_a, true, false, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + char path[256]; + put_path(path, sizeof(path), seeded.observation, "yawamf", "species"); + + /* A body naming another writer is refused, before body validation. */ + const char *other[] = {",\"writer_namespace\":\"frigate-bridge\"", ",\"writer\":\"owner\""}; + for (int i = 0; i < 2; i++) { + result_t result = put(path, body_for(seeded.asset, "automated", 0, "Robin", other[i])); + expect_error(&result, 403, "namespace_forbidden"); + release(&result); + } + /* Reserved and invalid usernames have no namespace at all. */ + const char *names[][2] = {{"system", "system"}, {"Lightnvr", "lightnvr"}, {"-bad", "bad"}}; + for (int i = 0; i < 3; i++) { + safe_strcpy(principal_name, names[i][0], sizeof(principal_name), 0); + char own[256]; + put_path(own, sizeof(own), seeded.observation, names[i][1], "species"); + result_t result = put(own, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + expect_error(&result, 403, "namespace_forbidden"); + release(&result); + } + TEST_ASSERT_EQUAL_INT(0, classification_rows(seeded.observation)); + + /* Naming the caller's own namespace is accepted and ignored. */ + safe_strcpy(principal_name, "YaWamf", sizeof(principal_name), 0); + result_t own = put(path, body_for(seeded.asset, "automated", 0, "Robin", + ",\"writer_namespace\":\"yawamf\",\"writer\":\"yawamf\"")); + TEST_ASSERT_EQUAL_INT(201, own.response.status_code); + const cJSON *annotation = cJSON_GetObjectItem(own.json, "annotation"); + TEST_ASSERT_EQUAL_STRING("yawamf/species", str(annotation, "key")); + TEST_ASSERT_EQUAL_STRING("yawamf", str(annotation, "writer_namespace")); + TEST_ASSERT_NULL(cJSON_GetObjectItem(annotation, "writer_user_id")); + release(&own); + + /* D3: a later user reusing the username cannot touch the key, and the + * stored values are withheld. */ + principal_id = 43; + result_t reused = put(path, body_for(seeded.asset, "automated", 1, "Sparrow", NULL)); + expect_error(&reused, 403, "namespace_forbidden"); + TEST_ASSERT_NULL(cJSON_GetObjectItem(reused.json, "current")); + TEST_ASSERT_NULL(strstr((const char *)reused.response.body, "Robin")); + expect_annotate_audit(seeded.observation, "denied", "namespace_forbidden"); + release(&reused); + TEST_ASSERT_EQUAL_INT(3, revision_of(seeded.observation)); +} + +static void test_put_validates_the_closed_body(void) { + grant_all(cam_a, true, false, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + char path[256]; + put_path(path, sizeof(path), seeded.observation, "yawamf", "species"); + char long65[66], long129[130]; + memset(long65, 'a', 65); long65[65] = '\0'; + memset(long129, 'a', 129); long129[129] = '\0'; + char long65_json[80], long129_json[150]; + snprintf(long65_json, sizeof(long65_json), "\"%s\"", long65); + snprintf(long129_json, sizeof(long129_json), "\"%s\"", long129); + /* {field, raw JSON value or NULL to remove the field} */ + const char *cases[][2] = { + {"classifier", NULL}, {"classifier", "\"\""}, {"classifier", long65_json}, + {"classifier", "5"}, {"model_version", long65_json}, {"model_version", "5"}, + {"label", NULL}, {"label", "\"\""}, {"label", long129_json}, {"label", "\"\\u0000x\""}, + {"label", "\"\xff\xfe\""}, {"taxonomy_id", long65_json}, {"taxonomy_id", "true"}, + {"confidence", NULL}, {"confidence", "1.5"}, {"confidence", "-0.1"}, + {"confidence", "\"0.5\""}, {"classified_at", NULL}, {"classified_at", "\"yesterday\""}, + {"classified_at", "5"}, {"input_asset_id", NULL}, {"input_asset_id", "\"nope\""}, + {"input_revision", NULL}, {"input_revision", "0"}, {"input_revision", "1.5"}, + {"input_revision", "\"2\""}, {"input_revision", "3000000000"}, {"kind", NULL}, + {"kind", "\"manual\""}, {"expected_revision", "-1"}, {"expected_revision", "1.5"}, + {"expected_revision", "null"}, {"writer_namespace", "5"}, {"extra", "1"}, + }; + for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + cJSON *body = cJSON_Parse(body_for(seeded.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_NOT_NULL(body); + cJSON_DeleteItemFromObject(body, cases[i][0]); + if (cases[i][1]) { + cJSON *value = cJSON_Parse(cases[i][1]); + if (!value) { /* invalid UTF-8 or embedded NUL: splice the raw text */ + char *text = cJSON_PrintUnformatted(body); + char raw[1024]; + snprintf(raw, sizeof(raw), "%.*s,\"%s\":%s}", (int)strlen(text) - 1, text, + cases[i][0], cases[i][1]); + cJSON_free(text); + cJSON_Delete(body); + result_t result = put(path, raw); + char message[160]; + snprintf(message, sizeof(message), "raw %s=%s", cases[i][0], cases[i][1]); + TEST_ASSERT_EQUAL_INT_MESSAGE(400, result.response.status_code, message); + TEST_ASSERT_EQUAL_STRING_MESSAGE("invalid_request", str(result.json, "code"), + message); + release(&result); + continue; + } + cJSON_AddItemToObject(body, cases[i][0], value); + } + char *text = cJSON_PrintUnformatted(body); + cJSON_Delete(body); + result_t result = put(path, text); + cJSON_free(text); + char message[160]; + snprintf(message, sizeof(message), "%s=%s", cases[i][0], + cases[i][1] ? cases[i][1] : "(absent)"); + TEST_ASSERT_EQUAL_INT_MESSAGE(400, result.response.status_code, message); + TEST_ASSERT_EQUAL_STRING_MESSAGE("invalid_request", str(result.json, "code"), message); + TEST_ASSERT_EQUAL_STRING_MESSAGE(cases[i][0], str(result.json, "field"), message); + release(&result); + } + const char *documents[] = {NULL, "", "not json", "[]", "null", "{\"classifier\":"}; + for (size_t i = 0; i < sizeof(documents) / sizeof(documents[0]); i++) { + result_t result = put(path, documents[i]); + TEST_ASSERT_EQUAL_INT_MESSAGE(400, result.response.status_code, + documents[i] ? documents[i] : "(no body)"); + TEST_ASSERT_EQUAL_STRING("invalid_request", str(result.json, "code")); + release(&result); + } + /* Oversized bodies are refused without parsing. */ + char *large = malloc(OBS_PUT_BODY_MAX + 64); + TEST_ASSERT_NOT_NULL(large); + snprintf(large, OBS_PUT_BODY_MAX + 64, "{\"classifier\":\"%*s\"}", OBS_PUT_BODY_MAX, "x"); + result_t oversized = put(path, large); + free(large); + expect_error(&oversized, 400, "invalid_request"); + release(&oversized); + /* Path segments follow ^[a-z0-9][a-z0-9_.-]{0,31}$. */ + const char *names[] = {"Species", "-species", "a-name-that-is-longer-than-32-bytes", "sp%2Fx"}; + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + char bad[256]; + put_path(bad, sizeof(bad), seeded.observation, "yawamf", names[i]); + result_t result = put(bad, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + expect_error(&result, 400, "invalid_request"); + TEST_ASSERT_EQUAL_STRING_MESSAGE("name", str(result.json, "field"), names[i]); + release(&result); + } + char bad_id[256]; + put_path(bad_id, sizeof(bad_id), "not-a-uuid", "yawamf", "species"); + result_t malformed = put(bad_id, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + expect_error(&malformed, 400, "invalid_request"); + release(&malformed); + + TEST_ASSERT_EQUAL_INT(0, classification_rows(seeded.observation)); + TEST_ASSERT_EQUAL_INT(2, revision_of(seeded.observation)); + expect_annotate_audit(seeded.observation, "failure", "invalid_request"); + + /* Optional fields may be absent; uppercase UUIDs are normalized. */ + char upper[OBS_UUID_SIZE]; + safe_strcpy(upper, seeded.asset, sizeof(upper), 0); + for (char *p = upper; *p; p++) if (*p >= 'a' && *p <= 'f') *p = (char)(*p - 32); + char minimal[512]; + snprintf(minimal, sizeof(minimal), + "{\"classifier\":\"yawamf\",\"label\":\"Robin\",\"confidence\":1," + "\"classified_at\":\"2026-11-08T16:03:15+02:00\",\"input_asset_id\":\"%s\"," + "\"input_revision\":2,\"kind\":\"owner_confirmed\"}", upper); + result_t created = put(path, minimal); + TEST_ASSERT_EQUAL_INT(201, created.response.status_code); + const cJSON *annotation = cJSON_GetObjectItem(created.json, "annotation"); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItem(annotation, "model_version"))); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItem(annotation, "taxonomy_id"))); + TEST_ASSERT_EQUAL_STRING(seeded.asset, str(annotation, "input_asset_id")); + TEST_ASSERT_EQUAL_STRING("2026-11-08T14:03:15.000Z", str(annotation, "classified_at")); + release(&created); +} + +static void test_put_update_limit_and_absent_key_conflict(void) { + grant_all(cam_a, true, false, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + char path[256]; + + /* expected_revision on a key that does not exist: 409 without current. */ + put_path(path, sizeof(path), seeded.observation, "yawamf", "species"); + result_t absent = put(path, body_for(seeded.asset, "automated", 3, "Robin", NULL)); + expect_error(&absent, 409, "revision_conflict"); + TEST_ASSERT_NULL(cJSON_GetObjectItem(absent.json, "current")); + release(&absent); + + result_t created = put(path, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_EQUAL_INT(201, created.response.status_code); + release(&created); + /* An automated update with the right revision: 200 changed=true. */ + result_t updated = put(path, body_for(seeded.asset, "automated", 1, "American Robin", NULL)); + TEST_ASSERT_EQUAL_INT(200, updated.response.status_code); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItem(updated.json, "changed"))); + TEST_ASSERT_EQUAL_INT(4, cJSON_GetObjectItem(updated.json, "observation_revision")->valueint); + const cJSON *annotation = cJSON_GetObjectItem(updated.json, "annotation"); + TEST_ASSERT_EQUAL_INT(2, cJSON_GetObjectItem(annotation, "annotation_revision")->valueint); + TEST_ASSERT_EQUAL_STRING("American Robin", str(annotation, "label")); + release(&updated); + + for (int i = 1; i < OBS_CLS_MAX_PER_OBSERVATION; i++) { + char name[16]; + snprintf(name, sizeof(name), "k%d", i); + put_path(path, sizeof(path), seeded.observation, "yawamf", name); + result_t result = put(path, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_EQUAL_INT(201, result.response.status_code); + release(&result); + } + put_path(path, sizeof(path), seeded.observation, "yawamf", "ninth"); + result_t limited = put(path, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + expect_error(&limited, 409, "annotation_limit"); + TEST_ASSERT_EQUAL_INT(8, cJSON_GetObjectItem(limited.json, "limit")->valueint); + expect_annotate_audit(seeded.observation, "failure", "annotation_limit"); + release(&limited); + TEST_ASSERT_EQUAL_INT(8, classification_rows(seeded.observation)); +} + +/* Section 10.5: GET and history items carry the annotations split by kind. */ +static void test_get_and_list_include_classifications(void) { + grant_all(cam_a, true, false, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + seeded_t plain = seed(cam_a, "bird", OBS_ASSET_READY); + char path[256]; + put_path(path, sizeof(path), seeded.observation, "yawamf", "species"); + result_t automated = put(path, body_for(seeded.asset, "automated", 0, "Robin", NULL)); + TEST_ASSERT_EQUAL_INT(201, automated.response.status_code); + put_path(path, sizeof(path), seeded.observation, "yawamf", "owner"); + result_t owner = put(path, body_for(seeded.asset, "owner_confirmed", 0, "Wren", NULL)); + TEST_ASSERT_EQUAL_INT(201, owner.response.status_code); + + obs_path(path, sizeof(path), seeded.observation, NULL); + result_t got = call(handle_get_observation, HTTP_METHOD_GET, path, NULL, NULL); + TEST_ASSERT_EQUAL_INT(200, got.response.status_code); + TEST_ASSERT_EQUAL_INT(4, cJSON_GetObjectItem(got.json, "revision")->valueint); + const cJSON *lists = cJSON_GetObjectItem(got.json, "classifications"); + TEST_ASSERT_EQUAL_INT(1, cJSON_GetArraySize(cJSON_GetObjectItem(lists, "automated"))); + TEST_ASSERT_EQUAL_INT(1, cJSON_GetArraySize(cJSON_GetObjectItem(lists, "owner_confirmed"))); + assert_json_equal(cJSON_GetObjectItem(automated.json, "annotation"), + cJSON_GetArrayItem(cJSON_GetObjectItem(lists, "automated"), 0), + "GET automated"); + assert_json_equal(cJSON_GetObjectItem(owner.json, "annotation"), + cJSON_GetArrayItem(cJSON_GetObjectItem(lists, "owner_confirmed"), 0), + "GET owner_confirmed"); + /* The original detector label and confidence stay visible. */ + TEST_ASSERT_EQUAL_STRING("bird", str(got.json, "label")); + + result_t page = call(handle_get_observations, HTTP_METHOD_GET, "/api/observations", NULL, + NULL); + TEST_ASSERT_EQUAL_INT(200, page.response.status_code); + TEST_ASSERT_EQUAL_INT(2, item_count(&page)); + const cJSON *item; + cJSON_ArrayForEach(item, cJSON_GetObjectItem(page.json, "items")) { + const cJSON *item_lists = cJSON_GetObjectItem(item, "classifications"); + if (strcmp(str(item, "id"), seeded.observation) == 0) { + assert_json_equal(lists, item_lists, "history item classifications"); + } else { + TEST_ASSERT_EQUAL_STRING(plain.observation, str(item, "id")); + TEST_ASSERT_EQUAL_INT(0, cJSON_GetArraySize(cJSON_GetObjectItem(item_lists, + "automated"))); + TEST_ASSERT_EQUAL_INT(0, cJSON_GetArraySize(cJSON_GetObjectItem(item_lists, + "owner_confirmed"))); + } + } + release(&automated); + release(&owner); + release(&got); + release(&page); +} + +/* The classification change is staged for the relay (MQTT) with the + * section 5.4 message ID, which a retry neither changes nor duplicates. */ +/* L15: the summary embedded in detection results. */ +static void test_detection_link_summary_json(void) { + observation_detection_link_t link; + memset(&link, 0, sizeof(link)); + TEST_ASSERT_NULL(observation_detection_link_to_json(NULL)); + TEST_ASSERT_NULL(observation_detection_link_to_json(&link)); /* unlinked: no member */ + + link.linked = true; + safe_strcpy(link.observation_uuid, "0b8f5c6e-8a63-4a0e-9b0e-2f9c0f6d7a10", + sizeof(link.observation_uuid), 0); + cJSON *bare = observation_detection_link_to_json(&link); + TEST_ASSERT_NOT_NULL(bare); + TEST_ASSERT_EQUAL_INT(3, cJSON_GetArraySize(bare)); + TEST_ASSERT_EQUAL_STRING(link.observation_uuid, + cJSON_GetObjectItemCaseSensitive(bare, "id")->valuestring); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItemCaseSensitive(bare, "owner_confirmed"))); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItemCaseSensitive(bare, "classification"))); + cJSON_Delete(bare); + + link.has_classification = true; + link.owner_confirmed = true; + observation_classification_t *a = &link.classification; + safe_strcpy(a->key, "yawamf/species", sizeof(a->key), 0); + safe_strcpy(a->writer_namespace, "yawamf", sizeof(a->writer_namespace), 0); + a->writer_user_id = 41; + safe_strcpy(a->classifier, "yawamf", sizeof(a->classifier), 0); + safe_strcpy(a->label, "Carolina Chickadee", sizeof(a->label), 0); + a->confidence = 1.0; + a->classified_at_ms = 1794015641100LL; + safe_strcpy(a->input_asset_uuid, "6f1d2b3c-4a5e-4f60-8a7b-9c0d1e2f3a4b", + sizeof(a->input_asset_uuid), 0); + a->input_revision = 3; + a->kind = OBS_CLS_KIND_OWNER_CONFIRMED; + a->annotation_revision = 2; + a->created_at_ms = 1794000195100LL; + a->updated_at_ms = 1794015641200LL; + cJSON *full = observation_detection_link_to_json(&link); + TEST_ASSERT_NOT_NULL(full); + TEST_ASSERT_TRUE(cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(full, "owner_confirmed"))); + cJSON *classification = cJSON_GetObjectItemCaseSensitive(full, "classification"); + cJSON *expected = observation_classification_to_json(a); + TEST_ASSERT_TRUE(cJSON_Compare(expected, classification, true)); + TEST_ASSERT_NULL(cJSON_GetObjectItemCaseSensitive(classification, "writer_user_id")); + cJSON_Delete(expected); + cJSON_Delete(full); +} + +static void test_put_stages_a_classification_change_with_stable_message_id(void) { + grant_all(cam_a, true, false, true); + seeded_t seeded = seed(cam_a, "bird", OBS_ASSET_READY); + char path[256]; + put_path(path, sizeof(path), seeded.observation, "yawamf", "species"); + const char *body = body_for(seeded.asset, "automated", 0, "Robin", NULL); + char body_copy[2048]; + safe_strcpy(body_copy, body, sizeof(body_copy), 0); + result_t created = put(path, body_copy); + TEST_ASSERT_EQUAL_INT(201, created.response.status_code); + TEST_ASSERT_EQUAL_INT(3, cJSON_GetObjectItem(created.json, "observation_revision")->valueint); + + observation_publication_t *pending = calloc(64, sizeof(*pending)); + TEST_ASSERT_NOT_NULL(pending); + int count = db_observation_pending_publications(pending, 64); + TEST_ASSERT_GREATER_OR_EQUAL(1, count); + const observation_publication_t *publication = NULL; + for (int i = 0; i < count; i++) + if (strcmp(pending[i].observation_uuid, seeded.observation) == 0 && + pending[i].revision == 3) + publication = &pending[i]; + TEST_ASSERT_NOT_NULL(publication); + TEST_ASSERT_EQUAL_INT(OBS_CAUSE_CLASSIFICATION, publication->cause); + char expected_id[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_message_id(source, seeded.observation, 3, expected_id)); + TEST_ASSERT_EQUAL_STRING(expected_id, publication->message_id); + + char error[256] = ""; + event_envelope_t first, second; + TEST_ASSERT_EQUAL_INT(0, observation_relay_build_envelope(publication, source, &first, error, + sizeof(error))); + TEST_ASSERT_EQUAL_INT(0, observation_relay_build_envelope(publication, source, &second, error, + sizeof(error))); + TEST_ASSERT_EQUAL_STRING(expected_id, first.id); + TEST_ASSERT_EQUAL_STRING(first.id, second.id); + TEST_ASSERT_EQUAL_STRING("classification", + str(first.data, "change_cause")); + event_envelope_clear(&first); + event_envelope_clear(&second); + + /* A retry after a lost response stages nothing new. */ + int publications = publication_rows(seeded.observation); + result_t retried = put(path, body_copy); + TEST_ASSERT_EQUAL_INT(200, retried.response.status_code); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItem(retried.json, "changed"))); + TEST_ASSERT_EQUAL_INT(publications, publication_rows(seeded.observation)); + TEST_ASSERT_EQUAL_INT(3, revision_of(seeded.observation)); + + /* Audit context: camera, key, kind, result and the annotation revisions; + * never the request body beyond those fields. */ + const audit_record_t *record = last_annotate_audit(); + TEST_ASSERT_NOT_NULL(record); + TEST_ASSERT_EQUAL_STRING("annotate", record->operation); + cJSON *details = cJSON_Parse(record->details); + TEST_ASSERT_EQUAL_STRING(cam_a, str(details, "camera_uuid")); + TEST_ASSERT_EQUAL_STRING("yawamf/species", str(details, "key")); + TEST_ASSERT_EQUAL_STRING("automated", str(details, "kind")); + TEST_ASSERT_EQUAL_STRING("noop", str(details, "result")); + TEST_ASSERT_EQUAL_INT(1, cJSON_GetObjectItem(details, "annotation_revision_before")->valueint); + TEST_ASSERT_EQUAL_INT(1, cJSON_GetObjectItem(details, "annotation_revision_after")->valueint); + TEST_ASSERT_NULL(strstr(record->details, "Robin")); + cJSON_Delete(details); + + free(pending); + release(&created); + release(&retried); +} + +int main(void) { + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_observation_api_%ld.db", (long)getpid()); + unlink(db_path); + snprintf(storage_dir, sizeof(storage_dir), "/tmp/lightnvr_observation_api_%ld", + (long)getpid()); + mkdir(storage_dir, 0700); + memset(&g_config, 0, sizeof(g_config)); + safe_strcpy(g_config.storage_path, storage_dir, sizeof(g_config.storage_path), 0); + safe_strcpy(g_config.mqtt_topic_prefix, "lightnvr", sizeof(g_config.mqtt_topic_prefix), 0); + g_config.mqtt_enabled = true; + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0 || event_identity_get_source(source, sizeof(source)) != 0) + return 1; + + UNITY_BEGIN(); + make_camera("feeder-east", cam_a); + make_camera("feeder-west", cam_b); + make_camera("yard", cam_c); + RUN_TEST(test_every_route_requires_authentication); + RUN_TEST(test_time_and_namespace_helpers); + RUN_TEST(test_serializer_reproduces_every_observation_fixture); + RUN_TEST(test_capabilities_scope_without_grants_or_namespace); + RUN_TEST(test_get_returns_db_rows_in_fixture_shape); + RUN_TEST(test_get_unknown_unauthorized_and_malformed); + RUN_TEST(test_list_intersects_with_authorized_cameras); + RUN_TEST(test_list_without_any_grant_is_empty); + RUN_TEST(test_list_rejects_invalid_parameters); + RUN_TEST(test_list_filters_time_and_limit_and_pages); + RUN_TEST(test_list_cursor_errors); + RUN_TEST(test_snapshot_ready_streams_the_exact_asset); + RUN_TEST(test_snapshot_ready_with_missing_file_never_substitutes); + RUN_TEST(test_snapshot_rejects_paths_outside_observation_storage); + RUN_TEST(test_snapshot_pending_unavailable_expired); + RUN_TEST(test_snapshot_authorization_d4); + RUN_TEST(test_capabilities_document); + RUN_TEST(test_put_fixture_chain_create_retry_conflict_owner); + RUN_TEST(test_put_fixture_rejections_write_nothing); + RUN_TEST(test_put_authorization_on_capture_camera); + RUN_TEST(test_put_namespace_rules); + RUN_TEST(test_put_validates_the_closed_body); + RUN_TEST(test_put_update_limit_and_absent_key_conflict); + RUN_TEST(test_get_and_list_include_classifications); + RUN_TEST(test_put_stages_a_classification_change_with_stable_message_id); + RUN_TEST(test_detection_link_summary_json); + int result = UNITY_END(); + + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char sidecar[300]; + snprintf(sidecar, sizeof(sidecar), "%s-wal", db_path); unlink(sidecar); + snprintf(sidecar, sizeof(sidecar), "%s-shm", db_path); unlink(sidecar); + char command[400]; + snprintf(command, sizeof(command), "rm -rf '%s'", storage_dir); + if (system(command) != 0) fprintf(stderr, "failed to remove %s\n", storage_dir); + return result; +} diff --git a/tests/unit/test_api_handlers_system.c b/tests/unit/test_api_handlers_system.c index a8c51bf0f..c6053fa9f 100644 --- a/tests/unit/test_api_handlers_system.c +++ b/tests/unit/test_api_handlers_system.c @@ -278,6 +278,30 @@ void test_handle_get_system_info_includes_empty_stream_storage_array(void) { http_response_free(&res); } +/* INT_01 L10: observation diagnostics ride along with system info. With + * evidence unused the pipeline is inactive and no evidence queue exists. */ +void test_handle_get_system_info_includes_inactive_observation_pipeline(void) { + http_request_t req; + http_response_t res; + http_request_init(&req); + http_response_init(&res); + + handle_get_system_info(&req, &res); + + TEST_ASSERT_EQUAL_INT(200, res.status_code); + cJSON *root = parse_response_json(&res); + cJSON *pipeline = cJSON_GetObjectItemCaseSensitive(root, "observation_pipeline"); + TEST_ASSERT_TRUE(cJSON_IsObject(pipeline)); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItemCaseSensitive(pipeline, "active"))); + cJSON *evidence = cJSON_GetObjectItemCaseSensitive(pipeline, "evidence"); + TEST_ASSERT_TRUE(cJSON_IsFalse(cJSON_GetObjectItemCaseSensitive(evidence, "queue_allocated"))); + TEST_ASSERT_EQUAL_INT(0, cJSON_GetObjectItemCaseSensitive(pipeline, "accepted")->valueint); + TEST_ASSERT_TRUE(cJSON_IsArray(cJSON_GetObjectItemCaseSensitive(pipeline, "cameras"))); + + cJSON_Delete(root); + http_response_free(&res); +} + // Regression test: linux_process.c's real collector stamps process.rss_bytes // and process.threads observations with resource_id "lightnvr" (see its // prepare_observation()), but handle_get_system_info() looked them up with a @@ -1527,6 +1551,7 @@ int main(void) { UNITY_BEGIN(); RUN_TEST(test_handle_get_system_info_includes_versions_summary); + RUN_TEST(test_handle_get_system_info_includes_inactive_observation_pipeline); RUN_TEST(test_handle_get_system_info_does_not_wait_for_go2rtc_lifecycle); RUN_TEST(test_handle_get_system_info_includes_empty_stream_storage_array); RUN_TEST(test_handle_get_system_info_reports_process_memory_and_threads); diff --git a/tests/unit/test_authorization.c b/tests/unit/test_authorization.c index 93d4f8aa6..9a8f39e05 100644 --- a/tests/unit/test_authorization.c +++ b/tests/unit/test_authorization.c @@ -349,7 +349,14 @@ void test_action_catalog_is_stable_and_complete(void) { authorization_action_catalog(&count); TEST_ASSERT_EQUAL_INT(AUTHZ_ACTION_COUNT, count); TEST_ASSERT_EQUAL_STRING("live.view", catalog[0].key); - TEST_ASSERT_EQUAL_STRING("lpr.delete", catalog[count - 1].key); + TEST_ASSERT_EQUAL_STRING("observations.annotate", catalog[count - 1].key); + /* INT_01 P2: appended at bit 19, camera-scoped and non-destructive. */ + TEST_ASSERT_EQUAL_INT(19, (int)AUTHZ_OBSERVATIONS_ANNOTATE); + TEST_ASSERT_EQUAL_STRING("lpr.delete", catalog[AUTHZ_LPR_DELETE].key); + TEST_ASSERT_TRUE(catalog[AUTHZ_OBSERVATIONS_ANNOTATE].camera_scoped); + TEST_ASSERT_FALSE(catalog[AUTHZ_OBSERVATIONS_ANNOTATE].destructive); + /* L14 routes the annotation PUT through the evaluator. */ + TEST_ASSERT_TRUE(catalog[AUTHZ_OBSERVATIONS_ANNOTATE].enforced); for (int i = 0; i < count; i++) { TEST_ASSERT_EQUAL_INT( catalog[i].action, @@ -1893,6 +1900,9 @@ void test_effective_action_mask_reflects_grants_and_roles(void) { /* Operator deliberately excludes policy administration. */ TEST_ASSERT_TRUE((mask & authorization_action_bit(AUTHZ_USERS_MANAGE)) == 0); TEST_ASSERT_TRUE((mask & authorization_action_bit(AUTHZ_SYSTEM_ADMIN)) == 0); + /* Built-in roles fail closed for observations.annotate (migration 0096). */ + TEST_ASSERT_TRUE( + (mask & authorization_action_bit(AUTHZ_OBSERVATIONS_ANNOTATE)) == 0); /* A legacy administrator holds the entire catalog. */ user_t admin; diff --git a/tests/unit/test_db_observation_classifications.c b/tests/unit/test_db_observation_classifications.c new file mode 100644 index 000000000..95bbe023b --- /dev/null +++ b/tests/unit/test_db_observation_classifications.c @@ -0,0 +1,977 @@ +/* INT_01 L13: classification annotations (migration 0096) and the + * observations.annotate action. Every contract section 10.4 rule the DB owns, + * the C3 classification fixture outcomes, transactional revision bumps, and a + * migration up/down/up that keeps authz bit indexes unique. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "unity.h" +#include "core/authorization.h" +#include "core/event_identity.h" +#include "database/db_core.h" +#include "database/db_embedded_migrations.h" +#include "database/db_observation_classifications.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "database/sqlite_migrate.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +/* Unity is built without double support. */ +#define ASSERT_NEAR(expected, actual) TEST_ASSERT_TRUE(fabs((double)(expected) - (double)(actual)) < 1e-9) + +#define WRITER_ID 41 +#define OTHER_WRITER_ID 42 +#define ADMIN_ROLE "00000000-0000-4000-8000-000000000001" + +static char db_path[256]; +static char cam[OBS_UUID_SIZE]; +static char cam_b[OBS_UUID_SIZE]; + +/* ---- helpers ---------------------------------------------------------- */ + +static int scalar(const char *sql) { + sqlite3_stmt *stmt = NULL; + int value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, value, sql); + return value; +} + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + if (rc != SQLITE_OK) fprintf(stderr, "%s: %s\n", sql, error ? error : ""); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static int changes_for(const char *observation_uuid, const char *cause) { + char sql[256]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM observation_changes WHERE " + "observation_uuid = '%s' AND cause = '%s'", observation_uuid, cause); + return scalar(sql); +} + +static int annotation_rows(const char *observation_uuid) { + char sql[200]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM observation_classifications " + "WHERE observation_uuid = '%s'", observation_uuid); + return scalar(sql); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static int64_t base_time_ms = 1794000000000LL; + +typedef struct { + char observation_uuid[OBS_UUID_SIZE]; + char asset_uuid[OBS_UUID_SIZE]; +} fixture_obs_t; + +/* One "bird" observation; ready evidence puts it at revision 2 like the C3 + * single-ready fixture. */ +static fixture_obs_t make_observation_at(const char *camera_uuid, const char *label, + int64_t observed_at_ms, + observation_asset_state_t final_state) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, camera_uuid, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = observed_at_ms; + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + safe_strcpy(batch.model, "ssd_mobilenet_v2.sod", sizeof(batch.model), 0); + batch.object_count = 1; + batch.objects[0].object_index = 0; + safe_strcpy(batch.objects[0].label, label, sizeof(batch.objects[0].label), 0); + batch.objects[0].confidence = 0.87; + batch.objects[0].has_box = true; + batch.objects[0].box_x = 0.412; batch.objects[0].box_y = 0.288; + batch.objects[0].box_width = 0.12; batch.objects[0].box_height = 0.165; + + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.origin, "inference_input", sizeof(asset.origin), 0); + safe_strcpy(asset.media_type, "image/jpeg", sizeof(asset.media_type), 0); + + observation_insert_result_t inserted; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &inserted)); + fixture_obs_t out; + safe_strcpy(out.observation_uuid, inserted.observation_uuids[0], OBS_UUID_SIZE, 0); + safe_strcpy(out.asset_uuid, inserted.asset_uuid, OBS_UUID_SIZE, 0); + + if (final_state == OBS_ASSET_PENDING) return out; + static const observation_asset_media_t media = { + .path = "/tmp/observations/x.jpg", .bytes = 4096, .width = 640, .height = 360, + }; + observation_transition_result_t transition; + if (final_state == OBS_ASSET_UNAVAILABLE) { + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + out.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_UNAVAILABLE, "encode_failed", NULL, + &transition)); + return out; + } + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + out.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &media, &transition)); + if (final_state == OBS_ASSET_EXPIRED) { + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + out.asset_uuid, OBS_ASSET_READY, OBS_ASSET_EXPIRED, "retention_expired", NULL, + &transition)); + } + return out; +} + +static fixture_obs_t make_observation(observation_asset_state_t final_state) { + return make_observation_at(cam, "bird", base_time_ms++, final_state); +} + +/* C3 01-create-automated request body. */ +static observation_classification_input_t automated_input(const fixture_obs_t *obs) { + observation_classification_input_t in; + memset(&in, 0, sizeof(in)); + in.writer_namespace = "yawamf"; + in.writer_user_id = WRITER_ID; + in.name = "species"; + in.classifier = "yawamf"; + in.model_version = "convnext-v2-2026.10"; + in.label = "Black-capped Chickadee"; + in.taxonomy_id = "inat:144815"; + in.confidence = 0.93; + in.classified_at_ms = 1794000195002LL; + in.input_asset_uuid = obs->asset_uuid; + in.input_revision = 2; + in.kind = OBS_CLS_KIND_AUTOMATED; + in.expected_revision = 0; + return in; +} + +/* C3 04-owner-confirm request body. */ +static observation_classification_input_t owner_input(const fixture_obs_t *obs) { + observation_classification_input_t in = automated_input(obs); + in.model_version = NULL; + in.label = "Carolina Chickadee"; + in.taxonomy_id = NULL; + in.confidence = 1.0; + in.classified_at_ms = 1794015641100LL; + in.input_revision = 3; + in.kind = OBS_CLS_KIND_OWNER_CONFIRMED; + in.expected_revision = 1; + return in; +} + +static int put(const fixture_obs_t *obs, const observation_classification_input_t *in, + observation_classification_result_t *result) { + memset(result, 0x5a, sizeof(*result)); + return db_observation_classification_put(obs->observation_uuid, in, result); +} + +static observation_record_t get_record(const fixture_obs_t *obs) { + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(obs->observation_uuid, &record)); + return record; +} + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets;"); + exec_sql("DROP TRIGGER IF EXISTS temp.fail_publication;"); +} +void tearDown(void) {} + +/* ---- create / no-op ---------------------------------------------------- */ + +static void test_create_automated_bumps_revision_and_stages_publication(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_record_t before = get_record(&obs); + TEST_ASSERT_EQUAL_INT(2, before.revision); + + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + TEST_ASSERT_TRUE(r.has_annotation); + TEST_ASSERT_EQUAL_INT(3, r.observation_revision); + TEST_ASSERT_TRUE(r.change_seq > 0); + TEST_ASSERT_EQUAL_STRING("yawamf/species", r.annotation.key); + TEST_ASSERT_EQUAL_STRING("yawamf", r.annotation.writer_namespace); + TEST_ASSERT_EQUAL_INT64(WRITER_ID, r.annotation.writer_user_id); + TEST_ASSERT_EQUAL_STRING("yawamf", r.annotation.classifier); + TEST_ASSERT_EQUAL_STRING("convnext-v2-2026.10", r.annotation.model_version); + TEST_ASSERT_EQUAL_STRING("Black-capped Chickadee", r.annotation.label); + TEST_ASSERT_EQUAL_STRING("inat:144815", r.annotation.taxonomy_id); + ASSERT_NEAR(0.93, r.annotation.confidence); + TEST_ASSERT_EQUAL_INT64(1794000195002LL, r.annotation.classified_at_ms); + TEST_ASSERT_EQUAL_STRING(obs.asset_uuid, r.annotation.input_asset_uuid); + TEST_ASSERT_EQUAL_INT(2, r.annotation.input_revision); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_AUTOMATED, r.annotation.kind); + TEST_ASSERT_EQUAL_INT(1, r.annotation.annotation_revision); + TEST_ASSERT_TRUE(r.annotation.created_at_ms > 0); + TEST_ASSERT_EQUAL_INT64(r.annotation.created_at_ms, r.annotation.updated_at_ms); + + /* Change row snapshots the evidence state; publication pending; same tx. */ + char where[256]; + snprintf(where, sizeof(where), "SELECT count(*) FROM observation_changes c " + "JOIN observation_publications p ON p.change_seq = c.seq WHERE c.seq = %lld " + "AND c.revision = 3 AND c.cause = 'classification' AND c.evidence_state = 'ready' " + "AND p.state = 'pending'", (long long)r.change_seq); + TEST_ASSERT_EQUAL_INT(1, scalar(where)); + char message_id[OBS_UUID_SIZE]; + char source[160]; + TEST_ASSERT_EQUAL_INT(0, event_identity_get_source(source, sizeof(source))); + TEST_ASSERT_EQUAL_INT(0, observation_message_id(source, obs.observation_uuid, 3, message_id)); + snprintf(where, sizeof(where), "SELECT count(*) FROM observation_publications " + "WHERE change_seq = %lld AND message_id = '%s'", (long long)r.change_seq, + message_id); + TEST_ASSERT_EQUAL_INT(1, scalar(where)); + + /* Detector fields are never modified. */ + observation_record_t after = get_record(&obs); + TEST_ASSERT_EQUAL_INT(3, after.revision); + TEST_ASSERT_EQUAL_STRING("bird", after.label); + ASSERT_NEAR(0.87, after.confidence); + TEST_ASSERT_TRUE(after.has_box); + ASSERT_NEAR(before.box_x, after.box_x); + ASSERT_NEAR(before.box_height, after.box_height); + TEST_ASSERT_EQUAL_STRING(before.engine_key, after.engine_key); + TEST_ASSERT_EQUAL_STRING(before.model, after.model); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, after.asset.state); +} + +static void test_identical_retry_is_noop_regardless_of_expected_revision(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t created; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &created)); + int classification_changes = changes_for(obs.observation_uuid, "classification"); + int publications = scalar("SELECT count(*) FROM observation_publications"); + + const int expected[] = {0, 1, 7}; + for (size_t i = 0; i < sizeof(expected) / sizeof(expected[0]); ++i) { + in.expected_revision = expected[i]; + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_NOOP, put(&obs, &in, &r)); + TEST_ASSERT_TRUE(r.has_annotation); + TEST_ASSERT_EQUAL_INT(1, r.annotation.annotation_revision); + TEST_ASSERT_EQUAL_INT(3, r.observation_revision); + TEST_ASSERT_EQUAL_INT64(0, r.change_seq); + TEST_ASSERT_EQUAL_INT64(created.annotation.updated_at_ms, r.annotation.updated_at_ms); + } + /* Confidence is compared to 1e-6. */ + in.confidence = 0.93 + 5e-7; + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_NOOP, put(&obs, &in, &r)); + /* Null and empty optional strings are the same stored value. */ + in.confidence = 0.93; + observation_classification_input_t nulls = owner_input(&obs); + nulls.expected_revision = 0; + nulls.name = "nulls"; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &nulls, &r)); + TEST_ASSERT_EQUAL_STRING("", r.annotation.model_version); + TEST_ASSERT_EQUAL_STRING("", r.annotation.taxonomy_id); + nulls.model_version = ""; + nulls.taxonomy_id = ""; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_NOOP, put(&obs, &nulls, &r)); + + TEST_ASSERT_EQUAL_INT(classification_changes + 1, + changes_for(obs.observation_uuid, "classification")); + TEST_ASSERT_EQUAL_INT(publications + 1, scalar("SELECT count(*) FROM observation_publications")); + TEST_ASSERT_EQUAL_INT(4, get_record(&obs).revision); +} + +/* ---- revision CAS ----------------------------------------------------- */ + +static void test_expected_revision_compare_and_set(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t r; + + /* Absent key: only 0 / omitted is accepted. */ + in.expected_revision = 1; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_REVISION_CONFLICT, put(&obs, &in, &r)); + TEST_ASSERT_FALSE(r.has_annotation); + TEST_ASSERT_EQUAL_INT(0, annotation_rows(obs.observation_uuid)); + in.expected_revision = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + + /* C3 03: a different result with a stale expected_revision returns current. */ + observation_classification_input_t changed = in; + changed.model_version = "convnext-v2-2026.11"; + changed.confidence = 0.95; + changed.classified_at_ms = 1794000242550LL; + changed.expected_revision = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_REVISION_CONFLICT, put(&obs, &changed, &r)); + TEST_ASSERT_TRUE(r.has_annotation); + TEST_ASSERT_EQUAL_STRING("convnext-v2-2026.10", r.annotation.model_version); + TEST_ASSERT_EQUAL_INT(1, r.annotation.annotation_revision); + TEST_ASSERT_EQUAL_INT(3, r.observation_revision); + changed.expected_revision = 2; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_REVISION_CONFLICT, put(&obs, &changed, &r)); + TEST_ASSERT_EQUAL_INT(3, get_record(&obs).revision); + + /* Matching revision stores it: annotation 1 -> 2, observation 3 -> 4. */ + changed.expected_revision = 1; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_UPDATED, put(&obs, &changed, &r)); + TEST_ASSERT_EQUAL_INT(2, r.annotation.annotation_revision); + TEST_ASSERT_EQUAL_INT(4, r.observation_revision); + TEST_ASSERT_EQUAL_STRING("convnext-v2-2026.11", r.annotation.model_version); + TEST_ASSERT_EQUAL_INT(2, changes_for(obs.observation_uuid, "classification")); +} + +/* ---- owner precedence ------------------------------------------------- */ + +static void test_owner_confirmed_replaces_automated_and_locks_it(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t created; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &created)); + + /* C3 04: owner correction, annotation 1 -> 2, observation 3 -> 4. */ + observation_classification_input_t owner = owner_input(&obs); + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_UPDATED, put(&obs, &owner, &r)); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_OWNER_CONFIRMED, r.annotation.kind); + TEST_ASSERT_EQUAL_STRING("Carolina Chickadee", r.annotation.label); + TEST_ASSERT_EQUAL_STRING("", r.annotation.model_version); + TEST_ASSERT_EQUAL_STRING("", r.annotation.taxonomy_id); + TEST_ASSERT_EQUAL_INT(3, r.annotation.input_revision); + TEST_ASSERT_EQUAL_INT(2, r.annotation.annotation_revision); + TEST_ASSERT_EQUAL_INT(4, r.observation_revision); + TEST_ASSERT_EQUAL_INT64(created.annotation.created_at_ms, r.annotation.created_at_ms); + TEST_ASSERT_TRUE(r.annotation.updated_at_ms >= created.annotation.updated_at_ms); + + /* C3 05: automated re-run with the right revision is still locked. */ + observation_classification_input_t rerun = in; + rerun.model_version = "convnext-v2-2026.11"; + rerun.confidence = 0.95; + rerun.input_revision = 4; + rerun.expected_revision = 2; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_OWNER_LOCKED, put(&obs, &rerun, &r)); + TEST_ASSERT_TRUE(r.has_annotation); + TEST_ASSERT_EQUAL_STRING("Carolina Chickadee", r.annotation.label); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_OWNER_CONFIRMED, r.annotation.kind); + /* Precedence is checked before the revision CAS. */ + rerun.expected_revision = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_OWNER_LOCKED, put(&obs, &rerun, &r)); + TEST_ASSERT_EQUAL_INT(4, get_record(&obs).revision); + + /* An identical owner retry is a no-op; another owner decision may replace it. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_NOOP, put(&obs, &owner, &r)); + observation_classification_input_t again = owner; + again.label = "Black-capped Chickadee"; + again.expected_revision = 2; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_UPDATED, put(&obs, &again, &r)); + TEST_ASSERT_EQUAL_INT(3, r.annotation.annotation_revision); + TEST_ASSERT_EQUAL_INT(5, r.observation_revision); + + /* owner_confirmed may also create a key directly. */ + observation_classification_input_t direct = owner_input(&obs); + direct.name = "owner-direct"; + direct.expected_revision = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &direct, &r)); +} + +/* ---- evidence --------------------------------------------------------- */ + +static void test_evidence_mismatch_rejections(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t r; + + /* C3 06: another asset. */ + in.input_asset_uuid = "99999999-9999-4999-8999-999999999999"; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_EVIDENCE_MISMATCH, put(&obs, &in, &r)); + TEST_ASSERT_EQUAL_STRING(obs.asset_uuid, r.observation_asset_uuid); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, r.evidence_state); + TEST_ASSERT_FALSE(r.has_annotation); + + /* input_revision beyond the current revision. */ + in = automated_input(&obs); + in.input_revision = 3; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_EVIDENCE_MISMATCH, put(&obs, &in, &r)); + + /* Asset UUIDs compare case-insensitively and are stored canonical. */ + char upper[OBS_UUID_SIZE]; + safe_strcpy(upper, obs.asset_uuid, sizeof(upper), 0); + for (char *c = upper; *c; ++c) if (*c >= 'a' && *c <= 'f') *c = (char)(*c - 'a' + 'A'); + in = automated_input(&obs); + in.input_asset_uuid = upper; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + TEST_ASSERT_EQUAL_STRING(obs.asset_uuid, r.annotation.input_asset_uuid); + + /* C3 07: pending evidence. */ + fixture_obs_t pending = make_observation(OBS_ASSET_PENDING); + in = automated_input(&pending); + in.input_revision = 1; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_EVIDENCE_MISMATCH, put(&pending, &in, &r)); + TEST_ASSERT_EQUAL_STRING(pending.asset_uuid, r.observation_asset_uuid); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_PENDING, r.evidence_state); + TEST_ASSERT_EQUAL_INT(0, annotation_rows(pending.observation_uuid)); + TEST_ASSERT_EQUAL_INT(1, get_record(&pending).revision); + + /* Unavailable evidence. */ + fixture_obs_t unavailable = make_observation(OBS_ASSET_UNAVAILABLE); + in = automated_input(&unavailable); + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_EVIDENCE_MISMATCH, put(&unavailable, &in, &r)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, r.evidence_state); + + /* Expired evidence still accepts a classification of the frame it was. */ + fixture_obs_t expired = make_observation(OBS_ASSET_EXPIRED); + in = automated_input(&expired); + in.input_revision = 3; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&expired, &in, &r)); + TEST_ASSERT_EQUAL_INT(4, r.observation_revision); + char sql[256]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM observation_changes WHERE seq = %lld " + "AND evidence_state = 'expired' AND evidence_reason = 'retention_expired'", + (long long)r.change_seq); + TEST_ASSERT_EQUAL_INT(1, scalar(sql)); +} + +/* ---- writer identity -------------------------------------------------- */ + +static void test_writer_mismatch_on_existing_key_is_rejected(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + + /* A later user that derives the same namespace cannot touch the key, even + * with an identical body or as owner, and learns nothing about it. */ + observation_classification_input_t other = in; + other.writer_user_id = OTHER_WRITER_ID; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_WRITER_MISMATCH, put(&obs, &other, &r)); + TEST_ASSERT_FALSE(r.has_annotation); + other = owner_input(&obs); + other.writer_user_id = OTHER_WRITER_ID; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_WRITER_MISMATCH, put(&obs, &other, &r)); + /* Writer check precedes the evidence check (contract step 2 before 4). */ + other.input_asset_uuid = "99999999-9999-4999-8999-999999999999"; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_WRITER_MISMATCH, put(&obs, &other, &r)); + TEST_ASSERT_EQUAL_INT(3, get_record(&obs).revision); + + /* The other user may still create its own key. */ + other = automated_input(&obs); + other.writer_user_id = OTHER_WRITER_ID; + other.name = "species-2"; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &other, &r)); + TEST_ASSERT_EQUAL_INT64(OTHER_WRITER_ID, r.annotation.writer_user_id); +} + +/* ---- validation and limit --------------------------------------------- */ + +static void test_validation_rejects_out_of_contract_input(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_result_t r; + char long_label[OBS_CLS_LABEL_SIZE + 1]; + memset(long_label, 'x', sizeof(long_label) - 1); + long_label[sizeof(long_label) - 1] = '\0'; + char long_segment[OBS_CLS_SEGMENT_MAX + 2]; + memset(long_segment, 'a', sizeof(long_segment) - 1); + long_segment[sizeof(long_segment) - 1] = '\0'; + char long_text[66]; + memset(long_text, 'm', sizeof(long_text) - 1); + long_text[sizeof(long_text) - 1] = '\0'; + + observation_classification_input_t in; +#define REJECT(mutation) do { \ + in = automated_input(&obs); mutation; \ + TEST_ASSERT_EQUAL_INT_MESSAGE(DB_OBS_CLS_INVALID, put(&obs, &in, &r), #mutation); \ + } while (0) + REJECT(in.name = ""); + REJECT(in.name = "Species"); + REJECT(in.name = "-species"); + REJECT(in.name = "spe/cies"); + REJECT(in.name = long_segment); + REJECT(in.writer_namespace = "YAWAMF"); + REJECT(in.writer_namespace = NULL); + REJECT(in.writer_namespace = long_segment); + REJECT(in.writer_user_id = 0); + REJECT(in.classifier = ""); + REJECT(in.classifier = long_text); + REJECT(in.model_version = long_text); + REJECT(in.taxonomy_id = long_text); + REJECT(in.label = ""); + REJECT(in.label = NULL); + REJECT(in.label = long_label); + REJECT(in.confidence = -0.01); + REJECT(in.confidence = 1.01); + REJECT(in.confidence = NAN); + REJECT(in.classified_at_ms = -1); + REJECT(in.input_asset_uuid = "not-a-uuid"); + REJECT(in.input_revision = 0); + REJECT(in.kind = (observation_classification_kind_t)7); + REJECT(in.expected_revision = -1); +#undef REJECT + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_INVALID, + db_observation_classification_put("nope", &in, &r)); + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_INVALID, + db_observation_classification_put(obs.observation_uuid, NULL, &r)); + TEST_ASSERT_EQUAL_INT(0, annotation_rows(obs.observation_uuid)); + + /* Unknown observation. */ + in = automated_input(&obs); + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_NOT_FOUND, db_observation_classification_put( + "12345678-1234-4234-8234-123456789abc", &in, &r)); + + /* Boundaries are accepted: 128-byte label, 32-byte segments, 0 and 1. */ + long_label[OBS_CLS_LABEL_SIZE - 1] = '\0'; + long_segment[OBS_CLS_SEGMENT_MAX] = '\0'; + in = automated_input(&obs); + in.label = long_label; + in.name = long_segment; + in.confidence = 0.0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + TEST_ASSERT_EQUAL_INT(128, (int)strlen(r.annotation.label)); + TEST_ASSERT_EQUAL_INT(7 + OBS_CLS_SEGMENT_MAX, (int)strlen(r.annotation.key)); + + TEST_ASSERT_TRUE(observation_classification_segment_is_valid("a")); + TEST_ASSERT_TRUE(observation_classification_segment_is_valid("0_a.b-c")); + TEST_ASSERT_FALSE(observation_classification_segment_is_valid("_a")); + TEST_ASSERT_FALSE(observation_classification_segment_is_valid(NULL)); + observation_classification_kind_t kind; + TEST_ASSERT_EQUAL_INT(0, observation_classification_kind_parse("owner_confirmed", &kind)); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_OWNER_CONFIRMED, kind); + TEST_ASSERT_EQUAL_INT(-1, observation_classification_kind_parse("owner", &kind)); + TEST_ASSERT_EQUAL_STRING("automated", + observation_classification_kind_name(OBS_CLS_KIND_AUTOMATED)); +} + +static void test_ninth_key_hits_the_annotation_limit(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_result_t r; + char names[OBS_CLS_MAX_PER_OBSERVATION + 1][16]; + for (int i = 0; i <= OBS_CLS_MAX_PER_OBSERVATION; ++i) { + snprintf(names[i], sizeof(names[i]), "k%d", i); + observation_classification_input_t in = automated_input(&obs); + in.name = names[i]; + int expected = i < OBS_CLS_MAX_PER_OBSERVATION ? DB_OBS_CLS_CREATED : DB_OBS_CLS_LIMIT; + TEST_ASSERT_EQUAL_INT(expected, put(&obs, &in, &r)); + } + TEST_ASSERT_EQUAL_INT(OBS_CLS_MAX_PER_OBSERVATION, r.limit); + TEST_ASSERT_EQUAL_INT(OBS_CLS_MAX_PER_OBSERVATION, annotation_rows(obs.observation_uuid)); + TEST_ASSERT_EQUAL_INT(2 + OBS_CLS_MAX_PER_OBSERVATION, get_record(&obs).revision); + + /* Updating an existing key at the limit still works. */ + observation_classification_input_t in = automated_input(&obs); + in.name = names[0]; + in.label = "Tufted Titmouse"; + in.expected_revision = 1; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_UPDATED, put(&obs, &in, &r)); +} + +/* ---- atomicity -------------------------------------------------------- */ + +static void test_publication_failure_rolls_back_the_annotation(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + int changes = scalar("SELECT count(*) FROM observation_changes"); + exec_sql("CREATE TEMP TRIGGER fail_publication BEFORE INSERT ON observation_publications " + "BEGIN SELECT RAISE(ABORT, 'injected'); END;"); + observation_classification_input_t in = automated_input(&obs); + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_ERROR, put(&obs, &in, &r)); + exec_sql("DROP TRIGGER temp.fail_publication;"); + TEST_ASSERT_EQUAL_INT(0, annotation_rows(obs.observation_uuid)); + TEST_ASSERT_EQUAL_INT(2, get_record(&obs).revision); + TEST_ASSERT_EQUAL_INT(changes, scalar("SELECT count(*) FROM observation_changes")); + /* The same request then succeeds as a create. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + TEST_ASSERT_EQUAL_INT(3, r.observation_revision); +} + +/* ---- detection -> observation link (L15) ------------------------------ */ + +#define LINK_TS 1795000000LL + +static observation_detection_link_t link_one(const char *camera_uuid, const char *label, + int64_t timestamp) { + observation_detection_ref_t ref = {.label = label, .timestamp = timestamp}; + observation_detection_link_t out; + memset(&out, 0x5a, sizeof(out)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_link_detections(camera_uuid, &ref, 1, &out)); + return out; +} + +static void test_link_detections_nearest_within_window_case_insensitive(void) { + fixture_obs_t a = make_observation_at(cam, "bird", LINK_TS * 1000 + 400, OBS_ASSET_READY); + fixture_obs_t b = make_observation_at(cam, "Bird", (LINK_TS + 1) * 1000 + 100, OBS_ASSET_READY); + fixture_obs_t c = make_observation_at(cam, "bird", (LINK_TS - 2) * 1000 + 999, OBS_ASSET_READY); + fixture_obs_t d = make_observation_at(cam, "person", LINK_TS * 1000, OBS_ASSET_READY); + + observation_detection_ref_t refs[] = { + {"BIRD", LINK_TS}, /* a (same second) beats b (1 s away) */ + {"bird", LINK_TS + 1}, /* b (same second) beats a (1 s away) */ + {"bird", LINK_TS - 3}, /* c is exactly 1 s away: inside the window */ + {"bird", LINK_TS + 3}, /* b is 2 s away: outside the window */ + {"cat", LINK_TS}, /* label must match */ + {"person", LINK_TS}, + {NULL, LINK_TS}, /* no label: never linked */ + }; + size_t n = sizeof(refs) / sizeof(refs[0]); + observation_detection_link_t out[sizeof(refs) / sizeof(refs[0])]; + memset(out, 0x5a, sizeof(out)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_link_detections(cam, refs, n, out)); + TEST_ASSERT_TRUE(out[0].linked); + TEST_ASSERT_EQUAL_STRING(a.observation_uuid, out[0].observation_uuid); + TEST_ASSERT_TRUE(out[1].linked); + TEST_ASSERT_EQUAL_STRING(b.observation_uuid, out[1].observation_uuid); + TEST_ASSERT_TRUE(out[2].linked); + TEST_ASSERT_EQUAL_STRING(c.observation_uuid, out[2].observation_uuid); + TEST_ASSERT_FALSE(out[3].linked); + TEST_ASSERT_EQUAL_STRING("", out[3].observation_uuid); + TEST_ASSERT_FALSE(out[4].linked); + TEST_ASSERT_TRUE(out[5].linked); + TEST_ASSERT_EQUAL_STRING(d.observation_uuid, out[5].observation_uuid); + TEST_ASSERT_FALSE(out[6].linked); + for (size_t i = 0; i < n; ++i) { + TEST_ASSERT_FALSE(out[i].has_classification); /* nothing annotated yet */ + TEST_ASSERT_FALSE(out[i].owner_confirmed); + } + + /* Equal distance: the earliest observation wins, deterministically. */ + observation_detection_link_t tie = link_one(cam, "bird", LINK_TS - 1); + TEST_ASSERT_TRUE(tie.linked); + TEST_ASSERT_EQUAL_STRING(c.observation_uuid, tie.observation_uuid); +} + +static void test_link_detections_never_crosses_cameras(void) { + fixture_obs_t other = make_observation_at(cam_b, "bird", LINK_TS * 1000, OBS_ASSET_READY); + observation_classification_input_t in = automated_input(&other); + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&other, &in, &r)); + + /* Same second and label, but captured by another camera: not linked. */ + observation_detection_link_t mine = link_one(cam, "bird", LINK_TS); + TEST_ASSERT_FALSE(mine.linked); + TEST_ASSERT_FALSE(mine.has_classification); + + observation_detection_link_t theirs = link_one(cam_b, "bird", LINK_TS); + TEST_ASSERT_TRUE(theirs.linked); + TEST_ASSERT_EQUAL_STRING(other.observation_uuid, theirs.observation_uuid); + TEST_ASSERT_TRUE(theirs.has_classification); +} + +static void test_link_detections_prefers_owner_confirmed_annotation(void) { + fixture_obs_t obs = make_observation_at(cam, "bird", LINK_TS * 1000 + 10, OBS_ASSET_READY); + observation_detection_link_t link = link_one(cam, "bird", LINK_TS); + TEST_ASSERT_TRUE(link.linked); + TEST_ASSERT_FALSE(link.has_classification); + TEST_ASSERT_FALSE(link.owner_confirmed); + + observation_classification_input_t automated = automated_input(&obs); + observation_classification_result_t r; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &automated, &r)); + link = link_one(cam, "bird", LINK_TS); + TEST_ASSERT_TRUE(link.has_classification); + TEST_ASSERT_FALSE(link.owner_confirmed); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_AUTOMATED, link.classification.kind); + TEST_ASSERT_EQUAL_STRING("Black-capped Chickadee", link.classification.label); + TEST_ASSERT_EQUAL_STRING("yawamf", link.classification.classifier); + ASSERT_NEAR(0.93, link.classification.confidence); + + /* A newer automated key never outranks an owner decision. */ + observation_classification_input_t owner = owner_input(&obs); + owner.name = "owner"; + owner.expected_revision = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &owner, &r)); + observation_classification_input_t later = automated_input(&obs); + later.name = "later"; + later.label = "House Sparrow"; + later.input_revision = 4; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &later, &r)); + exec_sql("UPDATE observation_classifications SET updated_at_ms = CASE key " + "WHEN 'yawamf/owner' THEN 100 ELSE 900 END;"); + link = link_one(cam, "bird", LINK_TS); + TEST_ASSERT_TRUE(link.has_classification); + TEST_ASSERT_TRUE(link.owner_confirmed); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_OWNER_CONFIRMED, link.classification.kind); + TEST_ASSERT_EQUAL_STRING("Carolina Chickadee", link.classification.label); + TEST_ASSERT_EQUAL_STRING("yawamf/owner", link.classification.key); + + /* The detector fields are untouched by the join. */ + observation_record_t record = get_record(&obs); + TEST_ASSERT_EQUAL_STRING("bird", record.label); + ASSERT_NEAR(0.87, record.confidence); +} + +static void test_link_detections_rejects_bad_arguments(void) { + observation_detection_ref_t ref = {.label = "bird", .timestamp = LINK_TS}; + observation_detection_link_t out; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_link_detections(NULL, &ref, 1, &out)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_link_detections("not-a-uuid", &ref, 1, &out)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_link_detections(cam, NULL, 1, &out)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_link_detections(cam, &ref, 1, NULL)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_link_detections(cam, NULL, 0, NULL)); +} + +/* ---- reads ------------------------------------------------------------ */ + +static void test_list_separates_automated_and_owner_by_updated_at(void) { + fixture_obs_t obs = make_observation(OBS_ASSET_READY); + observation_classification_set_t set; + memset(&set, 0x5a, sizeof(set)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_classifications_list(obs.observation_uuid, &set)); + TEST_ASSERT_EQUAL_INT(0, set.automated_count); + TEST_ASSERT_EQUAL_INT(0, set.owner_confirmed_count); + + const char *names[] = {"a1", "o1", "a2", "o2", "a3"}; + observation_classification_result_t r; + for (size_t i = 0; i < 5; ++i) { + observation_classification_input_t in = + names[i][0] == 'o' ? owner_input(&obs) : automated_input(&obs); + in.name = names[i]; + in.expected_revision = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_CREATED, put(&obs, &in, &r)); + } + /* Deterministic updated_at ordering for the assertion. */ + exec_sql("UPDATE observation_classifications SET updated_at_ms = CASE key " + "WHEN 'yawamf/a1' THEN 300 WHEN 'yawamf/a2' THEN 100 WHEN 'yawamf/a3' THEN 200 " + "WHEN 'yawamf/o1' THEN 100 WHEN 'yawamf/o2' THEN 900 END;"); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_classifications_list(obs.observation_uuid, &set)); + TEST_ASSERT_EQUAL_INT(3, set.automated_count); + TEST_ASSERT_EQUAL_STRING("yawamf/a1", set.automated[0].key); + TEST_ASSERT_EQUAL_STRING("yawamf/a3", set.automated[1].key); + TEST_ASSERT_EQUAL_STRING("yawamf/a2", set.automated[2].key); + TEST_ASSERT_EQUAL_INT(2, set.owner_confirmed_count); + TEST_ASSERT_EQUAL_STRING("yawamf/o2", set.owner_confirmed[0].key); + TEST_ASSERT_EQUAL_STRING("yawamf/o1", set.owner_confirmed[1].key); + TEST_ASSERT_EQUAL_INT(OBS_CLS_KIND_OWNER_CONFIRMED, set.owner_confirmed[0].kind); + TEST_ASSERT_EQUAL_STRING("Carolina Chickadee", set.owner_confirmed[0].label); + + /* An owner decision moves a key between lists; each key is in exactly one. */ + observation_classification_input_t promote = owner_input(&obs); + promote.name = "a1"; + promote.expected_revision = 1; + TEST_ASSERT_EQUAL_INT(DB_OBS_CLS_UPDATED, put(&obs, &promote, &r)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_classifications_list(obs.observation_uuid, &set)); + TEST_ASSERT_EQUAL_INT(2, set.automated_count); + TEST_ASSERT_EQUAL_INT(3, set.owner_confirmed_count); + TEST_ASSERT_EQUAL_STRING("yawamf/a1", set.owner_confirmed[0].key); + + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_classifications_list("bad", &set)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_classifications_list(obs.observation_uuid, NULL)); + + /* Observation deletion (history pruning) removes its annotations. */ + char sql[200]; + snprintf(sql, sizeof(sql), "DELETE FROM object_observations WHERE uuid = '%s';", + obs.observation_uuid); + exec_sql(sql); + TEST_ASSERT_EQUAL_INT(0, annotation_rows(obs.observation_uuid)); +} + +/* ---- authorization action and migration ------------------------------- */ + +static void test_annotate_action_is_bit_19_and_administrator_only(void) { + TEST_ASSERT_EQUAL_INT(19, (int)AUTHZ_OBSERVATIONS_ANNOTATE); + TEST_ASSERT_EQUAL_INT(AUTHZ_OBSERVATIONS_ANNOTATE, + authorization_action_from_key("observations.annotate")); + const authorization_action_metadata_t *meta = + authorization_action_metadata(AUTHZ_OBSERVATIONS_ANNOTATE); + TEST_ASSERT_NOT_NULL(meta); + TEST_ASSERT_EQUAL_STRING("Integrations", meta->category); + TEST_ASSERT_TRUE(meta->camera_scoped); + TEST_ASSERT_FALSE(meta->destructive); + + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM authz_actions WHERE " + "action_key = 'observations.annotate' AND bit_index = 19 AND sort_order = 200 " + "AND camera_scoped = 1 AND destructive = 0 AND category = 'Integrations'")); + TEST_ASSERT_EQUAL_INT(19, scalar("SELECT max(bit_index) FROM authz_actions")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM (SELECT bit_index FROM authz_actions " + "GROUP BY bit_index HAVING count(*) > 1)")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM authz_role_actions " + "WHERE action_key = 'observations.annotate'")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM authz_role_actions WHERE " + "action_key = 'observations.annotate' AND role_uuid = '" ADMIN_ROLE "'")); +} + +static sqlite3 *mem_db; +static sqlite_migrate_t *mem_migration; + +static int mem_scalar(const char *sql) { + sqlite3_stmt *stmt = NULL; + int value = -999; + if (sqlite3_prepare_v2(mem_db, sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, value, sql); + return value; +} + +static char *mem_text(const char *sql) { + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, sqlite3_prepare_v2(mem_db, sql, -1, &stmt, NULL), sql); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_ROW, sqlite3_step(stmt), sql); + const unsigned char *value = sqlite3_column_text(stmt, 0); + char *copy = strdup(value ? (const char *)value : ""); + sqlite3_finalize(stmt); + TEST_ASSERT_NOT_NULL(copy); + return copy; +} + +static void mem_exec(const char *sql, int expected) { + int rc = sqlite3_exec(mem_db, sql, NULL, NULL, NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(expected, rc & 0xff, sql); +} + +static size_t index_of(const char *version) { + for (size_t i = 0; i < EMBEDDED_MIGRATIONS_COUNT; ++i) + if (strcmp(embedded_migrations_data[i].version, version) == 0) return i; + TEST_FAIL_MESSAGE(version); + return 0; +} + +static void mem_migrate_first(size_t count) { + migrate_free(mem_migration); + migrate_config_t config = {.embedded_migrations = embedded_migrations_data, + .embedded_count = count}; + mem_migration = migrate_init(mem_db, &config); + TEST_ASSERT_NOT_NULL(mem_migration); + TEST_ASSERT_EQUAL_INT(0, migrate_up(mem_migration, NULL)); +} + +static char *mem_schema(void) { + return mem_text("SELECT coalesce(group_concat(type || ' ' || name || ' ' || coalesce(sql, ''), " + "char(10)), '') FROM (SELECT * FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' " + "AND name <> 'schema_migrations' ORDER BY type, name)"); +} + +static char *mem_authz(void) { + return mem_text("SELECT coalesce(group_concat(line, ','), '') FROM (SELECT action_key || ':' || " + "bit_index AS line FROM authz_actions ORDER BY bit_index) UNION ALL " + "SELECT coalesce(group_concat(role_uuid || ':' || action_key, ','), '') FROM " + "(SELECT * FROM authz_role_actions ORDER BY role_uuid, action_key)"); +} + +static void test_migration_0096_up_down_up(void) { + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &mem_db)); + mem_exec("PRAGMA foreign_keys = ON;", SQLITE_OK); + size_t through_0095 = index_of("0095") + 1; + size_t through_0096 = index_of("0096") + 1; + TEST_ASSERT_EQUAL_size_t(through_0095 + 1, through_0096); + + mem_migrate_first(through_0095); + /* Bit 19 must still be free before 0096 (no later migration took it). */ + TEST_ASSERT_EQUAL_INT(18, mem_scalar("SELECT max(bit_index) FROM authz_actions")); + TEST_ASSERT_EQUAL_INT(0, mem_scalar("SELECT count(*) FROM authz_actions WHERE bit_index = 19")); + char *before = mem_schema(); + char *before_authz = mem_authz(); + + mem_migrate_first(through_0096); + char *upgraded = mem_schema(); + TEST_ASSERT_TRUE(strcmp(before, upgraded) != 0); + TEST_ASSERT_EQUAL_INT(1, mem_scalar("SELECT count(*) FROM sqlite_master WHERE type = 'table' " + "AND name = 'observation_classifications'")); + TEST_ASSERT_EQUAL_INT(0, mem_scalar("SELECT count(*) FROM (SELECT bit_index FROM authz_actions " + "GROUP BY bit_index HAVING count(*) > 1)")); + TEST_ASSERT_EQUAL_INT(19, mem_scalar("SELECT bit_index FROM authz_actions " + "WHERE action_key = 'observations.annotate'")); + /* The unique index rejects any second action at bit 19. */ + mem_exec("INSERT INTO authz_actions (action_key, category, description, camera_scoped, " + "destructive, sort_order, bit_index) VALUES ('x.y', 'X', 'x', 1, 0, 210, 19);", + SQLITE_CONSTRAINT); + + /* Constraints on a seeded row. */ + mem_exec("INSERT INTO observation_assets (uuid, camera_uuid, state, created_at_ms, updated_at_ms) " + "VALUES ('88888888-8888-4888-8888-888888888888', 'c', 'ready', 1, 1);", SQLITE_OK); + mem_exec("INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, " + "object_index, observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, " + "asset_uuid, revision) VALUES ('55555555-5555-4555-8555-555555555555', 'c', 's', 'b', 0, 1, " + "1, 'e', 'object', 'bird', 0.8, '88888888-8888-4888-8888-888888888888', 2);", SQLITE_OK); +#define CLS_ROW(key, ns, kind, conf) \ + "INSERT INTO observation_classifications (observation_uuid, key, writer_namespace, " \ + "writer_user_id, classifier, label, confidence, classified_at_ms, input_asset_uuid, " \ + "input_revision, kind, annotation_revision, created_at_ms, updated_at_ms) VALUES " \ + "('55555555-5555-4555-8555-555555555555', '" key "', '" ns "', 1, 'yawamf', 'Chickadee', " \ + conf ", 1, '88888888-8888-4888-8888-888888888888', 2, '" kind "', 1, 1, 1);" + mem_exec(CLS_ROW("yawamf/species", "yawamf", "automated", "0.9"), SQLITE_OK); + mem_exec(CLS_ROW("yawamf/species", "yawamf", "automated", "0.9"), SQLITE_CONSTRAINT); + mem_exec(CLS_ROW("other/species", "yawamf", "automated", "0.9"), SQLITE_CONSTRAINT); + mem_exec(CLS_ROW("yawamf/s2", "yawamf", "owner", "0.9"), SQLITE_CONSTRAINT); + mem_exec(CLS_ROW("yawamf/s3", "yawamf", "automated", "1.5"), SQLITE_CONSTRAINT); + mem_exec(CLS_ROW("yawamf/s4", "yawamf", "owner_confirmed", "1.0"), SQLITE_OK); +#undef CLS_ROW + + /* Down restores the 0095 schema and authz catalog, dropping annotations. */ + TEST_ASSERT_EQUAL_INT(1, migrate_down(mem_migration)); + char *after_down = mem_schema(); + char *after_down_authz = mem_authz(); + TEST_ASSERT_EQUAL_STRING(before, after_down); + TEST_ASSERT_EQUAL_STRING(before_authz, after_down_authz); + + /* Up again reaches the same schema and catalog. */ + mem_migrate_first(through_0096); + char *reapplied = mem_schema(); + TEST_ASSERT_EQUAL_STRING(upgraded, reapplied); + TEST_ASSERT_EQUAL_INT(0, mem_scalar("SELECT count(*) FROM observation_classifications")); + TEST_ASSERT_EQUAL_INT(1, mem_scalar("SELECT count(*) FROM authz_role_actions " + "WHERE action_key = 'observations.annotate'")); + + free(before); free(before_authz); free(upgraded); free(after_down); + free(after_down_authz); free(reapplied); + migrate_free(mem_migration); mem_migration = NULL; + sqlite3_close(mem_db); mem_db = NULL; +} + +int main(void) { + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_obs_classifications_%ld.db", (long)getpid()); + unlink(db_path); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0) return 1; + + UNITY_BEGIN(); + make_camera("cls_cam", cam); + make_camera("cls_cam_b", cam_b); + RUN_TEST(test_create_automated_bumps_revision_and_stages_publication); + RUN_TEST(test_identical_retry_is_noop_regardless_of_expected_revision); + RUN_TEST(test_expected_revision_compare_and_set); + RUN_TEST(test_owner_confirmed_replaces_automated_and_locks_it); + RUN_TEST(test_evidence_mismatch_rejections); + RUN_TEST(test_writer_mismatch_on_existing_key_is_rejected); + RUN_TEST(test_validation_rejects_out_of_contract_input); + RUN_TEST(test_ninth_key_hits_the_annotation_limit); + RUN_TEST(test_publication_failure_rolls_back_the_annotation); + RUN_TEST(test_list_separates_automated_and_owner_by_updated_at); + RUN_TEST(test_link_detections_nearest_within_window_case_insensitive); + RUN_TEST(test_link_detections_never_crosses_cameras); + RUN_TEST(test_link_detections_prefers_owner_confirmed_annotation); + RUN_TEST(test_link_detections_rejects_bad_arguments); + RUN_TEST(test_annotate_action_is_bit_19_and_administrator_only); + RUN_TEST(test_migration_0096_up_down_up); + int result = UNITY_END(); + + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char sidecar[300]; + snprintf(sidecar, sizeof(sidecar), "%s-wal", db_path); unlink(sidecar); + snprintf(sidecar, sizeof(sidecar), "%s-shm", db_path); unlink(sidecar); + return result; +} diff --git a/tests/unit/test_db_observations.c b/tests/unit/test_db_observations.c new file mode 100644 index 000000000..3dd8a6b53 --- /dev/null +++ b/tests/unit/test_db_observations.c @@ -0,0 +1,843 @@ +/* INT_01 L2: observation DB layer. Idempotent batch writes, compare-and-set + * evidence transitions, the section 7.3 cursor page algorithm, publication + * staging and history pruning, against a real migrated database. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include "unity.h" +#include "core/event_identity.h" +#include "database/db_core.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +static char db_path[256]; +static char cam_a[OBS_UUID_SIZE]; +static char cam_b[OBS_UUID_SIZE]; +static char source[128]; + +/* ---- helpers ---------------------------------------------------------- */ + +static int scalar(const char *sql) { + sqlite3_stmt *stmt = NULL; + int value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, value, sql); + return value; +} + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + if (rc != SQLITE_OK) fprintf(stderr, "%s: %s\n", sql, error ? error : ""); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static int count_where(const char *table, const char *where) { + char sql[512]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM %s WHERE %s", table, where); + return scalar(sql); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static int64_t base_time_ms = 1793000000000LL; + +static observation_batch_input_t make_batch(const char *camera, int objects, + const char *label) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, camera, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = base_time_ms++; + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + safe_strcpy(batch.model, "ssd_mobilenet_v2.sod", sizeof(batch.model), 0); + batch.object_count = objects; + for (int i = 0; i < objects; ++i) { + observation_object_input_t *object = &batch.objects[i]; + object->object_index = i; + safe_strcpy(object->label, label, sizeof(object->label), 0); + object->confidence = 0.8; + object->has_box = true; + object->box_x = 0.1; object->box_y = 0.2; + object->box_width = 0.3; object->box_height = 0.4; + } + return batch; +} + +static observation_asset_input_t pending_asset(void) { + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.origin, "inference_input", sizeof(asset.origin), 0); + safe_strcpy(asset.media_type, "image/jpeg", sizeof(asset.media_type), 0); + safe_strcpy(asset.transform_json, "{\"mode\":\"none\"}", sizeof(asset.transform_json), 0); + return asset; +} + +static observation_insert_result_t insert_ok(const char *camera, int objects, + const char *label) { + observation_batch_input_t batch = make_batch(camera, objects, label); + observation_asset_input_t asset = pending_asset(); + observation_insert_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &result)); + return result; +} + +static const observation_asset_media_t ready_media = { + .path = "/tmp/observations/x.jpg", .bytes = 4096, .width = 640, .height = 360, + .expires_at_ms = 0, +}; + +static int list_page(const observation_list_query_t *query, const char *cursor, + observation_page_t *page) { + memset(page, 0, sizeof(*page)); + return db_observations_list(query, cursor, page); +} + +static observation_list_query_t all_query(int limit) { + observation_list_query_t query; + memset(&query, 0, sizeof(query)); + query.all_cameras = true; + query.limit = limit; + return query; +} + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets;"); + exec_sql("DROP TRIGGER IF EXISTS temp.fail_publication;"); +} +void tearDown(void) {} + +/* ---- insert ----------------------------------------------------------- */ + +static void test_batch_insert_writes_shared_asset_changes_and_publications(void) { + observation_insert_result_t result = insert_ok(cam_a, 2, "bird"); + TEST_ASSERT_EQUAL_INT(2, result.object_count); + TEST_ASSERT_TRUE(lightnvr_uuid_is_valid(result.asset_uuid)); + TEST_ASSERT_TRUE(result.last_seq > 0); + + char where[256]; + snprintf(where, sizeof(where), "asset_uuid = '%s' AND revision = 1", result.asset_uuid); + TEST_ASSERT_EQUAL_INT(2, count_where("object_observations", where)); + snprintf(where, sizeof(where), "uuid = '%s' AND state = 'pending' AND reason IS NULL", + result.asset_uuid); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_assets", where)); + + for (int i = 0; i < 2; ++i) { + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(result.observation_uuids[i], &record)); + TEST_ASSERT_EQUAL_STRING(cam_a, record.camera_uuid); + TEST_ASSERT_EQUAL_STRING("cam_a", record.stream_name_at_capture); + TEST_ASSERT_EQUAL_INT(i, record.object_index); + TEST_ASSERT_EQUAL_INT(1, record.revision); + TEST_ASSERT_EQUAL_STRING(result.asset_uuid, record.asset.uuid); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_PENDING, record.asset.state); + TEST_ASSERT_TRUE(record.has_box); + TEST_ASSERT_TRUE(record.box_width > 0.2999999 && record.box_width < 0.3000001); + + snprintf(where, sizeof(where), + "observation_uuid = '%s' AND revision = 1 AND cause = 'created' " + "AND evidence_state = 'pending' AND evidence_reason IS NULL", + result.observation_uuids[i]); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_changes", where)); + + char message_id[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_message_id(source, result.observation_uuids[i], + 1, message_id)); + snprintf(where, sizeof(where), + "message_id = '%s' AND state = 'pending' AND attempts = 0 AND change_seq = " + "(SELECT seq FROM observation_changes WHERE observation_uuid = '%s')", + message_id, result.observation_uuids[i]); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_publications", where)); + } +} + +static void test_duplicate_batch_retry_is_a_noop_returning_existing_ids(void) { + observation_batch_input_t batch = make_batch(cam_a, 3, "bird"); + observation_asset_input_t asset = pending_asset(); + observation_insert_result_t first, second; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &first)); + int changes = scalar("SELECT count(*) FROM observation_changes"); + int assets = scalar("SELECT count(*) FROM observation_assets"); + + TEST_ASSERT_EQUAL_INT(DB_OBS_DUPLICATE, db_observation_insert_batch(&batch, &asset, &second)); + TEST_ASSERT_EQUAL_INT(3, second.object_count); + TEST_ASSERT_EQUAL_STRING(first.asset_uuid, second.asset_uuid); + for (int i = 0; i < 3; ++i) + TEST_ASSERT_EQUAL_STRING(first.observation_uuids[i], second.observation_uuids[i]); + TEST_ASSERT_EQUAL_INT64(0, second.last_seq); + TEST_ASSERT_EQUAL_INT(changes, scalar("SELECT count(*) FROM observation_changes")); + TEST_ASSERT_EQUAL_INT(assets, scalar("SELECT count(*) FROM observation_assets")); + + /* The same batch ID claimed by another camera is a conflict, not a retry. */ + safe_strcpy(batch.camera_uuid, cam_b, sizeof(batch.camera_uuid), 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_CONFLICT, db_observation_insert_batch(&batch, &asset, &second)); +} + +static void test_insert_validation_rejects_contract_violations(void) { + observation_insert_result_t result; + observation_asset_input_t asset = pending_asset(); + observation_batch_input_t batch; + + batch = make_batch("99999999-9999-4999-8999-999999999999", 1, "bird"); + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_insert_batch(&batch, &asset, &result)); + + batch = make_batch(cam_a, 2, "bird"); + batch.objects[1].object_index = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 1, "bird"); + batch.objects[0].object_index = 20; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 1, "bird"); + batch.objects[0].confidence = 1.5; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 1, "bird"); + batch.objects[0].box_x = 0.9; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 1, ""); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 1, "bird"); + safe_strcpy(batch.engine_type, "motion", sizeof(batch.engine_type), 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 1, "bird"); + safe_strcpy(batch.model, "/var/lib/models/x.sod", sizeof(batch.model), 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + batch = make_batch(cam_a, 0, "bird"); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + + batch = make_batch(cam_a, 1, "bird"); + asset.state = OBS_ASSET_READY; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + asset.state = OBS_ASSET_UNAVAILABLE; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + safe_strcpy(asset.reason, "retention_expired", sizeof(asset.reason), 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.reason, "queue_full", sizeof(asset.reason), 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_insert_batch(&batch, &asset, &result)); + + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM observation_assets")); + + /* A dropped frame is created directly as unavailable(queue_full) with no box. */ + asset = pending_asset(); + asset.state = OBS_ASSET_UNAVAILABLE; + safe_strcpy(asset.reason, "queue_full", sizeof(asset.reason), 0); + batch.objects[0].has_box = false; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &result)); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_changes", + "evidence_state = 'unavailable' AND evidence_reason = 'queue_full'")); +} + +/* ---- transitions ------------------------------------------------------ */ + +static void test_transition_bumps_every_sharing_observation(void) { + observation_insert_result_t inserted = insert_ok(cam_a, 2, "bird"); + observation_transition_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + inserted.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &result)); + TEST_ASSERT_EQUAL_INT(2, result.observations_bumped); + TEST_ASSERT_TRUE(result.last_seq > inserted.last_seq); + + for (int i = 0; i < 2; ++i) { + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(inserted.observation_uuids[i], &record)); + TEST_ASSERT_EQUAL_INT(2, record.revision); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, record.asset.state); + TEST_ASSERT_EQUAL_INT(640, record.asset.width); + TEST_ASSERT_EQUAL_INT64(4096, record.asset.bytes); + TEST_ASSERT_EQUAL_STRING(ready_media.path, record.asset.path); + char where[256], message_id[OBS_UUID_SIZE]; + snprintf(where, sizeof(where), + "observation_uuid = '%s' AND revision = 2 AND cause = 'evidence' " + "AND evidence_state = 'ready'", inserted.observation_uuids[i]); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_changes", where)); + TEST_ASSERT_EQUAL_INT(0, observation_message_id(source, inserted.observation_uuids[i], + 2, message_id)); + snprintf(where, sizeof(where), "message_id = '%s'", message_id); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_publications", where)); + } + + /* ready -> expired keeps metadata and records the reason at that revision. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + inserted.asset_uuid, OBS_ASSET_READY, OBS_ASSET_EXPIRED, "retention_expired", NULL, &result)); + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(inserted.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT(3, record.revision); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, record.asset.state); + TEST_ASSERT_EQUAL_STRING("retention_expired", record.asset.reason); + TEST_ASSERT_EQUAL_INT(640, record.asset.width); + TEST_ASSERT_EQUAL_INT(2, count_where("observation_changes", + "revision = 3 AND evidence_state = 'expired' AND evidence_reason = 'retention_expired'")); +} + +static void test_transition_rejects_invalid_edges_and_reasons(void) { + observation_insert_result_t inserted = insert_ok(cam_a, 1, "bird"); + observation_transition_result_t result; + const char *asset = inserted.asset_uuid; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_EXPIRED, "deleted", NULL, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_UNAVAILABLE, "queue_full", NULL, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_UNAVAILABLE, "bogus", NULL, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, NULL, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_READY, "write_failed", &ready_media, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, db_observation_asset_transition( + asset, OBS_ASSET_UNAVAILABLE, OBS_ASSET_READY, NULL, &ready_media, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_asset_transition( + "99999999-9999-4999-8999-999999999999", OBS_ASSET_PENDING, OBS_ASSET_READY, + NULL, &ready_media, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_UNAVAILABLE, "encode_failed", NULL, &result)); + /* Terminal: a later attempt from pending loses the CAS. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_CAS_LOST, db_observation_asset_transition( + asset, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &result)); + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(inserted.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT(2, record.revision); +} + +typedef struct { + const char *asset; + observation_asset_state_t to; + const char *reason; + const observation_asset_media_t *media; + pthread_barrier_t *barrier; + int rc; +} race_arg_t; + +static void *race_worker(void *opaque) { + race_arg_t *arg = opaque; + observation_transition_result_t result; + pthread_barrier_wait(arg->barrier); + arg->rc = db_observation_asset_transition(arg->asset, OBS_ASSET_PENDING, arg->to, + arg->reason, arg->media, &result); + return NULL; +} + +static void test_cas_transition_race_has_exactly_one_winner(void) { + for (int round = 0; round < 20; ++round) { + observation_insert_result_t inserted = insert_ok(cam_a, 2, "bird"); + pthread_barrier_t barrier; + pthread_barrier_init(&barrier, NULL, 2); + race_arg_t a = {inserted.asset_uuid, OBS_ASSET_READY, NULL, &ready_media, &barrier, -99}; + race_arg_t b = {inserted.asset_uuid, OBS_ASSET_UNAVAILABLE, "interrupted", NULL, &barrier, -99}; + pthread_t ta, tb; + pthread_create(&ta, NULL, race_worker, &a); + pthread_create(&tb, NULL, race_worker, &b); + pthread_join(ta, NULL); + pthread_join(tb, NULL); + pthread_barrier_destroy(&barrier); + TEST_ASSERT_TRUE((a.rc == DB_OBS_OK && b.rc == DB_OBS_CAS_LOST) || + (a.rc == DB_OBS_CAS_LOST && b.rc == DB_OBS_OK)); + char where[160]; + for (int i = 0; i < 2; ++i) { + snprintf(where, sizeof(where), "observation_uuid = '%s'", inserted.observation_uuids[i]); + TEST_ASSERT_EQUAL_INT(2, count_where("observation_changes", where)); + snprintf(where, sizeof(where), "uuid = '%s' AND revision = 2", inserted.observation_uuids[i]); + TEST_ASSERT_EQUAL_INT(1, count_where("object_observations", where)); + } + } +} + +/* ---- rollback --------------------------------------------------------- */ + +static void test_injected_publication_failure_rolls_back_everything(void) { + int observations = scalar("SELECT count(*) FROM object_observations"); + int assets = scalar("SELECT count(*) FROM observation_assets"); + int changes = scalar("SELECT count(*) FROM observation_changes"); + observation_insert_result_t survivor = insert_ok(cam_a, 1, "bird"); + + /* Fires after the change row is inserted, before the publication row. */ + exec_sql("CREATE TEMP TRIGGER fail_publication BEFORE INSERT ON observation_publications " + "BEGIN SELECT RAISE(ABORT, 'injected'); END;"); + observation_batch_input_t batch = make_batch(cam_a, 2, "bird"); + observation_asset_input_t asset = pending_asset(); + observation_insert_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_ERROR, db_observation_insert_batch(&batch, &asset, &result)); + TEST_ASSERT_EQUAL_INT(observations + 1, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(assets + 1, scalar("SELECT count(*) FROM observation_assets")); + TEST_ASSERT_EQUAL_INT(changes + 1, scalar("SELECT count(*) FROM observation_changes")); + + observation_transition_result_t transition; + TEST_ASSERT_EQUAL_INT(DB_OBS_ERROR, db_observation_asset_transition( + survivor.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &transition)); + exec_sql("DROP TRIGGER temp.fail_publication;"); + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(survivor.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT(1, record.revision); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_PENDING, record.asset.state); + TEST_ASSERT_EQUAL_INT(changes + 1, scalar("SELECT count(*) FROM observation_changes")); + + /* The connection is usable and the retry succeeds afterwards. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &result)); +} + +/* ---- cursor pages ----------------------------------------------------- */ + +static bool page_has(const observation_page_t *page, const char *uuid, int *revision) { + for (size_t i = 0; i < page->count; ++i) { + if (strcmp(page->items[i].uuid, uuid) == 0) { + if (revision) *revision = page->items[i].revision; + return true; + } + } + return false; +} + +static void test_ready_transition_is_seen_after_cursor_passed_created(void) { + observation_insert_result_t inserted = insert_ok(cam_a, 1, "bird"); + observation_list_query_t query = all_query(100); + observation_page_t page; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + int revision = 0; + TEST_ASSERT_TRUE(page_has(&page, inserted.observation_uuids[0], &revision)); + TEST_ASSERT_EQUAL_INT(1, revision); + TEST_ASSERT_FALSE(page.has_more); + char cursor[OBS_CURSOR_SIZE]; + safe_strcpy(cursor, page.next_cursor, sizeof(cursor), 0); + db_observation_page_free(&page); + + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, cursor, &page)); + TEST_ASSERT_EQUAL_UINT(0, page.count); + TEST_ASSERT_EQUAL_STRING(cursor, page.next_cursor); + db_observation_page_free(&page); + + observation_transition_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + inserted.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &result)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, cursor, &page)); + TEST_ASSERT_EQUAL_UINT(1, page.count); + TEST_ASSERT_EQUAL_STRING(inserted.observation_uuids[0], page.items[0].uuid); + TEST_ASSERT_EQUAL_INT(2, page.items[0].revision); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, page.items[0].asset.state); + db_observation_page_free(&page); +} + +static void test_limit_counts_deduplicated_observations_and_cursor_tracks_changes(void) { + observation_insert_result_t first = insert_ok(cam_a, 1, "bird"); + observation_insert_result_t second = insert_ok(cam_a, 1, "bird"); + observation_transition_result_t result; + /* Changes: first@1, second@1, first@2, second@2. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + first.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &result)); + int64_t first_ready_seq = result.last_seq; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + second.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &result)); + + observation_list_query_t query = all_query(1); + observation_page_t page; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(1, page.count); + TEST_ASSERT_EQUAL_STRING(first.observation_uuids[0], page.items[0].uuid); + TEST_ASSERT_EQUAL_INT(2, page.items[0].revision); /* current state at read time */ + TEST_ASSERT_TRUE(page.has_more); + int64_t seq; + char epoch[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(page.next_cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_INT64(first.last_seq, seq); /* stopped before second's change */ + db_observation_page_free(&page); + + query.limit = 2; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(2, page.count); + TEST_ASSERT_FALSE(page.has_more); /* all 4 changes scanned; 2 observations */ + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(page.next_cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_INT64(result.last_seq, seq); + TEST_ASSERT_TRUE(seq > first_ready_seq); + db_observation_page_free(&page); +} + +static void test_filters_exclude_other_camera_and_labels(void) { + observation_insert_result_t a = insert_ok(cam_a, 1, "Bird"); + observation_insert_result_t b = insert_ok(cam_b, 1, "bird"); + observation_insert_result_t c = insert_ok(cam_a, 1, "person"); + + char allowed[1][OBS_UUID_SIZE]; + safe_strcpy(allowed[0], cam_a, OBS_UUID_SIZE, 0); + + observation_list_query_t query = all_query(100); + query.all_cameras = false; + query.camera_uuids = (const char (*)[OBS_UUID_SIZE])allowed; + query.camera_count = 1; + observation_page_t page; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(2, page.count); + TEST_ASSERT_TRUE(page_has(&page, a.observation_uuids[0], NULL)); + TEST_ASSERT_TRUE(page_has(&page, c.observation_uuids[0], NULL)); + TEST_ASSERT_FALSE(page_has(&page, b.observation_uuids[0], NULL)); + TEST_ASSERT_FALSE(page.has_more); + db_observation_page_free(&page); + + query.label_count = 1; + safe_strcpy(query.labels[0], "BIRD", sizeof(query.labels[0]), 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(1, page.count); + TEST_ASSERT_EQUAL_STRING(a.observation_uuids[0], page.items[0].uuid); + db_observation_page_free(&page); + + /* An empty authorized set yields nothing, but the cursor still advances. */ + query.label_count = 0; + query.camera_count = 0; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(0, page.count); + int64_t seq; + char epoch[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(page.next_cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_INT64(c.last_seq, seq); + db_observation_page_free(&page); + + /* Time bounds: since inclusive, until exclusive. */ + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(b.observation_uuids[0], &record)); + query = all_query(100); + query.has_since = true; + query.since_ms = record.observed_at_ms; + query.has_until = true; + query.until_ms = record.observed_at_ms + 1; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(1, page.count); + TEST_ASSERT_EQUAL_STRING(b.observation_uuids[0], page.items[0].uuid); + db_observation_page_free(&page); + + /* A deleted capture camera hides its observations from list and get. */ + char cam_c[OBS_UUID_SIZE]; + make_camera("cam_c", cam_c); + observation_insert_result_t gone = insert_ok(cam_c, 1, "bird"); + exec_sql("DELETE FROM streams WHERE name = 'cam_c';"); + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_get(gone.observation_uuids[0], &record)); + query = all_query(100); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, NULL, &page)); + TEST_ASSERT_FALSE(page_has(&page, gone.observation_uuids[0], NULL)); + TEST_ASSERT_TRUE(page_has(&page, b.observation_uuids[0], NULL)); + db_observation_page_free(&page); + + query.limit = 501; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, list_page(&query, NULL, &page)); + query.limit = 10; + query.has_since = query.has_until = true; + query.since_ms = query.until_ms = 5; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID, list_page(&query, NULL, &page)); +} + +typedef struct { + volatile int stop; + int inserted; +} writer_arg_t; + +static void *writer(void *opaque) { + writer_arg_t *arg = opaque; + while (!arg->stop && arg->inserted < 200) { + observation_batch_input_t batch = make_batch(cam_a, 1, "bird"); + observation_asset_input_t asset = pending_asset(); + observation_insert_result_t result; + if (db_observation_insert_batch(&batch, &asset, &result) == DB_OBS_OK) arg->inserted++; + } + return NULL; +} + +static void test_watermark_pages_with_concurrent_writes_never_skip_or_loop(void) { + char seeded[30][OBS_UUID_SIZE]; + for (int i = 0; i < 30; ++i) { + observation_insert_result_t r = insert_ok(cam_a, 1, "bird"); + safe_strcpy(seeded[i], r.observation_uuids[0], OBS_UUID_SIZE, 0); + } + writer_arg_t arg = {0, 0}; + pthread_t thread; + pthread_create(&thread, NULL, writer, &arg); + + bool seen[30] = {false}; + char cursor[OBS_CURSOR_SIZE] = ""; + int64_t previous = -1; + int pages = 0; + observation_list_query_t query = all_query(7); + for (;;) { + observation_page_t page; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, cursor[0] ? cursor : NULL, &page)); + for (int i = 0; i < 30; ++i) if (page_has(&page, seeded[i], NULL)) seen[i] = true; + int64_t seq; + char epoch[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(page.next_cursor, &seq, epoch)); + TEST_ASSERT_TRUE(seq > previous || (!page.has_more && page.count == 0)); + previous = seq; + safe_strcpy(cursor, page.next_cursor, sizeof(cursor), 0); + bool more = page.has_more; + db_observation_page_free(&page); + TEST_ASSERT_TRUE_MESSAGE(++pages < 200, "pages never converged"); + if (!more) { + if (arg.stop) break; + arg.stop = 1; + pthread_join(thread, NULL); + } + } + for (int i = 0; i < 30; ++i) TEST_ASSERT_TRUE(seen[i]); + TEST_ASSERT_TRUE(arg.inserted > 0); + /* The final cursor is at the head: everything committed has been paged. */ + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + TEST_ASSERT_EQUAL_INT64(meta.head_seq, previous); +} + +static void test_head_and_expired_cursors(void) { + observation_insert_result_t old = insert_ok(cam_a, 1, "bird"); + observation_insert_result_t mid = insert_ok(cam_a, 1, "bird"); + observation_insert_result_t recent = insert_ok(cam_a, 1, "bird"); + (void)mid; + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + + observation_list_query_t query = all_query(100); + observation_page_t page; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, "head", &page)); + TEST_ASSERT_EQUAL_UINT(0, page.count); + TEST_ASSERT_FALSE(page.has_more); + char expected[OBS_CURSOR_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(meta.head_seq, meta.epoch_uuid, + expected, sizeof(expected))); + TEST_ASSERT_EQUAL_STRING(expected, page.next_cursor); + db_observation_page_free(&page); + + char before_old[OBS_CURSOR_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(old.last_seq - 1, meta.epoch_uuid, + before_old, sizeof(before_old))); + /* Prune the first two observations' changes. */ + char sql[256]; + snprintf(sql, sizeof(sql), "UPDATE observation_changes SET created_at_ms = 1 WHERE seq < %lld;", + (long long)recent.last_seq); + exec_sql(sql); + observation_prune_result_t pruned; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observations_prune(1000, 100, &pruned)); + TEST_ASSERT_TRUE(pruned.changes_deleted >= 2); + TEST_ASSERT_EQUAL_INT64(recent.last_seq, pruned.oldest_seq); + + TEST_ASSERT_EQUAL_INT(DB_OBS_CURSOR_EXPIRED, list_page(&query, before_old, &page)); + int64_t seq; + char epoch[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(page.oldest_cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_INT64(recent.last_seq - 1, seq); + TEST_ASSERT_EQUAL_STRING(meta.epoch_uuid, epoch); + TEST_ASSERT_TRUE(page.has_oldest_observed_at); + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(recent.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT64(record.observed_at_ms, page.oldest_observed_at_ms); + db_observation_page_free(&page); + + /* The advertised oldest cursor resumes cleanly. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, expected, &page)); + db_observation_page_free(&page); + char oldest[OBS_CURSOR_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(recent.last_seq - 1, meta.epoch_uuid, + oldest, sizeof(oldest))); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, list_page(&query, oldest, &page)); + TEST_ASSERT_TRUE(page_has(&page, recent.observation_uuids[0], NULL)); + db_observation_page_free(&page); + + /* seq beyond the head (rewind without an epoch change). */ + char beyond[OBS_CURSOR_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(meta.head_seq + 5, meta.epoch_uuid, + beyond, sizeof(beyond))); + TEST_ASSERT_EQUAL_INT(DB_OBS_CURSOR_EXPIRED, list_page(&query, beyond, &page)); + db_observation_page_free(&page); + + /* Epoch mismatch, then a regenerated epoch invalidates old cursors. */ + char foreign[OBS_CURSOR_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(meta.head_seq, + "66666666-6666-4666-8666-666666666666", foreign, sizeof(foreign))); + TEST_ASSERT_EQUAL_INT(DB_OBS_CURSOR_EXPIRED, list_page(&query, foreign, &page)); + db_observation_page_free(&page); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_regenerate_epoch()); + TEST_ASSERT_EQUAL_INT(DB_OBS_CURSOR_EXPIRED, list_page(&query, expected, &page)); + observation_meta_t fresh; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&fresh)); + TEST_ASSERT_TRUE(strcmp(fresh.epoch_uuid, meta.epoch_uuid) != 0); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(page.oldest_cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_STRING(fresh.epoch_uuid, epoch); + db_observation_page_free(&page); + + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, list_page(&query, "%%%", &page)); +} + +/* ---- publications ----------------------------------------------------- */ + +static void test_pending_publications_in_seq_order_and_mark(void) { + exec_sql("DELETE FROM observation_publications;"); + observation_insert_result_t inserted = insert_ok(cam_b, 2, "bird"); + observation_transition_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + inserted.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_UNAVAILABLE, "write_failed", NULL, &result)); + + observation_publication_t pubs[8]; + int count = db_observation_pending_publications(pubs, 8); + TEST_ASSERT_EQUAL_INT(4, count); + for (int i = 1; i < count; ++i) TEST_ASSERT_TRUE(pubs[i].change_seq > pubs[i - 1].change_seq); + TEST_ASSERT_EQUAL_INT(OBS_CAUSE_CREATED, pubs[0].cause); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_PENDING, pubs[0].evidence_state); + TEST_ASSERT_EQUAL_STRING(cam_b, pubs[0].camera_uuid); + TEST_ASSERT_EQUAL_STRING("bird", pubs[0].label); + TEST_ASSERT_EQUAL_STRING(inserted.asset_uuid, pubs[0].asset_uuid); + TEST_ASSERT_EQUAL_INT(OBS_CAUSE_EVIDENCE, pubs[3].cause); + TEST_ASSERT_EQUAL_INT(2, pubs[3].revision); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, pubs[3].evidence_state); + TEST_ASSERT_EQUAL_STRING("write_failed", pubs[3].evidence_reason); + char message_id[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_message_id(source, pubs[3].observation_uuid, 2, message_id)); + TEST_ASSERT_EQUAL_STRING(message_id, pubs[3].message_id); + TEST_ASSERT_EQUAL_INT(2, db_observation_pending_publications(pubs, 2)); + + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_publication_mark( + pubs[0].change_seq, OBS_PUBLICATION_PENDING, "outbox full")); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_publication_mark( + pubs[0].change_seq, OBS_PUBLICATION_RELAYED, NULL)); + TEST_ASSERT_EQUAL_INT(DB_OBS_CAS_LOST, db_observation_publication_mark( + pubs[0].change_seq, OBS_PUBLICATION_FAILED, "late")); + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_publication_mark( + 999999, OBS_PUBLICATION_RELAYED, NULL)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_publication_mark( + pubs[1].change_seq, OBS_PUBLICATION_RELAYED_NO_ROUTE, NULL)); + char where[128]; + snprintf(where, sizeof(where), "change_seq = %lld AND state = 'relayed' AND attempts = 2 " + "AND last_error IS NULL", (long long)pubs[0].change_seq); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_publications", where)); + TEST_ASSERT_EQUAL_INT(2, db_observation_pending_publications(pubs, 8)); +} + +/* ---- prune and bump --------------------------------------------------- */ + +static void test_prune_deletes_history_in_order_and_advances_boundary(void) { + observation_insert_result_t dropped = insert_ok(cam_a, 1, "bird"); + observation_transition_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + dropped.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_UNAVAILABLE, "encode_failed", NULL, &result)); + observation_insert_result_t ready = insert_ok(cam_a, 1, "bird"); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + ready.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &ready_media, &result)); + observation_insert_result_t kept = insert_ok(cam_a, 1, "bird"); + + char sql[256]; + snprintf(sql, sizeof(sql), "UPDATE observation_changes SET created_at_ms = 10 WHERE seq < %lld;", + (long long)kept.last_seq); + exec_sql(sql); + observation_meta_t before; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&before)); + + /* Bounded batch: one change per call. */ + observation_prune_result_t pruned; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observations_prune(100, 1, &pruned)); + TEST_ASSERT_EQUAL_INT(1, pruned.changes_deleted); + TEST_ASSERT_TRUE(pruned.oldest_seq > before.oldest_seq); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observations_prune(100, 100, &pruned)); + TEST_ASSERT_EQUAL_INT64(kept.last_seq, pruned.oldest_seq); + + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_get(dropped.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_get(ready.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(kept.observation_uuids[0], &record)); + /* Unavailable asset row is gone; the ready one stays for L4 to delete its file. */ + observation_asset_record_t asset; + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_asset_get(dropped.asset_uuid, &asset)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(ready.asset_uuid, &asset)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset.state); + + observation_meta_t after; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&after)); + TEST_ASSERT_EQUAL_INT64(kept.last_seq, after.oldest_seq); + TEST_ASSERT_EQUAL_STRING(before.epoch_uuid, after.epoch_uuid); + + /* Nothing old left: a no-op that never moves the boundary back. */ + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observations_prune(100, 100, &pruned)); + TEST_ASSERT_EQUAL_INT(0, pruned.changes_deleted); + TEST_ASSERT_EQUAL_INT64(kept.last_seq, pruned.oldest_seq); +} + +static void test_bump_revision_locked_for_classification(void) { + observation_insert_result_t inserted = insert_ok(cam_a, 1, "bird"); + sqlite3 *db = get_db_handle(); + pthread_mutex_lock(get_db_mutex()); + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_exec(db, "BEGIN IMMEDIATE;", NULL, NULL, NULL)); + int64_t seq = 0; + int revision = 0; + int rc = db_observation_bump_revision_locked(db, source, inserted.observation_uuids[0], + OBS_CAUSE_CLASSIFICATION, 1234, &seq, &revision); + sqlite3_exec(db, rc == DB_OBS_OK ? "COMMIT;" : "ROLLBACK;", NULL, NULL, NULL); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, rc); + TEST_ASSERT_EQUAL_INT(2, revision); + TEST_ASSERT_TRUE(seq > inserted.last_seq); + char where[200]; + snprintf(where, sizeof(where), "seq = %lld AND cause = 'classification' AND " + "evidence_state = 'pending'", (long long)seq); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_changes", where)); + snprintf(where, sizeof(where), "change_seq = %lld", (long long)seq); + TEST_ASSERT_EQUAL_INT(1, count_where("observation_publications", where)); +} + +int main(void) { + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_observations_%ld.db", (long)getpid()); + unlink(db_path); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0 || event_identity_get_source(source, sizeof(source)) != 0) + return 1; + + UNITY_BEGIN(); + make_camera("cam_a", cam_a); + make_camera("cam_b", cam_b); + RUN_TEST(test_batch_insert_writes_shared_asset_changes_and_publications); + RUN_TEST(test_duplicate_batch_retry_is_a_noop_returning_existing_ids); + RUN_TEST(test_insert_validation_rejects_contract_violations); + RUN_TEST(test_transition_bumps_every_sharing_observation); + RUN_TEST(test_transition_rejects_invalid_edges_and_reasons); + RUN_TEST(test_cas_transition_race_has_exactly_one_winner); + RUN_TEST(test_injected_publication_failure_rolls_back_everything); + RUN_TEST(test_ready_transition_is_seen_after_cursor_passed_created); + RUN_TEST(test_limit_counts_deduplicated_observations_and_cursor_tracks_changes); + RUN_TEST(test_filters_exclude_other_camera_and_labels); + RUN_TEST(test_watermark_pages_with_concurrent_writes_never_skip_or_loop); + RUN_TEST(test_head_and_expired_cursors); + RUN_TEST(test_pending_publications_in_seq_order_and_mark); + RUN_TEST(test_prune_deletes_history_in_order_and_advances_boundary); + RUN_TEST(test_bump_revision_locked_for_classification); + int result = UNITY_END(); + + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char sidecar[300]; + snprintf(sidecar, sizeof(sidecar), "%s-wal", db_path); unlink(sidecar); + snprintf(sidecar, sizeof(sidecar), "%s-shm", db_path); unlink(sidecar); + return result; +} diff --git a/tests/unit/test_db_streams.c b/tests/unit/test_db_streams.c index 20178290d..f67fc3dcf 100644 --- a/tests/unit/test_db_streams.c +++ b/tests/unit/test_db_streams.c @@ -778,6 +778,66 @@ void test_eptz_config_round_trip_update_and_get_all(void) { TEST_ASSERT_EQUAL_STRING(updated_config, out[0].eptz_config); } +/* ================================================================ + * INT_01 per-camera observation evidence settings (migration 0095) + * ================================================================ */ + +void test_observation_evidence_settings_default_from_zeroed_config(void) { + stream_config_t s = make_stream("cam_obs_default", true); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(&s)); + + stream_config_t got; + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name("cam_obs_default", &got)); + TEST_ASSERT_FALSE(got.observation_evidence_enabled); + TEST_ASSERT_EQUAL_STRING("", got.observation_labels); + TEST_ASSERT_EQUAL_INT(7, got.observation_max_age_days); + TEST_ASSERT_EQUAL_INT64(2147483648LL, got.observation_max_bytes); + + /* An update from a config that never loaded the fields keeps valid values. */ + stream_config_t zeroed = make_stream("cam_obs_default", true); + TEST_ASSERT_EQUAL_INT(0, update_stream_config("cam_obs_default", &zeroed)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name("cam_obs_default", &got)); + TEST_ASSERT_EQUAL_INT(7, got.observation_max_age_days); + TEST_ASSERT_EQUAL_INT64(2147483648LL, got.observation_max_bytes); +} + +void test_observation_evidence_settings_round_trip_update_and_get_all(void) { + stream_config_t s = make_stream("cam_obs", true); + s.observation_evidence_enabled = true; + safe_strcpy(s.observation_labels, "bird,cat", sizeof(s.observation_labels), 0); + s.observation_max_age_days = 14; + s.observation_max_bytes = 3LL * 1024 * 1024 * 1024; + TEST_ASSERT_GREATER_THAN(0, add_stream_config(&s)); + + stream_config_t got; + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name("cam_obs", &got)); + TEST_ASSERT_TRUE(got.observation_evidence_enabled); + TEST_ASSERT_EQUAL_STRING("bird,cat", got.observation_labels); + TEST_ASSERT_EQUAL_INT(14, got.observation_max_age_days); + TEST_ASSERT_EQUAL_INT64(3LL * 1024 * 1024 * 1024, got.observation_max_bytes); + + got.observation_evidence_enabled = false; + safe_strcpy(got.observation_labels, "bird", sizeof(got.observation_labels), 0); + got.observation_max_age_days = 30; + got.observation_max_bytes = 64LL * 1024 * 1024; + TEST_ASSERT_EQUAL_INT(0, update_stream_config("cam_obs", &got)); + + stream_config_t out[2]; + TEST_ASSERT_EQUAL_INT(1, get_all_stream_configs(out, 2)); + TEST_ASSERT_FALSE(out[0].observation_evidence_enabled); + TEST_ASSERT_EQUAL_STRING("bird", out[0].observation_labels); + TEST_ASSERT_EQUAL_INT(30, out[0].observation_max_age_days); + TEST_ASSERT_EQUAL_INT64(64LL * 1024 * 1024, out[0].observation_max_bytes); + + /* Reactivating a soft-deleted stream writes the evidence settings too. */ + TEST_ASSERT_EQUAL_INT(0, delete_stream_config("cam_obs")); + s.observation_max_age_days = 21; + TEST_ASSERT_GREATER_THAN(0, add_stream_config(&s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name("cam_obs", &got)); + TEST_ASSERT_TRUE(got.observation_evidence_enabled); + TEST_ASSERT_EQUAL_INT(21, got.observation_max_age_days); +} + /* ================================================================ * main * ================================================================ */ @@ -830,6 +890,8 @@ int main(void) { RUN_TEST(test_playback_transport_round_trip_and_update); RUN_TEST(test_playback_transport_is_returned_by_get_all); RUN_TEST(test_eptz_config_round_trip_update_and_get_all); + RUN_TEST(test_observation_evidence_settings_default_from_zeroed_config); + RUN_TEST(test_observation_evidence_settings_round_trip_update_and_get_all); int result = UNITY_END(); shutdown_database(); diff --git a/tests/unit/test_detection_current_frame.c b/tests/unit/test_detection_current_frame.c index 920d44c4d..60ac52047 100644 --- a/tests/unit/test_detection_current_frame.c +++ b/tests/unit/test_detection_current_frame.c @@ -17,10 +17,13 @@ int __wrap_detect_objects_api_snapshot_with_options(const char *url, const char return -2; } -int __wrap_detect_objects_api_with_options(const char *url, const unsigned char *data, +int __wrap_detect_objects_api_with_evidence(const char *url, const unsigned char *data, int width, int height, int channels, detection_result_t *result, const char *name, float threshold, uint64_t recording_id, - time_t timestamp, const api_detection_options_t *options) { + time_t timestamp, const api_detection_options_t *options, + const api_detection_evidence_t *evidence) { + TEST_ASSERT_NOT_NULL(evidence); + TEST_ASSERT_EQUAL_STRING(ctx->camera_uuid, evidence->camera_uuid); TEST_ASSERT_EQUAL_STRING("http://127.0.0.1:9001/detect", url); TEST_ASSERT_EQUAL_STRING("current-frame", name); TEST_ASSERT_EQUAL_INT(64, width); diff --git a/tests/unit/test_detection_evidence.c b/tests/unit/test_detection_evidence.c new file mode 100644 index 000000000..e20267677 --- /dev/null +++ b/tests/unit/test_detection_evidence.c @@ -0,0 +1,283 @@ +/* INT_01 L6: pure batch building, label eligibility, box clamping and the + * inference transform for local detector evidence (contract sections 3, 4.4). */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include + +#include "unity.h" +#include "core/logger.h" +#include "core/observation_evidence.h" +#include "utils/uuid.h" +#include "video/detection_evidence.h" +#include "video/ffmpeg_utils.h" + +/* Unity is built without double support. */ +#define ASSERT_NEAR(tol, expected, actual) \ + TEST_ASSERT_TRUE_MESSAGE(fabs((double)(expected) - (double)(actual)) <= (tol), #actual) + +#define CAMERA "22222222-2222-4222-8222-222222222222" + +static detection_evidence_camera_t camera(const char *labels, bool enabled) { + detection_evidence_camera_t c = { + .camera_uuid = CAMERA, .stream_name = "feeder-east", + .evidence_enabled = enabled, .labels_csv = labels, + }; + return c; +} + +static detection_evidence_engine_t engine(const char *key, const char *type) { + detection_evidence_engine_t e = { + .engine_key = key, .engine_type = type, + .model_path = "/etc/lightnvr/models/ssd_mobilenet_v2.sod", + .model_version = NULL, + }; + return e; +} + +static detection_t det(const char *label, float conf, float x, float y, float w, + float h, int track) { + detection_t d; + memset(&d, 0, sizeof(d)); + snprintf(d.label, sizeof(d.label), "%s", label); + d.confidence = conf; + d.x = x; d.y = y; d.width = w; d.height = h; + d.track_id = track; + return d; +} + +void setUp(void) {} +void tearDown(void) {} + +static void test_label_eligibility(void) { + TEST_ASSERT_TRUE(detection_evidence_label_eligible(NULL, "bird")); + TEST_ASSERT_TRUE(detection_evidence_label_eligible("", "bird")); + TEST_ASSERT_TRUE(detection_evidence_label_eligible(" ", "squirrel")); + TEST_ASSERT_TRUE(detection_evidence_label_eligible("bird", "bird")); + TEST_ASSERT_TRUE(detection_evidence_label_eligible("cat, Bird ,dog", "bird")); + TEST_ASSERT_TRUE(detection_evidence_label_eligible("cat,dog", "DOG")); + TEST_ASSERT_FALSE(detection_evidence_label_eligible("bird", "birds")); + TEST_ASSERT_FALSE(detection_evidence_label_eligible("birds", "bird")); + TEST_ASSERT_FALSE(detection_evidence_label_eligible("cat,dog", "bird")); + TEST_ASSERT_FALSE(detection_evidence_label_eligible(NULL, "")); + TEST_ASSERT_FALSE(detection_evidence_label_eligible("bird", NULL)); +} + +static void test_box_clamping(void) { + double x, y, w, h; + TEST_ASSERT_TRUE(detection_evidence_clamp_box(0.1, 0.2, 0.3, 0.4, &x, &y, &w, &h)); + ASSERT_NEAR(1e-9, 0.1, x); + ASSERT_NEAR(1e-9, 0.4, h); + /* Spills over the right/bottom edge and starts left of 0. */ + TEST_ASSERT_TRUE(detection_evidence_clamp_box(-0.1, 0.8, 0.5, 0.5, &x, &y, &w, &h)); + ASSERT_NEAR(1e-9, 0.0, x); + ASSERT_NEAR(1e-9, 0.4, w); + ASSERT_NEAR(1e-9, 0.8, y); + ASSERT_NEAR(1e-9, 0.2, h); + TEST_ASSERT_TRUE(x + w <= 1.0 && y + h <= 1.0); + /* Entirely outside, zero area, NaN. */ + TEST_ASSERT_FALSE(detection_evidence_clamp_box(1.2, 0.1, 0.3, 0.3, &x, &y, &w, &h)); + TEST_ASSERT_FALSE(detection_evidence_clamp_box(0.1, 0.1, 0.0, 0.3, &x, &y, &w, &h)); + TEST_ASSERT_FALSE(detection_evidence_clamp_box(NAN, 0.1, 0.2, 0.3, &x, &y, &w, &h)); +} + +static void test_build_batch_fields_tracks_and_eligibility(void) { + detection_t d[4] = { + det("bird", 0.87f, 0.412f, 0.288f, 0.12f, 0.165f, 17), + det("squirrel", 0.9f, 0.1f, 0.1f, 0.2f, 0.2f, -1), + det("bird", 1.4f, 0.9f, 0.9f, 0.3f, 0.3f, -1), /* clamp conf + box */ + det("bird", 0.6f, 2.0f, 2.0f, 0.1f, 0.1f, 3), /* box outside: no box */ + }; + detection_evidence_camera_t c = camera("bird", true); + detection_evidence_engine_t e = engine("birds-sod", "object"); + observation_batch_input_t b; + int n = detection_evidence_build_batch(&c, &e, d, 4, 1793000000123LL, &b); + TEST_ASSERT_EQUAL_INT(3, n); + TEST_ASSERT_EQUAL_INT(3, b.object_count); + TEST_ASSERT_TRUE(lightnvr_uuid_is_valid(b.batch_id)); + TEST_ASSERT_EQUAL_STRING(CAMERA, b.camera_uuid); + TEST_ASSERT_EQUAL_STRING("feeder-east", b.stream_name); + TEST_ASSERT_EQUAL_INT64(1793000000123LL, b.observed_at_ms); + TEST_ASSERT_EQUAL_STRING("birds-sod", b.engine_key); + TEST_ASSERT_EQUAL_STRING("object", b.engine_type); + TEST_ASSERT_EQUAL_STRING("ssd_mobilenet_v2.sod", b.model); + TEST_ASSERT_EQUAL_STRING("", b.model_version); + TEST_ASSERT_EQUAL_INT64(0, b.recording_id); + + for (int i = 0; i < 3; ++i) { + TEST_ASSERT_EQUAL_INT(i, b.objects[i].object_index); + TEST_ASSERT_EQUAL_STRING("bird", b.objects[i].label); + } + /* Object 0: track with namespace = engine key and session = boot UUID. */ + TEST_ASSERT_TRUE(b.objects[0].has_track); + TEST_ASSERT_EQUAL_STRING("17", b.objects[0].track_id); + TEST_ASSERT_EQUAL_STRING("birds-sod", b.objects[0].track_namespace); + TEST_ASSERT_EQUAL_STRING(detection_evidence_boot_session(), b.objects[0].track_session); + TEST_ASSERT_TRUE(lightnvr_uuid_is_valid(b.objects[0].track_session)); + ASSERT_NEAR(1e-6, 0.87, b.objects[0].confidence); + TEST_ASSERT_TRUE(b.objects[0].has_box); + ASSERT_NEAR(1e-6, 0.412, b.objects[0].box_x); + /* Object 1: no track (track_id < 0), confidence and box clamped. */ + TEST_ASSERT_FALSE(b.objects[1].has_track); + TEST_ASSERT_EQUAL_STRING("", b.objects[1].track_id); + ASSERT_NEAR(1e-9, 1.0, b.objects[1].confidence); + TEST_ASSERT_TRUE(b.objects[1].has_box); + TEST_ASSERT_TRUE(b.objects[1].box_x + b.objects[1].box_width <= 1.0); + ASSERT_NEAR(1e-6, 0.1, b.objects[1].box_width); + /* Object 2: box clamped away entirely -> null box, still an observation. */ + TEST_ASSERT_FALSE(b.objects[2].has_box); + TEST_ASSERT_TRUE(b.objects[2].has_track); + + /* Boot session is stable for the process. */ + const char *s1 = detection_evidence_boot_session(); + TEST_ASSERT_EQUAL_STRING(s1, detection_evidence_boot_session()); + + /* Each accepted result gets its own batch_id. */ + observation_batch_input_t b2; + TEST_ASSERT_EQUAL_INT(3, detection_evidence_build_batch(&c, &e, d, 4, 1793000000124LL, &b2)); + TEST_ASSERT_NOT_EQUAL(0, strcmp(b.batch_id, b2.batch_id)); +} + +static void test_build_batch_defaults_and_limits(void) { + detection_t d[OBS_MAX_BATCH + 3]; + for (int i = 0; i < OBS_MAX_BATCH + 3; ++i) + d[i] = det("bird", 0.5f, 0.1f, 0.1f, 0.1f, 0.1f, -1); + detection_evidence_camera_t c = camera(NULL, true); + detection_evidence_engine_t e = { .engine_key = NULL, .engine_type = "object", + .model_path = NULL, .model_version = "v3" }; + observation_batch_input_t b; + TEST_ASSERT_EQUAL_INT(OBS_MAX_BATCH, + detection_evidence_build_batch(&c, &e, d, OBS_MAX_BATCH + 3, 1000, &b)); + TEST_ASSERT_EQUAL_STRING("legacy-primary", b.engine_key); + TEST_ASSERT_EQUAL_STRING("", b.model); + TEST_ASSERT_EQUAL_STRING("v3", b.model_version); + + /* Nothing eligible: 0 and no batch id. */ + detection_evidence_camera_t only_cats = camera("cat", true); + TEST_ASSERT_EQUAL_INT(0, detection_evidence_build_batch(&only_cats, &e, d, 2, 1000, &b)); + TEST_ASSERT_EQUAL_STRING("", b.batch_id); + + /* Invalid input. */ + detection_evidence_camera_t no_uuid = camera(NULL, true); + no_uuid.camera_uuid = ""; + TEST_ASSERT_EQUAL_INT(-1, detection_evidence_build_batch(&no_uuid, &e, d, 1, 1000, &b)); + TEST_ASSERT_EQUAL_INT(-1, detection_evidence_build_batch(&c, &e, d, 1, 0, &b)); + TEST_ASSERT_EQUAL_INT(-1, detection_evidence_build_batch(&c, &e, NULL, 1, 1000, &b)); +} + +static void test_transform_modes(void) { + observation_evidence_transform_t t; + detection_evidence_transform_compute(640, 360, 0, 0, false, &t); + TEST_ASSERT_EQUAL_INT(OBS_TRANSFORM_NONE, t.mode); + TEST_ASSERT_EQUAL_INT(640, t.source_width); + TEST_ASSERT_EQUAL_INT(640, t.inference_width); + ASSERT_NEAR(1e-12, 1.0, t.scale_x); + + detection_evidence_transform_compute(640, 360, 640, 360, true, &t); + TEST_ASSERT_EQUAL_INT(OBS_TRANSFORM_NONE, t.mode); + + /* SOD: independent per-axis resize to the network size. */ + detection_evidence_transform_compute(1280, 720, 416, 416, false, &t); + TEST_ASSERT_EQUAL_INT(OBS_TRANSFORM_RESIZE, t.mode); + TEST_ASSERT_EQUAL_INT(1280, t.source_width); + TEST_ASSERT_EQUAL_INT(720, t.source_height); + TEST_ASSERT_EQUAL_INT(416, t.inference_width); + TEST_ASSERT_EQUAL_INT(416, t.inference_height); + ASSERT_NEAR(1e-9, 416.0 / 1280.0, t.scale_x); + ASSERT_NEAR(1e-9, 416.0 / 720.0, t.scale_y); + ASSERT_NEAR(1e-12, 0.0, t.pad_x); + + /* LiteRT: uniform scale + centred padding, as letterbox_rgb24(). */ + detection_evidence_transform_compute(1280, 720, 640, 640, true, &t); + TEST_ASSERT_EQUAL_INT(OBS_TRANSFORM_LETTERBOX, t.mode); + ASSERT_NEAR(1e-6, 0.5, t.scale_x); + ASSERT_NEAR(1e-6, 0.5, t.scale_y); + ASSERT_NEAR(1e-12, 0.0, t.pad_x); + ASSERT_NEAR(1e-12, 140.0, t.pad_y); /* (640 - 360) / 2 */ + + /* Mapping an inference pixel back lands on the stored frame (section 4.4). */ + double u = 320.0, v = 320.0; /* centre of the tensor */ + ASSERT_NEAR(1e-6, 640.0, (u - t.pad_x) / t.scale_x); + ASSERT_NEAR(1e-6, 360.0, (v - t.pad_y) / t.scale_y); + + /* The transform serializes through the L3 helper. */ + char json[OBS_TRANSFORM_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_transform_json(&t, 1280, 720, json, sizeof(json))); + TEST_ASSERT_NOT_NULL(strstr(json, "\"mode\":\"letterbox\"")); +} + +static void test_model_geometry_without_model(void) { + int w = -1, h = -1; + bool lb = true; + TEST_ASSERT_EQUAL_INT(-1, detection_evidence_model_geometry(NULL, &w, &h, &lb)); +} + +static void test_observed_at_ms(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + int64_t ms = detection_evidence_observed_at_ms(tv.tv_sec); + TEST_ASSERT_TRUE(ms / 1000 == (int64_t)tv.tv_sec || ms / 1000 == (int64_t)tv.tv_sec + 1 || + ms == (int64_t)tv.tv_sec * 1000); + /* An old frame second keeps that second exactly. */ + TEST_ASSERT_EQUAL_INT64(1700000000000LL, detection_evidence_observed_at_ms(1700000000)); +} + +static uint8_t *rgb_buffer(int w, int h) { + uint8_t *buf = calloc(1, (size_t)w * h * 3 + RGB24_SWS_TAIL_PADDING); + TEST_ASSERT_NOT_NULL(buf); + return buf; +} + +/* With evidence disabled, motion engines or nothing eligible, capture frees + * the buffer (ASan would flag a leak) and never starts the L3 queue. */ +static void test_capture_skips_without_allocating(void) { + detection_t d[1] = { det("bird", 0.9f, 0.1f, 0.1f, 0.2f, 0.2f, -1) }; + detection_evidence_camera_t off = camera(NULL, false); + detection_evidence_camera_t on = camera(NULL, true); + detection_evidence_camera_t cats = camera("cat", true); + detection_evidence_engine_t obj = engine("legacy-primary", "object"); + detection_evidence_engine_t motion = engine("motion", "motion"); + detection_evidence_engine_t api = engine("api", "api"); + char asset[OBS_UUID_SIZE] = "x"; + + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_ENABLED, + detection_evidence_capture(&off, &obj, d, 1, 1000, rgb_buffer(8, 8), 8, 8, NULL, NULL, asset)); + TEST_ASSERT_EQUAL_STRING("", asset); + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_OBJECT_ENGINE, + detection_evidence_capture(&on, &motion, d, 1, 1000, rgb_buffer(8, 8), 8, 8, NULL, NULL, asset)); + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_OBJECT_ENGINE, + detection_evidence_capture(&on, &api, d, 1, 1000, rgb_buffer(8, 8), 8, 8, NULL, NULL, asset)); + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_NOT_ELIGIBLE, + detection_evidence_capture(&cats, &obj, d, 1, 1000, rgb_buffer(8, 8), 8, 8, NULL, NULL, asset)); + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_ERROR, + detection_evidence_capture(&on, &obj, d, 1, 1000, NULL, 8, 8, NULL, NULL, asset)); + + /* Not initialized: submitted, L3 says DISABLED, still nothing allocated. */ + observation_evidence_result_t r = OBS_EVIDENCE_ACCEPTED; + TEST_ASSERT_EQUAL_INT(DETECTION_EVIDENCE_SUBMITTED, + detection_evidence_capture(&on, &obj, d, 1, 1000, rgb_buffer(8, 8), 8, 8, NULL, &r, asset)); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DISABLED, r); + + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_FALSE(s.worker_running); + TEST_ASSERT_EQUAL_UINT64(0, s.accepted); +} + +int main(void) { + init_logger(); + set_log_level(LOG_LEVEL_ERROR); + UNITY_BEGIN(); + RUN_TEST(test_label_eligibility); + RUN_TEST(test_box_clamping); + RUN_TEST(test_build_batch_fields_tracks_and_eligibility); + RUN_TEST(test_build_batch_defaults_and_limits); + RUN_TEST(test_transform_modes); + RUN_TEST(test_model_geometry_without_model); + RUN_TEST(test_observed_at_ms); + RUN_TEST(test_capture_skips_without_allocating); + return UNITY_END(); +} diff --git a/tests/unit/test_detection_observation_evidence.c b/tests/unit/test_detection_observation_evidence.c new file mode 100644 index 000000000..0972b48cb --- /dev/null +++ b/tests/unit/test_detection_observation_evidence.c @@ -0,0 +1,508 @@ +/* INT_01 L6: a synthetic decoded frame run through the production + * detect_on_decoded_frame() dispatch with a fake engine result yields N + * observations sharing one evidence asset whose JPEG decodes back to the + * inference frame (A2: compared against the exact RGB the engine saw, by + * PSNR), and nothing is queued or allocated when evidence is disabled. + * Real migrated DB + real L3 worker. */ +#include "../../src/video/unified_detection_thread.c" + +#include +#include + +#include "unity.h" +#include "core/event_identity.h" +#include "core/observation_evidence.h" +#include "database/db_core.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "video/detection_evidence.h" +#include "video/detection_model_internal.h" + +#define FRAME_W 320 +#define FRAME_H 240 + +static char db_path[256]; +static char storage_dir[256]; +static char cam_uuid[OBS_UUID_SIZE]; +static unified_detection_ctx_t *ctx; +static model_t fake_model; + +/* ---- fakes -------------------------------------------------------------- */ + +static detection_result_t fake_result; /* what the object engine reports */ +static uint8_t *seen_rgb[4]; /* copies of every inference input */ +static int seen_count; +static int object_calls, motion_calls; +static int geometry_w = 300, geometry_h = 300; + +static void remember_input(const unsigned char *frame, int w, int h) { + TEST_ASSERT_LESS_THAN_INT(4, seen_count); + size_t size = (size_t)w * h * 3; + seen_rgb[seen_count] = malloc(size); + TEST_ASSERT_NOT_NULL(seen_rgb[seen_count]); + memcpy(seen_rgb[seen_count], frame, size); + seen_count++; +} + +int __wrap_detect_objects(detection_model_t model, const unsigned char *frame, + int w, int h, int channels, detection_result_t *result) { + TEST_ASSERT_NOT_NULL(model); + TEST_ASSERT_EQUAL_INT(3, channels); + TEST_ASSERT_EQUAL_INT(FRAME_W, w); + TEST_ASSERT_EQUAL_INT(FRAME_H, h); + remember_input(frame, w, h); + object_calls++; + *result = fake_result; + return 0; +} + +int __wrap_detect_motion(const char *stream_name, const unsigned char *frame, + int w, int h, int channels, time_t ts, detection_result_t *result) { + (void)stream_name; (void)frame; (void)w; (void)h; (void)channels; (void)ts; + motion_calls++; + memset(result, 0, sizeof(*result)); + result->count = 1; + snprintf(result->detections[0].label, sizeof(result->detections[0].label), "motion"); + result->detections[0].confidence = 1.0f; + result->detections[0].x = 0.0f; result->detections[0].y = 0.0f; + result->detections[0].width = 1.0f; result->detections[0].height = 1.0f; + result->detections[0].track_id = -1; + return 0; +} + +int __wrap_detection_evidence_model_geometry(detection_model_t model, int *w, int *h, + bool *letterbox) { + (void)model; + if (geometry_w <= 0) return -1; + *w = geometry_w; + *h = geometry_h; + *letterbox = false; + return 0; +} + +int __wrap_filter_detections_by_zones(const char *name, detection_result_t *result) { + (void)name; (void)result; + return 0; +} +int __wrap_filter_detections_by_stream_objects(const char *name, detection_result_t *result) { + (void)name; (void)result; + return 0; +} + +/* ---- helpers -------------------------------------------------------------- */ + +static int scalar(const char *sql) { + sqlite3_stmt *stmt = NULL; + int value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, value, sql); + return value; +} + +static void text_column(const char *sql, char *out, size_t size) { + sqlite3_stmt *stmt = NULL; + out[0] = '\0'; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW && sqlite3_column_text(stmt, 0)) + snprintf(out, size, "%s", (const char *)sqlite3_column_text(stmt, 0)); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); +} + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static void wait_idle(void) { + for (int i = 0; i < 1000; ++i) { + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + if (s.backlog_items == 0 && s.drop_ring_depth == 0) { + usleep(20000); + observation_evidence_get_stats(&s); + if (s.backlog_items == 0 && s.drop_ring_depth == 0) return; + } + usleep(10000); + } + TEST_FAIL_MESSAGE("evidence worker did not go idle"); +} + +/* Synthetic YUV420P frame: smooth gradient plus a bright 40x40 "bird" whose + * left edge is at bird_x (the A2 moving-bird fixture moves it). */ +static AVFrame *make_frame(int bird_x) { + AVFrame *f = av_frame_alloc(); + TEST_ASSERT_NOT_NULL(f); + f->format = AV_PIX_FMT_YUV420P; + f->width = FRAME_W; + f->height = FRAME_H; + TEST_ASSERT_EQUAL_INT(0, av_frame_get_buffer(f, 0)); + for (int y = 0; y < FRAME_H; ++y) + for (int x = 0; x < FRAME_W; ++x) { + bool bird = x >= bird_x && x < bird_x + 40 && y >= 100 && y < 140; + f->data[0][y * f->linesize[0] + x] = bird ? 235 : (uint8_t)(32 + (x + y) / 4); + } + for (int y = 0; y < FRAME_H / 2; ++y) + for (int x = 0; x < FRAME_W / 2; ++x) { + f->data[1][y * f->linesize[1] + x] = (uint8_t)(96 + x / 4); + f->data[2][y * f->linesize[2] + x] = (uint8_t)(160 - y / 4); + } + return f; +} + +static uint8_t *read_file(const char *path, size_t *size) { + FILE *f = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, path); + fseek(f, 0, SEEK_END); + long n = ftell(f); + fseek(f, 0, SEEK_SET); + uint8_t *buf = malloc((size_t)n); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL_size_t((size_t)n, fread(buf, 1, (size_t)n, f)); + fclose(f); + *size = (size_t)n; + return buf; +} + +/* Decode a stored JPEG to packed RGB24 (caller frees). */ +static uint8_t *decode_jpeg_rgb(const char *path, int *w, int *h) { + size_t size = 0; + uint8_t *data = read_file(path, &size); + const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); + TEST_ASSERT_NOT_NULL(codec); + AVCodecContext *dec = avcodec_alloc_context3(codec); + TEST_ASSERT_EQUAL_INT(0, avcodec_open2(dec, codec, NULL)); + AVPacket *pkt = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + TEST_ASSERT_EQUAL_INT(0, av_new_packet(pkt, (int)size)); + memcpy(pkt->data, data, size); + TEST_ASSERT_EQUAL_INT(0, avcodec_send_packet(dec, pkt)); + TEST_ASSERT_EQUAL_INT(0, avcodec_receive_frame(dec, frame)); + *w = frame->width; + *h = frame->height; + uint8_t *rgb = malloc((size_t)frame->width * frame->height * 3 + RGB24_SWS_TAIL_PADDING); + TEST_ASSERT_NOT_NULL(rgb); + struct SwsContext *sws = sws_getContext(frame->width, frame->height, frame->format, + frame->width, frame->height, AV_PIX_FMT_RGB24, + SWS_BILINEAR, NULL, NULL, NULL); + TEST_ASSERT_NOT_NULL(sws); + uint8_t *dst[4] = {rgb, NULL, NULL, NULL}; + int dst_lines[4] = {frame->width * 3, 0, 0, 0}; + sws_scale(sws, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, + dst, dst_lines); + sws_freeContext(sws); + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&dec); + free(data); + return rgb; +} + +static double psnr(const uint8_t *a, const uint8_t *b, size_t n) { + double mse = 0.0; + for (size_t i = 0; i < n; ++i) { + double d = (double)a[i] - (double)b[i]; + mse += d * d; + } + mse /= (double)n; + if (mse <= 1e-12) return 99.0; + return 10.0 * log10(255.0 * 255.0 / mse); +} + +static void asset_file(const char *asset_uuid, char *out, size_t size) { + char sql[256], rel[512]; + snprintf(sql, sizeof(sql), + "SELECT path FROM observation_assets WHERE uuid = '%s' AND state = 'ready'", + asset_uuid); + text_column(sql, rel, sizeof(rel)); + TEST_ASSERT_TRUE_MESSAGE(rel[0], "asset is not ready"); + snprintf(out, size, "%s/%s", storage_dir, rel); +} + +static void set_detections(int n, const char *const *labels, const int *tracks) { + memset(&fake_result, 0, sizeof(fake_result)); + fake_result.count = n; + for (int i = 0; i < n; ++i) { + detection_t *d = &fake_result.detections[i]; + snprintf(d->label, sizeof(d->label), "%s", labels[i]); + d->confidence = 0.9f - 0.1f * (float)i; + d->x = 0.1f + 0.2f * (float)i; d->y = 0.4f; + d->width = 0.15f; d->height = 0.2f; + d->track_id = tracks[i]; + } +} + +/* ---- fixtures ------------------------------------------------------------- */ + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets;"); + observation_evidence_config_t cfg = { .storage_path = storage_dir }; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_init(&cfg)); + + ctx = calloc(1, sizeof(*ctx)); + TEST_ASSERT_NOT_NULL(ctx); + pthread_mutex_init(&ctx->mutex, NULL); + snprintf(ctx->stream_name, sizeof(ctx->stream_name), "feeder-east"); + snprintf(ctx->camera_uuid, sizeof(ctx->camera_uuid), "%s", cam_uuid); + snprintf(ctx->model_path, sizeof(ctx->model_path), "/etc/lightnvr/models/ssd_mobilenet_v2.sod"); + ctx->detection_threshold = 0.5f; + ctx->model = &fake_model; + ctx->engine_count = 1; + snprintf(ctx->engines[0].engine_key, sizeof(ctx->engines[0].engine_key), "legacy-primary"); + snprintf(ctx->engines[0].engine_type, sizeof(ctx->engines[0].engine_type), "object"); + snprintf(ctx->engines[0].model_path, sizeof(ctx->engines[0].model_path), "%s", ctx->model_path); + ctx->engines[0].enabled = true; + ctx->engines[0].threshold = 0.5f; + atomic_store(&ctx->observation_evidence_enabled, true); + ctx->observation_labels[0] = '\0'; + + seen_count = object_calls = motion_calls = 0; + geometry_w = geometry_h = 300; +} + +void tearDown(void) { + observation_evidence_shutdown(2000); + for (int i = 0; i < seen_count; ++i) free(seen_rgb[i]); + seen_count = 0; + pthread_mutex_destroy(&ctx->mutex); + free(ctx); + ctx = NULL; +} + +/* ---- tests ------------------------------------------------------------------- */ + +static void test_single_engine_batch_shares_one_asset_matching_frame(void) { + const char *labels[] = {"bird", "bird", "squirrel"}; + const int tracks[] = {17, -1, 4}; + set_detections(3, labels, tracks); + snprintf(ctx->observation_labels, sizeof(ctx->observation_labels), "bird"); + + AVFrame *frame = make_frame(40); + time_t now = time(NULL); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, now, &result)); + av_frame_free(&frame); + /* The detection result (DB rows, MQTT) is untouched by eligibility. */ + TEST_ASSERT_EQUAL_INT(3, result.count); + TEST_ASSERT_EQUAL_INT(1, object_calls); + wait_idle(); + + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(DISTINCT asset_uuid) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(DISTINCT batch_id) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_assets WHERE state = 'ready' " + "AND origin = 'inference_input' AND width = 320 AND height = 240")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations WHERE label != 'bird'")); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations WHERE " + "engine_key = 'legacy-primary' AND engine_type = 'object' AND " + "model = 'ssd_mobilenet_v2.sod' AND " + "stream_name_at_capture = 'feeder-east'")); + char sql[512], value[1024]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM object_observations WHERE camera_uuid = '%s' " + "AND observed_at_ms / 1000 = %lld", cam_uuid, (long long)now); + TEST_ASSERT_EQUAL_INT(2, scalar(sql)); + /* Track only where track_id >= 0: namespace = engine key, session = boot UUID. */ + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM object_observations WHERE track_id IS NULL")); + snprintf(sql, sizeof(sql), "SELECT count(*) FROM object_observations WHERE track_id = '17' " + "AND track_namespace = 'legacy-primary' AND track_session = '%s' AND object_index = 0", + detection_evidence_boot_session()); + TEST_ASSERT_EQUAL_INT(1, scalar(sql)); + /* SOD-style pre-resize is recorded against the decoded frame. */ + text_column("SELECT transform_json FROM observation_assets", value, sizeof(value)); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(value, "\"mode\":\"resize\""), value); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(value, "\"source_width\":320"), value); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(value, "\"inference_width\":300"), value); + + char asset[OBS_UUID_SIZE], path[1024]; + text_column("SELECT asset_uuid FROM object_observations LIMIT 1", asset, sizeof(asset)); + asset_file(asset, path, sizeof(path)); + int w = 0, h = 0; + uint8_t *decoded = decode_jpeg_rgb(path, &w, &h); + TEST_ASSERT_EQUAL_INT(FRAME_W, w); + TEST_ASSERT_EQUAL_INT(FRAME_H, h); + double q = psnr(decoded, seen_rgb[0], (size_t)w * h * 3); + char msg[64]; + snprintf(msg, sizeof(msg), "PSNR %.2f dB", q); + TEST_ASSERT_TRUE_MESSAGE(q > 30.0, msg); + free(decoded); +} + +/* A2 moving bird: each asset is its own inference frame, never a later one. */ +static void test_moving_bird_assets_match_their_inference_frames(void) { + const char *labels[] = {"bird"}; + const int tracks[] = {-1}; + set_detections(1, labels, tracks); + detection_result_t result; + + AVFrame *first = make_frame(20); + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, first, time(NULL), &result)); + av_frame_free(&first); + AVFrame *second = make_frame(240); + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, second, time(NULL), &result)); + av_frame_free(&second); + wait_idle(); + + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM observation_assets WHERE state = 'ready'")); + for (int i = 0; i < 2; ++i) { + char sql[256], asset[OBS_UUID_SIZE], path[1024]; + snprintf(sql, sizeof(sql), + "SELECT asset_uuid FROM object_observations ORDER BY observed_at_ms, rowid " + "LIMIT 1 OFFSET %d", i); + text_column(sql, asset, sizeof(asset)); + asset_file(asset, path, sizeof(path)); + int w = 0, h = 0; + uint8_t *decoded = decode_jpeg_rgb(path, &w, &h); + size_t n = (size_t)w * h * 3; + double own = psnr(decoded, seen_rgb[i], n); + double other = psnr(decoded, seen_rgb[1 - i], n); + char msg[96]; + snprintf(msg, sizeof(msg), "asset %d: own %.2f dB, other %.2f dB", i, own, other); + TEST_ASSERT_TRUE_MESSAGE(own > 30.0, msg); + TEST_ASSERT_TRUE_MESSAGE(own > other + 6.0, msg); + free(decoded); + } +} + +static void test_disabled_submits_nothing_and_allocates_nothing(void) { + const char *labels[] = {"bird", "bird"}; + const int tracks[] = {-1, -1}; + set_detections(2, labels, tracks); + atomic_store(&ctx->observation_evidence_enabled, false); + + AVFrame *frame = make_frame(40); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + TEST_ASSERT_EQUAL_INT(2, result.count); + + /* Enabled but no eligible label: no submit either. */ + atomic_store(&ctx->observation_evidence_enabled, true); + snprintf(ctx->observation_labels, sizeof(ctx->observation_labels), "cat,dog"); + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + av_frame_free(&frame); + + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_FALSE(s.worker_running); + TEST_ASSERT_EQUAL_UINT64(0, s.accepted); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM observation_assets")); +} + +/* Multi-engine fan-out: one batch (and asset) per object engine with + * survivors; the motion engine's result never becomes an observation. */ +static void test_multi_engine_one_batch_per_object_engine(void) { + const char *labels[] = {"bird", "bird"}; + const int tracks[] = {5, -1}; + set_detections(2, labels, tracks); + + ctx->engine_count = 3; + const char *keys[] = {"motion", "birds-a", "birds-b"}; + const char *types[] = {"motion", "object", "object"}; + for (int i = 0; i < 3; ++i) { + stream_detection_engine_t *e = &ctx->engines[i]; + memset(e, 0, sizeof(*e)); + snprintf(e->engine_key, sizeof(e->engine_key), "%s", keys[i]); + snprintf(e->engine_type, sizeof(e->engine_type), "%s", types[i]); + snprintf(e->model_path, sizeof(e->model_path), "/m/%s.tflite", keys[i]); + e->enabled = true; + e->threshold = 0.5f; + e->interval_seconds = 1; + ctx->engine_models[i] = (i > 0) ? &fake_model : NULL; + ctx->engine_last_run[i] = 0; + } + geometry_w = -1; /* no fixed input: mode none */ + + AVFrame *frame = make_frame(100); + detection_result_t result; + TEST_ASSERT_TRUE(detect_on_decoded_frame(ctx, frame, time(NULL), &result)); + av_frame_free(&frame); + TEST_ASSERT_EQUAL_INT(1, motion_calls); + TEST_ASSERT_EQUAL_INT(2, object_calls); + TEST_ASSERT_EQUAL_INT(5, result.count); /* motion + 2 + 2, unchanged */ + wait_idle(); + + TEST_ASSERT_EQUAL_INT(4, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations " + "WHERE label = 'motion' OR engine_key = 'motion'")); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(DISTINCT asset_uuid) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(DISTINCT batch_id) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(DISTINCT asset_uuid) FROM object_observations " + "WHERE engine_key = 'birds-a'")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM object_observations WHERE " + "engine_key = 'birds-b' AND model = 'birds-b.tflite' " + "AND track_namespace = 'birds-b' AND track_id = '5'")); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM observation_assets WHERE state = 'ready' " + "AND transform_json LIKE '%\"mode\":\"none\"%'")); + /* Both copies are the shared inference frame. */ + for (int i = 0; i < 2; ++i) { + char sql[256], asset[OBS_UUID_SIZE], path[1024]; + snprintf(sql, sizeof(sql), "SELECT asset_uuid FROM object_observations WHERE engine_key = '%s'", + i == 0 ? "birds-a" : "birds-b"); + text_column(sql, asset, sizeof(asset)); + asset_file(asset, path, sizeof(path)); + int w = 0, h = 0; + uint8_t *decoded = decode_jpeg_rgb(path, &w, &h); + TEST_ASSERT_TRUE(psnr(decoded, seen_rgb[i], (size_t)w * h * 3) > 30.0); + free(decoded); + } +} + +static void test_settings_setter_reaches_context(void) { + /* No running context with this name. */ + TEST_ASSERT_EQUAL_INT(-1, unified_detection_set_observation_settings("nope", true, "bird")); +} + +int main(void) { + init_logger(); + set_log_level(LOG_LEVEL_ERROR); + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_det_evidence_%ld.db", (long)getpid()); + snprintf(storage_dir, sizeof(storage_dir), "/tmp/lightnvr_det_evidence_%ld", (long)getpid()); + unlink(db_path); + mkdir(storage_dir, 0750); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0) return 1; + + stream_config_t *s = calloc(1, sizeof(*s)); + if (!s) return 1; + safe_strcpy(s->name, "feeder-east", sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + s->observation_evidence_enabled = true; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + if (add_stream_config(s) <= 0) return 1; + memset(s, 0, sizeof(*s)); + if (get_stream_config_by_name("feeder-east", s) != 0) return 1; + safe_strcpy(cam_uuid, s->camera_uuid, sizeof(cam_uuid), 0); + free(s); + safe_strcpy(fake_model.type, MODEL_TYPE_SOD, sizeof(fake_model.type), 0); + + UNITY_BEGIN(); + RUN_TEST(test_disabled_submits_nothing_and_allocates_nothing); + RUN_TEST(test_single_engine_batch_shares_one_asset_matching_frame); + RUN_TEST(test_moving_bird_assets_match_their_inference_frames); + RUN_TEST(test_multi_engine_one_batch_per_object_engine); + RUN_TEST(test_settings_setter_reaches_context); + int failures = UNITY_END(); + + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char cmd[600]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", storage_dir); + if (system(cmd) != 0) fprintf(stderr, "cleanup failed: %s\n", storage_dir); + return failures; +} diff --git a/tests/unit/test_event_envelope.c b/tests/unit/test_event_envelope.c index 4c31917cb..41e833331 100644 --- a/tests/unit/test_event_envelope.c +++ b/tests/unit/test_event_envelope.c @@ -3,10 +3,15 @@ * @brief Versioned event registry, envelope, schema, and privacy tests. */ +#define _GNU_SOURCE + #include #include #include #include +#include + +#include #include @@ -144,7 +149,7 @@ void test_registry_is_stable_and_versioned(void) { int count = 0; const event_type_definition_t *registry = event_registry_all(&count); TEST_ASSERT_NOT_NULL(registry); - TEST_ASSERT_EQUAL_INT(13, count); + TEST_ASSERT_EQUAL_INT(14, count); for (int index = 0; index < count; index++) { TEST_ASSERT_NOT_NULL(strstr(registry[index].type, "io.lightnvr.")); size_t length = strlen(registry[index].type); @@ -439,6 +444,239 @@ void test_lpr_notification_is_restricted_and_plate_free(void) { cJSON_Delete(data); } +/* ---- INT_01 L5: io.lightnvr.detection.observation_changed.v1 ---------- */ + +#define OBSERVATION_TYPE "io.lightnvr.detection.observation_changed.v1" +#ifndef LIGHTNVR_SOURCE_DIR +#error "LIGHTNVR_SOURCE_DIR must be defined" +#endif +#define OBSERVATION_EVENT_FIXTURES \ + LIGHTNVR_SOURCE_DIR "/tests/fixtures/integrations/observations/events" + +static char *read_text_file(const char *path) { + FILE *file = fopen(path, "rb"); + if (!file) return NULL; + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + char *buffer = calloc(1, (size_t)size + 1); + if (buffer && fread(buffer, 1, (size_t)size, file) != (size_t)size) { + free(buffer); + buffer = NULL; + } + fclose(file); + return buffer; +} + +static time_t parse_event_time(const char *text) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + TEST_ASSERT_EQUAL_INT(6, sscanf(text, "%4d-%2d-%2dT%2d:%2d:%2dZ", + &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec)); + tm.tm_year -= 1900; + tm.tm_mon -= 1; + return timegm(&tm); +} + +/* Decode a fixture MQTT envelope through the normal constructor. */ +static int create_from_fixture(const cJSON *fixture, const cJSON *data, + const char *subject_override, + event_envelope_t *event, char *error, + size_t error_size) { + const cJSON *subject = cJSON_GetObjectItemCaseSensitive(fixture, "subject"); + return event_envelope_create_with_severity_and_id( + event, cJSON_GetObjectItemCaseSensitive(fixture, "type")->valuestring, + cJSON_GetObjectItemCaseSensitive(fixture, "source")->valuestring, + subject_override ? subject_override : subject->valuestring, + parse_event_time( + cJSON_GetObjectItemCaseSensitive(fixture, "time")->valuestring), + data, EVENT_SEVERITY_INFO, + cJSON_GetObjectItemCaseSensitive(fixture, "id")->valuestring, error, + error_size); +} + +static cJSON *observation_fixture(const char *name) { + char path[1024]; + snprintf(path, sizeof(path), "%s/%s", OBSERVATION_EVENT_FIXTURES, name); + char *text = read_text_file(path); + TEST_ASSERT_NOT_NULL_MESSAGE(text, path); + cJSON *fixture = cJSON_Parse(text); + free(text); + TEST_ASSERT_NOT_NULL_MESSAGE(fixture, path); + return fixture; +} + +void test_observation_changed_registry_entry(void) { + const event_type_definition_t *definition = + event_registry_find(OBSERVATION_TYPE); + TEST_ASSERT_NOT_NULL(definition); + TEST_ASSERT_EQUAL_STRING("detection", definition->family); + TEST_ASSERT_EQUAL_STRING("A durable object observation was created or changed", + definition->description); + TEST_ASSERT_EQUAL_INT(EVENT_SEVERITY_INFO, definition->severity); + TEST_ASSERT_FALSE(definition->dynamic_severity); + TEST_ASSERT_EQUAL_INT(EVENT_SENSITIVITY_OPERATIONAL, definition->sensitivity); + TEST_ASSERT_EQUAL_INT(EVENT_MEDIA_REFERENCE_ALLOWED, definition->media_policy); + TEST_ASSERT_EQUAL_INT(EVENT_RATE_HIGH, definition->expected_rate); + TEST_ASSERT_EQUAL_INT(EVENT_SUBJECT_CAMERA, definition->subject_kind); + TEST_ASSERT_EQUAL_INT(3600, definition->default_expiry_seconds); +} + +void test_observation_changed_validator_accepts_c3_fixtures(void) { + DIR *dir = opendir(OBSERVATION_EVENT_FIXTURES); + TEST_ASSERT_NOT_NULL_MESSAGE(dir, OBSERVATION_EVENT_FIXTURES); + int checked = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + size_t length = strlen(entry->d_name); + if (length < 6 || strcmp(entry->d_name + length - 5, ".json") != 0) { + continue; + } + cJSON *fixture = observation_fixture(entry->d_name); + event_envelope_t event; + char error[256] = ""; + TEST_ASSERT_EQUAL_INT_MESSAGE( + 0, create_from_fixture(fixture, + cJSON_GetObjectItemCaseSensitive(fixture, "data"), + NULL, &event, error, sizeof(error)), + error); + char *serialized = event_envelope_serialize(&event, error, sizeof(error)); + char *expected = cJSON_PrintUnformatted(fixture); + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, serialized, entry->d_name); + free(serialized); + free(expected); + event_envelope_clear(&event); + cJSON_Delete(fixture); + checked++; + } + closedir(dir); + TEST_ASSERT_EQUAL_INT(8, checked); +} + +static void assert_observation_rejected(const char *fixture_name, + void (*mutate)(cJSON *data), + const char *subject_override, + const char *label) { + cJSON *fixture = observation_fixture(fixture_name); + cJSON *data = cJSON_Duplicate( + cJSON_GetObjectItemCaseSensitive(fixture, "data"), true); + if (mutate) mutate(data); + event_envelope_t event; + char error[256] = ""; + TEST_ASSERT_EQUAL_INT_MESSAGE( + -1, create_from_fixture(fixture, data, subject_override, &event, + error, sizeof(error)), + label); + TEST_ASSERT_NOT_EQUAL_MESSAGE(0, strlen(error), label); + cJSON_Delete(data); + cJSON_Delete(fixture); +} + +static void drop_observation_id(cJSON *d) { cJSON_DeleteItemFromObject(d, "observation_id"); } +static void drop_revision(cJSON *d) { cJSON_DeleteItemFromObject(d, "revision"); } +static void drop_camera(cJSON *d) { cJSON_DeleteItemFromObject(d, "camera_uuid"); } +static void drop_label(cJSON *d) { cJSON_DeleteItemFromObject(d, "label"); } +static void drop_observed_at(cJSON *d) { cJSON_DeleteItemFromObject(d, "observed_at"); } +static void drop_cause(cJSON *d) { cJSON_DeleteItemFromObject(d, "change_cause"); } +static void drop_state(cJSON *d) { cJSON_DeleteItemFromObject(d, "evidence_state"); } +static void drop_reason(cJSON *d) { cJSON_DeleteItemFromObject(d, "evidence_reason"); } +static void drop_asset(cJSON *d) { cJSON_DeleteItemFromObject(d, "asset_id"); } +static void drop_snapshot(cJSON *d) { cJSON_DeleteItemFromObject(d, "snapshot_url"); } +static void bad_observation_id(cJSON *d) { + cJSON_ReplaceItemInObject(d, "observation_id", cJSON_CreateString("bird-1")); +} +static void zero_revision(cJSON *d) { + cJSON_ReplaceItemInObject(d, "revision", cJSON_CreateNumber(0)); +} +static void fractional_revision(cJSON *d) { + cJSON_ReplaceItemInObject(d, "revision", cJSON_CreateNumber(2.5)); +} +static void long_label(cJSON *d) { + cJSON_ReplaceItemInObject(d, "label", + cJSON_CreateString("abcdefghijklmnopqrstuvwxyz012345")); +} +static void empty_label(cJSON *d) { + cJSON_ReplaceItemInObject(d, "label", cJSON_CreateString("")); +} +static void bad_observed_at(cJSON *d) { + cJSON_ReplaceItemInObject(d, "observed_at", cJSON_CreateString("yesterday")); +} +static void unknown_cause(cJSON *d) { + cJSON_ReplaceItemInObject(d, "change_cause", cJSON_CreateString("deleted")); +} +static void unknown_state(cJSON *d) { + cJSON_ReplaceItemInObject(d, "evidence_state", cJSON_CreateString("lost")); +} +static void reason_on_ready(cJSON *d) { + cJSON_ReplaceItemInObject(d, "evidence_reason", cJSON_CreateString("queue_full")); +} +static void null_reason(cJSON *d) { + cJSON_ReplaceItemInObject(d, "evidence_reason", cJSON_CreateNull()); +} +static void unknown_reason(cJSON *d) { + cJSON_ReplaceItemInObject(d, "evidence_reason", cJSON_CreateString("gremlins")); +} +static void mismatched_reason(cJSON *d) { + /* queue_full is an unavailable reason, not an expired one. */ + cJSON_ReplaceItemInObject(d, "evidence_reason", cJSON_CreateString("queue_full")); +} +static void snapshot_on_pending(cJSON *d) { + cJSON_AddStringToObject( + d, "snapshot_url", + "/api/observations/55555555-5555-4555-8555-555555555555/snapshot"); +} +static void wrong_snapshot(cJSON *d) { + cJSON_ReplaceItemInObject( + d, "snapshot_url", + cJSON_CreateString("/api/observations/99999999-9999-4999-8999-999999999999/snapshot")); +} +static void bad_asset(cJSON *d) { + cJSON_ReplaceItemInObject(d, "asset_id", cJSON_CreateNull()); +} +static void other_camera(cJSON *d) { + cJSON_ReplaceItemInObject( + d, "camera_uuid", cJSON_CreateString("33333333-3333-4333-8333-333333333333")); +} + +void test_observation_changed_validator_rejects_contract_violations(void) { + const char *ready = "a-r2-evidence-ready.json"; + const char *pending = "a-r1-created-pending.json"; + const char *expired = "a-r5-evidence-expired.json"; + const char *unavailable = "b-r1-created-unavailable.json"; + assert_observation_rejected(ready, drop_observation_id, NULL, "missing observation_id"); + assert_observation_rejected(ready, drop_revision, NULL, "missing revision"); + assert_observation_rejected(ready, drop_camera, NULL, "missing camera_uuid"); + assert_observation_rejected(ready, drop_label, NULL, "missing label"); + assert_observation_rejected(ready, drop_observed_at, NULL, "missing observed_at"); + assert_observation_rejected(ready, drop_cause, NULL, "missing change_cause"); + assert_observation_rejected(ready, drop_state, NULL, "missing evidence_state"); + assert_observation_rejected(ready, drop_reason, NULL, "missing evidence_reason"); + assert_observation_rejected(ready, drop_asset, NULL, "missing asset_id"); + assert_observation_rejected(ready, drop_snapshot, NULL, "ready without snapshot_url"); + assert_observation_rejected(ready, bad_observation_id, NULL, "non-UUID observation_id"); + assert_observation_rejected(ready, zero_revision, NULL, "revision 0"); + assert_observation_rejected(ready, fractional_revision, NULL, "fractional revision"); + assert_observation_rejected(ready, long_label, NULL, "32-byte label"); + assert_observation_rejected(ready, empty_label, NULL, "empty label"); + assert_observation_rejected(ready, bad_observed_at, NULL, "non-RFC3339 observed_at"); + assert_observation_rejected(ready, unknown_cause, NULL, "unknown cause"); + assert_observation_rejected(ready, unknown_state, NULL, "unknown state"); + assert_observation_rejected(ready, reason_on_ready, NULL, "reason on ready"); + assert_observation_rejected(ready, wrong_snapshot, NULL, "snapshot_url of another observation"); + assert_observation_rejected(ready, bad_asset, NULL, "null asset_id"); + assert_observation_rejected(ready, other_camera, NULL, "camera_uuid differs from subject"); + assert_observation_rejected(ready, NULL, + "camera/33333333-3333-4333-8333-333333333333", + "subject differs from camera_uuid"); + assert_observation_rejected(pending, snapshot_on_pending, NULL, "snapshot_url on pending"); + assert_observation_rejected(expired, null_reason, NULL, "expired without reason"); + assert_observation_rejected(expired, mismatched_reason, NULL, "reason invalid for expired"); + assert_observation_rejected(unavailable, null_reason, NULL, "unavailable without reason"); + assert_observation_rejected(unavailable, unknown_reason, NULL, "unknown reason"); + assert_observation_rejected(expired, snapshot_on_pending, NULL, "snapshot_url on expired"); +} + int main(void) { UNITY_BEGIN(); RUN_TEST(test_registry_is_stable_and_versioned); @@ -451,5 +689,8 @@ int main(void) { RUN_TEST(test_lpr_notification_is_restricted_and_plate_free); RUN_TEST(test_system_severity_range_subjects_and_persisted_identity); RUN_TEST(test_system_payload_severity_must_match_envelope); + RUN_TEST(test_observation_changed_registry_entry); + RUN_TEST(test_observation_changed_validator_accepts_c3_fixtures); + RUN_TEST(test_observation_changed_validator_rejects_contract_violations); return UNITY_END(); } diff --git a/tests/unit/test_go2rtc_process_config_generation.c b/tests/unit/test_go2rtc_process_config_generation.c index 4927a9ec3..4e0338c50 100644 --- a/tests/unit/test_go2rtc_process_config_generation.c +++ b/tests/unit/test_go2rtc_process_config_generation.c @@ -163,6 +163,75 @@ void test_generate_startup_config_uses_saved_webrtc_settings(void) { rmdir(config_dir); } +/* External IP accepts a list: one WebRTC candidate per entry, so clients on + * different paths (LAN, Tailscale, public) can each reach go2rtc. */ +void test_generate_startup_config_writes_one_candidate_per_external_ip(void) { + load_default_config(&g_config); + g_config.go2rtc_api_port = 31984; + g_config.go2rtc_rtsp_port = 31554; + g_config.go2rtc_webrtc_listen_port = 31555; + g_config.go2rtc_stun_enabled = false; + snprintf(g_config.go2rtc_external_ip, sizeof(g_config.go2rtc_external_ip), + "100.101.102.103, 192.168.101.112 *"); + g_config.go2rtc_ice_servers[0] = '\0'; + + char dir_template[] = "/tmp/lightnvr-go2rtc-config-XXXXXX"; + char *config_dir = mkdtemp(dir_template); + TEST_ASSERT_NOT_NULL(config_dir); + char config_path[PATH_MAX]; + snprintf(config_path, sizeof(config_path), "%s/go2rtc.yaml", config_dir); + + TEST_ASSERT_TRUE(go2rtc_process_generate_startup_config("/bin/true", config_dir, + g_config.go2rtc_api_port)); + char *contents = read_text_file(config_path); + TEST_ASSERT_NOT_NULL(contents); + TEST_ASSERT_NOT_NULL(strstr(contents, " - \"100.101.102.103:31555\"\n" + " - \"192.168.101.112:31555\"\n" + " - \"*:31555\"\n")); + TEST_ASSERT_NULL(strstr(contents, "103, 192")); + + free(contents); + unlink(config_path); + rmdir(config_dir); +} + +void test_external_candidates_parse_valid_and_reject_invalid(void) { + char out[GO2RTC_MAX_EXTERNAL_CANDIDATES][GO2RTC_CANDIDATE_SIZE]; + + TEST_ASSERT_EQUAL_INT(0, go2rtc_parse_external_candidates("", 8555, out, GO2RTC_MAX_EXTERNAL_CANDIDATES)); + TEST_ASSERT_EQUAL_INT(0, go2rtc_parse_external_candidates(" , ", 8555, out, GO2RTC_MAX_EXTERNAL_CANDIDATES)); + TEST_ASSERT_EQUAL_INT(0, go2rtc_parse_external_candidates(NULL, 8555, out, GO2RTC_MAX_EXTERNAL_CANDIDATES)); + + TEST_ASSERT_EQUAL_INT(1, go2rtc_parse_external_candidates("203.0.113.7", 8555, out, GO2RTC_MAX_EXTERNAL_CANDIDATES)); + TEST_ASSERT_EQUAL_STRING("203.0.113.7:8555", out[0]); + + TEST_ASSERT_EQUAL_INT(6, go2rtc_parse_external_candidates( + "auto,\nnvr.example.com:9000; [fd7a:115c::1]:8556 fd7a:115c::2 stun:stun.example.org:3478 203.0.113.7", + 8555, out, GO2RTC_MAX_EXTERNAL_CANDIDATES)); + TEST_ASSERT_EQUAL_STRING("*:8555", out[0]); + TEST_ASSERT_EQUAL_STRING("nvr.example.com:9000", out[1]); + TEST_ASSERT_EQUAL_STRING("[fd7a:115c::1]:8556", out[2]); + TEST_ASSERT_EQUAL_STRING("[fd7a:115c::2]:8555", out[3]); + TEST_ASSERT_EQUAL_STRING("stun:stun.example.org:3478", out[4]); + TEST_ASSERT_EQUAL_STRING("203.0.113.7:8555", out[5]); + + /* Duplicates collapse. */ + TEST_ASSERT_EQUAL_INT(1, go2rtc_parse_external_candidates("10.0.0.1, 10.0.0.1:8555", 8555, out, + GO2RTC_MAX_EXTERNAL_CANDIDATES)); + + /* Anything that could break the YAML or is not a host is rejected. */ + const char *invalid[] = { + "10.0.0.1\"", "10.0.0.1:0", "10.0.0.1:70000", "10.0.0.1:port", "host/name", + "a\\b", "[fd00::1", "fd00::1]:80", "stun:", "*:x", "-bad.example", "10.0.0.1:", + "1,2,3,4,5,6,7,8,9", + }; + for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) { + TEST_ASSERT_EQUAL_INT_MESSAGE(-1, go2rtc_parse_external_candidates(invalid[i], 8555, out, + GO2RTC_MAX_EXTERNAL_CANDIDATES), + invalid[i]); + } +} + void test_generate_startup_config_writes_db_override_file(void) { load_default_config(&g_config); g_config.go2rtc_api_port = 31984; @@ -537,6 +606,8 @@ int main(int argc, char **argv) { UNITY_BEGIN(); RUN_TEST(test_generate_startup_config_uses_saved_webrtc_settings); + RUN_TEST(test_generate_startup_config_writes_one_candidate_per_external_ip); + RUN_TEST(test_external_candidates_parse_valid_and_reject_invalid); RUN_TEST(test_generate_startup_config_writes_db_override_file); RUN_TEST(test_override_publication_preserves_old_file_on_io_failure_and_recovers); RUN_TEST(test_audio_policy_keeps_db_override_dormant); diff --git a/tests/unit/test_observation_codec.c b/tests/unit/test_observation_codec.c new file mode 100644 index 000000000..95b37f2bf --- /dev/null +++ b/tests/unit/test_observation_codec.c @@ -0,0 +1,145 @@ +/* INT_01 L2: UUIDv5 message IDs, base64url and the opaque history cursor + * (contract sections 5.4 and 7.4). */ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include "unity.h" +#include "database/db_observations.h" +#include "utils/base64.h" +#include "utils/uuid.h" + +void setUp(void) {} +void tearDown(void) {} + +#define EPOCH "66666666-6666-4666-8666-666666666666" +#define CONTRACT_CURSOR \ + "eyJ2IjoxLCJzZXEiOjEwNDIsImVwb2NoIjoiNjY2NjY2NjYtNjY2Ni00NjY2LTg2NjYtNjY2NjY2NjY2NjY2In0" + +static void test_uuid_v5_matches_rfc_and_contract_vectors(void) { + char out[LIGHTNVR_UUID_STRING_SIZE]; + /* The frozen namespace is itself UUIDv5(NAMESPACE_URL, ...). */ + const char *url = "https://lightnvr.io/ns/observation-change/v1"; + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v5( + "6ba7b811-9dad-11d1-80b4-00c04fd430c8", url, strlen(url), out)); + TEST_ASSERT_EQUAL_STRING(OBS_MESSAGE_ID_NAMESPACE, out); + /* Python: uuid.uuid5(uuid.NAMESPACE_DNS, "python.org") */ + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v5( + "6BA7B810-9DAD-11D1-80B4-00C04FD430C8", "python.org", 10, out)); + TEST_ASSERT_EQUAL_STRING("886313e1-3b8a-5372-9b90-0c9aee199e5d", out); + + TEST_ASSERT_EQUAL_INT(0, observation_message_id( + "urn:lightnvr:11111111-1111-4111-8111-111111111111", + "55555555-5555-4555-8555-555555555555", 2, out)); + TEST_ASSERT_EQUAL_STRING("43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9", out); +} + +static void test_uuid_v5_rejects_bad_namespace(void) { + char out[LIGHTNVR_UUID_STRING_SIZE] = "unchanged"; + TEST_ASSERT_EQUAL_INT(-1, lightnvr_uuid_generate_v5("not-a-uuid", "x", 1, out)); + TEST_ASSERT_EQUAL_INT(-1, lightnvr_uuid_generate_v5(NULL, "x", 1, out)); + TEST_ASSERT_EQUAL_INT(-1, lightnvr_uuid_generate_v5(OBS_MESSAGE_ID_NAMESPACE, NULL, 1, out)); + TEST_ASSERT_EQUAL_INT(-1, observation_message_id("urn:x", "bad", 1, out)); + TEST_ASSERT_EQUAL_INT(-1, observation_message_id( + "urn:x", "55555555-5555-4555-8555-555555555555", 0, out)); +} + +static void test_base64url_round_trip_and_strictness(void) { + const unsigned char bytes[] = {0xfb, 0xff, 0xfe, 0x00, 0x3e}; + char text[16]; + size_t length = 0; + TEST_ASSERT_EQUAL_INT(0, base64url_encode(bytes, sizeof(bytes), text, sizeof(text), &length)); + TEST_ASSERT_EQUAL_STRING("-__-AD4", text); + TEST_ASSERT_EQUAL_UINT(7, length); + unsigned char decoded[16]; + TEST_ASSERT_EQUAL_INT(0, base64url_decode(text, strlen(text), decoded, sizeof(decoded), &length)); + TEST_ASSERT_EQUAL_UINT(sizeof(bytes), length); + TEST_ASSERT_EQUAL_MEMORY(bytes, decoded, sizeof(bytes)); + /* Exact-size buffer works; one byte short fails. */ + TEST_ASSERT_EQUAL_INT(0, base64url_encode(bytes, sizeof(bytes), text, 8, NULL)); + TEST_ASSERT_EQUAL_INT(-1, base64url_encode(bytes, sizeof(bytes), text, 7, NULL)); + TEST_ASSERT_EQUAL_INT(-1, base64url_decode("-__-AD4", 7, decoded, 4, NULL)); + /* Standard alphabet, padding, bad length and non-canonical tail bits. */ + TEST_ASSERT_EQUAL_INT(-1, base64url_decode("+//+", 4, decoded, sizeof(decoded), NULL)); + TEST_ASSERT_EQUAL_INT(-1, base64url_decode("AA==", 4, decoded, sizeof(decoded), NULL)); + TEST_ASSERT_EQUAL_INT(-1, base64url_decode("AAAAA", 5, decoded, sizeof(decoded), NULL)); + TEST_ASSERT_EQUAL_INT(-1, base64url_decode("AB", 2, decoded, sizeof(decoded), NULL)); + TEST_ASSERT_EQUAL_INT(-1, base64url_decode("A A=", 4, decoded, sizeof(decoded), NULL)); + TEST_ASSERT_EQUAL_INT(0, base64url_decode("", 0, decoded, sizeof(decoded), &length)); + TEST_ASSERT_EQUAL_UINT(0, length); +} + +static void test_cursor_encoding_matches_contract_example(void) { + char cursor[OBS_CURSOR_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(1042, EPOCH, cursor, sizeof(cursor))); + TEST_ASSERT_EQUAL_STRING(CONTRACT_CURSOR, cursor); + int64_t seq = -1; + char epoch[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_INT64(1042, seq); + TEST_ASSERT_EQUAL_STRING(EPOCH, epoch); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_encode(0, EPOCH, cursor, sizeof(cursor))); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, observation_cursor_decode(cursor, &seq, epoch)); + TEST_ASSERT_EQUAL_INT64(0, seq); +} + +static void encode_json(const char *json, char *out, size_t size) { + TEST_ASSERT_EQUAL_INT(0, base64url_encode((const unsigned char *)json, strlen(json), + out, size, NULL)); +} + +static void test_cursor_rejects_malformed_values(void) { + int64_t seq; + char epoch[OBS_UUID_SIZE]; + char cursor[1024]; + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode("", &seq, epoch)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode("!!!!", &seq, epoch)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode("head", &seq, epoch)); + encode_json("{\"v\":2,\"seq\":1,\"epoch\":\"" EPOCH "\"}", cursor, sizeof(cursor)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); + encode_json("{\"v\":1,\"seq\":-1,\"epoch\":\"" EPOCH "\"}", cursor, sizeof(cursor)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); + encode_json("{\"v\":1,\"seq\":1.5,\"epoch\":\"" EPOCH "\"}", cursor, sizeof(cursor)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); + encode_json("{\"v\":1,\"seq\":1,\"epoch\":\"nope\"}", cursor, sizeof(cursor)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); + encode_json("{\"v\":1,\"epoch\":\"" EPOCH "\"}", cursor, sizeof(cursor)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); + encode_json("[1,2]", cursor, sizeof(cursor)); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); + /* Over 256 characters is invalid even when it decodes. */ + char padded[512]; + snprintf(padded, sizeof(padded), "{\"v\":1,\"seq\":1,\"epoch\":\"" EPOCH "\",\"x\":\"%0200d\"}", 0); + encode_json(padded, cursor, sizeof(cursor)); + TEST_ASSERT_TRUE(strlen(cursor) > 256); + TEST_ASSERT_EQUAL_INT(DB_OBS_INVALID_CURSOR, observation_cursor_decode(cursor, &seq, epoch)); +} + +static void test_reason_enum_is_bounded_per_state(void) { + TEST_ASSERT_TRUE(observation_reason_is_valid(OBS_ASSET_PENDING, NULL)); + TEST_ASSERT_TRUE(observation_reason_is_valid(OBS_ASSET_READY, "")); + TEST_ASSERT_FALSE(observation_reason_is_valid(OBS_ASSET_READY, "write_failed")); + const char *unavailable[] = {"queue_full", "budget_exhausted", "encode_failed", + "write_failed", "interrupted", "no_matching_media", + "disabled"}; + for (size_t i = 0; i < sizeof(unavailable) / sizeof(unavailable[0]); ++i) { + TEST_ASSERT_TRUE(observation_reason_is_valid(OBS_ASSET_UNAVAILABLE, unavailable[i])); + TEST_ASSERT_FALSE(observation_reason_is_valid(OBS_ASSET_EXPIRED, unavailable[i])); + } + TEST_ASSERT_TRUE(observation_reason_is_valid(OBS_ASSET_EXPIRED, "retention_expired")); + TEST_ASSERT_TRUE(observation_reason_is_valid(OBS_ASSET_EXPIRED, "deleted")); + TEST_ASSERT_FALSE(observation_reason_is_valid(OBS_ASSET_UNAVAILABLE, "deleted")); + TEST_ASSERT_FALSE(observation_reason_is_valid(OBS_ASSET_UNAVAILABLE, NULL)); + TEST_ASSERT_FALSE(observation_reason_is_valid(OBS_ASSET_UNAVAILABLE, "Queue_Full")); + TEST_ASSERT_FALSE(observation_reason_is_valid(OBS_ASSET_UNAVAILABLE, "made_up")); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_uuid_v5_matches_rfc_and_contract_vectors); + RUN_TEST(test_uuid_v5_rejects_bad_namespace); + RUN_TEST(test_base64url_round_trip_and_strictness); + RUN_TEST(test_cursor_encoding_matches_contract_example); + RUN_TEST(test_cursor_rejects_malformed_values); + RUN_TEST(test_reason_enum_is_bounded_per_state); + return UNITY_END(); +} diff --git a/tests/unit/test_observation_crash_boundaries.c b/tests/unit/test_observation_crash_boundaries.c new file mode 100644 index 000000000..a31e4a094 --- /dev/null +++ b/tests/unit/test_observation_crash_boundaries.c @@ -0,0 +1,461 @@ +/* INT_01 L11 / A6: SIGKILL at every observation persistence boundary, then a + * fresh process generation runs the main.c startup sequence (L4 reconcile -> + * L3 evidence init -> L5 relay start/drain) against the same database and + * storage tree. + * + * Each boundary forks a "generation 1" child that runs the real evidence + * worker, the real relay and the real router/outbox on a migrated database, + * submits one two-object batch and is killed with SIGKILL from a stage hook at + * exactly one boundary. The parent first checks that the kill landed where it + * was meant to (on-disk and DB state at the crash), then forks a + * "generation 2" child that restarts the pipeline, and finally inspects the + * database and tree directly with sqlite3 to assert: + * - no ready asset points at a missing, partial or temporary file, no + * pending asset survives, and no final file exists without a ready row; + * - every committed change has been relayed exactly once into the durable + * outbox under its deterministic message ID (no change lost, none + * duplicated), and an outbox row accepted before the crash is still + * pending delivery. + * + * The hooks (observation_evidence_set_stage_hook / observation_relay_set_ + * stage_hook) are only reachable from linked code, never from config, the + * environment or the API, so production cannot trigger these kill points. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity.h" +#include "core/config.h" +#include "core/event_identity.h" +#include "core/event_router.h" +#include "core/mqtt_delivery_worker.h" +#include "core/observation_evidence.h" +#include "core/observation_relay.h" +#include "core/observation_retention.h" +#include "database/db_core.h" +#include "database/db_event_routes.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +#define TOPIC_PREFIX "lightnvr-crash" +#define OBJECTS 2 +#define EXIT_NOT_KILLED 77 + +static char root_dir[256]; + +/* The exact bytes the "detector" submitted. JPEG markers around a pattern so + * any truncation or substitution is visible. */ +static uint8_t submitted[4096]; + +typedef enum { HOOK_EVIDENCE, HOOK_RELAY } hook_kind_t; + +typedef struct { + const char *name; + hook_kind_t kind; + int stage; + bool expect_ready; /* asset outcome after restart */ +} boundary_t; + +static const boundary_t *g_boundary; /* child side */ + +/* ---- generation helpers (run inside forked children) ------------------- */ + +#define CHILD_CHECK(cond, msg) do { if (!(cond)) { \ + fprintf(stderr, "[child %s] %s\n", g_boundary ? g_boundary->name : "?", msg); \ + _exit(2); } } while (0) + +static void paths(const boundary_t *b, char *storage, size_t ssize, char *db, size_t dsize) { + snprintf(storage, ssize, "%s/%s", root_dir, b->name); + snprintf(db, dsize, "%s/%s.db", root_dir, b->name); +} + +static int evidence_hook(observation_evidence_stage_t stage, const char *path, void *ctx) { + (void)path; + (void)ctx; + if (g_boundary && g_boundary->kind == HOOK_EVIDENCE && (int)stage == g_boundary->stage) + raise(SIGKILL); + return 0; +} + +static void relay_hook(observation_relay_stage_t stage, const observation_publication_t *row, + void *ctx) { + (void)ctx; + /* Only once the evidence change (pending -> ready) is committed, so the + * kill sits strictly after the last evidence-side boundary. */ + if (g_boundary && g_boundary->kind == HOOK_RELAY && (int)stage == g_boundary->stage && + row->cause == OBS_CAUSE_EVIDENCE) + raise(SIGKILL); +} + +/* The main.c startup sequence (design decision 2). */ +static void start_pipeline(const char *storage, const char *db) { + CHILD_CHECK(init_database(db) == 0, "init_database"); + CHILD_CHECK(event_identity_init() == 0, "event_identity_init"); + g_config.mqtt_enabled = true; + safe_strcpy(g_config.mqtt_topic_prefix, TOPIC_PREFIX, sizeof(g_config.mqtt_topic_prefix), 0); + safe_strcpy(g_config.storage_path, storage, sizeof(g_config.storage_path), 0); + + observation_reconcile_result_t reconcile; + CHILD_CHECK(observation_retention_startup(storage, &reconcile) == 0, "reconcile"); + observation_evidence_config_t evidence = {.storage_path = storage}; + CHILD_CHECK(observation_evidence_init(&evidence) == 0, "evidence init"); + observation_relay_options_t relay; + memset(&relay, 0, sizeof(relay)); + relay.storage_path = storage; + relay.poll_interval_ms = 50; + CHILD_CHECK(observation_relay_start(&relay) == 0, "relay start"); +} + +static void stop_pipeline(void) { + observation_relay_stop(); + observation_evidence_shutdown(OBS_EVIDENCE_DEFAULT_DRAIN_MS); + event_router_shutdown(); + event_identity_shutdown(); + shutdown_database(); +} + +static void seed_camera_and_route(char camera_uuid[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + CHILD_CHECK(s != NULL, "calloc"); + safe_strcpy(s->name, "crash-cam", sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + s->observation_evidence_enabled = true; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + CHILD_CHECK(add_stream_config(s) > 0, "add_stream_config"); + memset(s, 0, sizeof(*s)); + CHILD_CHECK(get_stream_config_by_name("crash-cam", s) == 0, "get_stream_config"); + safe_strcpy(camera_uuid, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); + + event_route_t route; + memset(&route, 0, sizeof(route)); + safe_strcpy(route.name, "Observation crash route", sizeof(route.name), 0); + route.enabled = true; + safe_strcpy(route.destination_key, EVENT_ROUTE_DEFAULT_DESTINATION, + sizeof(route.destination_key), 0); + safe_strcpy(route.scope_type, "all", sizeof(route.scope_type), 0); + safe_strcpy(route.predicate_json, "{\"version\":1}", sizeof(route.predicate_json), 0); + safe_strcpy(route.schedule_json, "{\"version\":1,\"timezone\":\"UTC\",\"windows\":[]}", + sizeof(route.schedule_json), 0); + safe_strcpy(route.event_types[0], OBSERVATION_CHANGED_EVENT_TYPE, + sizeof(route.event_types[0]), 0); + route.event_type_count = 1; + CHILD_CHECK(db_event_route_create(&route) == DB_EVENT_ROUTE_OK, "route create"); +} + +static int64_t wall_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* Generation 1: submit one batch and wait to be killed at the boundary. */ +static void generation_one(const boundary_t *b) { + g_boundary = b; + char storage[512], db[512]; + paths(b, storage, sizeof(storage), db, sizeof(db)); + start_pipeline(storage, db); + char camera[OBS_UUID_SIZE]; + seed_camera_and_route(camera); + observation_evidence_set_stage_hook(evidence_hook, NULL); + observation_relay_set_stage_hook(relay_hook, NULL); + + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + CHILD_CHECK(lightnvr_uuid_generate_v4(batch.batch_id) == 0, "batch id"); + safe_strcpy(batch.camera_uuid, camera, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = wall_ms(); + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "api", sizeof(batch.engine_type), 0); + batch.object_count = OBJECTS; + for (int i = 0; i < OBJECTS; ++i) { + batch.objects[i].object_index = i; + safe_strcpy(batch.objects[i].label, "bird", sizeof(batch.objects[i].label), 0); + batch.objects[i].confidence = 0.9; + } + observation_evidence_transform_t transform = { + .mode = OBS_TRANSFORM_NONE, .source_width = 64, .source_height = 48, + .inference_width = 64, .inference_height = 48, .scale_x = 1.0, .scale_y = 1.0, + }; + uint8_t *jpeg = malloc(sizeof(submitted)); + CHILD_CHECK(jpeg != NULL, "malloc"); + memcpy(jpeg, submitted, sizeof(submitted)); + char asset[OBS_UUID_SIZE]; + CHILD_CHECK(observation_evidence_submit_jpeg(&batch, jpeg, sizeof(submitted), 64, 48, + &transform, asset) == OBS_EVIDENCE_ACCEPTED, + "submit"); + for (int i = 0; i < 1000; ++i) usleep(10000); /* 10 s for the hook to fire */ + fprintf(stderr, "[child %s] boundary never reached\n", b->name); + _exit(EXIT_NOT_KILLED); +} + +/* Generation 2: the next process start. Returns once nothing is pending. */ +static void generation_two(const boundary_t *b) { + g_boundary = NULL; + char storage[512], db[512]; + paths(b, storage, sizeof(storage), db, sizeof(db)); + start_pipeline(storage, db); + for (int i = 0; i < 500; ++i) { + observation_publication_t rows[8]; + int pending = db_observation_pending_publications(rows, 8); + CHILD_CHECK(pending >= 0, "pending read"); + if (pending == 0) break; + usleep(10000); + } + stop_pipeline(); + _exit(0); +} + +static int run_child(void (*fn)(const boundary_t *), const boundary_t *b) { + fflush(stdout); + fflush(stderr); + pid_t pid = fork(); + TEST_ASSERT_TRUE_MESSAGE(pid >= 0, "fork"); + if (pid == 0) { + fn(b); + _exit(3); + } + int status = 0; + TEST_ASSERT_EQUAL_INT(pid, waitpid(pid, &status, 0)); + return status; +} + +/* ---- parent-side inspection (raw sqlite3, no lightNVR state) ----------- */ + +static sqlite3 *open_db(const boundary_t *b) { + char storage[512], db[512]; + paths(b, storage, sizeof(storage), db, sizeof(db)); + sqlite3 *handle = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open_v2(db, &handle, SQLITE_OPEN_READONLY, NULL)); + sqlite3_busy_timeout(handle, 3000); + return handle; +} + +static int64_t q64(sqlite3 *db, const char *sql) { + sqlite3_stmt *stmt = NULL; + int64_t value = -999; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int64(stmt, 0); + sqlite3_finalize(stmt); + TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(-999, value, sql); + return value; +} + +static int q(sqlite3 *db, const char *sql) { return (int)q64(db, sql); } + +/* Count files under /observations whose name ends with suffix. */ +static int count_files(const char *dir, const char *suffix) { + DIR *d = opendir(dir); + if (!d) return 0; + int n = 0; + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; + char path[1024]; + snprintf(path, sizeof(path), "%s/%s", dir, entry->d_name); + struct stat st; + if (lstat(path, &st) != 0) continue; + if (S_ISDIR(st.st_mode)) { + n += count_files(path, suffix); + } else { + size_t len = strlen(entry->d_name), slen = strlen(suffix); + if (len >= slen && strcmp(entry->d_name + len - slen, suffix) == 0) n++; + } + } + closedir(d); + return n; +} + +static int evidence_files(const boundary_t *b, const char *suffix) { + char dir[512]; + snprintf(dir, sizeof(dir), "%s/%s/observations", root_dir, b->name); + return count_files(dir, suffix); +} + +/* State at the crash proves the kill landed on the intended boundary. */ +static void assert_crash_state(const boundary_t *b) { + sqlite3 *db = open_db(b); + int pending_assets = q(db, "SELECT count(*) FROM observation_assets WHERE state = 'pending'"); + int ready_assets = q(db, "SELECT count(*) FROM observation_assets WHERE state = 'ready'"); + TEST_ASSERT_EQUAL_INT_MESSAGE(OBJECTS, q(db, "SELECT count(*) FROM object_observations"), + b->name); + if (b->kind == HOOK_EVIDENCE) { + TEST_ASSERT_EQUAL_INT_MESSAGE(1, pending_assets, b->name); + TEST_ASSERT_EQUAL_INT(0, ready_assets); + int tmp = evidence_files(b, ".jpg.tmp"), final = evidence_files(b, ".jpg"); + if (b->stage == OBS_EVIDENCE_STAGE_AFTER_INSERT) { + TEST_ASSERT_EQUAL_INT(0, tmp); + TEST_ASSERT_EQUAL_INT(0, final); + } else if (b->stage == OBS_EVIDENCE_STAGE_BEFORE_RENAME) { + TEST_ASSERT_EQUAL_INT_MESSAGE(1, tmp, "tmp file written and fsynced at the crash"); + TEST_ASSERT_EQUAL_INT(0, final); + } else { + TEST_ASSERT_EQUAL_INT(0, tmp); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, final, "final file renamed before the commit"); + } + /* Only the created changes exist; nothing claims ready. */ + TEST_ASSERT_EQUAL_INT(OBJECTS, q(db, "SELECT count(*) FROM observation_changes")); + } else { + TEST_ASSERT_EQUAL_INT(0, pending_assets); + TEST_ASSERT_EQUAL_INT(1, ready_assets); + TEST_ASSERT_EQUAL_INT(2 * OBJECTS, q(db, "SELECT count(*) FROM observation_changes")); + /* At least the killed evidence change is not fully through the relay. */ + if (b->stage == OBS_RELAY_STAGE_AFTER_MARK) { + TEST_ASSERT_TRUE(q(db, "SELECT count(*) FROM observation_publications p JOIN " + "observation_changes c ON c.seq = p.change_seq WHERE " + "c.cause = 'evidence' AND p.state = 'relayed'") >= 1); + } else { + TEST_ASSERT_TRUE(q(db, "SELECT count(*) FROM observation_publications p JOIN " + "observation_changes c ON c.seq = p.change_seq WHERE " + "c.cause = 'evidence' AND p.state = 'pending'") >= 1); + } + int in_outbox = q(db, "SELECT count(*) FROM event_outbox e JOIN observation_publications p " + "ON p.message_id = e.event_id JOIN observation_changes c ON " + "c.seq = p.change_seq WHERE c.cause = 'evidence'"); + if (b->stage == OBS_RELAY_STAGE_BEFORE_ENQUEUE) + TEST_ASSERT_TRUE_MESSAGE(in_outbox < OBJECTS, "killed before the outbox step"); + else + TEST_ASSERT_TRUE_MESSAGE(in_outbox >= 1, "outbox accepted before the crash"); + } + sqlite3_close(db); +} + +static void read_file_exact(const char *path, uint8_t *out, size_t size) { + FILE *f = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, path); + TEST_ASSERT_EQUAL_size_t(size, fread(out, 1, size, f)); + TEST_ASSERT_EQUAL_INT(EOF, fgetc(f)); + fclose(f); +} + +static void assert_recovered(const boundary_t *b) { + sqlite3 *db = open_db(b); + /* No unresolved or unfinished evidence. */ + TEST_ASSERT_EQUAL_INT(0, q(db, "SELECT count(*) FROM observation_assets WHERE state = 'pending'")); + TEST_ASSERT_EQUAL_INT(0, evidence_files(b, ".jpg.tmp")); + int ready = q(db, "SELECT count(*) FROM observation_assets WHERE state = 'ready'"); + TEST_ASSERT_EQUAL_INT_MESSAGE(evidence_files(b, ".jpg"), ready, + "every final file has a ready row and vice versa"); + if (b->expect_ready) { + TEST_ASSERT_EQUAL_INT(1, ready); + sqlite3_stmt *stmt = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_prepare_v2(db, + "SELECT path, bytes FROM observation_assets WHERE state = 'ready'", -1, &stmt, NULL)); + TEST_ASSERT_EQUAL_INT(SQLITE_ROW, sqlite3_step(stmt)); + char full[1024]; + snprintf(full, sizeof(full), "%s/%s/%s", root_dir, b->name, + (const char *)sqlite3_column_text(stmt, 0)); + TEST_ASSERT_EQUAL_INT64((int64_t)sizeof(submitted), sqlite3_column_int64(stmt, 1)); + sqlite3_finalize(stmt); + static uint8_t stored[sizeof(submitted)]; + read_file_exact(full, stored, sizeof(stored)); + TEST_ASSERT_EQUAL_MEMORY_MESSAGE(submitted, stored, sizeof(submitted), + "ready asset is the complete submitted image"); + } else { + TEST_ASSERT_EQUAL_INT(0, ready); + TEST_ASSERT_EQUAL_INT(1, q(db, "SELECT count(*) FROM observation_assets WHERE " + "state = 'unavailable' AND reason = 'interrupted'")); + } + /* Every observation reached revision 2 (created + evidence outcome). */ + TEST_ASSERT_EQUAL_INT(OBJECTS, q(db, "SELECT count(*) FROM object_observations WHERE revision = 2")); + int changes = q(db, "SELECT count(*) FROM observation_changes"); + TEST_ASSERT_EQUAL_INT(2 * OBJECTS, changes); + + /* Every committed change was relayed, exactly once, under its message ID. */ + TEST_ASSERT_EQUAL_INT(changes, q(db, "SELECT count(*) FROM observation_publications WHERE " + "state = 'relayed'")); + TEST_ASSERT_EQUAL_INT(changes, q(db, "SELECT count(*) FROM event_outbox WHERE event_type = '" + OBSERVATION_CHANGED_EVENT_TYPE "' AND destination = '" + MQTT_EVENT_OUTBOX_DESTINATION "'")); + TEST_ASSERT_EQUAL_INT(changes, q(db, "SELECT count(DISTINCT e.event_id) FROM event_outbox e " + "JOIN observation_publications p ON p.message_id = e.event_id")); + /* No broker in this test: the accepted rows are still deliverable. */ + TEST_ASSERT_EQUAL_INT(changes, q(db, "SELECT count(*) FROM event_outbox WHERE state = 'pending'")); + sqlite3_close(db); +} + +static void run_boundary(const boundary_t *b) { + int status = run_child(generation_one, b); + if (WIFEXITED(status)) + TEST_FAIL_MESSAGE(WEXITSTATUS(status) == EXIT_NOT_KILLED ? "boundary never reached" + : "generation 1 failed"); + TEST_ASSERT_TRUE(WIFSIGNALED(status)); + TEST_ASSERT_EQUAL_INT(SIGKILL, WTERMSIG(status)); + assert_crash_state(b); + + status = run_child(generation_two, b); + TEST_ASSERT_TRUE_MESSAGE(WIFEXITED(status) && WEXITSTATUS(status) == 0, "restart failed"); + assert_recovered(b); + + /* A further restart changes nothing (idempotent recovery, no replays). */ + status = run_child(generation_two, b); + TEST_ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0); + assert_recovered(b); +} + +/* ---- tests -------------------------------------------------------------- */ + +static const boundary_t AFTER_BATCH_INSERT = { + "after_batch_insert", HOOK_EVIDENCE, OBS_EVIDENCE_STAGE_AFTER_INSERT, false}; +static const boundary_t AFTER_TMP_WRITE = { + "after_tmp_write", HOOK_EVIDENCE, OBS_EVIDENCE_STAGE_BEFORE_RENAME, false}; +static const boundary_t AFTER_RENAME_BEFORE_COMMIT = { + "after_rename_before_commit", HOOK_EVIDENCE, OBS_EVIDENCE_STAGE_BEFORE_COMMIT, false}; +static const boundary_t AFTER_COMMIT_BEFORE_RELAY = { + "after_commit_before_relay", HOOK_RELAY, OBS_RELAY_STAGE_BEFORE_ENQUEUE, true}; +static const boundary_t AFTER_OUTBOX_BEFORE_MARK = { + "after_outbox_enqueue_before_mark", HOOK_RELAY, OBS_RELAY_STAGE_AFTER_ENQUEUE, true}; +static const boundary_t AFTER_RELAY_BEFORE_DELIVERY = { + "after_relay_before_delivery", HOOK_RELAY, OBS_RELAY_STAGE_AFTER_MARK, true}; + +void test_kill_after_batch_insert(void) { run_boundary(&AFTER_BATCH_INSERT); } +void test_kill_after_tmp_write(void) { run_boundary(&AFTER_TMP_WRITE); } +void test_kill_after_rename_before_commit(void) { run_boundary(&AFTER_RENAME_BEFORE_COMMIT); } +void test_kill_after_commit_before_relay(void) { run_boundary(&AFTER_COMMIT_BEFORE_RELAY); } +void test_kill_after_outbox_enqueue_before_mark(void) { run_boundary(&AFTER_OUTBOX_BEFORE_MARK); } +void test_kill_after_relay_before_delivery(void) { run_boundary(&AFTER_RELAY_BEFORE_DELIVERY); } + +void setUp(void) {} +void tearDown(void) {} + +int main(void) { + snprintf(root_dir, sizeof(root_dir), "/tmp/lightnvr_obs_crash_%ld", (long)getpid()); + if (mkdir(root_dir, 0700) != 0 && errno != EEXIST) return 1; + for (size_t i = 0; i < sizeof(submitted); ++i) submitted[i] = (uint8_t)((i * 31 + 7) & 0xff); + submitted[0] = 0xff; submitted[1] = 0xd8; + submitted[sizeof(submitted) - 2] = 0xff; submitted[sizeof(submitted) - 1] = 0xd9; + + UNITY_BEGIN(); + RUN_TEST(test_kill_after_batch_insert); + RUN_TEST(test_kill_after_tmp_write); + RUN_TEST(test_kill_after_rename_before_commit); + RUN_TEST(test_kill_after_commit_before_relay); + RUN_TEST(test_kill_after_outbox_enqueue_before_mark); + RUN_TEST(test_kill_after_relay_before_delivery); + int failures = UNITY_END(); + + if (failures == 0) { + char cmd[512]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s'", root_dir); + if (system(cmd) != 0) fprintf(stderr, "cleanup failed\n"); + } else { + fprintf(stderr, "Crash-boundary evidence retained in %s\n", root_dir); + } + return failures; +} diff --git a/tests/unit/test_observation_diagnostics.c b/tests/unit/test_observation_diagnostics.c new file mode 100644 index 000000000..8a87980c0 --- /dev/null +++ b/tests/unit/test_observation_diagnostics.c @@ -0,0 +1,525 @@ +/* INT_01 L10: observation pipeline diagnostics. Metric and JSON + * serialization, disabled-mode zeros (no queue allocated), DB-derived + * publication/history counts, counters moving under an evidence queue + * saturation, and the contract section 8.2 Retry-After rule. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "unity.h" +#include "core/event_identity.h" +#include "core/observation_diagnostics.h" +#include "core/observation_evidence.h" +#include "database/db_core.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" +#include "video/ffmpeg_utils.h" +#include "web/api_handlers_metrics.h" + +static char db_path[256]; +static char storage_dir[256]; +static char cam_a[OBS_UUID_SIZE]; + +/* ---- helpers ---------------------------------------------------------- */ + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + if (rc != SQLITE_OK) fprintf(stderr, "%s: %s\n", sql, error ? error : ""); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static int64_t base_time_ms = 1786000000000LL; + +static observation_batch_input_t make_batch(int objects) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, cam_a, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = base_time_ms++; + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + batch.object_count = objects; + for (int i = 0; i < objects; ++i) { + batch.objects[i].object_index = i; + safe_strcpy(batch.objects[i].label, "bird", sizeof(batch.objects[i].label), 0); + batch.objects[i].confidence = 0.9; + } + return batch; +} + +static void insert_pending(int objects) { + observation_batch_input_t batch = make_batch(objects); + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.origin, "inference_input", sizeof(asset.origin), 0); + safe_strcpy(asset.media_type, "image/jpeg", sizeof(asset.media_type), 0); + observation_insert_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &result)); +} + +static uint8_t *make_rgb(int width, int height) { + size_t size = (size_t)width * (size_t)height * 3; + uint8_t *buf = calloc(1, size + RGB24_SWS_TAIL_PADDING); + TEST_ASSERT_NOT_NULL(buf); + return buf; +} + +static bool has_line(const char *text, const char *line) { + size_t length = strlen(line); + for (const char *p = strstr(text, line); p; p = strstr(p + 1, line)) { + bool starts = p == text || p[-1] == '\n'; + bool ends = p[length] == '\n' || p[length] == '\0'; + if (starts && ends) return true; + } + return false; +} + +/* Unity is built without double support. */ +#define ASSERT_NUM(expected, actual) \ + TEST_ASSERT_EQUAL_INT64((int64_t)((expected) * 1000.0), (int64_t)((actual) * 1000.0)) + +#define ASSERT_LINE(text, line) TEST_ASSERT_TRUE_MESSAGE(has_line((text), (line)), (line)) + +static double json_number(const cJSON *object, const char *path) { + char copy[256]; + safe_strcpy(copy, path, sizeof(copy), 0); + const cJSON *node = object; + for (char *save = NULL, *part = strtok_r(copy, ".", &save); part; + part = strtok_r(NULL, ".", &save)) + node = cJSON_GetObjectItemCaseSensitive(node, part); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsNumber(node), path); + return node->valuedouble; +} + +static bool json_bool(const cJSON *object, const char *path) { + char copy[256]; + safe_strcpy(copy, path, sizeof(copy), 0); + const cJSON *node = object; + for (char *save = NULL, *part = strtok_r(copy, ".", &save); part; + part = strtok_r(NULL, ".", &save)) + node = cJSON_GetObjectItemCaseSensitive(node, part); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsBool(node), path); + return cJSON_IsTrue(node); +} + +/* ---- stall hook (saturation) ------------------------------------------ */ + +static pthread_mutex_t hook_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t hook_cond = PTHREAD_COND_INITIALIZER; +static bool stall_enabled; +static bool stall_entered; + +static int stall_hook(observation_evidence_stage_t stage, const char *path, void *ctx) { + (void)path; (void)ctx; + if (stage != OBS_EVIDENCE_STAGE_ITEM_BEGIN) return 0; + pthread_mutex_lock(&hook_lock); + stall_entered = true; + pthread_cond_broadcast(&hook_cond); + while (stall_enabled) pthread_cond_wait(&hook_cond, &hook_lock); + pthread_mutex_unlock(&hook_lock); + return 0; +} + +static void release_worker(void) { + pthread_mutex_lock(&hook_lock); + stall_enabled = false; + pthread_cond_broadcast(&hook_cond); + pthread_mutex_unlock(&hook_lock); +} + +static void wait_stalled(void) { + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_sec += 5; + pthread_mutex_lock(&hook_lock); + while (!stall_entered) + if (pthread_cond_timedwait(&hook_cond, &hook_lock, &deadline) != 0) break; + bool entered = stall_entered; + pthread_mutex_unlock(&hook_lock); + TEST_ASSERT_TRUE_MESSAGE(entered, "worker never reached the stall hook"); +} + +/* ---- fixtures --------------------------------------------------------- */ + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets;"); + observation_relay_reset_stats(); + stall_enabled = false; + stall_entered = false; +} + +void tearDown(void) { + release_worker(); + observation_evidence_shutdown(0); + observation_evidence_set_stage_hook(NULL, NULL); +} + +/* ---- tests ------------------------------------------------------------ */ + +void test_disabled_pipeline_reports_zeros_and_no_queue(void) { + observation_evidence_shutdown(0); + observation_diagnostics_t diag; + TEST_ASSERT_EQUAL_INT(0, observation_diagnostics_collect(&diag, 0)); + TEST_ASSERT_FALSE(diag.evidence.allocated); + TEST_ASSERT_FALSE(diag.evidence.worker_running); + TEST_ASSERT_EQUAL_UINT64(0, diag.evidence.accepted); + TEST_ASSERT_EQUAL_UINT32(0, diag.evidence.backlog_items); + TEST_ASSERT_TRUE(diag.db_available); + TEST_ASSERT_EQUAL_INT64(0, diag.db.publications_pending); + TEST_ASSERT_EQUAL_INT64(0, diag.db.observations); + TEST_ASSERT_FALSE(diag.db.has_oldest_observed_at); + TEST_ASSERT_EQUAL_INT(0, diag.camera_count); + ASSERT_NUM(0.0, observation_diagnostics_relay_lag_seconds(&diag)); + + char *text = api_metrics_render_observations(&diag); + TEST_ASSERT_NOT_NULL(text); + ASSERT_LINE(text, "lightnvr_observation_accepted_total 0"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"queue_full\"} 0"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"budget_exhausted\"} 0"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"disabled\"} 0"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"untracked\"} 0"); + ASSERT_LINE(text, "lightnvr_observation_evidence_queue_allocated 0"); + ASSERT_LINE(text, "lightnvr_observation_evidence_worker_running 0"); + ASSERT_LINE(text, "lightnvr_observation_evidence_backlog_items 0"); + ASSERT_LINE(text, "lightnvr_observation_evidence_backlog_bytes 0"); + ASSERT_LINE(text, "lightnvr_observation_publications{state=\"pending\"} 0"); + ASSERT_LINE(text, "lightnvr_observation_relay_lag_seconds 0.000"); + ASSERT_LINE(text, "lightnvr_observation_evidence_budget_exhausted_cameras 0"); + /* No retained observation: the timestamp gauge has no sample. */ + TEST_ASSERT_NULL(strstr(text, "\nlightnvr_observation_history_oldest_observed_timestamp_seconds ")); + free(text); + + cJSON *json = observation_diagnostics_to_json(&diag); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_FALSE(json_bool(json, "active")); + TEST_ASSERT_FALSE(json_bool(json, "evidence.queue_allocated")); + ASSERT_NUM(0, json_number(json, "accepted")); + ASSERT_NUM(0, json_number(json, "dropped.queue_full")); + ASSERT_NUM(0, json_number(json, "publications.pending")); + TEST_ASSERT_TRUE(cJSON_IsNull(cJSON_GetObjectItemCaseSensitive( + cJSON_GetObjectItemCaseSensitive(json, "history"), "oldest_observed_at"))); + TEST_ASSERT_EQUAL_INT(0, cJSON_GetArraySize(cJSON_GetObjectItemCaseSensitive(json, "cameras"))); + cJSON_Delete(json); + observation_diagnostics_free(&diag); + /* Collecting never started the worker. */ + observation_evidence_stats_t after; + observation_evidence_get_stats(&after); + TEST_ASSERT_FALSE(after.allocated); +} + +void test_db_counts_publications_and_history_bounds(void) { + insert_pending(2); + insert_pending(1); + /* One failed, one expired publication; the third stays pending. */ + exec_sql("UPDATE observation_publications SET state = 'failed' WHERE change_seq = " + "(SELECT MIN(change_seq) FROM observation_publications);"); + exec_sql("UPDATE observation_publications SET state = 'expired' WHERE change_seq = " + "(SELECT MAX(change_seq) FROM observation_publications);"); + exec_sql("UPDATE observation_changes SET created_at_ms = 1786000005000 WHERE seq = " + "(SELECT change_seq FROM observation_publications WHERE state = 'pending');"); + + observation_diagnostics_t diag; + TEST_ASSERT_EQUAL_INT(0, observation_diagnostics_collect(&diag, 1786000065000LL)); + TEST_ASSERT_TRUE(diag.db_available); + TEST_ASSERT_EQUAL_INT64(3, diag.db.observations); + TEST_ASSERT_EQUAL_INT64(1, diag.db.publications_pending); + TEST_ASSERT_EQUAL_INT64(1, diag.db.publications_failed); + TEST_ASSERT_EQUAL_INT64(1, diag.db.publications_expired); + TEST_ASSERT_EQUAL_INT64(1786000005000LL, diag.db.oldest_pending_created_at_ms); + ASSERT_NUM(60.0, observation_diagnostics_relay_lag_seconds(&diag)); + TEST_ASSERT_TRUE(diag.db.has_oldest_observed_at); + TEST_ASSERT_TRUE(diag.db.oldest_observed_at_ms > 0); + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + TEST_ASSERT_EQUAL_INT64(meta.oldest_seq, diag.db.meta.oldest_seq); + TEST_ASSERT_EQUAL_INT64(meta.head_seq, diag.db.meta.head_seq); + + char *text = api_metrics_render_observations(&diag); + TEST_ASSERT_NOT_NULL(text); + ASSERT_LINE(text, "lightnvr_observation_publications{state=\"pending\"} 1"); + ASSERT_LINE(text, "lightnvr_observation_publications{state=\"failed\"} 1"); + ASSERT_LINE(text, "lightnvr_observation_publications{state=\"expired\"} 1"); + ASSERT_LINE(text, "lightnvr_observation_relay_lag_seconds 60.000"); + char line[160]; + snprintf(line, sizeof(line), "lightnvr_observation_history_oldest_seq %lld", + (long long)meta.oldest_seq); + ASSERT_LINE(text, line); + snprintf(line, sizeof(line), "lightnvr_observation_history_head_seq %lld", + (long long)meta.head_seq); + ASSERT_LINE(text, line); + TEST_ASSERT_NOT_NULL(strstr(text, "\nlightnvr_observation_history_oldest_observed_timestamp_seconds ")); + free(text); + + cJSON *json = observation_diagnostics_to_json(&diag); + ASSERT_NUM(1, json_number(json, "publications.failed")); + ASSERT_NUM(60.0, json_number(json, "publications.relay_lag_seconds")); + ASSERT_NUM((double)meta.oldest_seq, json_number(json, "history.oldest_seq")); + const cJSON *cursor = cJSON_GetObjectItemCaseSensitive( + cJSON_GetObjectItemCaseSensitive(json, "history"), "oldest_observed_at"); + TEST_ASSERT_TRUE(cJSON_IsString(cursor)); + TEST_ASSERT_EQUAL_INT(24, (int)strlen(cursor->valuestring)); /* YYYY-MM-DDTHH:MM:SS.mmmZ */ + cJSON_Delete(json); + observation_diagnostics_free(&diag); +} + +void test_serialization_of_a_populated_snapshot(void) { + observation_diagnostics_t diag; + memset(&diag, 0, sizeof(diag)); + diag.collected_at_ms = 1786000010000LL; + diag.evidence.initialized = true; + diag.evidence.allocated = true; + diag.evidence.worker_running = true; + diag.evidence.accepted = 41; + diag.evidence.dropped_queue_full = 3; + diag.evidence.dropped_budget = 2; + diag.evidence.dropped_disabled = 1; + diag.evidence.dropped_untracked = 4; + diag.evidence.unavailable_submitted = 5; + diag.evidence.ready = 30; + diag.evidence.failed_encode = 6; + diag.evidence.failed_write = 7; + diag.evidence.failed_db = 8; + diag.evidence.disabled_at_encode = 9; + diag.evidence.interrupted = 10; + diag.evidence.bytes_written = 123456; + diag.evidence.backlog_items = 11; + diag.evidence.backlog_bytes = 2048; + diag.evidence.drop_ring_depth = 12; + diag.evidence.high_water_items = 13; + diag.evidence.high_water_bytes = 4096; + diag.evidence.submit_latency_bucket[0] = 2; /* <= 1 us */ + diag.evidence.submit_latency_bucket[1] = 3; /* <= 5 us */ + diag.evidence.submit_latency_bucket[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 2] = 1; /* <= 10 ms */ + diag.evidence.submit_latency_bucket[OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1] = 1; /* +Inf */ + diag.evidence.submit_latency_count = 7; + diag.evidence.submit_latency_sum_ns = 50000000; + diag.evidence.submit_latency_max_ns = 20000000; + diag.evidence.limit_items = 64; + diag.evidence.limit_camera_items = 8; + diag.evidence.limit_bytes = 33554432; + diag.evidence.limit_camera_bytes = 33554432; + diag.evidence.camera_high_water_items = 6; + diag.evidence.discard_deferred = 14; + diag.evidence.discard_inline = 2; + diag.evidence.discard_pending_bytes = 2764800; + diag.evidence.limit_discard_bytes = 33554432; + diag.relay.running = true; + diag.relay.relayed = 20; + diag.relay.relayed_no_route = 2; + diag.relay.epoch_regenerations = 1; + diag.db_available = true; + diag.db.oldest_pending_created_at_ms = 1786000000000LL; + diag.db.publications_pending = 7; + diag.budget_exhausted_cameras = 1; + observation_retention_camera_status_t cameras[2]; + memset(cameras, 0, sizeof(cameras)); + safe_strcpy(cameras[0].camera_uuid, "11111111-1111-4111-8111-111111111111", OBS_UUID_SIZE, 0); + cameras[0].retained_bytes = 5000; + cameras[0].max_bytes = 10000; + cameras[0].max_age_days = 7; + safe_strcpy(cameras[1].camera_uuid, "22222222-2222-4222-8222-222222222222", OBS_UUID_SIZE, 0); + cameras[1].retained_bytes = 9000; + cameras[1].max_bytes = 8000; + cameras[1].max_age_days = 3; + cameras[1].budget_exhausted = true; + diag.cameras = cameras; + diag.camera_count = 2; + + char *text = api_metrics_render_observations(&diag); + TEST_ASSERT_NOT_NULL(text); + ASSERT_LINE(text, "# TYPE lightnvr_observation_accepted_total counter"); + ASSERT_LINE(text, "lightnvr_observation_accepted_total 41"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"queue_full\"} 3"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"budget_exhausted\"} 2"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"disabled\"} 1"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"untracked\"} 4"); + ASSERT_LINE(text, "lightnvr_observation_evidence_unavailable_total 5"); + ASSERT_LINE(text, "lightnvr_observation_evidence_ready_total 30"); + ASSERT_LINE(text, "lightnvr_observation_evidence_failures_total{reason=\"encode_failed\"} 6"); + ASSERT_LINE(text, "lightnvr_observation_evidence_failures_total{reason=\"write_failed\"} 7"); + ASSERT_LINE(text, "lightnvr_observation_evidence_failures_total{reason=\"db_error\"} 8"); + ASSERT_LINE(text, "lightnvr_observation_evidence_failures_total{reason=\"disabled\"} 9"); + ASSERT_LINE(text, "lightnvr_observation_evidence_failures_total{reason=\"interrupted\"} 10"); + ASSERT_LINE(text, "lightnvr_observation_evidence_bytes_written_total 123456"); + ASSERT_LINE(text, "lightnvr_observation_evidence_backlog_items 11"); + ASSERT_LINE(text, "lightnvr_observation_evidence_backlog_bytes 2048"); + ASSERT_LINE(text, "lightnvr_observation_evidence_drop_ring_depth 12"); + ASSERT_LINE(text, "lightnvr_observation_evidence_high_water_items 13"); + ASSERT_LINE(text, "lightnvr_observation_evidence_high_water_bytes 4096"); + ASSERT_LINE(text, "lightnvr_observation_evidence_queue_allocated 1"); + ASSERT_LINE(text, "lightnvr_observation_evidence_worker_running 1"); + ASSERT_LINE(text, "lightnvr_observation_evidence_retained_bytes{camera_uuid=\"11111111-1111-4111-8111-111111111111\"} 5000"); + ASSERT_LINE(text, "lightnvr_observation_evidence_budget_bytes{camera_uuid=\"22222222-2222-4222-8222-222222222222\"} 8000"); + ASSERT_LINE(text, "lightnvr_observation_evidence_budget_exhausted{camera_uuid=\"11111111-1111-4111-8111-111111111111\"} 0"); + ASSERT_LINE(text, "lightnvr_observation_evidence_budget_exhausted{camera_uuid=\"22222222-2222-4222-8222-222222222222\"} 1"); + ASSERT_LINE(text, "lightnvr_observation_evidence_budget_exhausted_cameras 1"); + ASSERT_LINE(text, "lightnvr_observation_publications{state=\"pending\"} 7"); + ASSERT_LINE(text, "lightnvr_observation_relay_lag_seconds 10.000"); + ASSERT_LINE(text, "lightnvr_observation_relay_running 1"); + ASSERT_LINE(text, "lightnvr_observation_relay_events_total{outcome=\"relayed\"} 20"); + ASSERT_LINE(text, "lightnvr_observation_relay_events_total{outcome=\"no_route\"} 2"); + ASSERT_LINE(text, "lightnvr_observation_history_epoch_regenerations_total 1"); + /* X2: submit-latency histogram (cumulative buckets), configured limits + * and the per-camera high-water mark. */ + ASSERT_LINE(text, "# TYPE lightnvr_observation_submit_latency_seconds histogram"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_bucket{le=\"0.000001\"} 2"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_bucket{le=\"0.000005\"} 5"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_bucket{le=\"0.001\"} 5"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_bucket{le=\"0.01\"} 6"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_bucket{le=\"+Inf\"} 7"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_sum 0.050000000"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_seconds_count 7"); + ASSERT_LINE(text, "lightnvr_observation_submit_latency_max_seconds 0.020000000"); + ASSERT_LINE(text, "lightnvr_observation_evidence_limit_items{scope=\"global\"} 64"); + ASSERT_LINE(text, "lightnvr_observation_evidence_limit_items{scope=\"camera\"} 8"); + ASSERT_LINE(text, "lightnvr_observation_evidence_limit_bytes{scope=\"global\"} 33554432"); + ASSERT_LINE(text, "lightnvr_observation_evidence_limit_bytes{scope=\"camera\"} 33554432"); + ASSERT_LINE(text, "lightnvr_observation_evidence_camera_high_water_items 6"); + ASSERT_LINE(text, "lightnvr_observation_evidence_discards_total{mode=\"deferred\"} 14"); + ASSERT_LINE(text, "lightnvr_observation_evidence_discards_total{mode=\"inline\"} 2"); + ASSERT_LINE(text, "lightnvr_observation_evidence_discard_pending_bytes 2764800"); + ASSERT_LINE(text, "lightnvr_observation_evidence_limit_bytes{scope=\"discard\"} 33554432"); + free(text); + + cJSON *json = observation_diagnostics_to_json(&diag); + TEST_ASSERT_NOT_NULL(json); + TEST_ASSERT_TRUE(json_bool(json, "active")); + ASSERT_NUM(41, json_number(json, "accepted")); + ASSERT_NUM(4, json_number(json, "dropped.untracked")); + ASSERT_NUM(2, json_number(json, "dropped.budget_exhausted")); + ASSERT_NUM(11, json_number(json, "evidence.backlog_items")); + ASSERT_NUM(4096, json_number(json, "evidence.high_water_bytes")); + ASSERT_NUM(7, json_number(json, "evidence.failures.write_failed")); + ASSERT_NUM(7, json_number(json, "publications.pending")); + ASSERT_NUM(10.0, json_number(json, "publications.relay_lag_seconds")); + TEST_ASSERT_TRUE(json_bool(json, "publications.relay_running")); + ASSERT_NUM(1, json_number(json, "budget_exhausted_cameras")); + const cJSON *list = cJSON_GetObjectItemCaseSensitive(json, "cameras"); + TEST_ASSERT_EQUAL_INT(2, cJSON_GetArraySize(list)); + const cJSON *second = cJSON_GetArrayItem(list, 1); + TEST_ASSERT_EQUAL_STRING("22222222-2222-4222-8222-222222222222", + cJSON_GetObjectItemCaseSensitive(second, "camera_uuid")->valuestring); + ASSERT_NUM(9000, json_number(second, "retained_bytes")); + ASSERT_NUM(8000, json_number(second, "max_bytes")); + ASSERT_NUM(3, json_number(second, "max_age_days")); + TEST_ASSERT_TRUE(json_bool(second, "budget_exhausted")); + cJSON_Delete(json); +} + +void test_counters_move_under_queue_saturation(void) { + observation_evidence_config_t config; + memset(&config, 0, sizeof(config)); + config.storage_path = storage_dir; + config.per_camera_max_items = 2; /* the item being encoded counts toward the depth */ + TEST_ASSERT_EQUAL_INT(0, observation_evidence_init(&config)); + observation_evidence_set_stage_hook(stall_hook, NULL); + stall_enabled = true; + + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(32, 24), 32, 24, NULL, asset)); + wait_stalled(); /* the worker holds item 1 */ + batch = make_batch(1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(32, 24), 32, 24, NULL, asset)); + for (int i = 0; i < 3; ++i) { + batch = make_batch(1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, + observation_evidence_submit(&batch, make_rgb(32, 24), 32, 24, NULL, + asset)); + } + + observation_diagnostics_t diag; + TEST_ASSERT_EQUAL_INT(0, observation_diagnostics_collect(&diag, 0)); + char *text = api_metrics_render_observations(&diag); + TEST_ASSERT_NOT_NULL(text); + ASSERT_LINE(text, "lightnvr_observation_accepted_total 2"); + ASSERT_LINE(text, "lightnvr_observation_dropped_total{reason=\"queue_full\"} 3"); + ASSERT_LINE(text, "lightnvr_observation_evidence_queue_allocated 1"); + ASSERT_LINE(text, "lightnvr_observation_evidence_worker_running 1"); + TEST_ASSERT_TRUE(diag.evidence.backlog_items >= 1); + TEST_ASSERT_TRUE(diag.evidence.high_water_items >= diag.evidence.backlog_items); + char line[128]; + snprintf(line, sizeof(line), "lightnvr_observation_evidence_backlog_items %u", + (unsigned)diag.evidence.backlog_items); + ASSERT_LINE(text, line); + free(text); + observation_diagnostics_free(&diag); + release_worker(); +} + +void test_snapshot_retry_after_rule(void) { + /* Under 5 s old: always 1. */ + TEST_ASSERT_EQUAL_INT(1, observation_snapshot_retry_after_seconds(0, 0)); + TEST_ASSERT_EQUAL_INT(1, observation_snapshot_retry_after_seconds(4999, 100)); + /* Then min(30, 2 x ahead), at least 1. */ + TEST_ASSERT_EQUAL_INT(1, observation_snapshot_retry_after_seconds(5000, 0)); + TEST_ASSERT_EQUAL_INT(2, observation_snapshot_retry_after_seconds(5000, 1)); + TEST_ASSERT_EQUAL_INT(6, observation_snapshot_retry_after_seconds(60000, 3)); + TEST_ASSERT_EQUAL_INT(30, observation_snapshot_retry_after_seconds(60000, 15)); + TEST_ASSERT_EQUAL_INT(30, observation_snapshot_retry_after_seconds(60000, 16)); + TEST_ASSERT_EQUAL_INT(30, observation_snapshot_retry_after_seconds(60000, UINT64_MAX)); + /* Clock skew (asset in the future) counts as fresh. */ + TEST_ASSERT_EQUAL_INT(1, observation_snapshot_retry_after_seconds(-1000, 50)); +} + +int main(void) { + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_observation_diag_%ld.db", (long)getpid()); + snprintf(storage_dir, sizeof(storage_dir), "/tmp/lightnvr_observation_diag_%ld", + (long)getpid()); + unlink(db_path); + mkdir(storage_dir, 0700); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0) return 1; + + UNITY_BEGIN(); + make_camera("diag_cam", cam_a); + RUN_TEST(test_disabled_pipeline_reports_zeros_and_no_queue); + RUN_TEST(test_db_counts_publications_and_history_bounds); + RUN_TEST(test_serialization_of_a_populated_snapshot); + RUN_TEST(test_counters_move_under_queue_saturation); + RUN_TEST(test_snapshot_retry_after_rule); + int result = UNITY_END(); + + observation_evidence_shutdown(0); + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char command[512]; + snprintf(command, sizeof(command), "rm -rf '%s'", storage_dir); + (void)system(command); + return result; +} diff --git a/tests/unit/test_observation_evidence.c b/tests/unit/test_observation_evidence.c new file mode 100644 index 000000000..7a1508612 --- /dev/null +++ b/tests/unit/test_observation_evidence.c @@ -0,0 +1,843 @@ +/* INT_01 L3: evidence frame queue and encoder worker. Lock-free bounded + * admission (count + bytes, global and per camera), drop records for + * queue_full / budget_exhausted, the tmp + fsync + rename + dir-fsync write, + * failure transitions and shutdown, against a real migrated database. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "unity.h" +#include "core/event_identity.h" +#include "core/observation_evidence.h" +#include "database/db_core.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" +#include "video/ffmpeg_utils.h" + +static char db_path[256]; +static char storage_dir[256]; +static char cam_a[OBS_UUID_SIZE]; +static char cam_b[OBS_UUID_SIZE]; +static int64_t base_time_ms = 1793000000000LL; /* 2026-10-26 UTC */ + +/* ---- helpers ---------------------------------------------------------- */ + +static int scalar(const char *sql) { + sqlite3_stmt *stmt = NULL; + int value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, value, sql); + return value; +} + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + if (rc != SQLITE_OK) fprintf(stderr, "%s: %s\n", sql, error ? error : ""); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static int count_assets(const char *where) { + char sql[512]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM observation_assets WHERE %s", where); + return scalar(sql); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + s->observation_evidence_enabled = true; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static observation_batch_input_t make_batch(const char *camera, int objects) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, camera, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = base_time_ms++; + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + safe_strcpy(batch.model, "ssd_mobilenet_v2.sod", sizeof(batch.model), 0); + batch.object_count = objects; + for (int i = 0; i < objects; ++i) { + observation_object_input_t *object = &batch.objects[i]; + object->object_index = i; + safe_strcpy(object->label, "bird", sizeof(object->label), 0); + object->confidence = 0.8; + object->has_box = true; + object->box_x = 0.1; object->box_y = 0.2; + object->box_width = 0.3; object->box_height = 0.4; + } + return batch; +} + +/* Packed RGB24 with the swscale tail padding, like ffmpeg_frame_to_rgb24_padded(). */ +static uint8_t *make_rgb(int width, int height) { + size_t size = (size_t)width * (size_t)height * 3; + uint8_t *buf = malloc(size + RGB24_SWS_TAIL_PADDING); + TEST_ASSERT_NOT_NULL(buf); + for (size_t i = 0; i < size; ++i) buf[i] = (uint8_t)((i * 7) & 0xff); + memset(buf + size, 0, RGB24_SWS_TAIL_PADDING); + return buf; +} + +static int64_t now_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec * 1000000000LL + ts.tv_nsec; +} + +static observation_evidence_stats_t stats(void) { + observation_evidence_stats_t s; + observation_evidence_get_stats(&s); + return s; +} + +/* Wait until the worker has nothing queued (items and drop records). */ +static void wait_idle(void) { + for (int i = 0; i < 1000; ++i) { + observation_evidence_stats_t s = stats(); + if (s.backlog_items == 0 && s.drop_ring_depth == 0) { + usleep(20000); /* let the last DB commit land */ + s = stats(); + if (s.backlog_items == 0 && s.drop_ring_depth == 0) return; + } + usleep(10000); + } + TEST_FAIL_MESSAGE("evidence worker did not go idle"); +} + +static void absolute_path(const char *relative, char *out, size_t size) { + snprintf(out, size, "%s/%s", storage_dir, relative); +} + +static bool file_exists(const char *path) { + struct stat st; + return stat(path, &st) == 0; +} + +/* ---- stall / fault hooks ---------------------------------------------- */ + +static pthread_mutex_t hook_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t hook_cond = PTHREAD_COND_INITIALIZER; +static bool stall_enabled; +static bool stall_entered; +static bool fail_before_rename; +static bool saw_tmp_without_final; + +static int test_hook(observation_evidence_stage_t stage, const char *path, void *ctx) { + (void)ctx; + if (stage == OBS_EVIDENCE_STAGE_ITEM_BEGIN) { + pthread_mutex_lock(&hook_lock); + stall_entered = true; + pthread_cond_broadcast(&hook_cond); + while (stall_enabled) pthread_cond_wait(&hook_cond, &hook_lock); + pthread_mutex_unlock(&hook_lock); + return 0; + } + if (stage == OBS_EVIDENCE_STAGE_BEFORE_RENAME && fail_before_rename) { + char tmp[1024]; + snprintf(tmp, sizeof(tmp), "%s.tmp", path); + saw_tmp_without_final = file_exists(tmp) && !file_exists(path); + return -1; + } + return 0; +} + +static void stall_worker(void) { + pthread_mutex_lock(&hook_lock); + stall_enabled = true; + stall_entered = false; + pthread_mutex_unlock(&hook_lock); +} + +static void wait_stalled(void) { + struct timespec deadline; + clock_gettime(CLOCK_REALTIME, &deadline); + deadline.tv_sec += 5; + pthread_mutex_lock(&hook_lock); + while (!stall_entered) { + if (pthread_cond_timedwait(&hook_cond, &hook_lock, &deadline) != 0) break; + } + bool entered = stall_entered; + pthread_mutex_unlock(&hook_lock); + TEST_ASSERT_TRUE_MESSAGE(entered, "worker never reached the stall hook"); +} + +static void release_worker(void) { + pthread_mutex_lock(&hook_lock); + stall_enabled = false; + pthread_cond_broadcast(&hook_cond); + pthread_mutex_unlock(&hook_lock); +} + +static bool probe_value = true; +static bool enabled_probe(const char *camera_uuid, void *ctx) { + (void)camera_uuid; (void)ctx; + return probe_value; +} + +static void init_evidence(int per_camera_items, int64_t global_bytes) { + observation_evidence_config_t config; + memset(&config, 0, sizeof(config)); + config.storage_path = storage_dir; + config.per_camera_max_items = per_camera_items; + config.global_max_bytes = global_bytes; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_init(&config)); +} + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets;"); + observation_evidence_set_stage_hook(test_hook, NULL); + observation_evidence_set_enabled_probe(NULL, NULL); + observation_evidence_set_budget_exhausted(cam_a, false); + observation_evidence_set_budget_exhausted(cam_b, false); + stall_enabled = false; + fail_before_rename = false; + saw_tmp_without_final = false; + probe_value = true; +} + +void tearDown(void) { + release_worker(); + observation_evidence_shutdown(0); + observation_evidence_set_stage_hook(NULL, NULL); +} + +/* ---- JPEG decode -------------------------------------------------------- */ + +static void decode_jpeg_dims(const uint8_t *data, size_t size, int *w, int *h) { + *w = *h = 0; + const AVCodec *codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); + TEST_ASSERT_NOT_NULL(codec); + AVCodecContext *ctx = avcodec_alloc_context3(codec); + TEST_ASSERT_NOT_NULL(ctx); + TEST_ASSERT_EQUAL_INT(0, avcodec_open2(ctx, codec, NULL)); + AVPacket *pkt = av_packet_alloc(); + AVFrame *frame = av_frame_alloc(); + TEST_ASSERT_EQUAL_INT(0, av_new_packet(pkt, (int)size)); + memcpy(pkt->data, data, size); + TEST_ASSERT_EQUAL_INT(0, avcodec_send_packet(ctx, pkt)); + TEST_ASSERT_EQUAL_INT(0, avcodec_receive_frame(ctx, frame)); + *w = frame->width; + *h = frame->height; + av_frame_free(&frame); + av_packet_free(&pkt); + avcodec_free_context(&ctx); +} + +static uint8_t *read_file(const char *path, size_t *size) { + FILE *f = fopen(path, "rb"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, path); + fseek(f, 0, SEEK_END); + long len = ftell(f); + fseek(f, 0, SEEK_SET); + uint8_t *buf = malloc((size_t)len); + TEST_ASSERT_NOT_NULL(buf); + TEST_ASSERT_EQUAL_size_t((size_t)len, fread(buf, 1, (size_t)len, f)); + fclose(f); + *size = (size_t)len; + return buf; +} + +static int count_tmp_files(const char *dir) { + int count = 0; + DIR *d = opendir(dir); + if (!d) return 0; + struct dirent *entry; + while ((entry = readdir(d)) != NULL) { + size_t len = strlen(entry->d_name); + if (len > 8 && strcmp(entry->d_name + len - 8, ".jpg.tmp") == 0) count++; + } + closedir(d); + return count; +} + +/* ---- tests -------------------------------------------------------------- */ + +static void test_disabled_mode_allocates_nothing(void) { + /* Not initialized: DISABLED, buffer freed (ASan proves no leak). */ + observation_evidence_shutdown(0); + observation_batch_input_t batch = make_batch(cam_a, 1); + char asset[OBS_UUID_SIZE] = "x"; + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DISABLED, + observation_evidence_submit(&batch, make_rgb(64, 48), 64, 48, NULL, asset)); + TEST_ASSERT_EQUAL_STRING("", asset); + + init_evidence(0, 0); + observation_evidence_stats_t s = stats(); + TEST_ASSERT_TRUE(s.initialized); + TEST_ASSERT_FALSE(s.worker_running); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_EQUAL_UINT64(0, s.accepted); + /* Budget flags live in static memory and do not start the worker. */ + observation_evidence_set_budget_exhausted(cam_a, true); + TEST_ASSERT_TRUE(observation_evidence_is_budget_exhausted(cam_a)); + s = stats(); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_FALSE(s.worker_running); + TEST_ASSERT_EQUAL_UINT32(1, s.budget_exhausted_cameras); + observation_evidence_set_budget_exhausted(cam_a, false); + TEST_ASSERT_EQUAL_INT(0, count_assets("1")); +} + +static void test_ready_jpeg_decodes_to_frame_dimensions(void) { + init_evidence(0, 0); + observation_batch_input_t batch = make_batch(cam_a, 2); + char asset[OBS_UUID_SIZE] = ""; + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(320, 240), 320, 240, NULL, asset)); + TEST_ASSERT_TRUE(lightnvr_uuid_is_valid(asset)); + TEST_ASSERT_TRUE(stats().allocated); /* lazily started by the first submit */ + wait_idle(); + TEST_ASSERT_TRUE(stats().worker_running); + + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, record.state); + TEST_ASSERT_EQUAL_INT(320, record.width); + TEST_ASSERT_EQUAL_INT(240, record.height); + TEST_ASSERT_EQUAL_STRING("inference_input", record.origin); + TEST_ASSERT_EQUAL_STRING("image/jpeg", record.media_type); + TEST_ASSERT_NOT_NULL(strstr(record.transform_json, "\"mode\":\"none\"")); + + char expected[512]; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_relative_path(cam_a, asset, + batch.observed_at_ms, expected, sizeof(expected))); + TEST_ASSERT_EQUAL_STRING(expected, record.path); + char layout[512]; + snprintf(layout, sizeof(layout), "observations/%s/2026-10-26/%s.jpg", cam_a, asset); + TEST_ASSERT_EQUAL_STRING(layout, record.path); + + char path[1024]; + absolute_path(record.path, path, sizeof(path)); + size_t size = 0; + uint8_t *jpeg = read_file(path, &size); + TEST_ASSERT_EQUAL_INT64((int64_t)size, record.bytes); + int w = 0, h = 0; + decode_jpeg_dims(jpeg, size, &w, &h); + free(jpeg); + TEST_ASSERT_EQUAL_INT(320, w); + TEST_ASSERT_EQUAL_INT(240, h); + + /* Both observations of the batch share the asset and moved to revision 2. */ + char where[256]; + snprintf(where, sizeof(where), + "SELECT count(*) FROM object_observations WHERE asset_uuid = '%s' AND revision = 2", + asset); + TEST_ASSERT_EQUAL_INT(2, scalar(where)); + + char *slash = strrchr(path, '/'); + *slash = '\0'; + TEST_ASSERT_EQUAL_INT(0, count_tmp_files(path)); + observation_evidence_stats_t s = stats(); + TEST_ASSERT_EQUAL_UINT64(1, s.accepted); + TEST_ASSERT_EQUAL_UINT64(1, s.ready); + TEST_ASSERT_EQUAL_UINT64(size, s.bytes_written); +} + +static void test_jpeg_variant_stores_exact_bytes(void) { + init_evidence(0, 0); + /* Produce a real JPEG with the shared encoder, then submit those bytes. */ + uint8_t *rgb = make_rgb(160, 120); + jpeg_encoder_cache_t *encoder = jpeg_encoder_cache_create(160, 120, 3, 80); + TEST_ASSERT_NOT_NULL(encoder); + unsigned char *jpeg = NULL; + size_t jpeg_size = 0; + TEST_ASSERT_EQUAL_INT(0, jpeg_encoder_cache_encode_to_memory(encoder, rgb, &jpeg, &jpeg_size)); + jpeg_encoder_cache_destroy(encoder); + free(rgb); + uint8_t *copy = malloc(jpeg_size); + memcpy(copy, jpeg, jpeg_size); + + observation_evidence_transform_t transform = { + .mode = OBS_TRANSFORM_LETTERBOX, .source_width = 1920, .source_height = 1080, + .inference_width = 640, .inference_height = 640, + .scale_x = 4.0, .scale_y = 4.0, .pad_x = 0, .pad_y = 140, + }; + observation_batch_input_t batch = make_batch(cam_a, 1); + safe_strcpy(batch.engine_type, "api", sizeof(batch.engine_type), 0); + char asset[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit_jpeg(&batch, copy, jpeg_size, 160, 120, &transform, asset)); + wait_idle(); + + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, record.state); + TEST_ASSERT_EQUAL_STRING("submitted_image", record.origin); + TEST_ASSERT_NOT_NULL(strstr(record.transform_json, "\"mode\":\"letterbox\"")); + TEST_ASSERT_NOT_NULL(strstr(record.transform_json, "\"pad_y\":140")); + char path[1024]; + absolute_path(record.path, path, sizeof(path)); + size_t size = 0; + uint8_t *stored = read_file(path, &size); + TEST_ASSERT_EQUAL_size_t(jpeg_size, size); + TEST_ASSERT_EQUAL_MEMORY(jpeg, stored, size); + free(stored); + free(jpeg); +} + +static void test_queue_saturation_drops_without_blocking(void) { + init_evidence(3, 0); + stall_worker(); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(64, 48), 64, 48, NULL, asset)); + wait_stalled(); + + /* The item being processed still holds its reservation: 2 more fit. */ + int accepted = 1, dropped = 0; + int64_t worst_ns = 0; + char dropped_asset[OBS_UUID_SIZE] = ""; + for (int i = 0; i < 6; ++i) { + observation_batch_input_t b = make_batch(cam_a, 1); + uint8_t *rgb = make_rgb(64, 48); + int64_t start = now_ns(); + observation_evidence_result_t rc = + observation_evidence_submit(&b, rgb, 64, 48, NULL, asset); + int64_t elapsed = now_ns() - start; + if (elapsed > worst_ns) worst_ns = elapsed; + if (rc == OBS_EVIDENCE_ACCEPTED) accepted++; + else { + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, rc); + TEST_ASSERT_TRUE(lightnvr_uuid_is_valid(asset)); + safe_strcpy(dropped_asset, asset, sizeof(dropped_asset), 0); + dropped++; + } + } + TEST_ASSERT_EQUAL_INT(3, accepted); + TEST_ASSERT_EQUAL_INT(4, dropped); + TEST_ASSERT_TRUE_MESSAGE(worst_ns < 1000000, "submit took over 1 ms"); + + /* Another camera has its own depth. */ + observation_batch_input_t other = make_batch(cam_b, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&other, make_rgb(64, 48), 64, 48, NULL, asset)); + + observation_evidence_stats_t s = stats(); + TEST_ASSERT_EQUAL_UINT64(4, s.accepted); + TEST_ASSERT_EQUAL_UINT64(4, s.dropped_queue_full); + TEST_ASSERT_EQUAL_UINT32(4, s.backlog_items); + TEST_ASSERT_EQUAL_UINT32(4, s.high_water_items); + TEST_ASSERT_EQUAL_UINT32(4, s.drop_ring_depth); + + release_worker(); + wait_idle(); + TEST_ASSERT_EQUAL_INT(4, count_assets("state = 'ready'")); + TEST_ASSERT_EQUAL_INT(4, count_assets("state = 'unavailable' AND reason = 'queue_full' " + "AND path IS NULL")); + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(dropped_asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, record.state); +} + +/* X2 (A11): every admission that reaches the queue is timed into a + * fixed-bucket histogram, so the saturation gate can prove from the running + * binary that producers never wait on I/O. The configured limits and the + * per-camera high-water mark are reported so "limits hold" is checkable too. */ +static void test_submit_latency_histogram_and_limits(void) { + observation_evidence_config_t config; + memset(&config, 0, sizeof(config)); + config.storage_path = storage_dir; + config.per_camera_max_items = 2; + config.global_max_items = 5; + config.global_max_bytes = 500000; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_init(&config)); + + observation_evidence_stats_t s = stats(); + TEST_ASSERT_EQUAL_UINT64(0, s.submit_latency_count); + TEST_ASSERT_EQUAL_UINT32(5, s.limit_items); + TEST_ASSERT_EQUAL_UINT32(2, s.limit_camera_items); + TEST_ASSERT_EQUAL_UINT64(500000, s.limit_bytes); + TEST_ASSERT_EQUAL_UINT64(500000, s.limit_camera_bytes); /* defaults to global */ + TEST_ASSERT_EQUAL_UINT32(0, s.camera_high_water_items); + + stall_worker(); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(64, 48), 64, 48, NULL, asset)); + wait_stalled(); + int calls = 1; + for (int i = 0; i < 4; ++i, ++calls) { /* 1 accepted, 3 queue_full */ + observation_batch_input_t b = make_batch(cam_a, 1); + observation_evidence_submit(&b, make_rgb(64, 48), 64, 48, NULL, asset); + } + observation_batch_input_t onvif = make_batch(cam_b, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit_unavailable(&onvif, "no_matching_media", asset)); + calls++; + uint8_t *jpeg = malloc(16); + TEST_ASSERT_NOT_NULL(jpeg); + memset(jpeg, 0xff, 16); + observation_batch_input_t api = make_batch(cam_b, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit_jpeg(&api, jpeg, 16, 8, 8, NULL, asset)); + calls++; + + s = stats(); + TEST_ASSERT_EQUAL_UINT64((uint64_t)calls, s.submit_latency_count); + uint64_t in_buckets = 0; + for (int i = 0; i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS; ++i) + in_buckets += s.submit_latency_bucket[i]; + TEST_ASSERT_EQUAL_UINT64(s.submit_latency_count, in_buckets); + TEST_ASSERT_TRUE(s.submit_latency_max_ns > 0); + TEST_ASSERT_TRUE(s.submit_latency_sum_ns >= s.submit_latency_max_ns); + TEST_ASSERT_TRUE_MESSAGE(s.submit_latency_max_ns < 1000000, "a submit took over 1 ms"); + /* Bounds are strictly increasing and end at the +Inf bucket. */ + for (int i = 1; i < OBS_EVIDENCE_SUBMIT_LATENCY_BUCKETS - 1; ++i) + TEST_ASSERT_TRUE(observation_evidence_submit_latency_bounds_ns[i] > + observation_evidence_submit_latency_bounds_ns[i - 1]); + TEST_ASSERT_EQUAL_UINT32(2, s.camera_high_water_items); + TEST_ASSERT_TRUE(s.high_water_items <= s.limit_items); + TEST_ASSERT_TRUE(s.high_water_bytes <= s.limit_bytes); + + release_worker(); + wait_idle(); + observation_evidence_shutdown(0); + init_evidence(0, 0); /* counters reset with the process-run counters */ + s = stats(); + TEST_ASSERT_EQUAL_UINT64(0, s.submit_latency_count); + TEST_ASSERT_EQUAL_UINT64(0, s.submit_latency_max_ns); + TEST_ASSERT_EQUAL_UINT32(0, s.camera_high_water_items); + TEST_ASSERT_EQUAL_UINT32(OBS_EVIDENCE_GLOBAL_DEPTH, s.limit_items); + TEST_ASSERT_EQUAL_UINT32(OBS_EVIDENCE_PER_CAMERA_DEPTH, s.limit_camera_items); + TEST_ASSERT_EQUAL_UINT64((uint64_t)OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES, s.limit_bytes); +} + +/* X2 finding: free() of a dropped 720p RGB frame (2.7 MB, an munmap) took + * 1.5-2.6 ms on the detection thread. Rejected frames are handed to the worker + * for release, bounded by limit_discard_bytes (max(queue bytes, 32 MiB)); + * past that bound the producer frees inline. */ +static void test_rejected_frames_are_released_by_the_worker(void) { + init_evidence(1, 0); + stall_worker(); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t first = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&first, make_rgb(32, 24), 32, 24, NULL, asset)); + wait_stalled(); + + const int w = 1280, h = 720; + const uint64_t frame = (uint64_t)w * h * 3; + observation_evidence_stats_t s = stats(); + TEST_ASSERT_EQUAL_UINT64((uint64_t)OBS_EVIDENCE_DEFAULT_GLOBAL_BYTES, s.limit_discard_bytes); + const int fit = (int)(s.limit_discard_bytes / frame); /* 12 frames in 32 MiB */ + for (int i = 0; i < fit + 2; ++i) { + observation_batch_input_t b = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, + observation_evidence_submit(&b, make_rgb(w, h), w, h, NULL, asset)); + } + s = stats(); + TEST_ASSERT_EQUAL_UINT64((uint64_t)fit, s.discard_deferred); + TEST_ASSERT_EQUAL_UINT64(2, s.discard_inline); + TEST_ASSERT_EQUAL_UINT64((uint64_t)fit * frame, s.discard_pending_bytes); + TEST_ASSERT_TRUE(s.discard_pending_bytes <= s.limit_discard_bytes); + + release_worker(); + wait_idle(); + s = stats(); + TEST_ASSERT_EQUAL_UINT64(0, s.discard_pending_bytes); + TEST_ASSERT_EQUAL_INT(fit + 2, count_assets("reason = 'queue_full'")); + + /* A larger queue budget raises the deferral bound with it. */ + observation_evidence_shutdown(0); + init_evidence(0, 64LL * 1024 * 1024); + TEST_ASSERT_EQUAL_UINT64(64ULL * 1024 * 1024, stats().limit_discard_bytes); +} + +static void test_global_byte_budget_bounds_admission(void) { + /* 64x48 RGB24 = 9216 bytes (+ overhead); a 20000-byte budget fits one. */ + init_evidence(0, 20000); + stall_worker(); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t a = make_batch(cam_a, 1); + observation_batch_input_t b = make_batch(cam_b, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&a, make_rgb(64, 48), 64, 48, NULL, asset)); + wait_stalled(); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, + observation_evidence_submit(&b, make_rgb(64, 48), 64, 48, NULL, asset)); + /* A frame larger than the whole budget never fits. */ + observation_batch_input_t c = make_batch(cam_b, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, + observation_evidence_submit(&c, make_rgb(128, 96), 128, 96, NULL, asset)); + TEST_ASSERT_TRUE(stats().high_water_bytes > 9216); + release_worker(); + wait_idle(); + TEST_ASSERT_EQUAL_INT(1, count_assets("state = 'ready'")); + TEST_ASSERT_EQUAL_INT(2, count_assets("reason = 'queue_full'")); +} + +static void test_drop_ring_overflow_counts_untracked(void) { + init_evidence(1, 0); + stall_worker(); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t first = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&first, make_rgb(32, 32), 32, 32, NULL, asset)); + wait_stalled(); + int total = OBS_EVIDENCE_DROP_RING_SIZE + 10; + for (int i = 0; i < total; ++i) { + observation_batch_input_t b = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, + observation_evidence_submit(&b, make_rgb(32, 32), 32, 32, NULL, asset)); + } + observation_evidence_stats_t s = stats(); + TEST_ASSERT_EQUAL_UINT64((uint64_t)total, s.dropped_queue_full); + TEST_ASSERT_EQUAL_UINT64(10, s.dropped_untracked); + TEST_ASSERT_EQUAL_UINT32(OBS_EVIDENCE_DROP_RING_SIZE, s.drop_ring_depth); + /* Tracked drops hand their frame to the worker; untracked ones (ring + * full) can only be freed inline. */ + TEST_ASSERT_EQUAL_UINT64(OBS_EVIDENCE_DROP_RING_SIZE, s.discard_deferred); + TEST_ASSERT_EQUAL_UINT64(10, s.discard_inline); + release_worker(); + wait_idle(); + char where[64]; + snprintf(where, sizeof(where), "reason = 'queue_full'"); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROP_RING_SIZE, count_assets(where)); +} + +static void test_budget_flag_drops_as_budget_exhausted(void) { + init_evidence(0, 0); + observation_evidence_set_budget_exhausted(cam_a, true); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(cam_a, 2); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_BUDGET, + observation_evidence_submit(&batch, make_rgb(64, 48), 64, 48, NULL, asset)); + /* Unaffected camera still accepted. */ + observation_batch_input_t other = make_batch(cam_b, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&other, make_rgb(64, 48), 64, 48, NULL, NULL)); + wait_idle(); + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, record.state); + TEST_ASSERT_EQUAL_STRING("budget_exhausted", record.reason); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations o JOIN " + "observation_assets a ON a.uuid = o.asset_uuid " + "WHERE a.reason = 'budget_exhausted'")); + observation_evidence_stats_t s = stats(); + TEST_ASSERT_EQUAL_UINT64(1, s.dropped_budget); + TEST_ASSERT_EQUAL_UINT64(1, s.accepted); +} + +static void test_failure_before_rename_never_leaves_final_file(void) { + init_evidence(0, 0); + fail_before_rename = true; + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(64, 48), 64, 48, NULL, asset)); + wait_idle(); + TEST_ASSERT_TRUE_MESSAGE(saw_tmp_without_final, + "tmp must be complete and the final name absent before rename"); + + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, record.state); + TEST_ASSERT_EQUAL_STRING("write_failed", record.reason); + + char relative[512], path[1024]; + observation_evidence_relative_path(cam_a, asset, batch.observed_at_ms, relative, sizeof(relative)); + absolute_path(relative, path, sizeof(path)); + TEST_ASSERT_FALSE(file_exists(path)); + char tmp[1100]; + snprintf(tmp, sizeof(tmp), "%s.tmp", path); + TEST_ASSERT_FALSE(file_exists(tmp)); + TEST_ASSERT_EQUAL_UINT64(1, stats().failed_write); +} + +static void test_disabled_between_admission_and_encode(void) { + init_evidence(0, 0); + observation_evidence_set_enabled_probe(enabled_probe, NULL); + probe_value = false; + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&batch, make_rgb(64, 48), 64, 48, NULL, asset)); + wait_idle(); + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, record.state); + TEST_ASSERT_EQUAL_STRING("disabled", record.reason); + TEST_ASSERT_EQUAL_UINT64(1, stats().disabled_at_encode); +} + +static void test_stock_probe_reads_stream_setting(void) { + TEST_ASSERT_TRUE(observation_evidence_stream_enabled_probe(cam_a, NULL)); + char sql[256]; + snprintf(sql, sizeof(sql), + "UPDATE streams SET observation_evidence_enabled = 0 WHERE camera_uuid = '%s'", cam_a); + exec_sql(sql); + TEST_ASSERT_FALSE(observation_evidence_stream_enabled_probe(cam_a, NULL)); + snprintf(sql, sizeof(sql), + "UPDATE streams SET observation_evidence_enabled = 1 WHERE camera_uuid = '%s'", cam_a); + exec_sql(sql); + TEST_ASSERT_FALSE(observation_evidence_stream_enabled_probe( + "00000000-0000-4000-8000-000000000000", NULL)); +} + +static void test_unavailable_submission_records_reason(void) { + init_evidence(0, 0); + char asset[OBS_UUID_SIZE]; + observation_batch_input_t batch = make_batch(cam_a, 1); + safe_strcpy(batch.engine_type, "onvif", sizeof(batch.engine_type), 0); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_INVALID, + observation_evidence_submit_unavailable(&batch, "write_failed", asset)); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit_unavailable(&batch, "no_matching_media", asset)); + wait_idle(); + observation_asset_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(asset, &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, record.state); + TEST_ASSERT_EQUAL_STRING("no_matching_media", record.reason); + TEST_ASSERT_EQUAL_STRING("", record.transform_json); +} + +static void test_invalid_arguments_free_buffer(void) { + init_evidence(0, 0); + observation_batch_input_t batch = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_INVALID, + observation_evidence_submit(&batch, make_rgb(8, 8), 0, 8, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_INVALID, + observation_evidence_submit(NULL, make_rgb(8, 8), 8, 8, NULL, NULL)); + batch.object_count = 0; + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_INVALID, + observation_evidence_submit(&batch, make_rgb(8, 8), 8, 8, NULL, NULL)); + TEST_ASSERT_FALSE(stats().allocated); +} + +static void *shutdown_thread(void *arg) { + (void)arg; + observation_evidence_shutdown(0); + return NULL; +} + +static void test_shutdown_with_full_queue_marks_interrupted(void) { + init_evidence(4, 0); + stall_worker(); + char asset[OBS_UUID_SIZE]; + for (int i = 0; i < 4; ++i) { + observation_batch_input_t b = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_ACCEPTED, + observation_evidence_submit(&b, make_rgb(64, 48), 64, 48, NULL, asset)); + if (i == 0) wait_stalled(); + } + observation_batch_input_t over = make_batch(cam_a, 1); + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DROPPED_QUEUE_FULL, + observation_evidence_submit(&over, make_rgb(64, 48), 64, 48, NULL, asset)); + /* Stop admission first, then let the stalled worker continue: with no + * drain budget everything still queued is abandoned as interrupted. */ + pthread_t stopper; + TEST_ASSERT_EQUAL_INT(0, pthread_create(&stopper, NULL, shutdown_thread, NULL)); + for (int i = 0; i < 500 && stats().initialized; ++i) usleep(2000); + TEST_ASSERT_FALSE(stats().initialized); + release_worker(); + pthread_join(stopper, NULL); + + observation_evidence_stats_t s = stats(); + TEST_ASSERT_FALSE(s.worker_running); + TEST_ASSERT_FALSE(s.allocated); + TEST_ASSERT_EQUAL_UINT32(0, s.backlog_items); + TEST_ASSERT_EQUAL_UINT64(0, s.backlog_bytes); + /* Every admitted batch is visible: ready, or unavailable(interrupted). */ + TEST_ASSERT_EQUAL_UINT64(4, s.ready + s.interrupted); + TEST_ASSERT_TRUE(s.interrupted >= 3); + TEST_ASSERT_EQUAL_INT((int)s.interrupted, + count_assets("state = 'unavailable' AND reason = 'interrupted'")); + TEST_ASSERT_EQUAL_INT(1, count_assets("reason = 'queue_full'")); + TEST_ASSERT_EQUAL_INT(0, count_assets("state = 'pending'")); + + TEST_ASSERT_EQUAL_INT(OBS_EVIDENCE_DISABLED, + observation_evidence_submit(&over, make_rgb(8, 8), 8, 8, NULL, asset)); +} + +static void test_relative_path_and_transform_json(void) { + char out[256]; + TEST_ASSERT_EQUAL_INT(0, observation_evidence_relative_path( + "22222222-2222-4222-8222-222222222222", "88888888-8888-4888-8888-888888888888", + 1794146592481LL, out, sizeof(out))); + TEST_ASSERT_EQUAL_STRING("observations/22222222-2222-4222-8222-222222222222/2026-11-08/" + "88888888-8888-4888-8888-888888888888.jpg", out); + TEST_ASSERT_EQUAL_INT(-1, observation_evidence_relative_path("../etc", cam_a, 0, out, sizeof(out))); + TEST_ASSERT_EQUAL_INT(0, observation_evidence_transform_json(NULL, 640, 360, out, sizeof(out))); + TEST_ASSERT_EQUAL_STRING("{\"mode\":\"none\",\"source_width\":640,\"source_height\":360," + "\"inference_width\":640,\"inference_height\":360," + "\"scale_x\":1,\"scale_y\":1,\"pad_x\":0,\"pad_y\":0}", out); + observation_evidence_transform_t bad = {.mode = OBS_TRANSFORM_RESIZE, .scale_x = 0}; + TEST_ASSERT_EQUAL_INT(-1, observation_evidence_transform_json(&bad, 10, 10, out, sizeof(out))); +} + +int main(void) { + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_obs_evidence_%ld.db", (long)getpid()); + snprintf(storage_dir, sizeof(storage_dir), "/tmp/lightnvr_obs_evidence_%ld", (long)getpid()); + unlink(db_path); + mkdir(storage_dir, 0750); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0) return 1; + make_camera("evidence-a", cam_a); + make_camera("evidence-b", cam_b); + + UNITY_BEGIN(); + RUN_TEST(test_disabled_mode_allocates_nothing); + RUN_TEST(test_relative_path_and_transform_json); + RUN_TEST(test_ready_jpeg_decodes_to_frame_dimensions); + RUN_TEST(test_jpeg_variant_stores_exact_bytes); + RUN_TEST(test_queue_saturation_drops_without_blocking); + RUN_TEST(test_submit_latency_histogram_and_limits); + RUN_TEST(test_rejected_frames_are_released_by_the_worker); + RUN_TEST(test_global_byte_budget_bounds_admission); + RUN_TEST(test_drop_ring_overflow_counts_untracked); + RUN_TEST(test_budget_flag_drops_as_budget_exhausted); + RUN_TEST(test_failure_before_rename_never_leaves_final_file); + RUN_TEST(test_disabled_between_admission_and_encode); + RUN_TEST(test_stock_probe_reads_stream_setting); + RUN_TEST(test_unavailable_submission_records_reason); + RUN_TEST(test_invalid_arguments_free_buffer); + RUN_TEST(test_shutdown_with_full_queue_marks_interrupted); + int failures = UNITY_END(); + + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char cmd[1200]; + snprintf(cmd, sizeof(cmd), "rm -rf '%s' '%s-wal' '%s-shm'", storage_dir, db_path, db_path); + if (system(cmd) != 0) fprintf(stderr, "cleanup failed\n"); + return failures; +} diff --git a/tests/unit/test_observation_migration.c b/tests/unit/test_observation_migration.c new file mode 100644 index 000000000..c5f464d7d --- /dev/null +++ b/tests/unit/test_observation_migration.c @@ -0,0 +1,320 @@ +/* Migration 0095 (INT_01 L1): object observations, evidence assets, change log, + * publications and the cursor epoch row. Exercises the shipped embedded SQL + * through the real runner on a fresh database, including rollback. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include "unity.h" +#include "database/sqlite_migrate.h" +#include "database/db_embedded_migrations.h" + +#define CAM "22222222-2222-4222-8222-222222222222" +#define ASSET "88888888-8888-4888-8888-888888888888" +#define OBS "55555555-5555-4555-8555-555555555555" +#define BATCH "77777777-7777-4777-8777-777777777777" + +static sqlite3 *db; +static sqlite_migrate_t *migration; + +static int scalar(const char *sql) { + sqlite3_stmt *statement = NULL; + int prepared = sqlite3_prepare_v2(db, sql, -1, &statement, NULL); + int stepped = prepared == SQLITE_OK ? sqlite3_step(statement) : prepared; + int value = stepped == SQLITE_ROW ? sqlite3_column_int(statement, 0) : -1; + if (statement) sqlite3_finalize(statement); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, prepared, sql); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_ROW, stepped, sql); + return value; +} +static char *text(const char *sql) { + sqlite3_stmt *statement = NULL; + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, sqlite3_prepare_v2(db, sql, -1, &statement, NULL), sql); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_ROW, sqlite3_step(statement), sql); + const unsigned char *value = sqlite3_column_text(statement, 0); + char *copy = strdup(value ? (const char *)value : ""); + sqlite3_finalize(statement); + TEST_ASSERT_NOT_NULL(copy); + return copy; +} +static void ok(const char *sql) { + char *error = NULL; + int rc = sqlite3_exec(db, sql, NULL, NULL, &error); + if (rc != SQLITE_OK) fprintf(stderr, "unexpected failure: %s\n %s\n", error ? error : "", sql); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} +static void rejected(const char *sql) { + int rc = sqlite3_exec(db, sql, NULL, NULL, NULL); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_CONSTRAINT, rc & 0xff, sql); +} +static const migration_t *find_migration(const char *version) { + for (size_t i = 0; i < EMBEDDED_MIGRATIONS_COUNT; ++i) + if (strcmp(embedded_migrations_data[i].version, version) == 0) return &embedded_migrations_data[i]; + return NULL; +} +static size_t index_of(const char *version) { + for (size_t i = 0; i < EMBEDDED_MIGRATIONS_COUNT; ++i) + if (strcmp(embedded_migrations_data[i].version, version) == 0) return i; + TEST_FAIL_MESSAGE(version); + return 0; +} +static void migrate_first(size_t count) { + migrate_free(migration); + migrate_config_t config = {.embedded_migrations = embedded_migrations_data, .embedded_count = count}; + migration = migrate_init(db, &config); + TEST_ASSERT_NOT_NULL(migration); + TEST_ASSERT_EQUAL_INT(0, migrate_up(migration, NULL)); +} +static void migrate_through(const char *version) { migrate_first(index_of(version) + 1); } + +/* Structural snapshot: every table's columns plus the full SQL of every other + * schema object. Table SQL text is compared separately because DROP COLUMN + * rewrites CREATE TABLE text. */ +static char *structure(void) { + return text( + "SELECT coalesce(group_concat(line, char(10)), '') FROM (" + " SELECT m.type || ' ' || m.name || ' ' || p.cid || ' ' || p.name || ' ' || p.type || ' ' ||" + " p.\"notnull\" || ' ' || coalesce(p.dflt_value, '-') || ' ' || p.pk AS line, m.type AS t, m.name AS n, p.cid AS c" + " FROM sqlite_master m JOIN pragma_table_info(m.name) p" + " WHERE m.type = 'table' AND m.name NOT LIKE 'sqlite_%' AND m.name <> 'schema_migrations'" + " UNION ALL" + " SELECT type || ' ' || name || ' ' || coalesce(sql, ''), type, name, 0 FROM sqlite_master" + " WHERE type <> 'table' AND name NOT LIKE 'sqlite_%'" + " ORDER BY t, n, c)"); +} +static char *full_sql(void) { + return text("SELECT coalesce(group_concat(type || ' ' || name || ' ' || coalesce(sql, ''), char(10)), '') FROM (" + "SELECT * FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' AND name <> 'schema_migrations' ORDER BY type, name)"); +} +static char *settings(void) { + return text("SELECT coalesce(group_concat(key || '=' || value, ','), '') FROM (SELECT * FROM system_settings ORDER BY key)"); +} + +void setUp(void) { + migration = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); + ok("PRAGMA foreign_keys = ON;"); +} +void tearDown(void) { + migrate_free(migration); migration = NULL; + sqlite3_close(db); db = NULL; +} + +static void seed_valid_rows(void) { + ok("INSERT INTO observation_assets (uuid, camera_uuid, state, reason, created_at_ms, updated_at_ms)" + " VALUES ('" ASSET "', '" CAM "', 'pending', NULL, 1000, 1000);"); + ok("INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index," + " observed_at_ms, created_at_ms, engine_key, engine_type, model, label, confidence," + " box_x, box_y, box_width, box_height, track_id, track_namespace, track_session, asset_uuid, revision)" + " VALUES ('" OBS "', '" CAM "', 'feeder-east', '" BATCH "', 0, 990, 1000, 'legacy-primary', 'object'," + " 'ssd_mobilenet_v2.sod', 'bird', 0.87, 0.412, 0.288, 0.12, 0.165, '17', 'legacy-primary'," + " '9b0c7d1e-5f43-4b8e-9d0e-0b6c1f2a3d4e', '" ASSET "', 1);"); + ok("INSERT INTO observation_changes (observation_uuid, revision, cause, evidence_state, evidence_reason, created_at_ms)" + " VALUES ('" OBS "', 1, 'created', 'pending', NULL, 1000);"); + ok("INSERT INTO observation_publications (change_seq, message_id, state, attempts, updated_at_ms)" + " SELECT seq, '43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9', 'pending', 0, 1000 FROM observation_changes;"); +} + +static void test_fresh_database_has_observation_schema(void) { + migrate_first(EMBEDDED_MIGRATIONS_COUNT); + TEST_ASSERT_NOT_NULL_MESSAGE(find_migration("0095"), "0095 is not embedded"); + const char *tables[] = {"object_observations", "observation_assets", "observation_changes", + "observation_publications", "observation_meta"}; + for (size_t i = 0; i < sizeof(tables) / sizeof(tables[0]); ++i) { + char sql[160]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM sqlite_master WHERE type='table' AND name='%s'", tables[i]); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, scalar(sql), tables[i]); + } + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM pragma_table_info('observation_changes')" + " WHERE name IN ('evidence_state', 'evidence_reason')")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_table_info('observation_assets') WHERE name='origin'")); + /* AUTOINCREMENT: seq is never reused after deletion. */ + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM sqlite_master WHERE name='observation_changes'" + " AND sql LIKE '%AUTOINCREMENT%'")); + const char *indexes[] = {"idx_object_observations_camera_time", "idx_object_observations_label_time", + "idx_object_observations_created", "idx_object_observations_asset", + "idx_object_observations_recording", "idx_observation_assets_state_camera", + "idx_observation_publications_pending"}; + for (size_t i = 0; i < sizeof(indexes) / sizeof(indexes[0]); ++i) { + char sql[160]; + snprintf(sql, sizeof(sql), "SELECT count(*) FROM sqlite_master WHERE type='index' AND name='%s'", indexes[i]); + TEST_ASSERT_EQUAL_INT_MESSAGE(1, scalar(sql), indexes[i]); + } + /* Single meta row with a lowercase UUIDv4 epoch and the initial prune boundary. */ + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_meta")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT oldest_seq FROM observation_meta")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT epoch_uuid GLOB '[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]-" + "[0-9a-f][0-9a-f][0-9a-f][0-9a-f]-4[0-9a-f][0-9a-f][0-9a-f]-[89ab][0-9a-f][0-9a-f][0-9a-f]-" + "[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'" + " FROM observation_meta")); + rejected("INSERT INTO observation_meta (id, epoch_uuid, oldest_seq) VALUES (2, '66666666-6666-4666-8666-666666666666', 1);"); + /* Per-camera stream settings and global defaults (contract section 13). */ + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_table_info('streams') WHERE name='observation_evidence_enabled' AND dflt_value='0' AND \"notnull\"=1")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_table_info('streams') WHERE name='observation_labels' AND dflt_value=\"''\"")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_table_info('streams') WHERE name='observation_max_age_days' AND dflt_value='7'")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM pragma_table_info('streams') WHERE name='observation_max_bytes' AND dflt_value='2147483648'")); + TEST_ASSERT_EQUAL_INT(30, scalar("SELECT CAST(value AS INTEGER) FROM system_settings WHERE key='observation_history_retention_days'")); + TEST_ASSERT_EQUAL_INT(33554432, scalar("SELECT CAST(value AS INTEGER) FROM system_settings WHERE key='observation_global_queue_bytes'")); +} + +static void test_constraints_accept_contract_rows_and_reject_bad_rows(void) { + migrate_first(EMBEDDED_MIGRATIONS_COUNT); + seed_valid_rows(); + /* Batch retry is a conflict on (batch_id, object_index). */ + rejected("INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index," + " observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, asset_uuid)" + " VALUES ('55555555-5555-4555-8555-000000000002', '" CAM "', 'feeder-east', '" BATCH "', 0, 990, 1000," + " 'legacy-primary', 'object', 'bird', 0.5, '" ASSET "');"); + TEST_ASSERT_EQUAL_INT(0, sqlite3_exec(db, + "INSERT OR IGNORE INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index," + " observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, asset_uuid)" + " VALUES ('55555555-5555-4555-8555-000000000002', '" CAM "', 'feeder-east', '" BATCH "', 0, 990, 1000," + " 'legacy-primary', 'object', 'bird', 0.5, '" ASSET "');", NULL, NULL, NULL)); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM object_observations")); + /* A sibling at the next index shares the asset; a box-less, track-less row is valid. */ + ok("INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index," + " observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, asset_uuid)" + " VALUES ('55555555-5555-4555-8555-000000000003', '" CAM "', 'feeder-east', '" BATCH "', 1, 990, 1000," + " 'onvif-1', 'onvif', 'bird', 1.0, '" ASSET "');"); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations WHERE asset_uuid='" ASSET "'")); + +#define OBS_COLS "INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index," \ + " observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, asset_uuid" +#define OBS_VALS " VALUES ('55555555-5555-4555-8555-0000000000ff', '" CAM "', 'feeder-east', '" BATCH "', 5, 990, 1000," \ + " 'legacy-primary', " + rejected(OBS_COLS ") " OBS_VALS "'object', 'bird', 1.01, '" ASSET "');"); + rejected(OBS_COLS ") " OBS_VALS "'object', 'bird', -0.01, '" ASSET "');"); + rejected(OBS_COLS ") " OBS_VALS "'object', 'bird', NULL, '" ASSET "');"); + rejected(OBS_COLS ") " OBS_VALS "'motion', 'bird', 0.5, '" ASSET "');"); + rejected(OBS_COLS ") " OBS_VALS "'object', '', 0.5, '" ASSET "');"); + rejected(OBS_COLS ") " OBS_VALS "'object', 'bird', 0.5, '99999999-9999-4999-8999-999999999999');"); + rejected(OBS_COLS ", revision) " OBS_VALS "'object', 'bird', 0.5, '" ASSET "', 0);"); + rejected(OBS_COLS ", box_x) " OBS_VALS "'object', 'bird', 0.5, '" ASSET "', 0.1);"); + rejected(OBS_COLS ", box_x, box_y, box_width, box_height) " OBS_VALS "'object', 'bird', 0.5, '" ASSET "', 0.5, 0.1, 0.6, 0.1);"); + rejected(OBS_COLS ", box_x, box_y, box_width, box_height) " OBS_VALS "'object', 'bird', 0.5, '" ASSET "', 0.1, 0.1, 0.0, 0.1);"); + rejected(OBS_COLS ", track_id) " OBS_VALS "'object', 'bird', 0.5, '" ASSET "', '17');"); + rejected("INSERT INTO object_observations (uuid, camera_uuid, stream_name_at_capture, batch_id, object_index," + " observed_at_ms, created_at_ms, engine_key, engine_type, label, confidence, asset_uuid)" + " VALUES ('55555555-5555-4555-8555-0000000000fe', '" CAM "', 'x', '" BATCH "', 20, 990, 1000," + " 'legacy-primary', 'object', 'bird', 0.5, '" ASSET "');"); + + /* Asset state machine values and reason pairing. */ +#define ASSET_ROW(id, state, reason) "INSERT INTO observation_assets (uuid, camera_uuid, state, reason, created_at_ms," \ + " updated_at_ms) VALUES ('" id "', '" CAM "', " state ", " reason ", 1, 1);" + rejected(ASSET_ROW("a1", "'deleted'", "NULL")); + rejected(ASSET_ROW("a2", "'ready'", "'queue_full'")); + rejected(ASSET_ROW("a3", "'unavailable'", "NULL")); + rejected(ASSET_ROW("a4", "'expired'", "NULL")); + rejected(ASSET_ROW("a5", "NULL", "NULL")); + ok(ASSET_ROW("a6", "'unavailable'", "'queue_full'")); + ok(ASSET_ROW("a7", "'expired'", "'retention_expired'")); + rejected("UPDATE observation_assets SET protected = 2 WHERE uuid = 'a6';"); + rejected("UPDATE observation_assets SET origin = 'recording_frame' WHERE uuid = 'a6';"); + ok("UPDATE observation_assets SET origin = 'inference_input', state = 'ready', reason = NULL WHERE uuid = '" ASSET "';"); + /* Referenced assets cannot be deleted out from under observations. */ + rejected("DELETE FROM observation_assets WHERE uuid = '" ASSET "';"); + + /* Change rows: cause and evidence-state CHECKs, unique revision per observation. */ +#define CHANGE(rev, cause, state, reason) "INSERT INTO observation_changes (observation_uuid, revision, cause," \ + " evidence_state, evidence_reason, created_at_ms) VALUES ('" OBS "', " rev ", " cause ", " state ", " reason ", 2000);" + rejected(CHANGE("1", "'evidence'", "'ready'", "NULL")); + rejected(CHANGE("2", "'updated'", "'ready'", "NULL")); + rejected(CHANGE("2", "'evidence'", "'gone'", "NULL")); + rejected(CHANGE("2", "'evidence'", "'unavailable'", "NULL")); + rejected(CHANGE("2", "'evidence'", "'ready'", "'write_failed'")); + rejected(CHANGE("0", "'evidence'", "'ready'", "NULL")); + rejected("INSERT INTO observation_changes (observation_uuid, revision, cause, evidence_state, created_at_ms)" + " VALUES ('00000000-0000-4000-8000-00000000dead', 1, 'created', 'pending', 1);"); + ok(CHANGE("2", "'evidence'", "'ready'", "NULL")); + ok(CHANGE("3", "'classification'", "'unavailable'", "'write_failed'")); + + /* Publications: the five D7 states only; one row per change; FK to the change. */ + const char *states[] = {"relayed", "relayed_no_route", "expired", "failed", "pending"}; + for (size_t i = 0; i < sizeof(states) / sizeof(states[0]); ++i) { + char sql[160]; + snprintf(sql, sizeof(sql), "UPDATE observation_publications SET state = '%s';", states[i]); + ok(sql); + } + rejected("UPDATE observation_publications SET state = 'delivered';"); + rejected("UPDATE observation_publications SET state = NULL;"); + rejected("UPDATE observation_publications SET attempts = -1;"); + rejected("INSERT INTO observation_publications (change_seq, message_id, state, attempts, updated_at_ms)" + " SELECT seq, 'dup', 'pending', 0, 1 FROM observation_changes WHERE revision = 1;"); + rejected("INSERT INTO observation_publications (change_seq, message_id, updated_at_ms) VALUES (9999, 'm-x', 1);"); + rejected("INSERT INTO observation_publications (change_seq, message_id, updated_at_ms)" + " SELECT seq, '43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9', 1 FROM observation_changes WHERE revision = 2;"); + ok("INSERT INTO observation_publications (change_seq, message_id, updated_at_ms)" + " SELECT seq, 'm-2', 1 FROM observation_changes WHERE revision = 2;"); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_publications WHERE change_seq =" + " (SELECT seq FROM observation_changes WHERE revision = 2) AND state = 'pending' AND attempts = 0")); + + /* Pruning changes cascades their publications; seq is never reused. */ + int max_seq = scalar("SELECT max(seq) FROM observation_changes"); + ok("DELETE FROM observation_changes WHERE revision >= 2;"); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_publications")); + ok(CHANGE("2", "'evidence'", "'ready'", "NULL")); + TEST_ASSERT_EQUAL_INT(max_seq + 1, scalar("SELECT max(seq) FROM observation_changes")); + /* Deleting an observation removes its change history. */ + ok("DELETE FROM object_observations WHERE uuid = '" OBS "';"); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM observation_changes")); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM observation_publications")); +} + +static void test_recording_delete_detaches_observation(void) { + migrate_first(EMBEDDED_MIGRATIONS_COUNT); + seed_valid_rows(); + ok("INSERT INTO recordings (id, stream_name, file_path, start_time) VALUES (81234, 'feeder-east', '/tmp/x.mp4', 1);"); + ok("UPDATE object_observations SET recording_id = 81234;"); + ok("DELETE FROM recordings WHERE id = 81234;"); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM object_observations WHERE recording_id IS NULL")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_assets")); +} + +static void test_upgrade_down_and_reapply_reach_same_schema(void) { + /* Schema and settings of a database at 0094. */ + migrate_through("0094"); + char *before = structure(); + char *before_settings = settings(); + /* Upgrade the 0094 database to 0095 (later migrations are tested on their own). */ + migrate_through("0095"); + char *upgraded = full_sql(); + char *upgraded_structure = structure(); + TEST_ASSERT_TRUE(strcmp(before, upgraded_structure) != 0); + seed_valid_rows(); + /* Down restores the 0094 structure and settings, dropping observation data. */ + TEST_ASSERT_EQUAL_INT(1, migrate_down(migration)); + char *after_down = structure(); + char *after_down_settings = settings(); + TEST_ASSERT_EQUAL_STRING(before, after_down); + TEST_ASSERT_EQUAL_STRING(before_settings, after_down_settings); + /* Up again reaches the same schema as an upgrade, and the same as a fresh database. */ + migrate_through("0095"); + char *reapplied = full_sql(); + TEST_ASSERT_EQUAL_STRING(upgraded, reapplied); + TEST_ASSERT_EQUAL_INT(0, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(1, scalar("SELECT count(*) FROM observation_meta")); + + sqlite3 *upgraded_db = db; db = NULL; + sqlite_migrate_t *upgraded_migration = migration; migration = NULL; + TEST_ASSERT_EQUAL_INT(SQLITE_OK, sqlite3_open(":memory:", &db)); + ok("PRAGMA foreign_keys = ON;"); + migrate_through("0095"); + char *fresh = full_sql(); + TEST_ASSERT_EQUAL_STRING(upgraded, fresh); + migrate_free(migration); sqlite3_close(db); + migration = upgraded_migration; db = upgraded_db; + + free(before); free(before_settings); free(upgraded); free(upgraded_structure); + free(after_down); free(after_down_settings); free(reapplied); free(fresh); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_fresh_database_has_observation_schema); + RUN_TEST(test_constraints_accept_contract_rows_and_reject_bad_rows); + RUN_TEST(test_recording_delete_detaches_observation); + RUN_TEST(test_upgrade_down_and_reapply_reach_same_schema); + return UNITY_END(); +} diff --git a/tests/unit/test_observation_relay.c b/tests/unit/test_observation_relay.c new file mode 100644 index 000000000..2e192379f --- /dev/null +++ b/tests/unit/test_observation_relay.c @@ -0,0 +1,699 @@ +/* INT_01 L5: observation publication relay. Crash-safe drain, outbox + * duplicate and no-route handling, D8 outbox cap, route suppression, delivery + * expiry, D9 cursor-head sidecar, and envelope equality with the C3 fixtures, + * against a real migrated database and the real router/outbox path. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "unity.h" +#include "core/config.h" +#include "core/event_envelope.h" +#include "core/event_identity.h" +#include "core/event_producers.h" +#include "core/event_router.h" +#include "core/mqtt_delivery_worker.h" +#include "core/observation_relay.h" +#include "core/observation_retention.h" +#include "database/db_core.h" +#include "database/db_event_outbox.h" +#include "database/db_event_routes.h" +#include "database/db_observations.h" +#include "database/db_streams.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +#ifndef LIGHTNVR_SOURCE_DIR +#error "LIGHTNVR_SOURCE_DIR must be defined" +#endif +#define FIXTURE_DIR LIGHTNVR_SOURCE_DIR "/tests/fixtures/integrations/observations/events" +#define TOPIC_PREFIX "lightnvr-test" + +static char db_path[256]; +static char storage_dir[256]; +static char cam_a[OBS_UUID_SIZE]; +static char source[128]; + +/* ---- helpers ---------------------------------------------------------- */ + +static int64_t scalar64(const char *sql) { + sqlite3_stmt *stmt = NULL; + int64_t value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int64(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(-999, value, sql); + return value; +} + +static int scalar(const char *sql) { return (int)scalar64(sql); } + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + if (rc != SQLITE_OK) fprintf(stderr, "%s: %s\n", sql, error ? error : ""); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static int publications_in(const char *state) { + char sql[160]; + snprintf(sql, sizeof(sql), + "SELECT count(*) FROM observation_publications WHERE state = '%s'", state); + return scalar(sql); +} + +static int outbox_rows(void) { + return scalar("SELECT count(*) FROM event_outbox WHERE event_type = '" + OBSERVATION_CHANGED_EVENT_TYPE "'"); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static int64_t now_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000; +} + +/* Insert one batch of `objects` pending observations sharing one asset. */ +static observation_insert_result_t insert_pending(int objects) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, cam_a, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = now_ms(); + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + batch.object_count = objects; + for (int i = 0; i < objects; ++i) { + batch.objects[i].object_index = i; + safe_strcpy(batch.objects[i].label, "bird", sizeof(batch.objects[i].label), 0); + batch.objects[i].confidence = 0.9; + } + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.origin, "inference_input", sizeof(asset.origin), 0); + safe_strcpy(asset.media_type, "image/jpeg", sizeof(asset.media_type), 0); + observation_insert_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &result)); + return result; +} + +static observation_relay_options_t test_options(void) { + observation_relay_options_t options; + memset(&options, 0, sizeof(options)); + options.storage_path = storage_dir; + return options; +} + +static observation_relay_pass_t run_pass(const observation_relay_options_t *options) { + observation_relay_pass_t pass; + memset(&pass, 0, sizeof(pass)); + observation_relay_run_once(options, &pass); + return pass; +} + +static event_route_t route_for(const char *name, const char *type) { + event_route_t route; + memset(&route, 0, sizeof(route)); + safe_strcpy(route.name, name, sizeof(route.name), 0); + route.enabled = true; + safe_strcpy(route.destination_key, EVENT_ROUTE_DEFAULT_DESTINATION, + sizeof(route.destination_key), 0); + safe_strcpy(route.scope_type, "all", sizeof(route.scope_type), 0); + safe_strcpy(route.predicate_json, "{\"version\":1}", sizeof(route.predicate_json), 0); + safe_strcpy(route.schedule_json, "{\"version\":1,\"timezone\":\"UTC\",\"windows\":[]}", + sizeof(route.schedule_json), 0); + safe_strcpy(route.event_types[0], type, sizeof(route.event_types[0]), 0); + route.event_type_count = 1; + return route; +} + +static char *read_file(const char *path) { + FILE *file = fopen(path, "rb"); + if (!file) return NULL; + fseek(file, 0, SEEK_END); + long size = ftell(file); + fseek(file, 0, SEEK_SET); + char *buffer = calloc(1, (size_t)size + 1); + if (buffer && fread(buffer, 1, (size_t)size, file) != (size_t)size) { + free(buffer); + buffer = NULL; + } + fclose(file); + return buffer; +} + +static int64_t parse_utc_ms(const char *text) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + int ms = 0; + int n = sscanf(text, "%4d-%2d-%2dT%2d:%2d:%2d.%3dZ", &tm.tm_year, &tm.tm_mon, + &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &ms); + TEST_ASSERT_TRUE_MESSAGE(n >= 6, text); + tm.tm_year -= 1900; + tm.tm_mon -= 1; + return (int64_t)timegm(&tm) * 1000 + (n == 7 ? ms : 0); +} + +static void sidecar_path(char *out, size_t size) { + snprintf(out, size, "%s/%s", storage_dir, OBSERVATION_CURSOR_HEAD_FILE); +} + +/* ---- fixtures --------------------------------------------------------- */ + +void setUp(void) { + observation_relay_stop(); + observation_relay_reset_stats(); + g_config.mqtt_enabled = true; + safe_strcpy(g_config.mqtt_topic_prefix, TOPIC_PREFIX, sizeof(g_config.mqtt_topic_prefix), 0); + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets; " + "DELETE FROM event_outbox; DELETE FROM event_routes; " + "DELETE FROM event_route_suppression_state;"); + exec_sql("DROP TRIGGER IF EXISTS temp.fail_outbox;"); + event_router_shutdown(); +} + +void tearDown(void) { + observation_relay_stop(); + exec_sql("DROP TRIGGER IF EXISTS temp.fail_outbox;"); + event_router_shutdown(); +} + +/* ---- tests ------------------------------------------------------------ */ + +void test_crash_before_relay_is_drained_at_start(void) { + /* Publications staged by a process that "crashed" before relaying. */ + observation_insert_result_t inserted = insert_pending(2); + TEST_ASSERT_EQUAL_INT(2, publications_in("pending")); + TEST_ASSERT_EQUAL_INT(0, outbox_rows()); + + /* A fresh start drains synchronously before steady state. */ + observation_relay_options_t options = test_options(); + options.poll_interval_ms = 60000; + TEST_ASSERT_EQUAL_INT(0, observation_relay_start(&options)); + TEST_ASSERT_EQUAL_INT(0, publications_in("pending")); + TEST_ASSERT_EQUAL_INT(2, publications_in("relayed")); + TEST_ASSERT_EQUAL_INT(2, outbox_rows()); + + /* The outbox row carries the deterministic message ID and topic. */ + char expected_id[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_message_id(source, inserted.observation_uuids[0], 1, + expected_id)); + char sql[512]; + snprintf(sql, sizeof(sql), + "SELECT count(*) FROM event_outbox WHERE event_id = '%s' AND destination = '%s' " + "AND topic = '" TOPIC_PREFIX "/v1/events/" OBSERVATION_CHANGED_EVENT_TYPE "/%s'", + expected_id, MQTT_EVENT_OUTBOX_DESTINATION, cam_a); + TEST_ASSERT_EQUAL_INT(1, scalar(sql)); + + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_TRUE(stats.running); + TEST_ASSERT_EQUAL_UINT64(2, stats.relayed); + observation_relay_stop(); + observation_relay_get_stats(&stats); + TEST_ASSERT_FALSE(stats.running); + + /* Restart is idempotent and finds nothing left. */ + TEST_ASSERT_EQUAL_INT(0, observation_relay_start(&options)); + TEST_ASSERT_EQUAL_INT(2, outbox_rows()); + observation_relay_stop(); +} + +void test_duplicate_relay_is_marked_relayed(void) { + observation_insert_result_t inserted = insert_pending(1); + observation_publication_t pending[4]; + TEST_ASSERT_EQUAL_INT(1, db_observation_pending_publications(pending, 4)); + + /* Crash after the outbox accepted the row but before the mark. */ + event_envelope_t event; + char error[256] = ""; + TEST_ASSERT_EQUAL_INT_MESSAGE(0, observation_relay_build_envelope(&pending[0], source, &event, + error, sizeof(error)), error); + TEST_ASSERT_EQUAL_INT(EVENT_PRODUCER_ENQUEUE_ENQUEUED, + event_producer_enqueue_durable(&event, error, sizeof(error))); + TEST_ASSERT_EQUAL_INT(EVENT_PRODUCER_ENQUEUE_DUPLICATE, + event_producer_enqueue_durable(&event, error, sizeof(error))); + event_envelope_clear(&event); + TEST_ASSERT_EQUAL_INT(1, outbox_rows()); + + observation_relay_options_t options = test_options(); + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(1, pass.processed); + TEST_ASSERT_FALSE(pass.backoff); + TEST_ASSERT_EQUAL_INT(1, publications_in("relayed")); + TEST_ASSERT_EQUAL_INT(1, outbox_rows()); + + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_EQUAL_UINT64(1, stats.relayed); + TEST_ASSERT_EQUAL_UINT64(1, stats.duplicates); + TEST_ASSERT_EQUAL_INT64(pending[0].change_seq, stats.last_relayed_seq); + (void)inserted; + + /* A second pass has nothing to do; the mark is not repeated. */ + pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(0, pass.read); +} + +void test_mqtt_disabled_and_no_route_are_relayed_no_route(void) { + g_config.mqtt_enabled = false; + insert_pending(1); + observation_relay_options_t options = test_options(); + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(1, pass.processed); + TEST_ASSERT_EQUAL_INT(1, publications_in("relayed_no_route")); + TEST_ASSERT_EQUAL_INT(0, outbox_rows()); + + /* Routes exist, but none selects this type. */ + g_config.mqtt_enabled = true; + event_route_t other = route_for("Offline only", "io.lightnvr.camera.offline.v1"); + TEST_ASSERT_EQUAL_INT(DB_EVENT_ROUTE_OK, db_event_route_create(&other)); + observation_publication_t pending[2]; + insert_pending(1); + TEST_ASSERT_EQUAL_INT(1, db_observation_pending_publications(pending, 2)); + event_envelope_t event; + char error[256] = ""; + TEST_ASSERT_EQUAL_INT(0, observation_relay_build_envelope(&pending[0], source, &event, + error, sizeof(error))); + TEST_ASSERT_EQUAL_INT(EVENT_PRODUCER_ENQUEUE_NO_ROUTE, + event_producer_enqueue_durable(&event, error, sizeof(error))); + event_envelope_clear(&event); + pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(1, pass.processed); + TEST_ASSERT_EQUAL_INT(2, publications_in("relayed_no_route")); + TEST_ASSERT_EQUAL_INT(0, outbox_rows()); + + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_EQUAL_UINT64(2, stats.relayed_no_route); + TEST_ASSERT_EQUAL_UINT64(0, stats.relayed); +} + +void test_route_suppression_applies_and_keeps_history(void) { + event_route_t route = route_for("Observation cooldown", OBSERVATION_CHANGED_EVENT_TYPE); + route.cooldown_seconds = 600; + TEST_ASSERT_EQUAL_INT(DB_EVENT_ROUTE_OK, db_event_route_create(&route)); + insert_pending(1); + insert_pending(1); + + observation_relay_options_t options = test_options(); + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(2, pass.processed); + TEST_ASSERT_EQUAL_INT(1, publications_in("relayed")); + TEST_ASSERT_EQUAL_INT(1, publications_in("relayed_no_route")); + TEST_ASSERT_EQUAL_INT(1, outbox_rows()); + + /* Suppression is not deletion: history and the cursor API keep both. */ + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM object_observations")); + TEST_ASSERT_EQUAL_INT(2, scalar("SELECT count(*) FROM observation_changes")); + observation_list_query_t query; + memset(&query, 0, sizeof(query)); + query.all_cameras = true; + observation_page_t page; + memset(&page, 0, sizeof(page)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observations_list(&query, NULL, &page)); + TEST_ASSERT_EQUAL_UINT(2, page.count); + db_observation_page_free(&page); +} + +void test_outbox_cap_pauses_and_resumes_in_seq_order(void) { + TEST_ASSERT_EQUAL_INT(2000, OBSERVATION_RELAY_OUTBOX_CAP); + for (int i = 0; i < 5; ++i) insert_pending(1); + + observation_relay_options_t options = test_options(); + options.outbox_cap = 3; + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(3, pass.processed); + TEST_ASSERT_TRUE(pass.cap_reached); + TEST_ASSERT_TRUE(pass.backoff); + TEST_ASSERT_EQUAL_INT(3, outbox_rows()); + TEST_ASSERT_EQUAL_INT(2, publications_in("pending")); + /* The relayed rows are the oldest ones. */ + TEST_ASSERT_EQUAL_INT(0, scalar( + "SELECT count(*) FROM observation_publications p WHERE p.state = 'relayed' AND " + "p.change_seq > (SELECT min(change_seq) FROM observation_publications " + "WHERE state = 'pending')")); + /* Paused rows are not charged an attempt. */ + TEST_ASSERT_EQUAL_INT(0, scalar( + "SELECT COALESCE(sum(attempts),0) FROM observation_publications WHERE state = 'pending'")); + + /* At the cap, a pass enqueues nothing. */ + pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(0, pass.processed); + TEST_ASSERT_TRUE(pass.cap_reached); + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_GREATER_OR_EQUAL_UINT64(2, stats.cap_pauses); + TEST_ASSERT_EQUAL_INT64(3, stats.outbox_type_pending); + + /* Delivered rows no longer count toward the cap. */ + exec_sql("UPDATE event_outbox SET state = 'delivered'"); + pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(2, pass.processed); + TEST_ASSERT_FALSE(pass.cap_reached); + TEST_ASSERT_EQUAL_INT(5, publications_in("relayed")); +} + +void test_outbox_error_backs_off_and_keeps_row_pending(void) { + insert_pending(1); + insert_pending(1); + exec_sql("CREATE TEMP TRIGGER fail_outbox BEFORE INSERT ON main.event_outbox " + "BEGIN SELECT RAISE(ABORT, 'injected outbox failure'); END;"); + + observation_relay_options_t options = test_options(); + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(0, pass.processed); + TEST_ASSERT_TRUE(pass.backoff); + TEST_ASSERT_EQUAL_INT(2, publications_in("pending")); + /* Only the head row was attempted, so seq order is preserved. */ + TEST_ASSERT_EQUAL_INT(1, scalar( + "SELECT sum(attempts) FROM observation_publications")); + TEST_ASSERT_EQUAL_INT(1, scalar( + "SELECT count(*) FROM observation_publications WHERE last_error IS NOT NULL")); + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_EQUAL_UINT64(1, stats.enqueue_errors); + TEST_ASSERT_GREATER_THAN_INT64(0, stats.oldest_pending_created_at_ms); + + exec_sql("DROP TRIGGER temp.fail_outbox;"); + pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(2, pass.processed); + TEST_ASSERT_EQUAL_INT(2, publications_in("relayed")); + TEST_ASSERT_EQUAL_INT(2, outbox_rows()); +} + +void test_publication_past_delivery_expiry_is_expired(void) { + insert_pending(1); + char sql[160]; + snprintf(sql, sizeof(sql), "UPDATE observation_changes SET created_at_ms = %lld", + (long long)(now_ms() - 2LL * 3600 * 1000)); + exec_sql(sql); + observation_relay_options_t options = test_options(); + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(1, pass.processed); + TEST_ASSERT_EQUAL_INT(1, publications_in("expired")); + TEST_ASSERT_EQUAL_INT(0, outbox_rows()); + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_EQUAL_UINT64(1, stats.expired); +} + +void test_commit_notify_wakes_the_relay_thread(void) { + observation_relay_options_t options = test_options(); + options.poll_interval_ms = 30000; /* only a notify can wake it in time */ + TEST_ASSERT_EQUAL_INT(0, observation_relay_start(&options)); + insert_pending(1); /* the DB layer's post-commit hook notifies */ + int64_t deadline = now_ms() + 3000; + while (publications_in("relayed") == 0 && now_ms() < deadline) usleep(10000); + TEST_ASSERT_EQUAL_INT(1, publications_in("relayed")); + observation_relay_stop(); +} + +void test_cursor_head_sidecar_and_rewind_detection(void) { + insert_pending(1); + observation_relay_options_t options = test_options(); + run_pass(&options); + + char path[512]; + sidecar_path(path, sizeof(path)); + char *text = read_file(path); + TEST_ASSERT_NOT_NULL_MESSAGE(text, path); + cJSON *json = cJSON_Parse(text); + free(text); + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + TEST_ASSERT_TRUE(cJSON_IsObject(json)); + TEST_ASSERT_EQUAL_STRING(meta.epoch_uuid, + cJSON_GetObjectItemCaseSensitive(json, "epoch")->valuestring); + TEST_ASSERT_EQUAL_INT64(meta.head_seq, (int64_t)cJSON_GetObjectItemCaseSensitive( + json, "head_seq")->valuedouble); + cJSON_Delete(json); + + /* Matching sidecar: nothing to do. */ + TEST_ASSERT_EQUAL_INT(0, observation_relay_check_cursor_head(storage_dir)); + + /* Missing sidecar is recreated without regenerating. */ + unlink(path); + TEST_ASSERT_EQUAL_INT(0, observation_relay_check_cursor_head(storage_dir)); + TEST_ASSERT_EQUAL_INT(0, access(path, F_OK)); + observation_meta_t after; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&after)); + TEST_ASSERT_EQUAL_STRING(meta.epoch_uuid, after.epoch_uuid); + + /* Sidecar ahead of the DB with the same epoch => the DB was restored. */ + FILE *file = fopen(path, "w"); + TEST_ASSERT_NOT_NULL(file); + fprintf(file, "{\"epoch\":\"%s\",\"head_seq\":%lld}", meta.epoch_uuid, + (long long)meta.head_seq + 100); + fclose(file); + TEST_ASSERT_EQUAL_INT(1, observation_relay_check_cursor_head(storage_dir)); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&after)); + TEST_ASSERT_NOT_EQUAL(0, strcmp(meta.epoch_uuid, after.epoch_uuid)); + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_EQUAL_UINT64(1, stats.epoch_regenerations); + + /* The rewritten sidecar carries the new epoch; a re-check is a no-op. */ + TEST_ASSERT_EQUAL_INT(0, observation_relay_check_cursor_head(storage_dir)); + + /* A different epoch (another DB generation) is not a rewind. */ + file = fopen(path, "w"); + TEST_ASSERT_NOT_NULL(file); + fprintf(file, "{\"epoch\":\"%s\",\"head_seq\":%lld}", + "99999999-9999-4999-8999-999999999999", (long long)after.head_seq + 100); + fclose(file); + TEST_ASSERT_EQUAL_INT(0, observation_relay_check_cursor_head(storage_dir)); +} + +void test_relay_sidecar_is_the_shared_l4_cursor_head(void) { + /* L10 consolidation: the relay writes the D9 sidecar through L4's + * observation_cursor_head_write(), so both modules share one path, one + * format and one writer (which creates missing directories). */ + insert_pending(1); + char nested[512]; + snprintf(nested, sizeof(nested), "%s/fresh-volume/storage", storage_dir); + TEST_ASSERT_EQUAL_INT(0, observation_relay_write_cursor_head(nested)); + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + char epoch[OBS_UUID_SIZE]; + int64_t head = -1; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_read(nested, epoch, &head)); + TEST_ASSERT_EQUAL_STRING(meta.epoch_uuid, epoch); + TEST_ASSERT_EQUAL_INT64(meta.head_seq, head); + + /* The relay's startup check is L4's reconcile: a rewind written by L4's + * writer is detected and the sidecar rewritten with the new epoch. */ + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_write(nested, meta.epoch_uuid, + meta.head_seq + 5)); + TEST_ASSERT_EQUAL_INT(1, observation_relay_check_cursor_head(nested)); + observation_meta_t after; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&after)); + TEST_ASSERT_NOT_EQUAL(0, strcmp(meta.epoch_uuid, after.epoch_uuid)); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_read(nested, epoch, &head)); + TEST_ASSERT_EQUAL_STRING(after.epoch_uuid, epoch); + TEST_ASSERT_EQUAL_INT(0, observation_relay_check_cursor_head(nested)); +} + +void test_envelopes_match_c3_fixtures(void) { + DIR *dir = opendir(FIXTURE_DIR); + TEST_ASSERT_NOT_NULL_MESSAGE(dir, FIXTURE_DIR); + int checked = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + size_t length = strlen(entry->d_name); + if (length < 6 || strcmp(entry->d_name + length - 5, ".json") != 0) continue; + char path[1024]; + snprintf(path, sizeof(path), "%s/%s", FIXTURE_DIR, entry->d_name); + char *text = read_file(path); + TEST_ASSERT_NOT_NULL_MESSAGE(text, path); + cJSON *fixture = cJSON_Parse(text); + free(text); + TEST_ASSERT_NOT_NULL_MESSAGE(fixture, path); + const cJSON *data = cJSON_GetObjectItemCaseSensitive(fixture, "data"); + + observation_publication_t pub; + memset(&pub, 0, sizeof(pub)); + pub.change_seq = 1; + safe_strcpy(pub.message_id, cJSON_GetObjectItemCaseSensitive(fixture, "id")->valuestring, + sizeof(pub.message_id), 0); + safe_strcpy(pub.observation_uuid, + cJSON_GetObjectItemCaseSensitive(data, "observation_id")->valuestring, + sizeof(pub.observation_uuid), 0); + pub.revision = cJSON_GetObjectItemCaseSensitive(data, "revision")->valueint; + const char *cause = cJSON_GetObjectItemCaseSensitive(data, "change_cause")->valuestring; + pub.cause = strcmp(cause, "created") == 0 ? OBS_CAUSE_CREATED + : strcmp(cause, "evidence") == 0 ? OBS_CAUSE_EVIDENCE + : OBS_CAUSE_CLASSIFICATION; + TEST_ASSERT_EQUAL_INT(0, observation_asset_state_parse( + cJSON_GetObjectItemCaseSensitive(data, "evidence_state")->valuestring, + &pub.evidence_state)); + const cJSON *reason = cJSON_GetObjectItemCaseSensitive(data, "evidence_reason"); + if (cJSON_IsString(reason)) + safe_strcpy(pub.evidence_reason, reason->valuestring, sizeof(pub.evidence_reason), 0); + safe_strcpy(pub.camera_uuid, + cJSON_GetObjectItemCaseSensitive(data, "camera_uuid")->valuestring, + sizeof(pub.camera_uuid), 0); + safe_strcpy(pub.label, cJSON_GetObjectItemCaseSensitive(data, "label")->valuestring, + sizeof(pub.label), 0); + pub.observed_at_ms = + parse_utc_ms(cJSON_GetObjectItemCaseSensitive(data, "observed_at")->valuestring); + safe_strcpy(pub.asset_uuid, cJSON_GetObjectItemCaseSensitive(data, "asset_id")->valuestring, + sizeof(pub.asset_uuid), 0); + /* Any sub-second offset must truncate to the fixture's time. */ + pub.change_created_at_ms = + parse_utc_ms(cJSON_GetObjectItemCaseSensitive(fixture, "time")->valuestring) + 999; + + /* The stored message ID is the section 5.4 derivation. */ + const char *fixture_source = cJSON_GetObjectItemCaseSensitive(fixture, "source")->valuestring; + char derived[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, observation_message_id(fixture_source, pub.observation_uuid, + pub.revision, derived)); + TEST_ASSERT_EQUAL_STRING_MESSAGE(pub.message_id, derived, entry->d_name); + + event_envelope_t event; + char error[256] = ""; + TEST_ASSERT_EQUAL_INT_MESSAGE(0, observation_relay_build_envelope( + &pub, fixture_source, &event, error, sizeof(error)), + error); + char *serialized = event_envelope_serialize(&event, error, sizeof(error)); + TEST_ASSERT_NOT_NULL_MESSAGE(serialized, error); + char *expected = cJSON_PrintUnformatted(fixture); + /* Byte equality: same keys, values and key order as event_envelope.c. */ + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, serialized, entry->d_name); + free(expected); + free(serialized); + event_envelope_clear(&event); + cJSON_Delete(fixture); + checked++; + } + closedir(dir); + TEST_ASSERT_EQUAL_INT(8, checked); +} + +void test_build_envelope_rejects_inconsistent_publications(void) { + observation_publication_t pub; + memset(&pub, 0, sizeof(pub)); + pub.change_seq = 7; + safe_strcpy(pub.message_id, "43ebdf02-b2d5-5403-a0ef-2a4e7d58ada9", sizeof(pub.message_id), 0); + safe_strcpy(pub.observation_uuid, "55555555-5555-4555-8555-555555555555", + sizeof(pub.observation_uuid), 0); + pub.revision = 2; + pub.cause = OBS_CAUSE_EVIDENCE; + pub.evidence_state = OBS_ASSET_READY; + safe_strcpy(pub.camera_uuid, "22222222-2222-4222-8222-222222222222", sizeof(pub.camera_uuid), 0); + safe_strcpy(pub.label, "bird", sizeof(pub.label), 0); + pub.observed_at_ms = 1793000000481LL; + pub.change_created_at_ms = 1793000000700LL; + safe_strcpy(pub.asset_uuid, "88888888-8888-4888-8888-888888888888", sizeof(pub.asset_uuid), 0); + + event_envelope_t event; + char error[256] = ""; + TEST_ASSERT_EQUAL_INT(0, observation_relay_build_envelope(&pub, source, &event, error, + sizeof(error))); + event_envelope_clear(&event); + + observation_publication_t bad = pub; + safe_strcpy(bad.evidence_reason, "queue_full", sizeof(bad.evidence_reason), 0); + TEST_ASSERT_EQUAL_INT(-1, observation_relay_build_envelope(&bad, source, &event, error, + sizeof(error))); + bad = pub; + bad.label[0] = '\0'; + TEST_ASSERT_EQUAL_INT(-1, observation_relay_build_envelope(&bad, source, &event, error, + sizeof(error))); + bad = pub; + bad.revision = 0; + TEST_ASSERT_EQUAL_INT(-1, observation_relay_build_envelope(&bad, source, &event, error, + sizeof(error))); + TEST_ASSERT_EQUAL_INT(-1, observation_relay_build_envelope(NULL, source, &event, error, + sizeof(error))); +} + +void test_invalid_staged_row_is_marked_failed_without_blocking(void) { + insert_pending(1); + insert_pending(1); + /* Corrupt the head row so its envelope cannot validate. */ + exec_sql("UPDATE observation_publications SET message_id = 'not-a-uuid' " + "WHERE change_seq = (SELECT min(change_seq) FROM observation_publications)"); + observation_relay_options_t options = test_options(); + observation_relay_pass_t pass = run_pass(&options); + TEST_ASSERT_EQUAL_INT(2, pass.processed); + TEST_ASSERT_EQUAL_INT(1, publications_in("failed")); + TEST_ASSERT_EQUAL_INT(1, publications_in("relayed")); + observation_relay_stats_t stats; + observation_relay_get_stats(&stats); + TEST_ASSERT_EQUAL_UINT64(1, stats.failed); +} + +int main(void) { + snprintf(db_path, sizeof(db_path), "/tmp/lightnvr_observation_relay_%ld.db", (long)getpid()); + snprintf(storage_dir, sizeof(storage_dir), "/tmp/lightnvr_observation_relay_%ld", + (long)getpid()); + unlink(db_path); + mkdir(storage_dir, 0700); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0 || event_identity_get_source(source, sizeof(source)) != 0) + return 1; + + UNITY_BEGIN(); + make_camera("relay_cam", cam_a); + RUN_TEST(test_crash_before_relay_is_drained_at_start); + RUN_TEST(test_duplicate_relay_is_marked_relayed); + RUN_TEST(test_mqtt_disabled_and_no_route_are_relayed_no_route); + RUN_TEST(test_route_suppression_applies_and_keeps_history); + RUN_TEST(test_outbox_cap_pauses_and_resumes_in_seq_order); + RUN_TEST(test_outbox_error_backs_off_and_keeps_row_pending); + RUN_TEST(test_publication_past_delivery_expiry_is_expired); + RUN_TEST(test_commit_notify_wakes_the_relay_thread); + RUN_TEST(test_cursor_head_sidecar_and_rewind_detection); + RUN_TEST(test_relay_sidecar_is_the_shared_l4_cursor_head); + RUN_TEST(test_envelopes_match_c3_fixtures); + RUN_TEST(test_build_envelope_rejects_inconsistent_publications); + RUN_TEST(test_invalid_staged_row_is_marked_failed_without_blocking); + int result = UNITY_END(); + + observation_relay_stop(); + event_router_shutdown(); + event_identity_shutdown(); + shutdown_database(); + unlink(db_path); + char sidecar[512]; + snprintf(sidecar, sizeof(sidecar), "%s-wal", db_path); unlink(sidecar); + snprintf(sidecar, sizeof(sidecar), "%s-shm", db_path); unlink(sidecar); + sidecar_path(sidecar, sizeof(sidecar)); unlink(sidecar); + snprintf(sidecar, sizeof(sidecar), "%s/observations", storage_dir); rmdir(sidecar); + rmdir(storage_dir); + return result; +} diff --git a/tests/unit/test_observation_retention.c b/tests/unit/test_observation_retention.c new file mode 100644 index 000000000..ff4f4c42f --- /dev/null +++ b/tests/unit/test_observation_retention.c @@ -0,0 +1,615 @@ +/* INT_01 L4: startup reconciliation, retention and eviction of observation + * evidence (contract sections 4.2, 4.5, 7.6 and 14), against a real migrated + * database and a temporary storage directory. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity.h" +#include "core/event_identity.h" +#include "core/observation_retention.h" +#include "core/path_utils.h" +#include "database/db_core.h" +#include "database/db_observations.h" +#include "database/db_recordings.h" +#include "database/db_streams.h" +#include "database/db_system_settings.h" +#include "utils/strings.h" +#include "utils/uuid.h" + +#define DAY_MS 86400000LL + +static char db_path[256]; +static char storage[256]; +static char cam_a[OBS_UUID_SIZE]; +static char cam_b[OBS_UUID_SIZE]; +static int64_t now_ms; + +/* ---- helpers ---------------------------------------------------------- */ + +static int64_t scalar64(const char *sql) { + sqlite3_stmt *stmt = NULL; + int64_t value = -999; + pthread_mutex_lock(get_db_mutex()); + if (sqlite3_prepare_v2(get_db_handle(), sql, -1, &stmt, NULL) == SQLITE_OK && + sqlite3_step(stmt) == SQLITE_ROW) + value = sqlite3_column_int64(stmt, 0); + sqlite3_finalize(stmt); + pthread_mutex_unlock(get_db_mutex()); + TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(-999, (int)value, sql); + return value; +} + +static void exec_sql(const char *sql) { + char *error = NULL; + pthread_mutex_lock(get_db_mutex()); + int rc = sqlite3_exec(get_db_handle(), sql, NULL, NULL, &error); + pthread_mutex_unlock(get_db_mutex()); + if (rc != SQLITE_OK) fprintf(stderr, "%s: %s\n", sql, error ? error : ""); + sqlite3_free(error); + TEST_ASSERT_EQUAL_INT_MESSAGE(SQLITE_OK, rc, sql); +} + +static void make_camera(const char *name, char out[OBS_UUID_SIZE]) { + stream_config_t *s = calloc(1, sizeof(*s)); + TEST_ASSERT_NOT_NULL(s); + safe_strcpy(s->name, name, sizeof(s->name), 0); + safe_strcpy(s->url, "rtsp://camera/stream", sizeof(s->url), 0); + safe_strcpy(s->codec, "h264", sizeof(s->codec), 0); + s->enabled = true; + s->priority = 5; + s->segment_duration = 60; + safe_strcpy(s->detection_object_filter, "none", sizeof(s->detection_object_filter), 0); + TEST_ASSERT_GREATER_THAN(0, add_stream_config(s)); + memset(s, 0, sizeof(*s)); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(name, s)); + safe_strcpy(out, s->camera_uuid, OBS_UUID_SIZE, 0); + free(s); +} + +static observation_insert_result_t insert_batch(const char *camera, int64_t observed_at_ms, + int objects, int64_t recording_id) { + observation_batch_input_t batch; + memset(&batch, 0, sizeof(batch)); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(batch.batch_id)); + safe_strcpy(batch.camera_uuid, camera, sizeof(batch.camera_uuid), 0); + batch.observed_at_ms = observed_at_ms; + safe_strcpy(batch.engine_key, "legacy-primary", sizeof(batch.engine_key), 0); + safe_strcpy(batch.engine_type, "object", sizeof(batch.engine_type), 0); + batch.recording_id = recording_id; + batch.object_count = objects; + for (int i = 0; i < objects; ++i) { + batch.objects[i].object_index = i; + safe_strcpy(batch.objects[i].label, "bird", sizeof(batch.objects[i].label), 0); + batch.objects[i].confidence = 0.9; + } + observation_asset_input_t asset; + memset(&asset, 0, sizeof(asset)); + asset.state = OBS_ASSET_PENDING; + safe_strcpy(asset.origin, "inference_input", sizeof(asset.origin), 0); + safe_strcpy(asset.media_type, "image/jpeg", sizeof(asset.media_type), 0); + observation_insert_result_t result; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_insert_batch(&batch, &asset, &result)); + return result; +} + +/* Relative path (stored in observation_assets.path) and absolute path. */ +static void asset_paths(const char *camera, const char *asset, const char *suffix, + char *rel, size_t rel_size, char *abs, size_t abs_size) { + snprintf(rel, rel_size, "observations/%s/2026-11-08/%s.jpg%s", camera, asset, suffix); + snprintf(abs, abs_size, "%s/%s", storage, rel); +} + +static void write_file(const char *path, size_t bytes) { + char dir[512]; + safe_strcpy(dir, path, sizeof(dir), 0); + char *slash = strrchr(dir, '/'); + TEST_ASSERT_NOT_NULL(slash); + *slash = '\0'; + TEST_ASSERT_EQUAL_INT(0, mkdir_recursive(dir)); + FILE *f = fopen(path, "wb"); + TEST_ASSERT_NOT_NULL_MESSAGE(f, path); + for (size_t i = 0; i < bytes; ++i) fputc(0xAB, f); + fclose(f); +} + +static bool exists(const char *path) { + struct stat st; + return lstat(path, &st) == 0; +} + +typedef struct { + observation_insert_result_t insert; + char rel[512]; + char abs[512]; +} ready_asset_t; + +static ready_asset_t make_ready(const char *camera, int64_t observed_at_ms, int64_t bytes, + int objects) { + ready_asset_t r; + memset(&r, 0, sizeof(r)); + r.insert = insert_batch(camera, observed_at_ms, objects, 0); + asset_paths(camera, r.insert.asset_uuid, "", r.rel, sizeof(r.rel), r.abs, sizeof(r.abs)); + write_file(r.abs, (size_t)bytes); + observation_asset_media_t media = { + .path = r.rel, .bytes = bytes, .width = 640, .height = 360, .expires_at_ms = 0, + }; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, + db_observation_asset_transition(r.insert.asset_uuid, OBS_ASSET_PENDING, + OBS_ASSET_READY, NULL, &media, NULL)); + return r; +} + +static observation_asset_record_t asset_of(const char *uuid) { + observation_asset_record_t asset; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_get(uuid, &asset)); + return asset; +} + +static void set_protected(const char *asset, int value) { + char sql[256]; + snprintf(sql, sizeof(sql), "UPDATE observation_assets SET protected = %d WHERE uuid = '%s';", + value, asset); + exec_sql(sql); +} + +static void set_budget(const char *camera, int days, int64_t bytes) { + char sql[256]; + snprintf(sql, sizeof(sql), + "UPDATE streams SET observation_max_age_days = %d, observation_max_bytes = %lld " + "WHERE camera_uuid = '%s';", days, (long long)bytes, camera); + exec_sql(sql); +} + +static int rm_entry(const char *path, const struct stat *st, int flag, struct FTW *ftw) { + (void)st; (void)flag; (void)ftw; + return remove(path); +} + +static void rm_tree(const char *path) { + nftw(path, rm_entry, 16, FTW_DEPTH | FTW_PHYS); +} + +static observation_retention_result_t run_retention(int64_t at_ms) { + observation_retention_result_t result; + TEST_ASSERT_EQUAL_INT(0, observation_retention_run(storage, at_ms, &result)); + return result; +} + +static void startup(observation_reconcile_result_t *result) { + TEST_ASSERT_EQUAL_INT(0, observation_retention_startup(storage, result)); + TEST_ASSERT_TRUE(observation_retention_startup_done()); +} + +void setUp(void) { + exec_sql("DELETE FROM object_observations; DELETE FROM observation_assets; " + "DELETE FROM observation_changes; DELETE FROM recordings;"); + char dir[512]; + snprintf(dir, sizeof(dir), "%s/observations", storage); + rm_tree(dir); + set_budget(cam_a, 7, 2147483648LL); + set_budget(cam_b, 7, 2147483648LL); + TEST_ASSERT_EQUAL_INT(0, db_set_system_setting("observation_history_retention_days", "30")); + observation_retention_reset_for_testing(); +} +void tearDown(void) {} + +/* ---- startup reconciliation ------------------------------------------- */ + +static void test_startup_reconciles_interrupted_missing_and_orphan_files(void) { + /* A: interrupted write, only the temp file exists. */ + observation_insert_result_t a = insert_batch(cam_a, now_ms, 2, 0); + char rel[512], a_tmp[512], b_final[512], orphan[512], scratch[512]; + asset_paths(cam_a, a.asset_uuid, ".tmp", rel, sizeof(rel), a_tmp, sizeof(a_tmp)); + write_file(a_tmp, 100); + /* B: crash after rename but before the ready commit. */ + observation_insert_result_t b = insert_batch(cam_a, now_ms, 1, 0); + asset_paths(cam_a, b.asset_uuid, "", rel, sizeof(rel), b_final, sizeof(b_final)); + write_file(b_final, 100); + /* C: ready whose file vanished; D: healthy ready asset. */ + ready_asset_t c = make_ready(cam_a, now_ms, 200, 1); + TEST_ASSERT_EQUAL_INT(0, unlink(c.abs)); + ready_asset_t d = make_ready(cam_b, now_ms, 300, 1); + /* An orphan final file, an orphan temp file, and files L4 must not touch. */ + char orphan_uuid[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(orphan_uuid)); + asset_paths(cam_b, orphan_uuid, "", rel, sizeof(rel), orphan, sizeof(orphan)); + write_file(orphan, 50); + char orphan_tmp[512]; + asset_paths(cam_b, orphan_uuid, ".tmp", rel, sizeof(rel), orphan_tmp, sizeof(orphan_tmp)); + write_file(orphan_tmp, 50); + snprintf(scratch, sizeof(scratch), "%s/observations/README.txt", storage); + write_file(scratch, 10); + char recording[512]; + snprintf(recording, sizeof(recording), "%s/recordings/cam_a/%s.jpg", storage, orphan_uuid); + write_file(recording, 10); + + observation_reconcile_result_t result; + startup(&result); + TEST_ASSERT_EQUAL_INT(2, result.tmp_files_deleted); + TEST_ASSERT_EQUAL_INT(2, result.stale_files_deleted); /* orphan + B's final */ + TEST_ASSERT_EQUAL_INT(2, result.pending_interrupted); + TEST_ASSERT_EQUAL_INT(1, result.ready_missing); + + TEST_ASSERT_FALSE(exists(a_tmp)); + TEST_ASSERT_FALSE(exists(b_final)); + TEST_ASSERT_FALSE(exists(orphan)); + TEST_ASSERT_FALSE(exists(orphan_tmp)); + TEST_ASSERT_TRUE(exists(d.abs)); + TEST_ASSERT_TRUE(exists(scratch)); + TEST_ASSERT_TRUE(exists(recording)); + + observation_asset_record_t asset = asset_of(a.asset_uuid); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, asset.state); + TEST_ASSERT_EQUAL_STRING("interrupted", asset.reason); + asset = asset_of(b.asset_uuid); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, asset.state); + TEST_ASSERT_EQUAL_STRING("interrupted", asset.reason); + asset = asset_of(c.insert.asset_uuid); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_UNAVAILABLE, asset.state); + TEST_ASSERT_EQUAL_STRING("write_failed", asset.reason); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(d.insert.asset_uuid).state); + + /* Every observation sharing A got an evidence revision (published). */ + for (int i = 0; i < 2; ++i) { + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(a.observation_uuids[i], &record)); + TEST_ASSERT_EQUAL_INT(2, record.revision); + } + + /* Idempotent: a second sweep finds nothing to do. */ + startup(&result); + TEST_ASSERT_EQUAL_INT(0, result.tmp_files_deleted + result.stale_files_deleted + + result.pending_interrupted + result.ready_missing); +} + +/* The sweep never follows a symlink below the evidence root: neither a + * symlinked directory nor a symlinked file. Files outside the root survive. */ +static void test_startup_sweep_does_not_follow_symlinks_below_root(void) { + char outside[512], out_tmp[512], out_final[512], uuid[OBS_UUID_SIZE]; + snprintf(outside, sizeof(outside), "%s/outside-evidence", storage); + rm_tree(outside); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(uuid)); + snprintf(out_tmp, sizeof(out_tmp), "%s/%s.jpg.tmp", outside, uuid); + snprintf(out_final, sizeof(out_final), "%s/%s.jpg", outside, uuid); + write_file(out_tmp, 10); + write_file(out_final, 10); + + char cam_dir[512], dir_link[512], file_link[512]; + snprintf(cam_dir, sizeof(cam_dir), "%s/observations/%s/2026-11-08", storage, cam_a); + TEST_ASSERT_EQUAL_INT(0, mkdir_recursive(cam_dir)); + snprintf(dir_link, sizeof(dir_link), "%s/observations/%s/2026-11-09", storage, cam_a); + TEST_ASSERT_EQUAL_INT(0, symlink(outside, dir_link)); + char other[OBS_UUID_SIZE]; + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(other)); + snprintf(file_link, sizeof(file_link), "%s/%s.jpg.tmp", cam_dir, other); + TEST_ASSERT_EQUAL_INT(0, symlink(out_tmp, file_link)); + + observation_reconcile_result_t result; + startup(&result); + TEST_ASSERT_EQUAL_INT(0, result.tmp_files_deleted); + TEST_ASSERT_EQUAL_INT(0, result.stale_files_deleted); + TEST_ASSERT_TRUE(exists(out_tmp)); + TEST_ASSERT_TRUE(exists(out_final)); + TEST_ASSERT_TRUE(exists(dir_link)); + TEST_ASSERT_TRUE(exists(file_link)); + rm_tree(outside); +} + +/* The evidence root itself may be a symlink (a mounted storage path); it is + * still swept. */ +static void test_startup_sweep_follows_a_symlinked_evidence_root(void) { + char real[512], root[512], tmp[512], uuid[OBS_UUID_SIZE]; + snprintf(real, sizeof(real), "%s/observations-real", storage); + snprintf(root, sizeof(root), "%s/observations", storage); + rm_tree(real); + rm_tree(root); + TEST_ASSERT_EQUAL_INT(0, lightnvr_uuid_generate_v4(uuid)); + snprintf(tmp, sizeof(tmp), "%s/%s/2026-11-08/%s.jpg.tmp", real, cam_a, uuid); + write_file(tmp, 10); + TEST_ASSERT_EQUAL_INT(0, symlink(real, root)); + + observation_reconcile_result_t result; + startup(&result); + TEST_ASSERT_EQUAL_INT(1, result.tmp_files_deleted); + TEST_ASSERT_FALSE(exists(tmp)); + TEST_ASSERT_EQUAL_INT(0, unlink(root)); + rm_tree(real); +} + +static void test_periodic_run_waits_for_startup_reconciliation(void) { + ready_asset_t old = make_ready(cam_a, now_ms - 30 * DAY_MS, 100, 1); + observation_retention_result_t result; + TEST_ASSERT_EQUAL_INT(1, observation_retention_run(storage, now_ms, &result)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(old.insert.asset_uuid).state); + TEST_ASSERT_TRUE(exists(old.abs)); +} + +/* ---- age and byte budgets ---------------------------------------------- */ + +static void test_age_budget_expires_media_and_keeps_metadata(void) { + observation_reconcile_result_t rec; + startup(&rec); + ready_asset_t old = make_ready(cam_a, now_ms - 8 * DAY_MS, 1000, 1); + ready_asset_t fresh = make_ready(cam_a, now_ms - 1 * DAY_MS, 1000, 1); + ready_asset_t protected_old = make_ready(cam_a, now_ms - 9 * DAY_MS, 1000, 1); + set_protected(protected_old.insert.asset_uuid, 1); + observation_insert_result_t pending_old = insert_batch(cam_a, now_ms - 20 * DAY_MS, 1, 0); + + observation_retention_result_t result = run_retention(now_ms); + TEST_ASSERT_EQUAL_INT(1, result.assets_expired); + TEST_ASSERT_FALSE(exists(old.abs)); + TEST_ASSERT_TRUE(exists(fresh.abs)); + TEST_ASSERT_TRUE(exists(protected_old.abs)); + + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(old.insert.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, record.asset.state); + TEST_ASSERT_EQUAL_STRING("retention_expired", record.asset.reason); + TEST_ASSERT_EQUAL_INT(640, record.asset.width); + TEST_ASSERT_EQUAL_INT(360, record.asset.height); + TEST_ASSERT_EQUAL_INT64(1000, record.asset.bytes); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(fresh.insert.asset_uuid).state); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(protected_old.insert.asset_uuid).state); + /* Pending is the worker's: never evicted. */ + TEST_ASSERT_EQUAL_INT(OBS_ASSET_PENDING, asset_of(pending_old.asset_uuid).state); + TEST_ASSERT_FALSE(observation_retention_budget_exhausted(cam_a)); +} + +static void test_shared_asset_survives_until_last_reference_expires(void) { + observation_reconcile_result_t rec; + startup(&rec); + ready_asset_t shared = make_ready(cam_a, now_ms - 8 * DAY_MS, 1000, 2); + /* The second reference is newer (e.g. a later sibling re-using the frame). */ + char sql[256]; + snprintf(sql, sizeof(sql), + "UPDATE object_observations SET observed_at_ms = %lld WHERE uuid = '%s';", + (long long)(now_ms - 2 * DAY_MS), shared.insert.observation_uuids[1]); + exec_sql(sql); + + run_retention(now_ms); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(shared.insert.asset_uuid).state); + TEST_ASSERT_TRUE(exists(shared.abs)); + + run_retention(now_ms + 6 * DAY_MS); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, asset_of(shared.insert.asset_uuid).state); + TEST_ASSERT_FALSE(exists(shared.abs)); + for (int i = 0; i < 2; ++i) { + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, + db_observation_get(shared.insert.observation_uuids[i], &record)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, record.asset.state); + } +} + +static void test_byte_budget_evicts_oldest_and_protected_assets_flip_the_flag(void) { + observation_reconcile_result_t rec; + startup(&rec); + ready_asset_t p1 = make_ready(cam_a, now_ms - 5 * 3600000LL, 6000, 1); + set_protected(p1.insert.asset_uuid, 1); + ready_asset_t u1 = make_ready(cam_a, now_ms - 4 * 3600000LL, 3000, 1); + ready_asset_t u2 = make_ready(cam_a, now_ms - 3 * 3600000LL, 3000, 1); + ready_asset_t other = make_ready(cam_b, now_ms - 6 * 3600000LL, 9000, 1); + set_budget(cam_a, 7, 10000); + set_budget(cam_b, 7, 10000); + + observation_retention_result_t result = run_retention(now_ms); + TEST_ASSERT_EQUAL_INT(1, result.assets_expired); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, asset_of(u1.insert.asset_uuid).state); + TEST_ASSERT_EQUAL_STRING("retention_expired", asset_of(u1.insert.asset_uuid).reason); + TEST_ASSERT_FALSE(exists(u1.abs)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(u2.insert.asset_uuid).state); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(other.insert.asset_uuid).state); + TEST_ASSERT_FALSE(observation_retention_budget_exhausted(cam_a)); + TEST_ASSERT_EQUAL_INT(0, observation_retention_budget_exhausted_count()); + + /* Shrink the budget below the protected asset alone. */ + set_budget(cam_a, 7, 5000); + result = run_retention(now_ms); + TEST_ASSERT_EQUAL_INT(1, result.assets_expired); + TEST_ASSERT_FALSE(exists(u2.abs)); + TEST_ASSERT_TRUE(exists(p1.abs)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(p1.insert.asset_uuid).state); + TEST_ASSERT_EQUAL_INT(1, result.cameras_budget_exhausted); + TEST_ASSERT_TRUE(observation_retention_budget_exhausted(cam_a)); + TEST_ASSERT_FALSE(observation_retention_budget_exhausted(cam_b)); + TEST_ASSERT_EQUAL_INT(1, observation_retention_budget_exhausted_count()); + + observation_retention_camera_status_t status[4]; + int n = observation_retention_camera_status(status, 4); + TEST_ASSERT_EQUAL_INT(2, n); + bool seen = false; + for (int i = 0; i < n; ++i) { + if (strcmp(status[i].camera_uuid, cam_a) != 0) continue; + seen = true; + TEST_ASSERT_TRUE(status[i].budget_exhausted); + TEST_ASSERT_EQUAL_INT64(6000, status[i].retained_bytes); + TEST_ASSERT_EQUAL_INT64(5000, status[i].max_bytes); + } + TEST_ASSERT_TRUE(seen); + + /* Unprotecting lets eviction proceed and clears the flag. */ + set_protected(p1.insert.asset_uuid, 0); + run_retention(now_ms); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, asset_of(p1.insert.asset_uuid).state); + TEST_ASSERT_FALSE(exists(p1.abs)); + TEST_ASSERT_FALSE(observation_retention_budget_exhausted(cam_a)); + TEST_ASSERT_EQUAL_INT(0, observation_retention_budget_exhausted_count()); +} + +/* ---- history ----------------------------------------------------------- */ + +static void test_history_prune_advances_oldest_seq_and_reclaims_assets(void) { + observation_reconcile_result_t rec; + startup(&rec); + ready_asset_t old = make_ready(cam_a, now_ms - 3 * DAY_MS, 500, 1); + ready_asset_t fresh = make_ready(cam_a, now_ms, 500, 1); + observation_meta_t before; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&before)); + + char sql[512]; + snprintf(sql, sizeof(sql), + "UPDATE observation_changes SET created_at_ms = %lld WHERE observation_uuid = '%s';", + (long long)(now_ms - 3 * DAY_MS), old.insert.observation_uuids[0]); + exec_sql(sql); + TEST_ASSERT_EQUAL_INT(0, db_set_system_setting("observation_history_retention_days", "1")); + + observation_retention_result_t result = run_retention(now_ms); + TEST_ASSERT_EQUAL_INT(2, result.changes_pruned); /* created + evidence */ + TEST_ASSERT_EQUAL_INT(1, result.observations_pruned); + + observation_meta_t after; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&after)); + TEST_ASSERT_TRUE(after.oldest_seq > before.oldest_seq); + TEST_ASSERT_EQUAL_INT64(after.oldest_seq, result.oldest_seq); + + /* The asset lost its last reference: file deleted and row removed. */ + observation_asset_record_t asset; + TEST_ASSERT_EQUAL_INT(DB_OBS_NOT_FOUND, db_observation_asset_get(old.insert.asset_uuid, &asset)); + TEST_ASSERT_FALSE(exists(old.abs)); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, asset_of(fresh.insert.asset_uuid).state); + TEST_ASSERT_TRUE(exists(fresh.abs)); +} + +/* ---- isolation from recordings ----------------------------------------- */ + +static void test_recording_deletion_leaves_assets_intact(void) { + observation_reconcile_result_t rec; + startup(&rec); + char rec_path[512]; + snprintf(rec_path, sizeof(rec_path), "%s/recordings/cam_a/clip.mp4", storage); + write_file(rec_path, 64); + recording_metadata_t meta; + memset(&meta, 0, sizeof(meta)); + safe_strcpy(meta.stream_name, "cam_a", sizeof(meta.stream_name), 0); + safe_strcpy(meta.file_path, rec_path, sizeof(meta.file_path), 0); + safe_strcpy(meta.codec, "h264", sizeof(meta.codec), 0); + safe_strcpy(meta.trigger_type, "detection", sizeof(meta.trigger_type), 0); + meta.start_time = (time_t)(now_ms / 1000) - 60; + meta.end_time = (time_t)(now_ms / 1000); + meta.is_complete = true; + meta.size_bytes = 64; + meta.retention_override_days = -1; + meta.retention_tier = 2; + meta.schedule_restricted = -1; + uint64_t recording_id = add_recording_metadata(&meta); + TEST_ASSERT_TRUE(recording_id > 0); + + observation_insert_result_t ins = insert_batch(cam_a, now_ms, 1, (int64_t)recording_id); + char rel[512], abs[512]; + asset_paths(cam_a, ins.asset_uuid, "", rel, sizeof(rel), abs, sizeof(abs)); + write_file(abs, 100); + observation_asset_media_t media = {.path = rel, .bytes = 100, .width = 64, .height = 48}; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_asset_transition( + ins.asset_uuid, OBS_ASSET_PENDING, OBS_ASSET_READY, NULL, &media, NULL)); + + char sql[128]; + snprintf(sql, sizeof(sql), "DELETE FROM recordings WHERE id = %llu;", + (unsigned long long)recording_id); + exec_sql(sql); + + run_retention(now_ms); + observation_record_t record; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_get(ins.observation_uuids[0], &record)); + TEST_ASSERT_EQUAL_INT64(0, record.recording_id); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_READY, record.asset.state); + TEST_ASSERT_TRUE(exists(abs)); + /* Evidence eviction never touches recordings either, even for a path that + * escapes the observations tree. */ + char evil[600]; + snprintf(evil, sizeof(evil), + "UPDATE observation_assets SET path = 'observations/../recordings/cam_a/clip.mp4' " + "WHERE uuid = '%s';", ins.asset_uuid); + exec_sql(evil); + run_retention(now_ms + 30 * DAY_MS); + TEST_ASSERT_EQUAL_INT(OBS_ASSET_EXPIRED, asset_of(ins.asset_uuid).state); + TEST_ASSERT_TRUE(exists(rec_path)); +} + +/* ---- D9 cursor head sidecar -------------------------------------------- */ + +static void test_cursor_head_sidecar_detects_rewind(void) { + insert_batch(cam_a, now_ms, 1, 0); + observation_meta_t meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + + /* Missing sidecar: created, epoch kept. */ + bool regenerated = true; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_reconcile(storage, ®enerated)); + TEST_ASSERT_FALSE(regenerated); + char epoch[OBS_UUID_SIZE]; + int64_t head = -1; + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_read(storage, epoch, &head)); + TEST_ASSERT_EQUAL_STRING(meta.epoch_uuid, epoch); + TEST_ASSERT_EQUAL_INT64(meta.head_seq, head); + + /* Sidecar is ahead of the DB in the same epoch: the DB was restored. */ + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_write(storage, meta.epoch_uuid, + meta.head_seq + 100)); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_reconcile(storage, ®enerated)); + TEST_ASSERT_TRUE(regenerated); + observation_meta_t now_meta; + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&now_meta)); + TEST_ASSERT_NOT_EQUAL(0, strcmp(meta.epoch_uuid, now_meta.epoch_uuid)); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_read(storage, epoch, &head)); + TEST_ASSERT_EQUAL_STRING(now_meta.epoch_uuid, epoch); + TEST_ASSERT_EQUAL_INT64(now_meta.head_seq, head); + + /* A different epoch (already regenerated in-process): no regeneration. */ + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_write( + storage, "66666666-6666-4666-8666-666666666666", now_meta.head_seq + 100)); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_reconcile(storage, ®enerated)); + TEST_ASSERT_FALSE(regenerated); + TEST_ASSERT_EQUAL_INT(DB_OBS_OK, db_observation_meta_get(&meta)); + TEST_ASSERT_EQUAL_STRING(now_meta.epoch_uuid, meta.epoch_uuid); + + /* A corrupt sidecar is treated as missing. */ + char path[512]; + snprintf(path, sizeof(path), "%s/observations/.cursor_head.json", storage); + write_file(path, 5); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_reconcile(storage, ®enerated)); + TEST_ASSERT_FALSE(regenerated); + TEST_ASSERT_EQUAL_INT(0, observation_cursor_head_read(storage, epoch, &head)); + TEST_ASSERT_EQUAL_STRING(meta.epoch_uuid, epoch); +} + +int main(void) { + const char *tmp = getenv("TMPDIR"); + snprintf(storage, sizeof(storage), "%s/lightnvr_obs_retention_XXXXXX", + tmp && *tmp ? tmp : "/tmp"); + if (!mkdtemp(storage)) return 1; + snprintf(db_path, sizeof(db_path), "%s/test.db", storage); + if (init_database(db_path) != 0) return 1; + if (event_identity_init() != 0) return 1; + now_ms = (int64_t)time(NULL) * 1000; + + UNITY_BEGIN(); + make_camera("cam_a", cam_a); + make_camera("cam_b", cam_b); + RUN_TEST(test_startup_reconciles_interrupted_missing_and_orphan_files); + RUN_TEST(test_startup_sweep_does_not_follow_symlinks_below_root); + RUN_TEST(test_startup_sweep_follows_a_symlinked_evidence_root); + RUN_TEST(test_periodic_run_waits_for_startup_reconciliation); + RUN_TEST(test_age_budget_expires_media_and_keeps_metadata); + RUN_TEST(test_shared_asset_survives_until_last_reference_expires); + RUN_TEST(test_byte_budget_evicts_oldest_and_protected_assets_flip_the_flag); + RUN_TEST(test_history_prune_advances_oldest_seq_and_reclaims_assets); + RUN_TEST(test_recording_deletion_leaves_assets_intact); + RUN_TEST(test_cursor_head_sidecar_detects_rewind); + int result = UNITY_END(); + + observation_retention_reset_for_testing(); + event_identity_shutdown(); + shutdown_database(); + rm_tree(storage); + return result; +} diff --git a/tests/unit/test_onvif_device_test.c b/tests/unit/test_onvif_device_test.c new file mode 100644 index 000000000..31f255fee --- /dev/null +++ b/tests/unit/test_onvif_device_test.c @@ -0,0 +1,199 @@ +/* ONVIF "Test connection": failures say what failed (credentials, reachability, + * or the stream), and a refused rtsps:// stream URI is retried as plain RTSP. + * A loopback fake ONVIF device answers the SOAP calls. */ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "unity.h" +#include "video/onvif_device_management.h" + +typedef enum { FAKE_OK, FAKE_PROFILES_NOT_AUTHORIZED } fake_mode_t; + +static int listen_fd = -1; +static unsigned short device_port; +static unsigned short closed_port; +static volatile fake_mode_t mode; +static pthread_t server_thread; +static volatile bool server_stop; + +static const char *ENVELOPE_START = + "" + ""; +static const char *ENVELOPE_END = ""; + +static unsigned short unused_port(void) { + int fd = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK)}; + TEST_ASSERT_EQUAL_INT(0, bind(fd, (struct sockaddr *)&addr, sizeof(addr))); + socklen_t len = sizeof(addr); + getsockname(fd, (struct sockaddr *)&addr, &len); + close(fd); /* nothing listens here afterwards: connections are refused */ + return ntohs(addr.sin_port); +} + +static void respond(int fd, int status, const char *body) { + char header[256]; + int n = snprintf(header, sizeof(header), + "HTTP/1.1 %d %s\r\nContent-Type: application/soap+xml\r\n" + "Content-Length: %zu\r\nConnection: close\r\n\r\n", + status, status == 200 ? "OK" : "Bad Request", strlen(body)); + (void)!write(fd, header, (size_t)n); + (void)!write(fd, body, strlen(body)); +} + +static void handle(int fd) { + char request[16384]; + size_t used = 0; + for (;;) { + ssize_t got = read(fd, request + used, sizeof(request) - 1 - used); + if (got <= 0) break; + used += (size_t)got; + request[used] = '\0'; + char *end = strstr(request, "\r\n\r\n"); + if (!end) continue; + const char *cl = strcasestr(request, "Content-Length:"); + size_t want = cl ? strtoul(cl + 15, NULL, 10) : 0; + if (used - (size_t)(end + 4 - request) >= want) break; + } + + char body[4096]; + if (strstr(request, "GetServices")) { + snprintf(body, sizeof(body), + "%s" + "http://www.onvif.org/ver10/media/wsdl" + "http://127.0.0.1:%u/onvif/media_service" + "%s", + ENVELOPE_START, device_port, ENVELOPE_END); + respond(fd, 200, body); + } else if (strstr(request, "GetProfiles") && mode == FAKE_PROFILES_NOT_AUTHORIZED) { + snprintf(body, sizeof(body), + "%senv:Sender" + "ter:NotAuthorized" + "Sender not Authorized" + "%s", + ENVELOPE_START, ENVELOPE_END); + respond(fd, 400, body); + } else if (strstr(request, "GetProfiles")) { + snprintf(body, sizeof(body), + "%s" + "main%s", + ENVELOPE_START, ENVELOPE_END); + respond(fd, 200, body); + } else if (strstr(request, "GetStreamUri")) { + snprintf(body, sizeof(body), + "%s" + "rtsps://127.0.0.1:%u/ch0" + "%s", + ENVELOPE_START, closed_port, ENVELOPE_END); + respond(fd, 200, body); + } else { + respond(fd, 400, ""); + } +} + +static void *serve(void *arg) { + (void)arg; + while (!server_stop) { + int fd = accept(listen_fd, NULL, NULL); + if (fd < 0) continue; + handle(fd); + close(fd); + } + return NULL; +} + +void setUp(void) { mode = FAKE_OK; } +void tearDown(void) {} + +static void device_url(char *out, size_t size) { + snprintf(out, size, "http://127.0.0.1:%u/onvif/device_service", device_port); +} + +void test_plain_rtsp_fallback_url(void) { + char out[512]; + TEST_ASSERT_EQUAL_INT(0, onvif_plain_rtsp_fallback_url("rtsps://192.168.52.4:322/ch0", out, sizeof(out))); + TEST_ASSERT_EQUAL_STRING("rtsp://192.168.52.4:554/ch0", out); + TEST_ASSERT_EQUAL_INT(0, onvif_plain_rtsp_fallback_url("RTSPS://user:p%40ss@cam.local/stream?x=1", out, sizeof(out))); + TEST_ASSERT_EQUAL_STRING("rtsp://user:p%40ss@cam.local:554/stream?x=1", out); + TEST_ASSERT_EQUAL_INT(0, onvif_plain_rtsp_fallback_url("rtsps://[fd00::4]:322/ch0", out, sizeof(out))); + TEST_ASSERT_EQUAL_STRING("rtsp://[fd00::4]:554/ch0", out); + TEST_ASSERT_EQUAL_INT(0, onvif_plain_rtsp_fallback_url("rtsps://cam:322", out, sizeof(out))); + TEST_ASSERT_EQUAL_STRING("rtsp://cam:554", out); + /* Only rtsps:// has a plain fallback. */ + TEST_ASSERT_EQUAL_INT(-1, onvif_plain_rtsp_fallback_url("rtsp://cam:554/ch0", out, sizeof(out))); + TEST_ASSERT_EQUAL_INT(-1, onvif_plain_rtsp_fallback_url("http://cam/ch0", out, sizeof(out))); + TEST_ASSERT_EQUAL_INT(-1, onvif_plain_rtsp_fallback_url("rtsps://[fd00::4/ch0", out, sizeof(out))); + TEST_ASSERT_EQUAL_INT(-1, onvif_plain_rtsp_fallback_url("rtsps://cam/ch0", out, 8)); +} + +void test_rejected_credentials_are_reported_as_auth_failure(void) { + mode = FAKE_PROFILES_NOT_AUTHORIZED; + char url[256], detail[512]; + device_url(url, sizeof(url)); + int result = test_onvif_connection_detail(url, "admin", "wrong", detail, sizeof(detail)); + TEST_ASSERT_EQUAL_INT(ONVIF_TEST_AUTH_FAILED, result); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(detail, "credentials"), detail); + TEST_ASSERT_NULL_MESSAGE(strstr(detail, "wrong"), detail); /* never echo the password */ +} + +void test_unreachable_device_is_reported_as_unreachable(void) { + char url[256], detail[512]; + snprintf(url, sizeof(url), "http://127.0.0.1:%u/onvif/device_service", closed_port); + int result = test_onvif_connection_detail(url, "admin", "secret", detail, sizeof(detail)); + TEST_ASSERT_EQUAL_INT(ONVIF_TEST_UNREACHABLE, result); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(detail, "reach"), detail); +} + +void test_refused_rtsps_stream_retries_plain_rtsp_and_reports_stream_failure(void) { + char url[256], detail[512], expected[128]; + device_url(url, sizeof(url)); + int result = test_onvif_connection_detail(url, "admin", "secret", detail, sizeof(detail)); + TEST_ASSERT_EQUAL_INT(ONVIF_TEST_STREAM_FAILED, result); + snprintf(expected, sizeof(expected), "rtsps://127.0.0.1:%u/ch0", closed_port); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(detail, expected), detail); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(detail, "rtsp://127.0.0.1:554/ch0"), detail); + TEST_ASSERT_NULL_MESSAGE(strstr(detail, "secret"), detail); + /* The legacy entry point keeps its 0 / non-zero contract. */ + TEST_ASSERT_NOT_EQUAL(0, test_onvif_connection(url, "admin", "secret")); +} + +int main(void) { + listen_fd = socket(AF_INET, SOCK_STREAM, 0); + int one = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + struct sockaddr_in addr = {.sin_family = AF_INET, .sin_addr.s_addr = htonl(INADDR_LOOPBACK)}; + if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(listen_fd, 8) != 0) { + perror("fake ONVIF server"); + return 1; + } + socklen_t len = sizeof(addr); + getsockname(listen_fd, (struct sockaddr *)&addr, &len); + device_port = ntohs(addr.sin_port); + closed_port = unused_port(); + pthread_create(&server_thread, NULL, serve, NULL); + + UNITY_BEGIN(); + RUN_TEST(test_plain_rtsp_fallback_url); + RUN_TEST(test_rejected_credentials_are_reported_as_auth_failure); + RUN_TEST(test_unreachable_device_is_reported_as_unreachable); + RUN_TEST(test_refused_rtsps_stream_retries_plain_rtsp_and_reports_stream_failure); + int failures = UNITY_END(); + + server_stop = true; + shutdown(listen_fd, SHUT_RDWR); + close(listen_fd); + pthread_join(server_thread, NULL); + return failures; +} diff --git a/tests/unit/test_put_stream_launch_failure.c b/tests/unit/test_put_stream_launch_failure.c index 2761c4436..20e7957af 100644 --- a/tests/unit/test_put_stream_launch_failure.c +++ b/tests/unit/test_put_stream_launch_failure.c @@ -458,6 +458,170 @@ void test_unrelated_change_does_not_restart_go2rtc(void) { TEST_ASSERT_EQUAL_UINT(0, run_captured_update("{\"priority\":7}")); } + +/* ---- INT_01 L9: per-camera observation evidence settings ---- */ + +static cJSON *get_stream_json(const char *name) { + http_request_t get_request; + http_response_t get_response; + http_request_init(&get_request); + http_response_init(&get_response); + snprintf(get_request.path, sizeof(get_request.path), "/api/streams/%s", name); + strcpy(get_request.method_str, "GET"); + handle_get_stream(&get_request, &get_response); + TEST_ASSERT_EQUAL_INT(200, get_response.status_code); + cJSON *json = cJSON_Parse((const char *)get_response.body); + http_response_free(&get_response); + TEST_ASSERT_NOT_NULL(json); + return json; +} + +static void assert_observation_json(cJSON *json, bool enabled, const char *labels, + int max_age_days, double max_bytes) { + cJSON *value = cJSON_GetObjectItemCaseSensitive(json, "observation_evidence_enabled"); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsBool(value), "observation_evidence_enabled missing"); + TEST_ASSERT_EQUAL(enabled, cJSON_IsTrue(value)); + value = cJSON_GetObjectItemCaseSensitive(json, "observation_labels"); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsString(value), "observation_labels missing"); + TEST_ASSERT_EQUAL_STRING(labels, value->valuestring); + value = cJSON_GetObjectItemCaseSensitive(json, "observation_max_age_days"); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsNumber(value), "observation_max_age_days missing"); + TEST_ASSERT_EQUAL_INT(max_age_days, value->valueint); + value = cJSON_GetObjectItemCaseSensitive(json, "observation_max_bytes"); + TEST_ASSERT_TRUE_MESSAGE(cJSON_IsNumber(value), "observation_max_bytes missing"); + TEST_ASSERT_TRUE_MESSAGE(value->valuedouble == max_bytes, "observation_max_bytes mismatch"); +} + +void test_observation_settings_default_off_in_get(void) { + cJSON *json = get_stream_json(original_config.name); + assert_observation_json(json, false, "", 7, 2147483648.0); + cJSON_Delete(json); +} + +void test_observation_settings_put_round_trip(void) { + TEST_ASSERT_EQUAL_UINT(0, run_captured_update( + "{\"observation_evidence_enabled\":true," + "\"observation_labels\":\" bird , Squirrel \"," + "\"observation_max_age_days\":14," + "\"observation_max_bytes\":134217728}")); + stream_config_t persisted = {0}; + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(original_config.name, &persisted)); + TEST_ASSERT_TRUE(persisted.observation_evidence_enabled); + TEST_ASSERT_EQUAL_STRING("bird,Squirrel", persisted.observation_labels); + TEST_ASSERT_EQUAL_INT(14, persisted.observation_max_age_days); + TEST_ASSERT_TRUE(persisted.observation_max_bytes == 134217728LL); + cJSON *json = get_stream_json(original_config.name); + assert_observation_json(json, true, "bird,Squirrel", 14, 134217728.0); + cJSON_Delete(json); + + /* Inclusive bounds are accepted; an empty list means every label. */ + run_captured_update( + "{\"observation_labels\":\"\",\"observation_max_age_days\":90," + "\"observation_max_bytes\":1099511627776}"); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(original_config.name, &persisted)); + TEST_ASSERT_TRUE(persisted.observation_evidence_enabled); + TEST_ASSERT_EQUAL_STRING("", persisted.observation_labels); + TEST_ASSERT_EQUAL_INT(90, persisted.observation_max_age_days); + TEST_ASSERT_TRUE(persisted.observation_max_bytes == 1099511627776LL); + run_captured_update( + "{\"observation_evidence_enabled\":false,\"observation_max_age_days\":1," + "\"observation_max_bytes\":67108864}"); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(original_config.name, &persisted)); + TEST_ASSERT_FALSE(persisted.observation_evidence_enabled); + TEST_ASSERT_EQUAL_INT(1, persisted.observation_max_age_days); + TEST_ASSERT_TRUE(persisted.observation_max_bytes == 67108864LL); +} + +static const char *const invalid_observation_bodies[] = { + "{\"observation_evidence_enabled\":\"yes\"}", + "{\"observation_evidence_enabled\":1}", + "{\"observation_max_age_days\":0}", + "{\"observation_max_age_days\":91}", + "{\"observation_max_age_days\":7.5}", + "{\"observation_max_age_days\":\"7\"}", + "{\"observation_max_bytes\":67108863}", + "{\"observation_max_bytes\":1099511627777}", + "{\"observation_max_bytes\":134217728.5}", + "{\"observation_max_bytes\":null}", + "{\"observation_labels\":5}", + "{\"observation_labels\":\"bird,,cat\"}", + "{\"observation_labels\":\"a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q\"}", + "{\"observation_labels\":\"abcdefghijklmnopqrstuvwxyz012345\"}", + "{\"observation_labels\":\"bird\\u0001\"}", + "{\"observation_evidence_enabled\":true,\"observation_max_age_days\":0}", +}; + +void test_observation_settings_put_bounds_rejected(void) { + for (size_t i = 0; i < sizeof(invalid_observation_bodies) / sizeof(invalid_observation_bodies[0]); i++) { + http_response_free(&response); + http_response_init(&response); + set_body(invalid_observation_bodies[i]); + capture_success = true; + handle_put_stream(&request, &response); + capture_success = false; + TEST_ASSERT_EQUAL_INT_MESSAGE(400, response.status_code, invalid_observation_bodies[i]); + TEST_ASSERT_EQUAL_UINT_MESSAGE(0, launch_attempts, invalid_observation_bodies[i]); + cJSON *json = cJSON_Parse((const char *)response.body); + TEST_ASSERT_NOT_NULL(json); + cJSON *error = cJSON_GetObjectItemCaseSensitive(json, "error"); + TEST_ASSERT_TRUE(cJSON_IsString(error)); + TEST_ASSERT_NOT_NULL_MESSAGE(strstr(error->valuestring, "observation_"), error->valuestring); + cJSON_Delete(json); + } + assert_unchanged(); +} + +void test_observation_settings_put_sixteen_labels_accepted(void) { + run_captured_update( + "{\"observation_labels\":\"a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,abcdefghijklmnopqrstuvwxyz01234\"}"); + stream_config_t persisted = {0}; + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name(original_config.name, &persisted)); + TEST_ASSERT_EQUAL_STRING("a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,abcdefghijklmnopqrstuvwxyz01234", + persisted.observation_labels); + TEST_ASSERT_FALSE(persisted.observation_evidence_enabled); +} + +static void post_body(const char *body) { + http_response_free(&response); + http_response_init(&response); + strcpy(request.path, "/api/streams"); + strcpy(request.method_str, "POST"); + set_body(body); + capture_success = true; + handle_post_stream(&request, &response); + capture_success = false; +} + +void test_observation_settings_post_round_trip_and_bounds(void) { + post_body("{\"name\":\"obs_post_camera\",\"url\":\"rtsp://127.0.0.1:1/obs\"," + "\"enabled\":false,\"observation_max_age_days\":91}"); + TEST_ASSERT_EQUAL_INT(400, response.status_code); + stream_config_t persisted = {0}; + TEST_ASSERT_NOT_EQUAL_INT(0, get_stream_config_by_name("obs_post_camera", &persisted)); + + post_body("{\"name\":\"obs_post_camera\",\"url\":\"rtsp://127.0.0.1:1/obs\"," + "\"enabled\":false,\"observation_evidence_enabled\":true," + "\"observation_labels\":\"bird\",\"observation_max_age_days\":3," + "\"observation_max_bytes\":268435456}"); + TEST_ASSERT_TRUE_MESSAGE(response.status_code == 200 || response.status_code == 201, + (const char *)response.body); + TEST_ASSERT_EQUAL_INT(0, get_stream_config_by_name("obs_post_camera", &persisted)); + TEST_ASSERT_TRUE(persisted.observation_evidence_enabled); + TEST_ASSERT_EQUAL_STRING("bird", persisted.observation_labels); + TEST_ASSERT_EQUAL_INT(3, persisted.observation_max_age_days); + TEST_ASSERT_TRUE(persisted.observation_max_bytes == 268435456LL); + cJSON *json = get_stream_json("obs_post_camera"); + assert_observation_json(json, true, "bird", 3, 268435456.0); + cJSON_Delete(json); + + /* Omitted settings take the contract defaults (off, all labels, 7 d, 2 GiB). */ + post_body("{\"name\":\"obs_post_default\",\"url\":\"rtsp://127.0.0.1:1/obs2\",\"enabled\":false}"); + TEST_ASSERT_TRUE(response.status_code == 200 || response.status_code == 201); + json = get_stream_json("obs_post_default"); + assert_observation_json(json, false, "", 7, 2147483648.0); + cJSON_Delete(json); +} + int main(void) { char directory[] = "/tmp/lightnvr-put-launch-XXXXXX"; if (!mkdtemp(directory)) return 1; @@ -488,6 +652,11 @@ int main(void) { RUN_TEST(test_privacy_toggle_does_not_restart_go2rtc_for_api_streams); RUN_TEST(test_publish_change_restarts_go2rtc_without_running_stream); RUN_TEST(test_unrelated_change_does_not_restart_go2rtc); + RUN_TEST(test_observation_settings_default_off_in_get); + RUN_TEST(test_observation_settings_put_round_trip); + RUN_TEST(test_observation_settings_put_bounds_rejected); + RUN_TEST(test_observation_settings_put_sixteen_labels_accepted); + RUN_TEST(test_observation_settings_post_round_trip_and_bounds); int result = UNITY_END(); shutdown_database(); unlink(path); diff --git a/web/generated/api/schema.d.ts b/web/generated/api/schema.d.ts index b4c66dee5..2a1b4f1f7 100644 --- a/web/generated/api/schema.d.ts +++ b/web/generated/api/schema.d.ts @@ -1225,6 +1225,26 @@ export interface paths { patch?: never; trace?: never; }; + "/api/integrations/capabilities": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get integration capabilities + * @description Requires an authenticated principal and returns only its scope: source identity, contract versions, operations (supported and authorized), limits, history, delivery and the cameras where the principal holds live.view, snapshot.create or observations.annotate (contract §9). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md. + */ + get: operations["getIntegrationCapabilities"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/investigation-bookmarks": { parameters: { query?: never; @@ -1674,6 +1694,86 @@ export interface paths { patch?: never; trace?: never; }; + "/api/observations": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List observation history + * @description Cursor-paged observation changes, ordered by persistence sequence and bounded by a watermark taken at query start (contract §7). Results are limited to capture cameras where the principal holds live.view; an explicitly requested camera without it returns 403. limit counts observations after in-page dedup. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md. + */ + get: operations["listObservations"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/observations/{id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get observation + * @description One observation with its classifications, authorized by live.view on its immutable capture camera (contract §8.1). Contract: docs/integrations/OBSERVATIONS_CONTRACT.md. + */ + get: operations["getObservation"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/observations/{id}/classifications/{namespace}/{name}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** + * Put observation classification + * @description Create or update one classification annotation on an observation (contract §10). Authorized with observations.annotate on the observation's capture-time camera (contract §12.1); authorization is evaluated before the namespace and the body, so an observation the caller cannot see is an indistinguishable 404. The key is two path segments, {namespace}/{name}; namespace must equal the server-derived writer namespace of the authenticated user (D3, advertised as capabilities principal.writer_namespace). Identical retries are no-ops (200, changed=false). A stored change bumps the observation revision and publishes an io.lightnvr.detection.observation_changed.v1 event with change_cause classification. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md. + */ + put: operations["putObservationClassification"]; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/observations/{id}/snapshot": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get observation evidence image + * @description The unannotated evidence JPEG that was the inference input or submitted image (contract §8.2). Requires snapshot.create on the capture camera; a caller with live.view but not snapshot.create gets 403 (contract D4). Never substitutes a latest frame or another asset. Contract: docs/integrations/OBSERVATIONS_CONTRACT.md. + */ + get: operations["getObservationSnapshot"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/onvif/device/add": { parameters: { query?: never; @@ -3628,7 +3728,7 @@ export interface components { }; AdminSimulationRequest: ({ /** @enum {string} */ - action: "live.view" | "audio.listen" | "audio.talk" | "recordings.replay" | "recordings.export" | "snapshot.create" | "ptz.control" | "evidence.protect" | "recording.delete" | "camera.configure" | "fleet.execute_job" | "lpr.read" | "lpr.search" | "lpr.export" | "lpr.delete"; + action: "live.view" | "audio.listen" | "audio.talk" | "recordings.replay" | "recordings.export" | "snapshot.create" | "ptz.control" | "evidence.protect" | "recording.delete" | "camera.configure" | "fleet.execute_job" | "lpr.read" | "lpr.search" | "lpr.export" | "lpr.delete" | "observations.annotate"; camera_uuid: string; user_id: number; } & { @@ -3911,7 +4011,7 @@ export interface components { [key: string]: unknown; }; /** @enum {string} */ - AuthorizationAction: "live.view" | "audio.listen" | "audio.talk" | "recordings.replay" | "recordings.export" | "snapshot.create" | "ptz.control" | "evidence.protect" | "recording.delete" | "camera.configure" | "fleet.execute_job" | "storage.configure" | "events.configure" | "users.manage" | "system.admin" | "lpr.read" | "lpr.search" | "lpr.export" | "lpr.delete"; + AuthorizationAction: "live.view" | "audio.listen" | "audio.talk" | "recordings.replay" | "recordings.export" | "snapshot.create" | "ptz.control" | "evidence.protect" | "recording.delete" | "camera.configure" | "fleet.execute_job" | "storage.configure" | "events.configure" | "users.manage" | "system.admin" | "lpr.read" | "lpr.search" | "lpr.export" | "lpr.delete" | "observations.annotate"; AuthSession: { created_at: number; current: boolean; @@ -4502,11 +4602,18 @@ export interface components { end_timestamp: number; height: number; label: string; + observation?: components["schemas"]["EvidenceDetectionObservation"]; timestamp: number; width: number; x: number; y: number; }; + /** @description INT_01 P2 (L15). Present only when the detection links to an observation of the same camera: observed in the same second ±1 with the same label (ASCII case-insensitive), nearest second first. classification is the annotation to display — owner_confirmed[0] before automated[0] of GET /api/observations/{id} — or null; the detection's own label and confidence are the detector's and never change. */ + EvidenceDetectionObservation: { + classification: components["schemas"]["ObservationClassification"] | null; + id: components["schemas"]["ObservationUuid"]; + owner_confirmed: boolean; + }; EvidenceDetections: { detections: components["schemas"]["EvidenceDetection"][]; stream_status: components["schemas"]["StreamStatus"]; @@ -5740,6 +5847,85 @@ export interface components { /** @constant */ success: true; }; + /** @description Capability document scoped to the calling principal (contract §9). */ + IntegrationCapabilities: { + cameras: ({ + actions: ("live.view" | "snapshot.create" | "observations.annotate")[]; + camera_uuid: components["schemas"]["ObservationUuid"]; + evidence_enabled: boolean; + } & { + [key: string]: unknown; + })[]; + contract_versions: { + classifications: number; + observation_event: string; + observations: number; + } & { + [key: string]: unknown; + }; + delivery: { + mqtt_enabled: boolean; + /** @description Configured topic with a literal {camera_uuid} placeholder. */ + topic: string | null; + } & { + [key: string]: unknown; + }; + history: { + oldest_cursor: string | null; + oldest_observed_at: components["schemas"]["ObservationTime"] | null; + retention_days: number; + } & { + [key: string]: unknown; + }; + limits: { + annotations_per_observation_max: number; + history_filter_cameras_max: number; + history_filter_labels_max: number; + history_page_default: number; + history_page_max: number; + label_max_bytes: number; + max_objects_per_batch: number; + snapshot_retry_after_max_seconds: number; + } & { + [key: string]: unknown; + }; + /** @description Unsupported features are declared explicitly; never infer support from an error. */ + operations: { + event_clip: components["schemas"]["IntegrationCapabilityOperation"]; + "observations.annotate": components["schemas"]["IntegrationCapabilityOperation"]; + "observations.history": components["schemas"]["IntegrationCapabilityOperation"]; + "observations.snapshot": components["schemas"]["IntegrationCapabilityOperation"]; + range_clip: components["schemas"]["IntegrationCapabilityOperation"]; + recorded_frame: components["schemas"]["IntegrationCapabilityOperation"]; + track_lifecycle: components["schemas"]["IntegrationCapabilityOperation"]; + } & { + [key: string]: components["schemas"]["IntegrationCapabilityOperation"]; + }; + principal: { + writer_namespace: string | null; + } & { + [key: string]: unknown; + }; + source: { + id: string; + installation_uuid: components["schemas"]["ObservationUuid"]; + /** @constant */ + kind: "lightnvr"; + product_version: string; + } & { + [key: string]: unknown; + }; + } & { + [key: string]: unknown; + }; + IntegrationCapabilityOperation: { + authorized: boolean; + /** @enum {string} */ + reason?: "not_implemented" | "not_available_in_this_version" | "not_authorized" | "invalid_writer_namespace" | "reserved_writer_namespace"; + supported: boolean; + } & { + [key: string]: unknown; + }; LegacyApiKeyCreated: { api_key: string; /** @constant */ @@ -5959,6 +6145,281 @@ export interface components { tags_applied: number; warning?: string; }; + /** @description One detected object in one accepted inference result (contract §3). Readers ignore unknown fields. */ + Observation: { + batch_id: components["schemas"]["ObservationUuid"]; + box: components["schemas"]["ObservationBox"] | null; + camera_uuid: components["schemas"]["ObservationUuid"]; + classifications: components["schemas"]["ObservationClassifications"]; + confidence: number; + created_at: components["schemas"]["ObservationTime"]; + detector: components["schemas"]["ObservationDetector"]; + evidence: components["schemas"]["ObservationEvidence"]; + id: components["schemas"]["ObservationUuid"]; + /** @description Original detector label (1..31 bytes); never modified by classification. */ + label: string; + object_index: number; + observed_at: components["schemas"]["ObservationTime"]; + recording_reference: components["schemas"]["ObservationRecordingReference"] | null; + revision: number; + source: string; + /** @description Display only. */ + stream_name_at_capture: string; + track: components["schemas"]["ObservationTrack"] | null; + } & { + [key: string]: unknown; + }; + /** @description Top-left corner and size normalized to the stored evidence image (contract §3.2). Consumers never re-apply transform. */ + ObservationBox: { + /** @constant */ + format: "xywh_normalized"; + height: number; + width: number; + x: number; + y: number; + } & { + [key: string]: unknown; + }; + /** @description Data payload of io.lightnvr.detection.observation_changed.v1 (contract §6.3). Consumers skip classification-only changes for re-classification. */ + ObservationChangeEvent: ({ + asset_id: components["schemas"]["ObservationUuid"]; + camera_uuid: components["schemas"]["ObservationUuid"]; + /** @enum {string} */ + change_cause: "created" | "evidence" | "classification"; + evidence_reason: components["schemas"]["ObservationEvidenceReason"] | null; + evidence_state: components["schemas"]["ObservationEvidenceState"]; + label: string; + observation_id: components["schemas"]["ObservationUuid"]; + observed_at: components["schemas"]["ObservationTime"]; + revision: number; + snapshot_url?: string; + } & { + [key: string]: unknown; + }) & (unknown & unknown); + /** @description Stored classification annotation (contract §10.3). P2. */ + ObservationClassification: { + annotation_revision: number; + classified_at: components["schemas"]["ObservationTime"]; + classifier: string; + confidence: number; + created_at: components["schemas"]["ObservationTime"]; + input_asset_id: components["schemas"]["ObservationUuid"]; + input_revision: number; + key: string; + /** @enum {string} */ + kind: "automated" | "owner_confirmed"; + label: string; + model_version: string | null; + taxonomy_id: string | null; + updated_at: components["schemas"]["ObservationTime"]; + writer_namespace: string; + } & { + [key: string]: unknown; + }; + ObservationClassificationResult: { + annotation: components["schemas"]["ObservationClassification"]; + changed: boolean; + observation_revision: number; + } & { + [key: string]: unknown; + }; + /** @description Annotations split by kind, each ordered by updated_at descending. Both lists are empty in P1. */ + ObservationClassifications: { + automated: components["schemas"]["ObservationClassification"][]; + owner_confirmed: components["schemas"]["ObservationClassification"][]; + } & { + [key: string]: unknown; + }; + /** @description PUT body (contract §10.3). Closed: unknown fields return 400. The writer namespace is derived by the server from the authenticated user, never from this body. */ + ObservationClassificationWrite: { + classified_at: components["schemas"]["ObservationTime"]; + classifier: string; + confidence: number; + /** @description 0 or absent means the key must not exist yet. */ + expected_revision?: number; + input_asset_id: components["schemas"]["ObservationUuid"]; + input_revision: number; + /** @enum {string} */ + kind: "automated" | "owner_confirmed"; + label: string; + model_version?: string | null; + taxonomy_id?: string | null; + /** @description Same rule as writer_namespace. */ + writer?: string; + /** @description Optional. Accepted and ignored when it names the principal's derived namespace; any other value returns 403 namespace_forbidden. */ + writer_namespace?: string; + }; + ObservationDetector: { + engine_key: string; + /** @enum {string} */ + engine_type: "object" | "api" | "onvif" | "external"; + /** @description Model basename only, never a path. */ + model: string | null; + model_version: string | null; + } & { + [key: string]: unknown; + }; + /** @description Coded error (contract §11, D2): a valid ApiError with a string error and a stable code. Clients branch on status and code, never on the message. */ + ObservationError: { + /** @enum {string} */ + code: "invalid_request" | "invalid_cursor" | "forbidden" | "namespace_forbidden" | "observation_not_found" | "evidence_unavailable" | "evidence_pending" | "evidence_expired" | "cursor_expired" | "revision_conflict" | "owner_decision_locked" | "annotation_limit" | "evidence_mismatch" | "not_implemented" | "service_unavailable" | "internal_error"; + current?: components["schemas"]["ObservationClassification"]; + error: string; + evidence_state?: components["schemas"]["ObservationEvidenceState"]; + expired_at?: components["schemas"]["ObservationTime"] | null; + field?: string; + limit?: number; + observation_asset_id?: components["schemas"]["ObservationUuid"]; + oldest_cursor?: string; + oldest_observed_at?: components["schemas"]["ObservationTime"] | null; + reason?: components["schemas"]["ObservationEvidenceReason"]; + retry_after_seconds?: number; + } & { + [key: string]: unknown; + }; + /** @description Evidence asset (contract §4). Every observation has one; siblings in a batch share asset_id. */ + ObservationEvidence: ({ + asset_id: components["schemas"]["ObservationUuid"]; + bytes: number | null; + captured_at: components["schemas"]["ObservationTime"] | null; + expires_at: components["schemas"]["ObservationTime"] | null; + height: number | null; + /** @enum {string|null} */ + media_type: "image/jpeg" | null; + /** @enum {string|null} */ + origin: "inference_input" | "submitted_image" | null; + reason: components["schemas"]["ObservationEvidenceReason"] | null; + /** @description Present only when state is ready. Relative and authenticated. */ + snapshot_url?: string; + state: components["schemas"]["ObservationEvidenceState"]; + transform: components["schemas"]["ObservationTransform"] | null; + width: number | null; + } & { + [key: string]: unknown; + }) & (unknown & unknown); + /** + * @description Bounded reason (contract §4.3). Consumers treat an unknown reason by its state. + * @enum {string} + */ + ObservationEvidenceReason: "queue_full" | "budget_exhausted" | "encode_failed" | "write_failed" | "interrupted" | "no_matching_media" | "disabled" | "retention_expired" | "deleted"; + /** @enum {string} */ + ObservationEvidenceState: "pending" | "ready" | "unavailable" | "expired"; + /** @description History page (contract §7.2-7.3). Empty items with has_more=true is valid; continue from next_cursor. */ + ObservationPage: { + has_more: boolean; + items: components["schemas"]["Observation"][]; + next_cursor: string; + } & { + [key: string]: unknown; + }; + /** @description INT_01 observation pipeline diagnostics (observation_diagnostics_to_json(), contract section 15). Counters are per process run unless noted; per-camera rows come from the last retention pass and may be up to 5 minutes old. */ + ObservationPipelineDiagnostics: { + accepted: number; + /** @description False when evidence is unused: no queue exists, counters are zero and no observations are retained. */ + active: boolean; + budget_exhausted_cameras: number; + cameras: ({ + budget_exhausted: boolean; + camera_uuid: string; + max_age_days: number; + max_bytes: number; + retained_bytes: number; + } & { + [key: string]: unknown; + })[]; + /** @description Observations dropped at admission, by reason. */ + dropped: { + budget_exhausted: number; + disabled: number; + queue_full: number; + untracked: number; + } & { + [key: string]: unknown; + }; + evidence: { + backlog_bytes: number; + backlog_items: number; + bytes_written: number; + drop_ring_depth: number; + failures: { + db_error: number; + disabled: number; + encode_failed: number; + interrupted: number; + write_failed: number; + } & { + [key: string]: unknown; + }; + high_water_bytes: number; + high_water_items: number; + queue_allocated: boolean; + ready: number; + unavailable_submitted: number; + worker_running: boolean; + } & { + [key: string]: unknown; + }; + history: { + /** @description False when the observation tables could not be read; the DB-derived values are then zero. */ + available: boolean; + epoch_regenerations: number; + head_seq: number; + observations: number; + oldest_observed_at: null | components["schemas"]["ObservationTime"]; + oldest_seq: number; + } & { + [key: string]: unknown; + }; + publications: { + expired: number; + failed: number; + pending: number; + /** @description Age of the oldest pending publication; 0 when none is pending. */ + relay_lag_seconds: number; + relay_running: boolean; + relayed: number; + relayed_no_route: number; + } & { + [key: string]: unknown; + }; + } & { + [key: string]: unknown; + }; + ObservationRecordingReference: { + recording_id: number; + } & { + [key: string]: unknown; + }; + /** + * Format: date-time + * @description UTC RFC 3339 with millisecond precision and a Z suffix (contract §2). + */ + ObservationTime: string; + /** @description Track hint only; compare id, namespace and session together. No lifecycle is implied (contract §3.4). */ + ObservationTrack: { + id: string; + namespace: string; + session: components["schemas"]["ObservationUuid"]; + } & { + [key: string]: unknown; + }; + /** @description Audit record of the inference-to-stored-image mapping: x = (u - pad_x) / scale_x, y = (v - pad_y) / scale_y (contract §4.4). */ + ObservationTransform: { + inference_height: number; + inference_width: number; + /** @enum {string} */ + mode: "none" | "resize" | "letterbox"; + pad_x: number; + pad_y: number; + scale_x: number; + scale_y: number; + source_height: number; + source_width: number; + } & { + [key: string]: unknown; + }; + /** @description Lowercase canonical UUID (contract §2). */ + ObservationUuid: string; OnvifAddRequest: { device_url: string; password?: string; @@ -6985,6 +7446,14 @@ export interface components { motion_trigger_source?: string | null; /** @description Optional identifying metadata; ignored. This operation does not rename the stream. */ name?: string; + /** @description INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_evidence_enabled?: boolean; + /** @description Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_labels?: string; + /** @description Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_max_age_days?: number; + /** @description Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_max_bytes?: number; /** @description Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response. */ onvif_password?: string | null; /** @description Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero. */ @@ -7102,6 +7571,14 @@ export interface components { /** @description Null or empty string clears this override; omission preserves the current value. */ motion_trigger_source?: string | null; name: string; + /** @description INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_evidence_enabled?: boolean; + /** @description Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_labels?: string; + /** @description Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_max_age_days?: number; + /** @description Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart. */ + observation_max_bytes?: number; /** @description Omission preserves the current value. Null is ignored by this typed field handler. Processed only when the resulting isOnvif value is true; empty string clears this field. ONVIF probing may precede the 202 response. */ onvif_password?: string | null; /** @description Omission or null preserves the current value. Integer-backed fields truncate numeric input toward zero. */ @@ -7209,6 +7686,26 @@ export interface components { motion_trigger_source: string; /** @description Current stream name; lookup is by name, not camera_uuid. */ name: string; + /** + * @description INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. + * @default false + */ + observation_evidence_enabled?: boolean; + /** + * @description Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). + * @default + */ + observation_labels?: string; + /** + * @description Evidence media maximum age in days (contract section 13). + * @default 7 + */ + observation_max_age_days?: number; + /** + * @description Evidence media byte budget for this camera, 64 MiB..1 TiB (contract section 13). + * @default 2147483648 + */ + observation_max_bytes?: number; /** @description Zero requests automatic port detection. */ onvif_port: number; onvif_profile: string; @@ -7703,6 +8200,7 @@ export interface components { } & { [key: string]: unknown; }; + observation_pipeline?: components["schemas"]["ObservationPipelineDiagnostics"]; recordings: { count: number; size: number; @@ -14331,7 +14829,7 @@ export interface operations { }; }; }; - listEvidenceBookmarks: { + getIntegrationCapabilities: { parameters: { query?: never; header?: never; @@ -14340,17 +14838,17 @@ export interface operations { }; requestBody?: never; responses: { - /** @description Validated success. */ + /** @description Capability document for the calling principal. */ 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["EvidenceBookmarks"]; + "application/json": components["schemas"]["IntegrationCapabilities"]; }; }; - /** @description Invalid request. */ - 400: { + /** @description No usable authenticated credential (existing auth layer body). */ + 401: { headers: { [name: string]: unknown; }; @@ -14358,35 +14856,48 @@ export interface operations { "application/json": components["schemas"]["ApiError"]; }; }; - /** @description Authentication required. */ - 401: { + /** @description Internal failure (code internal_error). */ + 500: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ApiError"]; + "application/json": components["schemas"]["ObservationError"]; }; }; - /** @description Denied by current camera policy, feature configuration or demo write restriction. */ - 403: { + /** @description Temporarily unavailable (code service_unavailable). */ + 503: { headers: { + /** @description Seconds before retrying. */ + "Retry-After"?: number; [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ApiError"]; + "application/json": components["schemas"]["ObservationError"]; }; }; - /** @description Resource not found or not visible. */ - 404: { + }; + }; + listEvidenceBookmarks: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Validated success. */ + 200: { headers: { [name: string]: unknown; }; content: { - "application/json": components["schemas"]["ApiError"]; + "application/json": components["schemas"]["EvidenceBookmarks"]; }; }; - /** @description Revision, protected media or transfer conflict. */ - 409: { + /** @description Invalid request. */ + 400: { headers: { [name: string]: unknown; }; @@ -14394,9 +14905,45 @@ export interface operations { "application/json": components["schemas"]["ApiError"]; }; }; - /** @description Request exceeds operation limits. */ - 413: { - headers: { + /** @description Authentication required. */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description Denied by current camera policy, feature configuration or demo write restriction. */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description Resource not found or not visible. */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description Revision, protected media or transfer conflict. */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description Request exceeds operation limits. */ + 413: { + headers: { [name: string]: unknown; }; content: { @@ -17155,6 +17702,391 @@ export interface operations { }; }; }; + listObservations: { + parameters: { + query?: { + /** @description Comma-separated capture camera UUIDs, at most 10 (contract D1). Default: all cameras where the principal holds live.view. */ + camera_uuid?: string; + /** @description Opaque cursor from next_cursor or oldest_cursor, or the literal head (start live-only). Absent: start of retained history. */ + cursor?: string; + /** @description Comma-separated original detector labels, at most 8 of 1..31 bytes; exact ASCII case-insensitive match (contract D1). */ + label?: string; + /** @description Observations per page. */ + limit?: number; + /** @description Inclusive lower bound on observed_at. */ + since?: components["schemas"]["ObservationTime"]; + /** @description Exclusive upper bound on observed_at; must be after since. */ + until?: components["schemas"]["ObservationTime"]; + }; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description History page. Items hold current state; dedupe by (source, id, revision). */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationPage"]; + }; + }; + /** @description Invalid filter (invalid_request) or malformed cursor (invalid_cursor). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description No usable authenticated credential (existing auth layer body). */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description A requested camera_uuid is not authorized for live.view (code forbidden). */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Cursor older than retained history, from another DB epoch, or ahead of the head (code cursor_expired); resume from oldest_cursor. */ + 410: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Internal failure (code internal_error). */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Temporarily unavailable (code service_unavailable). */ + 503: { + headers: { + /** @description Seconds before retrying. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + }; + }; + getObservation: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Observation ID. */ + id: components["schemas"]["ObservationUuid"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Current observation state. */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Observation"]; + }; + }; + /** @description Invalid observation ID (code invalid_request). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description No usable authenticated credential (existing auth layer body). */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description Unknown observation, no live.view on its capture camera, or the capture camera was deleted; indistinguishable (code observation_not_found). */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Internal failure (code internal_error). */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Temporarily unavailable (code service_unavailable). */ + 503: { + headers: { + /** @description Seconds before retrying. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + }; + }; + putObservationClassification: { + parameters: { + query?: never; + header?: never; + path: { + /** @description Observation ID. */ + id: components["schemas"]["ObservationUuid"]; + /** @description Annotation name. */ + name: string; + /** @description Writer namespace. */ + namespace: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ObservationClassificationWrite"]; + }; + }; + responses: { + /** @description Annotation updated, or identical retry (changed=false). */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationClassificationResult"]; + }; + }; + /** @description Annotation created. */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationClassificationResult"]; + }; + }; + /** @description Invalid or unknown body field (code invalid_request). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description No usable authenticated credential (existing auth layer body). */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description live.view without observations.annotate on the capture camera (forbidden), or a key namespace or body writer field that is not the caller's derived namespace, or a key created by another user (namespace_forbidden). */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Unknown observation, neither live.view nor observations.annotate on its capture camera, or the capture camera was deleted; indistinguishable (code observation_not_found). */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description revision_conflict or owner_decision_locked (current carries the stored annotation when the key exists), or annotation_limit (limit). */ + 409: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description input_asset_id, input_revision or the evidence state does not match the observation evidence (code evidence_mismatch, with observation_asset_id and evidence_state). */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Internal failure (code internal_error). */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Temporarily unavailable (code service_unavailable). */ + 503: { + headers: { + /** @description Seconds before retrying. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + }; + }; + getObservationSnapshot: { + parameters: { + query?: never; + header?: { + "If-None-Match"?: string; + }; + path: { + /** @description Observation ID. */ + id: components["schemas"]["ObservationUuid"]; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Evidence image bytes; not JSON. */ + 200: { + headers: { + /** @description private, max-age=300 */ + "Cache-Control"?: string; + /** @description Quoted asset ID. */ + ETag?: string; + [name: string]: unknown; + }; + content: { + "image/jpeg": string; + }; + }; + /** @description Evidence is still pending (code evidence_pending). Honour Retry-After and give up polling after 120 s total. */ + 202: { + headers: { + /** @description Integer seconds, 1..30. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description If-None-Match matched the ETag. */ + 304: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Invalid observation ID (code invalid_request). */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description No usable authenticated credential (existing auth layer body). */ + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ApiError"]; + }; + }; + /** @description live.view is held but snapshot.create is not (code forbidden). */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Evidence unavailable with a reason (evidence_unavailable), or the observation is unknown or not visible (observation_not_found). */ + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Evidence media expired (code evidence_expired); metadata remains readable. */ + 410: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Internal failure (code internal_error). */ + 500: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + /** @description Temporarily unavailable (code service_unavailable). */ + 503: { + headers: { + /** @description Seconds before retrying. */ + "Retry-After"?: number; + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ObservationError"]; + }; + }; + }; + }; addOnvifDevice: { parameters: { query?: never; diff --git a/web/generated/api/validators.d.ts b/web/generated/api/validators.d.ts index 20fed0f4b..c1226a0bf 100644 --- a/web/generated/api/validators.d.ts +++ b/web/generated/api/validators.d.ts @@ -156,6 +156,7 @@ export declare const validateImagingOptionsResponse: ContractValidator; export declare const validateImagingSettingsRequest: ContractValidator; export declare const validateImagingSettingsResponse: ContractValidator; +export declare const validateIntegrationCapabilities: ContractValidator; export declare const validateLegacyApiKeyCreated: ContractValidator; export declare const validateLiveCameraSlot: ContractValidator; export declare const validateLiveLayout: ContractValidator; @@ -173,6 +174,16 @@ export declare const validateLogoutResponse: ContractValidator; export declare const validateMotionTrigger: ContractValidator; export declare const validateMotionTriggered: ContractValidator; +export declare const validateObservation: ContractValidator; +export declare const validateObservationChangeEvent: ContractValidator; +export declare const validateObservationClassification: ContractValidator; +export declare const validateObservationClassificationResult: ContractValidator; +export declare const validateObservationClassificationWrite: ContractValidator; +export declare const validateObservationError: ContractValidator; +export declare const validateObservationEvidence: ContractValidator; +export declare const validateObservationPage: ContractValidator; +export declare const validateObservationPipelineDiagnostics: ContractValidator; +export declare const validateObservationTransform: ContractValidator; export declare const validateOnvifAddRequest: ContractValidator; export declare const validateOnvifAddResult: ContractValidator; export declare const validateOnvifClaimRequest: ContractValidator; diff --git a/web/generated/api/validators.js b/web/generated/api/validators.js index 937e6f2cb..b8210162d 100644 --- a/web/generated/api/validators.js +++ b/web/generated/api/validators.js @@ -188,7 +188,7 @@ var __getOwnPropNames = Object.getOwnPropertyNames, __commonJS = (cb, mod) => fu } }), __contract_AdminActionCatalog = validate183, schema93 = { type: "string", - enum: [ "live.view", "audio.listen", "audio.talk", "recordings.replay", "recordings.export", "snapshot.create", "ptz.control", "evidence.protect", "recording.delete", "camera.configure", "fleet.execute_job", "storage.configure", "events.configure", "users.manage", "system.admin", "lpr.read", "lpr.search", "lpr.export", "lpr.delete" ] + enum: [ "live.view", "audio.listen", "audio.talk", "recordings.replay", "recordings.export", "snapshot.create", "ptz.control", "evidence.protect", "recording.delete", "camera.configure", "fleet.execute_job", "storage.configure", "events.configure", "users.manage", "system.admin", "lpr.read", "lpr.search", "lpr.export", "lpr.delete", "observations.annotate" ] }; function validate182(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { @@ -279,7 +279,7 @@ function validate182(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("live.view" !== data0 && "audio.listen" !== data0 && "audio.talk" !== data0 && "recordings.replay" !== data0 && "recordings.export" !== data0 && "snapshot.create" !== data0 && "ptz.control" !== data0 && "evidence.protect" !== data0 && "recording.delete" !== data0 && "camera.configure" !== data0 && "fleet.execute_job" !== data0 && "storage.configure" !== data0 && "events.configure" !== data0 && "users.manage" !== data0 && "system.admin" !== data0 && "lpr.read" !== data0 && "lpr.search" !== data0 && "lpr.export" !== data0 && "lpr.delete" !== data0) return validate182.errors = [ { + if ("live.view" !== data0 && "audio.listen" !== data0 && "audio.talk" !== data0 && "recordings.replay" !== data0 && "recordings.export" !== data0 && "snapshot.create" !== data0 && "ptz.control" !== data0 && "evidence.protect" !== data0 && "recording.delete" !== data0 && "camera.configure" !== data0 && "fleet.execute_job" !== data0 && "storage.configure" !== data0 && "events.configure" !== data0 && "users.manage" !== data0 && "system.admin" !== data0 && "lpr.read" !== data0 && "lpr.search" !== data0 && "lpr.export" !== data0 && "lpr.delete" !== data0 && "observations.annotate" !== data0) return validate182.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -642,7 +642,7 @@ function validate185(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("live.view" !== data5 && "audio.listen" !== data5 && "audio.talk" !== data5 && "recordings.replay" !== data5 && "recordings.export" !== data5 && "snapshot.create" !== data5 && "ptz.control" !== data5 && "evidence.protect" !== data5 && "recording.delete" !== data5 && "camera.configure" !== data5 && "fleet.execute_job" !== data5 && "storage.configure" !== data5 && "events.configure" !== data5 && "users.manage" !== data5 && "system.admin" !== data5 && "lpr.read" !== data5 && "lpr.search" !== data5 && "lpr.export" !== data5 && "lpr.delete" !== data5) return validate185.errors = [ { + if ("live.view" !== data5 && "audio.listen" !== data5 && "audio.talk" !== data5 && "recordings.replay" !== data5 && "recordings.export" !== data5 && "snapshot.create" !== data5 && "ptz.control" !== data5 && "evidence.protect" !== data5 && "recording.delete" !== data5 && "camera.configure" !== data5 && "fleet.execute_job" !== data5 && "storage.configure" !== data5 && "events.configure" !== data5 && "users.manage" !== data5 && "system.admin" !== data5 && "lpr.read" !== data5 && "lpr.search" !== data5 && "lpr.export" !== data5 && "lpr.delete" !== data5 && "observations.annotate" !== data5) return validate185.errors = [ { instancePath: instancePath + "/actions/" + i0, schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -3091,7 +3091,7 @@ function validate188(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("live.view" !== data4 && "audio.listen" !== data4 && "audio.talk" !== data4 && "recordings.replay" !== data4 && "recordings.export" !== data4 && "snapshot.create" !== data4 && "ptz.control" !== data4 && "evidence.protect" !== data4 && "recording.delete" !== data4 && "camera.configure" !== data4 && "fleet.execute_job" !== data4 && "storage.configure" !== data4 && "events.configure" !== data4 && "users.manage" !== data4 && "system.admin" !== data4 && "lpr.read" !== data4 && "lpr.search" !== data4 && "lpr.export" !== data4 && "lpr.delete" !== data4) return validate188.errors = [ { + if ("live.view" !== data4 && "audio.listen" !== data4 && "audio.talk" !== data4 && "recordings.replay" !== data4 && "recordings.export" !== data4 && "snapshot.create" !== data4 && "ptz.control" !== data4 && "evidence.protect" !== data4 && "recording.delete" !== data4 && "camera.configure" !== data4 && "fleet.execute_job" !== data4 && "storage.configure" !== data4 && "events.configure" !== data4 && "users.manage" !== data4 && "system.admin" !== data4 && "lpr.read" !== data4 && "lpr.search" !== data4 && "lpr.export" !== data4 && "lpr.delete" !== data4 && "observations.annotate" !== data4) return validate188.errors = [ { instancePath: instancePath + "/actions/" + i0, schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -3437,7 +3437,7 @@ var __contract_AdminSimulation = validate200, schema205 = { }, required: [ "allowed", "action", "source", "policy_version", "explanation", "grant_uuid", "role_uuid", "role", "principal", "resource" ], additionalProperties: !1 -}, func5 = Object.prototype.hasOwnProperty; +}, func7 = Object.prototype.hasOwnProperty; function validate200(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { let vErrors = [], errors = 0, evaluated0 = validate200.evaluated; @@ -3533,7 +3533,7 @@ function validate200(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema205.properties, key0)) return validate200.errors = [ { + for (let key0 in data) if (!func7.call(schema205.properties, key0)) return validate200.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -3565,7 +3565,7 @@ function validate200(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("live.view" !== data1 && "audio.listen" !== data1 && "audio.talk" !== data1 && "recordings.replay" !== data1 && "recordings.export" !== data1 && "snapshot.create" !== data1 && "ptz.control" !== data1 && "evidence.protect" !== data1 && "recording.delete" !== data1 && "camera.configure" !== data1 && "fleet.execute_job" !== data1 && "storage.configure" !== data1 && "events.configure" !== data1 && "users.manage" !== data1 && "system.admin" !== data1 && "lpr.read" !== data1 && "lpr.search" !== data1 && "lpr.export" !== data1 && "lpr.delete" !== data1) return validate200.errors = [ { + if ("live.view" !== data1 && "audio.listen" !== data1 && "audio.talk" !== data1 && "recordings.replay" !== data1 && "recordings.export" !== data1 && "snapshot.create" !== data1 && "ptz.control" !== data1 && "evidence.protect" !== data1 && "recording.delete" !== data1 && "camera.configure" !== data1 && "fleet.execute_job" !== data1 && "storage.configure" !== data1 && "events.configure" !== data1 && "users.manage" !== data1 && "system.admin" !== data1 && "lpr.read" !== data1 && "lpr.search" !== data1 && "lpr.export" !== data1 && "lpr.delete" !== data1 && "observations.annotate" !== data1) return validate200.errors = [ { instancePath: instancePath + "/action", schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -3989,7 +3989,7 @@ var __contract_AdminSimulationRequest = validate199, schema204 = { }, action: { type: "string", - enum: [ "live.view", "audio.listen", "audio.talk", "recordings.replay", "recordings.export", "snapshot.create", "ptz.control", "evidence.protect", "recording.delete", "camera.configure", "fleet.execute_job", "lpr.read", "lpr.search", "lpr.export", "lpr.delete" ] + enum: [ "live.view", "audio.listen", "audio.talk", "recordings.replay", "recordings.export", "snapshot.create", "ptz.control", "evidence.protect", "recording.delete", "camera.configure", "fleet.execute_job", "lpr.read", "lpr.search", "lpr.export", "lpr.delete", "observations.annotate" ] }, camera_uuid: { type: "string", @@ -4104,7 +4104,7 @@ function validate199(data, {instancePath: instancePath = "", parentData: parentD }; vErrors.push(err6), errors++; } - if ("live.view" !== data1 && "audio.listen" !== data1 && "audio.talk" !== data1 && "recordings.replay" !== data1 && "recordings.export" !== data1 && "snapshot.create" !== data1 && "ptz.control" !== data1 && "evidence.protect" !== data1 && "recording.delete" !== data1 && "camera.configure" !== data1 && "fleet.execute_job" !== data1 && "lpr.read" !== data1 && "lpr.search" !== data1 && "lpr.export" !== data1 && "lpr.delete" !== data1) { + if ("live.view" !== data1 && "audio.listen" !== data1 && "audio.talk" !== data1 && "recordings.replay" !== data1 && "recordings.export" !== data1 && "snapshot.create" !== data1 && "ptz.control" !== data1 && "evidence.protect" !== data1 && "recording.delete" !== data1 && "camera.configure" !== data1 && "fleet.execute_job" !== data1 && "lpr.read" !== data1 && "lpr.search" !== data1 && "lpr.export" !== data1 && "lpr.delete" !== data1 && "observations.annotate" !== data1) { let err7 = { instancePath: instancePath + "/action", schemaPath: "#/anyOf/0/properties/action/enum", @@ -4462,7 +4462,7 @@ function validate174(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema181.properties, key0)) return validate174.errors = [ { + for (let key0 in data) if (!func7.call(schema181.properties, key0)) return validate174.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -5542,7 +5542,7 @@ function validate175(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs7 = 0; - for (let key1 in data1) if (!func5.call(schema181.properties, key1)) return validate175.errors = [ { + for (let key1 in data1) if (!func7.call(schema181.properties, key1)) return validate175.errors = [ { instancePath: instancePath + "/users/" + i0, schemaPath: "#/$defs/AdminUser/additionalProperties", keyword: "additionalProperties", @@ -6086,7 +6086,7 @@ function validate89(data, {instancePath: instancePath = "", parentData: parentDa type: "string" } } ], !1; - if ("live.view" !== data3 && "audio.listen" !== data3 && "audio.talk" !== data3 && "recordings.replay" !== data3 && "recordings.export" !== data3 && "snapshot.create" !== data3 && "ptz.control" !== data3 && "evidence.protect" !== data3 && "recording.delete" !== data3 && "camera.configure" !== data3 && "fleet.execute_job" !== data3 && "storage.configure" !== data3 && "events.configure" !== data3 && "users.manage" !== data3 && "system.admin" !== data3 && "lpr.read" !== data3 && "lpr.search" !== data3 && "lpr.export" !== data3 && "lpr.delete" !== data3) return validate89.errors = [ { + if ("live.view" !== data3 && "audio.listen" !== data3 && "audio.talk" !== data3 && "recordings.replay" !== data3 && "recordings.export" !== data3 && "snapshot.create" !== data3 && "ptz.control" !== data3 && "evidence.protect" !== data3 && "recording.delete" !== data3 && "camera.configure" !== data3 && "fleet.execute_job" !== data3 && "storage.configure" !== data3 && "events.configure" !== data3 && "users.manage" !== data3 && "system.admin" !== data3 && "lpr.read" !== data3 && "lpr.search" !== data3 && "lpr.export" !== data3 && "lpr.delete" !== data3 && "observations.annotate" !== data3) return validate89.errors = [ { instancePath: instancePath + "/actions/" + i0, schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -6374,7 +6374,7 @@ function validate91(data, {instancePath: instancePath = "", parentData: parentDa type: "string" } } ], !1; - if ("live.view" !== data8 && "audio.listen" !== data8 && "audio.talk" !== data8 && "recordings.replay" !== data8 && "recordings.export" !== data8 && "snapshot.create" !== data8 && "ptz.control" !== data8 && "evidence.protect" !== data8 && "recording.delete" !== data8 && "camera.configure" !== data8 && "fleet.execute_job" !== data8 && "storage.configure" !== data8 && "events.configure" !== data8 && "users.manage" !== data8 && "system.admin" !== data8 && "lpr.read" !== data8 && "lpr.search" !== data8 && "lpr.export" !== data8 && "lpr.delete" !== data8) return validate91.errors = [ { + if ("live.view" !== data8 && "audio.listen" !== data8 && "audio.talk" !== data8 && "recordings.replay" !== data8 && "recordings.export" !== data8 && "snapshot.create" !== data8 && "ptz.control" !== data8 && "evidence.protect" !== data8 && "recording.delete" !== data8 && "camera.configure" !== data8 && "fleet.execute_job" !== data8 && "storage.configure" !== data8 && "events.configure" !== data8 && "users.manage" !== data8 && "system.admin" !== data8 && "lpr.read" !== data8 && "lpr.search" !== data8 && "lpr.export" !== data8 && "lpr.delete" !== data8 && "observations.annotate" !== data8) return validate91.errors = [ { instancePath: instancePath + "/actions/" + i0, schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -6768,7 +6768,7 @@ function validate203(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("live.view" !== data0 && "audio.listen" !== data0 && "audio.talk" !== data0 && "recordings.replay" !== data0 && "recordings.export" !== data0 && "snapshot.create" !== data0 && "ptz.control" !== data0 && "evidence.protect" !== data0 && "recording.delete" !== data0 && "camera.configure" !== data0 && "fleet.execute_job" !== data0 && "storage.configure" !== data0 && "events.configure" !== data0 && "users.manage" !== data0 && "system.admin" !== data0 && "lpr.read" !== data0 && "lpr.search" !== data0 && "lpr.export" !== data0 && "lpr.delete" !== data0) return validate203.errors = [ { + if ("live.view" !== data0 && "audio.listen" !== data0 && "audio.talk" !== data0 && "recordings.replay" !== data0 && "recordings.export" !== data0 && "snapshot.create" !== data0 && "ptz.control" !== data0 && "evidence.protect" !== data0 && "recording.delete" !== data0 && "camera.configure" !== data0 && "fleet.execute_job" !== data0 && "storage.configure" !== data0 && "events.configure" !== data0 && "users.manage" !== data0 && "system.admin" !== data0 && "lpr.read" !== data0 && "lpr.search" !== data0 && "lpr.export" !== data0 && "lpr.delete" !== data0 && "observations.annotate" !== data0) return validate203.errors = [ { instancePath: instancePath + "/action", schemaPath: "#/$defs/AuthorizationAction/enum", keyword: "enum", @@ -7002,7 +7002,7 @@ function validate201(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema207.properties, key0)) return validate201.errors = [ { + for (let key0 in data) if (!func7.call(schema207.properties, key0)) return validate201.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -7488,7 +7488,7 @@ function validate202(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs15 = 0; - for (let key1 in data5) if (!func5.call(schema207.properties, key1)) return validate202.errors = [ { + for (let key1 in data5) if (!func7.call(schema207.properties, key1)) return validate202.errors = [ { instancePath: instancePath + "/events/" + i0, schemaPath: "#/$defs/AuditEvent/additionalProperties", keyword: "additionalProperties", @@ -7997,7 +7997,7 @@ function validate206(data, {instancePath: instancePath = "", parentData: parentD }; vErrors.push(err5), errors++; } - if ("live.view" !== key0 && "audio.listen" !== key0 && "audio.talk" !== key0 && "recordings.replay" !== key0 && "recordings.export" !== key0 && "snapshot.create" !== key0 && "ptz.control" !== key0 && "evidence.protect" !== key0 && "recording.delete" !== key0 && "camera.configure" !== key0 && "fleet.execute_job" !== key0 && "storage.configure" !== key0 && "events.configure" !== key0 && "users.manage" !== key0 && "system.admin" !== key0 && "lpr.read" !== key0 && "lpr.search" !== key0 && "lpr.export" !== key0 && "lpr.delete" !== key0) { + if ("live.view" !== key0 && "audio.listen" !== key0 && "audio.talk" !== key0 && "recordings.replay" !== key0 && "recordings.export" !== key0 && "snapshot.create" !== key0 && "ptz.control" !== key0 && "evidence.protect" !== key0 && "recording.delete" !== key0 && "camera.configure" !== key0 && "fleet.execute_job" !== key0 && "storage.configure" !== key0 && "events.configure" !== key0 && "users.manage" !== key0 && "system.admin" !== key0 && "lpr.read" !== key0 && "lpr.search" !== key0 && "lpr.export" !== key0 && "lpr.delete" !== key0 && "observations.annotate" !== key0) { let err6 = { instancePath: instancePath + "/allowed_decision_modes", schemaPath: "#/$defs/AuthorizationAction/enum", @@ -8136,7 +8136,7 @@ function validate206(data, {instancePath: instancePath = "", parentData: parentD }; vErrors.push(err12), errors++; } - if ("live.view" !== key2 && "audio.listen" !== key2 && "audio.talk" !== key2 && "recordings.replay" !== key2 && "recordings.export" !== key2 && "snapshot.create" !== key2 && "ptz.control" !== key2 && "evidence.protect" !== key2 && "recording.delete" !== key2 && "camera.configure" !== key2 && "fleet.execute_job" !== key2 && "storage.configure" !== key2 && "events.configure" !== key2 && "users.manage" !== key2 && "system.admin" !== key2 && "lpr.read" !== key2 && "lpr.search" !== key2 && "lpr.export" !== key2 && "lpr.delete" !== key2) { + if ("live.view" !== key2 && "audio.listen" !== key2 && "audio.talk" !== key2 && "recordings.replay" !== key2 && "recordings.export" !== key2 && "snapshot.create" !== key2 && "ptz.control" !== key2 && "evidence.protect" !== key2 && "recording.delete" !== key2 && "camera.configure" !== key2 && "fleet.execute_job" !== key2 && "storage.configure" !== key2 && "events.configure" !== key2 && "users.manage" !== key2 && "system.admin" !== key2 && "lpr.read" !== key2 && "lpr.search" !== key2 && "lpr.export" !== key2 && "lpr.delete" !== key2 && "observations.annotate" !== key2) { let err13 = { instancePath: instancePath + "/allowed_decision_modes", schemaPath: "#/$defs/AuthorizationAction/enum", @@ -9752,7 +9752,7 @@ function validate168(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema177.properties, key0)) return validate168.errors = [ { + for (let key0 in data) if (!func7.call(schema177.properties, key0)) return validate168.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -12021,7 +12021,7 @@ function validate80(data, {instancePath: instancePath = "", parentData: parentDa } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema88.properties, key0)) return validate80.errors = [ { + for (let key0 in data) if (!func7.call(schema88.properties, key0)) return validate80.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -12389,10 +12389,10 @@ validate131.evaluated = { dynamicItems: !1 }; -var __contract_DaynightRequest = validate426; +var __contract_DaynightRequest = validate436; -function validate426(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate426.evaluated; +function validate436(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate436.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs1 = errors, valid0 = !1, _errs2 = errors; if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.mode) { @@ -12593,10 +12593,10 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err16), errors++, validate426.errors = vErrors, !1; + return vErrors.push(err16), errors++, validate436.errors = vErrors, !1; } if (errors = _errs1, vErrors.length = _errs1, 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate426.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate436.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -12606,7 +12606,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs14 = errors; - for (let key0 in data) if ("mode" !== key0 && "daynight" !== key0 && "ir_cut_filter" !== key0 && "ircut" !== key0 && "force_persistence" !== key0) return validate426.errors = [ { + for (let key0 in data) if ("mode" !== key0 && "daynight" !== key0 && "ir_cut_filter" !== key0 && "ircut" !== key0 && "force_persistence" !== key0) return validate436.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -12618,7 +12618,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.mode) { let data4 = data.mode, _errs15 = errors; if (errors === _errs15) { - if ("string" != typeof data4) return validate426.errors = [ { + if ("string" != typeof data4) return validate436.errors = [ { instancePath: instancePath + "/mode", schemaPath: "#/properties/mode/type", keyword: "type", @@ -12626,7 +12626,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data4) > 15) return validate426.errors = [ { + if (func1(data4) > 15) return validate436.errors = [ { instancePath: instancePath + "/mode", schemaPath: "#/properties/mode/maxLength", keyword: "maxLength", @@ -12634,7 +12634,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD limit: 15 } } ], !1; - if (func1(data4) < 1) return validate426.errors = [ { + if (func1(data4) < 1) return validate436.errors = [ { instancePath: instancePath + "/mode", schemaPath: "#/properties/mode/minLength", keyword: "minLength", @@ -12649,7 +12649,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.daynight) { let data5 = data.daynight, _errs17 = errors; if (errors === _errs17) { - if ("string" != typeof data5) return validate426.errors = [ { + if ("string" != typeof data5) return validate436.errors = [ { instancePath: instancePath + "/daynight", schemaPath: "#/properties/daynight/type", keyword: "type", @@ -12657,7 +12657,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 15) return validate426.errors = [ { + if (func1(data5) > 15) return validate436.errors = [ { instancePath: instancePath + "/daynight", schemaPath: "#/properties/daynight/maxLength", keyword: "maxLength", @@ -12665,7 +12665,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD limit: 15 } } ], !1; - if (func1(data5) < 1) return validate426.errors = [ { + if (func1(data5) < 1) return validate436.errors = [ { instancePath: instancePath + "/daynight", schemaPath: "#/properties/daynight/minLength", keyword: "minLength", @@ -12680,7 +12680,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.ir_cut_filter) { let data6 = data.ir_cut_filter, _errs19 = errors; if (errors === _errs19) { - if ("string" != typeof data6) return validate426.errors = [ { + if ("string" != typeof data6) return validate436.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/type", keyword: "type", @@ -12688,7 +12688,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data6) > 15) return validate426.errors = [ { + if (func1(data6) > 15) return validate436.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/maxLength", keyword: "maxLength", @@ -12696,7 +12696,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD limit: 15 } } ], !1; - if (func1(data6) < 1) return validate426.errors = [ { + if (func1(data6) < 1) return validate436.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/minLength", keyword: "minLength", @@ -12711,7 +12711,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.ircut) { let data7 = data.ircut, _errs21 = errors; if (errors === _errs21) { - if ("string" != typeof data7) return validate426.errors = [ { + if ("string" != typeof data7) return validate436.errors = [ { instancePath: instancePath + "/ircut", schemaPath: "#/properties/ircut/type", keyword: "type", @@ -12719,7 +12719,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data7) > 15) return validate426.errors = [ { + if (func1(data7) > 15) return validate436.errors = [ { instancePath: instancePath + "/ircut", schemaPath: "#/properties/ircut/maxLength", keyword: "maxLength", @@ -12727,7 +12727,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD limit: 15 } } ], !1; - if (func1(data7) < 1) return validate426.errors = [ { + if (func1(data7) < 1) return validate436.errors = [ { instancePath: instancePath + "/ircut", schemaPath: "#/properties/ircut/minLength", keyword: "minLength", @@ -12738,7 +12738,7 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD } var valid8 = _errs21 === errors; } else valid8 = !0; - if (valid8 && void 0 !== data.force_persistence && "boolean" != typeof data.force_persistence) return validate426.errors = [ { + if (valid8 && void 0 !== data.force_persistence && "boolean" != typeof data.force_persistence) return validate436.errors = [ { instancePath: instancePath + "/force_persistence", schemaPath: "#/properties/force_persistence/type", keyword: "type", @@ -12752,16 +12752,16 @@ function validate426(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate426.errors = vErrors, 0 === errors; + return validate436.errors = vErrors, 0 === errors; } -validate426.evaluated = { +validate436.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DaynightResponse = validate427, schema516 = { +var __contract_DaynightResponse = validate437, schema528 = { type: "object", properties: { success: { @@ -12793,10 +12793,10 @@ var __contract_DaynightResponse = validate427, schema516 = { additionalProperties: !1 }; -function validate427(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate427.evaluated; +function validate437(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate437.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate427.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -12804,7 +12804,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate427.errors = [ { + if (void 0 === data.success) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -12812,7 +12812,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.source) return validate427.errors = [ { + if (void 0 === data.source) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -12820,7 +12820,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source" } } ], !1; - if (void 0 === data.video_source_token) return validate427.errors = [ { + if (void 0 === data.video_source_token) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -12828,7 +12828,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "video_source_token" } } ], !1; - if (void 0 === data.mode) return validate427.errors = [ { + if (void 0 === data.mode) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -12836,7 +12836,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "mode" } } ], !1; - if (void 0 === data.supported_modes) return validate427.errors = [ { + if (void 0 === data.supported_modes) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -12844,7 +12844,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "supported_modes" } } ], !1; - if (void 0 === data.options_available) return validate427.errors = [ { + if (void 0 === data.options_available) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -12854,7 +12854,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "source" !== key0 && "video_source_token" !== key0 && "mode" !== key0 && "ir_cut_filter" !== key0 && "supported_modes" !== key0 && "options_available" !== key0) return validate427.errors = [ { + for (let key0 in data) if ("success" !== key0 && "source" !== key0 && "video_source_token" !== key0 && "mode" !== key0 && "ir_cut_filter" !== key0 && "supported_modes" !== key0 && "options_available" !== key0) return validate437.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -12865,7 +12865,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.success) { let _errs2 = 0; - if (!0 !== data.success) return validate427.errors = [ { + if (!0 !== data.success) return validate437.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -12878,7 +12878,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.source) { let _errs3 = 0; - if ("onvif-imaging" !== data.source) return validate427.errors = [ { + if ("onvif-imaging" !== data.source) return validate437.errors = [ { instancePath: instancePath + "/source", schemaPath: "#/properties/source/const", keyword: "const", @@ -12891,7 +12891,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.video_source_token) { let _errs4 = 0; - if ("string" != typeof data.video_source_token) return validate427.errors = [ { + if ("string" != typeof data.video_source_token) return validate437.errors = [ { instancePath: instancePath + "/video_source_token", schemaPath: "#/properties/video_source_token/type", keyword: "type", @@ -12904,12 +12904,12 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.mode) { let data3 = data.mode, _errs6 = 0; - if ("day" !== data3 && "night" !== data3 && "auto" !== data3 && "unknown" !== data3) return validate427.errors = [ { + if ("day" !== data3 && "night" !== data3 && "auto" !== data3 && "unknown" !== data3) return validate437.errors = [ { instancePath: instancePath + "/mode", schemaPath: "#/properties/mode/enum", keyword: "enum", params: { - allowedValues: schema516.properties.mode.enum + allowedValues: schema528.properties.mode.enum } } ], !1; var valid3 = 0 === _errs6; @@ -12917,7 +12917,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.ir_cut_filter) { let _errs7 = 0; - if ("string" != typeof data.ir_cut_filter) return validate427.errors = [ { + if ("string" != typeof data.ir_cut_filter) return validate437.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/type", keyword: "type", @@ -12931,7 +12931,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.supported_modes) { let data5 = data.supported_modes, _errs9 = 0; if (0 === _errs9) { - if (!Array.isArray(data5)) return validate427.errors = [ { + if (!Array.isArray(data5)) return validate437.errors = [ { instancePath: instancePath + "/supported_modes", schemaPath: "#/properties/supported_modes/type", keyword: "type", @@ -12943,12 +12943,12 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD let len0 = data5.length; for (let i0 = 0; i0 < len0; i0++) { let data6 = data5[i0], _errs11 = 0; - if ("day" !== data6 && "night" !== data6 && "auto" !== data6) return validate427.errors = [ { + if ("day" !== data6 && "night" !== data6 && "auto" !== data6) return validate437.errors = [ { instancePath: instancePath + "/supported_modes/" + i0, schemaPath: "#/properties/supported_modes/items/enum", keyword: "enum", params: { - allowedValues: schema516.properties.supported_modes.items.enum + allowedValues: schema528.properties.supported_modes.items.enum } } ], !1; if (0 !== _errs11) break; @@ -12957,7 +12957,7 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD } var valid5 = 0 === _errs9; } else valid5 = !0; - if (valid5 && void 0 !== data.options_available && "boolean" != typeof data.options_available) return validate427.errors = [ { + if (valid5 && void 0 !== data.options_available && "boolean" != typeof data.options_available) return validate437.errors = [ { instancePath: instancePath + "/options_available", schemaPath: "#/properties/options_available/type", keyword: "type", @@ -12972,16 +12972,16 @@ function validate427(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate427.errors = [], !0; + return validate437.errors = [], !0; } -validate427.evaluated = { +validate437.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DaynightUpdated = validate428, schema517 = { +var __contract_DaynightUpdated = validate438, schema529 = { type: "object", properties: { success: { @@ -13001,10 +13001,10 @@ var __contract_DaynightUpdated = validate428, schema517 = { additionalProperties: !1 }; -function validate428(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate428.evaluated; +function validate438(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate438.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate428.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate438.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13012,7 +13012,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate428.errors = [ { + if (void 0 === data.success) return validate438.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13020,7 +13020,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.source) return validate428.errors = [ { + if (void 0 === data.source) return validate438.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13028,7 +13028,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source" } } ], !1; - if (void 0 === data.mode) return validate428.errors = [ { + if (void 0 === data.mode) return validate438.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13036,7 +13036,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "mode" } } ], !1; - if (void 0 === data.ir_cut_filter) return validate428.errors = [ { + if (void 0 === data.ir_cut_filter) return validate438.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13046,7 +13046,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "source" !== key0 && "mode" !== key0 && "ir_cut_filter" !== key0) return validate428.errors = [ { + for (let key0 in data) if ("success" !== key0 && "source" !== key0 && "mode" !== key0 && "ir_cut_filter" !== key0) return validate438.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -13057,7 +13057,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.success) { let _errs2 = 0; - if (!0 !== data.success) return validate428.errors = [ { + if (!0 !== data.success) return validate438.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -13070,7 +13070,7 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.source) { let _errs3 = 0; - if ("onvif-imaging" !== data.source) return validate428.errors = [ { + if ("onvif-imaging" !== data.source) return validate438.errors = [ { instancePath: instancePath + "/source", schemaPath: "#/properties/source/const", keyword: "const", @@ -13083,24 +13083,24 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.mode) { let data2 = data.mode, _errs4 = 0; - if ("day" !== data2 && "night" !== data2 && "auto" !== data2) return validate428.errors = [ { + if ("day" !== data2 && "night" !== data2 && "auto" !== data2) return validate438.errors = [ { instancePath: instancePath + "/mode", schemaPath: "#/properties/mode/enum", keyword: "enum", params: { - allowedValues: schema517.properties.mode.enum + allowedValues: schema529.properties.mode.enum } } ], !1; var valid2 = 0 === _errs4; } else valid2 = !0; if (valid2 && void 0 !== data.ir_cut_filter) { let data3 = data.ir_cut_filter; - if ("On" !== data3 && "Off" !== data3 && "Auto" !== data3) return validate428.errors = [ { + if ("On" !== data3 && "Off" !== data3 && "Auto" !== data3) return validate438.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/enum", keyword: "enum", params: { - allowedValues: schema517.properties.ir_cut_filter.enum + allowedValues: schema529.properties.ir_cut_filter.enum } } ], !1; } @@ -13108,21 +13108,21 @@ function validate428(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate428.errors = [], !0; + return validate438.errors = [], !0; } -validate428.evaluated = { +validate438.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DetectionZone = validate452, pattern190 = new RegExp("^#[0-9a-fA-F]{6}$", "u"); +var __contract_DetectionZone = validate462, pattern197 = new RegExp("^#[0-9a-fA-F]{6}$", "u"); -function validate452(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate452.evaluated; +function validate462(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate462.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate452.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13130,7 +13130,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.id) return validate452.errors = [ { + if (void 0 === data.id) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13138,7 +13138,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data.name) return validate452.errors = [ { + if (void 0 === data.name) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13146,7 +13146,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.enabled) return validate452.errors = [ { + if (void 0 === data.enabled) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13154,7 +13154,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.color) return validate452.errors = [ { + if (void 0 === data.color) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13162,7 +13162,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "color" } } ], !1; - if (void 0 === data.polygon) return validate452.errors = [ { + if (void 0 === data.polygon) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13170,7 +13170,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "polygon" } } ], !1; - if (void 0 === data.filter_classes) return validate452.errors = [ { + if (void 0 === data.filter_classes) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13178,7 +13178,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "filter_classes" } } ], !1; - if (void 0 === data.min_confidence) return validate452.errors = [ { + if (void 0 === data.min_confidence) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13186,7 +13186,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min_confidence" } } ], !1; - if (void 0 === data.stream_name) return validate452.errors = [ { + if (void 0 === data.stream_name) return validate462.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13197,7 +13197,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.id) { let data0 = data.id, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate452.errors = [ { + if ("string" != typeof data0) return validate462.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", @@ -13205,7 +13205,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 63) return validate452.errors = [ { + if (func1(data0) > 63) return validate462.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/maxLength", keyword: "maxLength", @@ -13213,7 +13213,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data0) < 1) return validate452.errors = [ { + if (func1(data0) < 1) return validate462.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/minLength", keyword: "minLength", @@ -13228,7 +13228,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data1 = data.name, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate452.errors = [ { + if ("string" != typeof data1) return validate462.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -13236,7 +13236,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 63) return validate452.errors = [ { + if (func1(data1) > 63) return validate462.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -13250,7 +13250,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = 0; - if ("boolean" != typeof data.enabled) return validate452.errors = [ { + if ("boolean" != typeof data.enabled) return validate462.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -13264,7 +13264,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.color) { let data3 = data.color, _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data3) return validate452.errors = [ { + if ("string" != typeof data3) return validate462.errors = [ { instancePath: instancePath + "/color", schemaPath: "#/properties/color/type", keyword: "type", @@ -13272,7 +13272,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern190.test(data3)) return validate452.errors = [ { + if (!pattern197.test(data3)) return validate462.errors = [ { instancePath: instancePath + "/color", schemaPath: "#/properties/color/pattern", keyword: "pattern", @@ -13287,7 +13287,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.polygon) { let data4 = data.polygon, _errs10 = 0; if (0 === _errs10) { - if (!Array.isArray(data4)) return validate452.errors = [ { + if (!Array.isArray(data4)) return validate462.errors = [ { instancePath: instancePath + "/polygon", schemaPath: "#/properties/polygon/type", keyword: "type", @@ -13295,7 +13295,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 32) return validate452.errors = [ { + if (data4.length > 32) return validate462.errors = [ { instancePath: instancePath + "/polygon", schemaPath: "#/properties/polygon/maxItems", keyword: "maxItems", @@ -13303,7 +13303,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD limit: 32 } } ], !1; - if (data4.length < 3) return validate452.errors = [ { + if (data4.length < 3) return validate462.errors = [ { instancePath: instancePath + "/polygon", schemaPath: "#/properties/polygon/minItems", keyword: "minItems", @@ -13316,7 +13316,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data5 = data4[i0], _errs12 = 0; if (0 === _errs12) { - if (!data5 || "object" != typeof data5 || Array.isArray(data5)) return validate452.errors = [ { + if (!data5 || "object" != typeof data5 || Array.isArray(data5)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0, schemaPath: "#/properties/polygon/items/type", keyword: "type", @@ -13324,7 +13324,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data5.x) return validate452.errors = [ { + if (void 0 === data5.x) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0, schemaPath: "#/properties/polygon/items/required", keyword: "required", @@ -13332,7 +13332,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "x" } } ], !1; - if (void 0 === data5.y) return validate452.errors = [ { + if (void 0 === data5.y) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0, schemaPath: "#/properties/polygon/items/required", keyword: "required", @@ -13343,7 +13343,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data5.x) { let data6 = data5.x, _errs15 = 0; if (0 === _errs15) { - if ("number" != typeof data6 || !isFinite(data6)) return validate452.errors = [ { + if ("number" != typeof data6 || !isFinite(data6)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/x", schemaPath: "#/properties/polygon/items/properties/x/type", keyword: "type", @@ -13351,7 +13351,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data6 > 1 || isNaN(data6)) return validate452.errors = [ { + if (data6 > 1 || isNaN(data6)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/x", schemaPath: "#/properties/polygon/items/properties/x/maximum", keyword: "maximum", @@ -13360,7 +13360,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data6 < 0 || isNaN(data6)) return validate452.errors = [ { + if (data6 < 0 || isNaN(data6)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/x", schemaPath: "#/properties/polygon/items/properties/x/minimum", keyword: "minimum", @@ -13374,7 +13374,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD } else valid6 = !0; if (valid6 && void 0 !== data5.y) { let data7 = data5.y; - if ("number" != typeof data7 || !isFinite(data7)) return validate452.errors = [ { + if ("number" != typeof data7 || !isFinite(data7)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/y", schemaPath: "#/properties/polygon/items/properties/y/type", keyword: "type", @@ -13382,7 +13382,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data7 > 1 || isNaN(data7)) return validate452.errors = [ { + if (data7 > 1 || isNaN(data7)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/y", schemaPath: "#/properties/polygon/items/properties/y/maximum", keyword: "maximum", @@ -13391,7 +13391,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data7 < 0 || isNaN(data7)) return validate452.errors = [ { + if (data7 < 0 || isNaN(data7)) return validate462.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/y", schemaPath: "#/properties/polygon/items/properties/y/minimum", keyword: "minimum", @@ -13412,7 +13412,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.filter_classes) { let data8 = data.filter_classes, _errs19 = 0; if (0 === _errs19) { - if ("string" != typeof data8) return validate452.errors = [ { + if ("string" != typeof data8) return validate462.errors = [ { instancePath: instancePath + "/filter_classes", schemaPath: "#/properties/filter_classes/type", keyword: "type", @@ -13420,7 +13420,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data8) > 255) return validate452.errors = [ { + if (func1(data8) > 255) return validate462.errors = [ { instancePath: instancePath + "/filter_classes", schemaPath: "#/properties/filter_classes/maxLength", keyword: "maxLength", @@ -13435,7 +13435,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.min_confidence) { let data9 = data.min_confidence, _errs21 = 0; if (0 === _errs21) { - if ("number" != typeof data9 || !isFinite(data9)) return validate452.errors = [ { + if ("number" != typeof data9 || !isFinite(data9)) return validate462.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/type", keyword: "type", @@ -13443,7 +13443,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data9 > 1 || isNaN(data9)) return validate452.errors = [ { + if (data9 > 1 || isNaN(data9)) return validate462.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/maximum", keyword: "maximum", @@ -13452,7 +13452,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data9 < 0 || isNaN(data9)) return validate452.errors = [ { + if (data9 < 0 || isNaN(data9)) return validate462.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/minimum", keyword: "minimum", @@ -13464,7 +13464,7 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD } var valid9 = 0 === _errs21; } else valid9 = !0; - if (valid9 && void 0 !== data.stream_name && "string" != typeof data.stream_name) return validate452.errors = [ { + if (valid9 && void 0 !== data.stream_name && "string" != typeof data.stream_name) return validate462.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -13478,21 +13478,21 @@ function validate452(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate452.errors = [], !0; + return validate462.errors = [], !0; } -validate452.evaluated = { +validate462.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DetectionZoneWrite = validate451; +var __contract_DetectionZoneWrite = validate461; -function validate451(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate451.evaluated; +function validate461(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate461.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate451.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate461.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13500,7 +13500,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.id) return validate451.errors = [ { + if (void 0 === data.id) return validate461.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13508,7 +13508,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data.name) return validate451.errors = [ { + if (void 0 === data.name) return validate461.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13516,7 +13516,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.polygon) return validate451.errors = [ { + if (void 0 === data.polygon) return validate461.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13527,7 +13527,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.id) { let data0 = data.id, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate451.errors = [ { + if ("string" != typeof data0) return validate461.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", @@ -13535,7 +13535,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 63) return validate451.errors = [ { + if (func1(data0) > 63) return validate461.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/maxLength", keyword: "maxLength", @@ -13543,7 +13543,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data0) < 1) return validate451.errors = [ { + if (func1(data0) < 1) return validate461.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/minLength", keyword: "minLength", @@ -13558,7 +13558,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data1 = data.name, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate451.errors = [ { + if ("string" != typeof data1) return validate461.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -13566,7 +13566,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 63) return validate451.errors = [ { + if (func1(data1) > 63) return validate461.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -13580,7 +13580,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = 0; - if ("boolean" != typeof data.enabled) return validate451.errors = [ { + if ("boolean" != typeof data.enabled) return validate461.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -13594,7 +13594,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.color) { let data3 = data.color, _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data3) return validate451.errors = [ { + if ("string" != typeof data3) return validate461.errors = [ { instancePath: instancePath + "/color", schemaPath: "#/properties/color/type", keyword: "type", @@ -13602,7 +13602,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern190.test(data3)) return validate451.errors = [ { + if (!pattern197.test(data3)) return validate461.errors = [ { instancePath: instancePath + "/color", schemaPath: "#/properties/color/pattern", keyword: "pattern", @@ -13617,7 +13617,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.polygon) { let data4 = data.polygon, _errs10 = 0; if (0 === _errs10) { - if (!Array.isArray(data4)) return validate451.errors = [ { + if (!Array.isArray(data4)) return validate461.errors = [ { instancePath: instancePath + "/polygon", schemaPath: "#/properties/polygon/type", keyword: "type", @@ -13625,7 +13625,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 32) return validate451.errors = [ { + if (data4.length > 32) return validate461.errors = [ { instancePath: instancePath + "/polygon", schemaPath: "#/properties/polygon/maxItems", keyword: "maxItems", @@ -13633,7 +13633,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD limit: 32 } } ], !1; - if (data4.length < 3) return validate451.errors = [ { + if (data4.length < 3) return validate461.errors = [ { instancePath: instancePath + "/polygon", schemaPath: "#/properties/polygon/minItems", keyword: "minItems", @@ -13646,7 +13646,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data5 = data4[i0], _errs12 = 0; if (0 === _errs12) { - if (!data5 || "object" != typeof data5 || Array.isArray(data5)) return validate451.errors = [ { + if (!data5 || "object" != typeof data5 || Array.isArray(data5)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0, schemaPath: "#/properties/polygon/items/type", keyword: "type", @@ -13654,7 +13654,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data5.x) return validate451.errors = [ { + if (void 0 === data5.x) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0, schemaPath: "#/properties/polygon/items/required", keyword: "required", @@ -13662,7 +13662,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "x" } } ], !1; - if (void 0 === data5.y) return validate451.errors = [ { + if (void 0 === data5.y) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0, schemaPath: "#/properties/polygon/items/required", keyword: "required", @@ -13673,7 +13673,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data5.x) { let data6 = data5.x, _errs15 = 0; if (0 === _errs15) { - if ("number" != typeof data6 || !isFinite(data6)) return validate451.errors = [ { + if ("number" != typeof data6 || !isFinite(data6)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/x", schemaPath: "#/properties/polygon/items/properties/x/type", keyword: "type", @@ -13681,7 +13681,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data6 > 1 || isNaN(data6)) return validate451.errors = [ { + if (data6 > 1 || isNaN(data6)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/x", schemaPath: "#/properties/polygon/items/properties/x/maximum", keyword: "maximum", @@ -13690,7 +13690,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data6 < 0 || isNaN(data6)) return validate451.errors = [ { + if (data6 < 0 || isNaN(data6)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/x", schemaPath: "#/properties/polygon/items/properties/x/minimum", keyword: "minimum", @@ -13704,7 +13704,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD } else valid6 = !0; if (valid6 && void 0 !== data5.y) { let data7 = data5.y; - if ("number" != typeof data7 || !isFinite(data7)) return validate451.errors = [ { + if ("number" != typeof data7 || !isFinite(data7)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/y", schemaPath: "#/properties/polygon/items/properties/y/type", keyword: "type", @@ -13712,7 +13712,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data7 > 1 || isNaN(data7)) return validate451.errors = [ { + if (data7 > 1 || isNaN(data7)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/y", schemaPath: "#/properties/polygon/items/properties/y/maximum", keyword: "maximum", @@ -13721,7 +13721,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data7 < 0 || isNaN(data7)) return validate451.errors = [ { + if (data7 < 0 || isNaN(data7)) return validate461.errors = [ { instancePath: instancePath + "/polygon/" + i0 + "/y", schemaPath: "#/properties/polygon/items/properties/y/minimum", keyword: "minimum", @@ -13742,7 +13742,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.filter_classes) { let data8 = data.filter_classes, _errs19 = 0; if (0 === _errs19) { - if ("string" != typeof data8) return validate451.errors = [ { + if ("string" != typeof data8) return validate461.errors = [ { instancePath: instancePath + "/filter_classes", schemaPath: "#/properties/filter_classes/type", keyword: "type", @@ -13750,7 +13750,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data8) > 255) return validate451.errors = [ { + if (func1(data8) > 255) return validate461.errors = [ { instancePath: instancePath + "/filter_classes", schemaPath: "#/properties/filter_classes/maxLength", keyword: "maxLength", @@ -13763,7 +13763,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD } else valid8 = !0; if (valid8 && void 0 !== data.min_confidence) { let data9 = data.min_confidence; - if ("number" != typeof data9 || !isFinite(data9)) return validate451.errors = [ { + if ("number" != typeof data9 || !isFinite(data9)) return validate461.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/type", keyword: "type", @@ -13771,7 +13771,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data9 > 1 || isNaN(data9)) return validate451.errors = [ { + if (data9 > 1 || isNaN(data9)) return validate461.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/maximum", keyword: "maximum", @@ -13780,7 +13780,7 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data9 < 0 || isNaN(data9)) return validate451.errors = [ { + if (data9 < 0 || isNaN(data9)) return validate461.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/minimum", keyword: "minimum", @@ -13795,21 +13795,21 @@ function validate451(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate451.errors = [], !0; + return validate461.errors = [], !0; } -validate451.evaluated = { +validate461.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DeviceCommandResult = validate403; +var __contract_DeviceCommandResult = validate413; -function validate403(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate403.evaluated; +function validate413(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate413.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate403.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate413.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13817,7 +13817,7 @@ function validate403(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate403.errors = [ { + if (void 0 === data.success) return validate413.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13825,7 +13825,7 @@ function validate403(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.message) return validate403.errors = [ { + if (void 0 === data.message) return validate413.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13835,7 +13835,7 @@ function validate403(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "message" !== key0) return validate403.errors = [ { + for (let key0 in data) if ("success" !== key0 && "message" !== key0) return validate413.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -13846,7 +13846,7 @@ function validate403(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.success) { let _errs2 = 0; - if (!0 !== data.success) return validate403.errors = [ { + if (!0 !== data.success) return validate413.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -13856,7 +13856,7 @@ function validate403(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid0 = 0 === _errs2; } else valid0 = !0; - if (valid0 && void 0 !== data.message && "string" != typeof data.message) return validate403.errors = [ { + if (valid0 && void 0 !== data.message && "string" != typeof data.message) return validate413.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", @@ -13866,21 +13866,21 @@ function validate403(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate403.errors = [], !0; + return validate413.errors = [], !0; } -validate403.evaluated = { +validate413.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DeviceEmptyRequest = validate405; +var __contract_DeviceEmptyRequest = validate415; -function validate405(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate405.evaluated; +function validate415(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate415.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate405.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate415.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13888,7 +13888,7 @@ function validate405(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - for (let key0 in data) return validate405.errors = [ { + for (let key0 in data) return validate415.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -13896,21 +13896,21 @@ function validate405(data, {instancePath: instancePath = "", parentData: parentD additionalProperty: key0 } } ], !1; - return validate405.errors = [], !0; + return validate415.errors = [], !0; } -validate405.evaluated = { +validate415.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DeviceFloatRange = validate402; +var __contract_DeviceFloatRange = validate412; -function validate402(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate402.evaluated; +function validate412(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate412.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate402.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate412.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13918,7 +13918,7 @@ function validate402(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.min) return validate402.errors = [ { + if (void 0 === data.min) return validate412.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13926,7 +13926,7 @@ function validate402(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data.max) return validate402.errors = [ { + if (void 0 === data.max) return validate412.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -13936,7 +13936,7 @@ function validate402(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("min" !== key0 && "max" !== key0) return validate402.errors = [ { + for (let key0 in data) if ("min" !== key0 && "max" !== key0) return validate412.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -13947,7 +13947,7 @@ function validate402(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.min) { let data0 = data.min, _errs2 = 0; - if ("number" != typeof data0 || !isFinite(data0)) return validate402.errors = [ { + if ("number" != typeof data0 || !isFinite(data0)) return validate412.errors = [ { instancePath: instancePath + "/min", schemaPath: "#/properties/min/type", keyword: "type", @@ -13959,7 +13959,7 @@ function validate402(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.max) { let data1 = data.max; - if ("number" != typeof data1 || !isFinite(data1)) return validate402.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate412.errors = [ { instancePath: instancePath + "/max", schemaPath: "#/properties/max/type", keyword: "type", @@ -13970,21 +13970,21 @@ function validate402(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate402.errors = [], !0; + return validate412.errors = [], !0; } -validate402.evaluated = { +validate412.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_DeviceSuccess = validate404; +var __contract_DeviceSuccess = validate414; -function validate404(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate404.evaluated; +function validate414(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate414.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate404.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate414.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -13992,7 +13992,7 @@ function validate404(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate404.errors = [ { + if (void 0 === data.success) return validate414.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14002,7 +14002,7 @@ function validate404(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0) return validate404.errors = [ { + for (let key0 in data) if ("success" !== key0) return validate414.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -14010,7 +14010,7 @@ function validate404(data, {instancePath: instancePath = "", parentData: parentD additionalProperty: key0 } } ], !1; - if (0 === _errs1 && void 0 !== data.success && !0 !== data.success) return validate404.errors = [ { + if (0 === _errs1 && void 0 !== data.success && !0 !== data.success) return validate414.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -14019,19 +14019,19 @@ function validate404(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate404.errors = [], !0; + return validate414.errors = [], !0; } -validate404.evaluated = { +validate414.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventCameraScope = validate290; +var __contract_EventCameraScope = validate293; -function validate290(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate290.evaluated; +function validate293(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate293.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.type) { @@ -14190,18 +14190,18 @@ function validate290(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err11), errors++, validate290.errors = vErrors, !1; + return vErrors.push(err11), errors++, validate293.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate290.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate293.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate290.evaluated = { +validate293.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_EventCatalog = validate303, schema331 = { +var __contract_EventCatalog = validate306, schema335 = { type: "object", properties: { schema_version: { @@ -14269,10 +14269,10 @@ var __contract_EventCatalog = validate303, schema331 = { additionalProperties: !1 }; -function validate303(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate303.evaluated; +function validate306(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate306.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate303.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate306.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -14280,7 +14280,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.schema_version) return validate303.errors = [ { + if (void 0 === data.schema_version) return validate306.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14288,7 +14288,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "schema_version" } } ], !1; - if (void 0 === data.count) return validate303.errors = [ { + if (void 0 === data.count) return validate306.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14296,7 +14296,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "count" } } ], !1; - if (void 0 === data.event_types) return validate303.errors = [ { + if (void 0 === data.event_types) return validate306.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14306,7 +14306,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("schema_version" !== key0 && "count" !== key0 && "event_types" !== key0) return validate303.errors = [ { + for (let key0 in data) if ("schema_version" !== key0 && "count" !== key0 && "event_types" !== key0) return validate306.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -14317,7 +14317,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.schema_version) { let data0 = data.schema_version, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate303.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate306.errors = [ { instancePath: instancePath + "/schema_version", schemaPath: "#/properties/schema_version/type", keyword: "type", @@ -14325,7 +14325,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data0) return validate303.errors = [ { + if (1 !== data0) return validate306.errors = [ { instancePath: instancePath + "/schema_version", schemaPath: "#/properties/schema_version/const", keyword: "const", @@ -14338,7 +14338,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.count) { let data1 = data.count, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate303.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate306.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -14346,7 +14346,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate303.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate306.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -14359,7 +14359,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.event_types) { let data2 = data.event_types; - if (!Array.isArray(data2)) return validate303.errors = [ { + if (!Array.isArray(data2)) return validate306.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/type", keyword: "type", @@ -14372,7 +14372,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs8 = 0; if (0 === _errs8) { - if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate303.errors = [ { + if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/type", keyword: "type", @@ -14380,7 +14380,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data3.type) return validate303.errors = [ { + if (void 0 === data3.type) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14388,7 +14388,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data3.expected_rate) return validate303.errors = [ { + if (void 0 === data3.expected_rate) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14396,7 +14396,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "expected_rate" } } ], !1; - if (void 0 === data3.severity) return validate303.errors = [ { + if (void 0 === data3.severity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14404,7 +14404,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "severity" } } ], !1; - if (void 0 === data3.dynamic_severity) return validate303.errors = [ { + if (void 0 === data3.dynamic_severity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14412,7 +14412,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "dynamic_severity" } } ], !1; - if (void 0 === data3.minimum_severity) return validate303.errors = [ { + if (void 0 === data3.minimum_severity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14420,7 +14420,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "minimum_severity" } } ], !1; - if (void 0 === data3.maximum_severity) return validate303.errors = [ { + if (void 0 === data3.maximum_severity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14428,7 +14428,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "maximum_severity" } } ], !1; - if (void 0 === data3.subject_kind) return validate303.errors = [ { + if (void 0 === data3.subject_kind) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14436,7 +14436,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "subject_kind" } } ], !1; - if (void 0 === data3.family) return validate303.errors = [ { + if (void 0 === data3.family) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14444,7 +14444,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "family" } } ], !1; - if (void 0 === data3.description) return validate303.errors = [ { + if (void 0 === data3.description) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14452,7 +14452,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "description" } } ], !1; - if (void 0 === data3.sensitivity) return validate303.errors = [ { + if (void 0 === data3.sensitivity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14460,7 +14460,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sensitivity" } } ], !1; - if (void 0 === data3.media_policy) return validate303.errors = [ { + if (void 0 === data3.media_policy) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14468,7 +14468,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "media_policy" } } ], !1; - if (void 0 === data3.default_expiry_seconds) return validate303.errors = [ { + if (void 0 === data3.default_expiry_seconds) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -14478,7 +14478,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs10 = 0; - for (let key1 in data3) if (!func5.call(schema331.properties.event_types.items.properties, key1)) return validate303.errors = [ { + for (let key1 in data3) if (!func7.call(schema335.properties.event_types.items.properties, key1)) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/additionalProperties", keyword: "additionalProperties", @@ -14489,7 +14489,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs10) { if (void 0 !== data3.type) { let _errs11 = 0; - if ("string" != typeof data3.type) return validate303.errors = [ { + if ("string" != typeof data3.type) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/type", schemaPath: "#/properties/event_types/items/properties/type/type", keyword: "type", @@ -14502,7 +14502,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data3.expected_rate) { let data5 = data3.expected_rate, _errs13 = 0; - if ("string" != typeof data5) return validate303.errors = [ { + if ("string" != typeof data5) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/expected_rate", schemaPath: "#/properties/event_types/items/properties/expected_rate/type", keyword: "type", @@ -14510,12 +14510,12 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("low" !== data5 && "medium" !== data5 && "high" !== data5) return validate303.errors = [ { + if ("low" !== data5 && "medium" !== data5 && "high" !== data5) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/expected_rate", schemaPath: "#/properties/event_types/items/properties/expected_rate/enum", keyword: "enum", params: { - allowedValues: schema331.properties.event_types.items.properties.expected_rate.enum + allowedValues: schema335.properties.event_types.items.properties.expected_rate.enum } } ], !1; var valid5 = 0 === _errs13; @@ -14523,7 +14523,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data3.severity) { let data6 = data3.severity, _errs15 = 0; - if ("string" != typeof data6) return validate303.errors = [ { + if ("string" != typeof data6) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/severity", schemaPath: "#/properties/event_types/items/properties/severity/type", keyword: "type", @@ -14531,12 +14531,12 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("debug" !== data6 && "info" !== data6 && "notice" !== data6 && "warning" !== data6 && "error" !== data6 && "critical" !== data6) return validate303.errors = [ { + if ("debug" !== data6 && "info" !== data6 && "notice" !== data6 && "warning" !== data6 && "error" !== data6 && "critical" !== data6) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/severity", schemaPath: "#/properties/event_types/items/properties/severity/enum", keyword: "enum", params: { - allowedValues: schema331.properties.event_types.items.properties.severity.enum + allowedValues: schema335.properties.event_types.items.properties.severity.enum } } ], !1; var valid6 = 0 === _errs15; @@ -14544,7 +14544,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data3.dynamic_severity) { let _errs17 = 0; - if ("boolean" != typeof data3.dynamic_severity) return validate303.errors = [ { + if ("boolean" != typeof data3.dynamic_severity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/dynamic_severity", schemaPath: "#/properties/event_types/items/properties/dynamic_severity/type", keyword: "type", @@ -14557,7 +14557,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data3.minimum_severity) { let data8 = data3.minimum_severity, _errs19 = 0; - if ("string" != typeof data8) return validate303.errors = [ { + if ("string" != typeof data8) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/minimum_severity", schemaPath: "#/properties/event_types/items/properties/minimum_severity/type", keyword: "type", @@ -14565,12 +14565,12 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("debug" !== data8 && "info" !== data8 && "notice" !== data8 && "warning" !== data8 && "error" !== data8 && "critical" !== data8) return validate303.errors = [ { + if ("debug" !== data8 && "info" !== data8 && "notice" !== data8 && "warning" !== data8 && "error" !== data8 && "critical" !== data8) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/minimum_severity", schemaPath: "#/properties/event_types/items/properties/minimum_severity/enum", keyword: "enum", params: { - allowedValues: schema331.properties.event_types.items.properties.minimum_severity.enum + allowedValues: schema335.properties.event_types.items.properties.minimum_severity.enum } } ], !1; var valid8 = 0 === _errs19; @@ -14578,7 +14578,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data3.maximum_severity) { let data9 = data3.maximum_severity, _errs21 = 0; - if ("string" != typeof data9) return validate303.errors = [ { + if ("string" != typeof data9) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/maximum_severity", schemaPath: "#/properties/event_types/items/properties/maximum_severity/type", keyword: "type", @@ -14586,12 +14586,12 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("debug" !== data9 && "info" !== data9 && "notice" !== data9 && "warning" !== data9 && "error" !== data9 && "critical" !== data9) return validate303.errors = [ { + if ("debug" !== data9 && "info" !== data9 && "notice" !== data9 && "warning" !== data9 && "error" !== data9 && "critical" !== data9) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/maximum_severity", schemaPath: "#/properties/event_types/items/properties/maximum_severity/enum", keyword: "enum", params: { - allowedValues: schema331.properties.event_types.items.properties.maximum_severity.enum + allowedValues: schema335.properties.event_types.items.properties.maximum_severity.enum } } ], !1; var valid9 = 0 === _errs21; @@ -14599,7 +14599,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data3.subject_kind) { let data10 = data3.subject_kind, _errs23 = 0; - if ("string" != typeof data10) return validate303.errors = [ { + if ("string" != typeof data10) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/subject_kind", schemaPath: "#/properties/event_types/items/properties/subject_kind/type", keyword: "type", @@ -14607,12 +14607,12 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("camera" !== data10 && "storage" !== data10 && "system" !== data10) return validate303.errors = [ { + if ("camera" !== data10 && "storage" !== data10 && "system" !== data10) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/subject_kind", schemaPath: "#/properties/event_types/items/properties/subject_kind/enum", keyword: "enum", params: { - allowedValues: schema331.properties.event_types.items.properties.subject_kind.enum + allowedValues: schema335.properties.event_types.items.properties.subject_kind.enum } } ], !1; var valid10 = 0 === _errs23; @@ -14620,7 +14620,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data3.family) { let _errs25 = 0; - if ("string" != typeof data3.family) return validate303.errors = [ { + if ("string" != typeof data3.family) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/family", schemaPath: "#/properties/event_types/items/properties/family/type", keyword: "type", @@ -14633,7 +14633,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data3.description) { let _errs27 = 0; - if ("string" != typeof data3.description) return validate303.errors = [ { + if ("string" != typeof data3.description) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/description", schemaPath: "#/properties/event_types/items/properties/description/type", keyword: "type", @@ -14646,7 +14646,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data3.sensitivity) { let _errs29 = 0; - if ("string" != typeof data3.sensitivity) return validate303.errors = [ { + if ("string" != typeof data3.sensitivity) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/sensitivity", schemaPath: "#/properties/event_types/items/properties/sensitivity/type", keyword: "type", @@ -14659,7 +14659,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data3.media_policy) { let _errs31 = 0; - if ("string" != typeof data3.media_policy) return validate303.errors = [ { + if ("string" != typeof data3.media_policy) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/media_policy", schemaPath: "#/properties/event_types/items/properties/media_policy/type", keyword: "type", @@ -14671,7 +14671,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD } else valid14 = !0; if (valid14 && void 0 !== data3.default_expiry_seconds) { let data15 = data3.default_expiry_seconds, _errs33 = 0; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate303.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/default_expiry_seconds", schemaPath: "#/properties/event_types/items/properties/default_expiry_seconds/type", keyword: "type", @@ -14679,7 +14679,7 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs33 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate303.errors = [ { + if (0 === _errs33 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate306.errors = [ { instancePath: instancePath + "/event_types/" + i0 + "/default_expiry_seconds", schemaPath: "#/properties/event_types/items/properties/default_expiry_seconds/minimum", keyword: "minimum", @@ -14709,16 +14709,16 @@ function validate303(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate303.errors = [], !0; + return validate306.errors = [], !0; } -validate303.evaluated = { +validate306.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventDestination = validate285, schema310 = { +var __contract_EventDestination = validate288, schema314 = { type: "object", properties: { uuid: { @@ -14844,12 +14844,12 @@ var __contract_EventDestination = validate285, schema310 = { }, required: [ "uuid", "key", "name", "description", "enabled", "type", "broker", "authentication", "tls", "managed", "revision", "created_at", "updated_at" ], additionalProperties: !1 -}, formats2 = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; +}, formats6 = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; -function validate285(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate285.evaluated; +function validate288(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate288.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate285.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -14857,7 +14857,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate285.errors = [ { + if (void 0 === data.uuid) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14865,7 +14865,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.key) return validate285.errors = [ { + if (void 0 === data.key) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14873,7 +14873,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data.name) return validate285.errors = [ { + if (void 0 === data.name) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14881,7 +14881,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.description) return validate285.errors = [ { + if (void 0 === data.description) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14889,7 +14889,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "description" } } ], !1; - if (void 0 === data.enabled) return validate285.errors = [ { + if (void 0 === data.enabled) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14897,7 +14897,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.type) return validate285.errors = [ { + if (void 0 === data.type) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14905,7 +14905,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data.broker) return validate285.errors = [ { + if (void 0 === data.broker) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14913,7 +14913,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "broker" } } ], !1; - if (void 0 === data.authentication) return validate285.errors = [ { + if (void 0 === data.authentication) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14921,7 +14921,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "authentication" } } ], !1; - if (void 0 === data.tls) return validate285.errors = [ { + if (void 0 === data.tls) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14929,7 +14929,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "tls" } } ], !1; - if (void 0 === data.managed) return validate285.errors = [ { + if (void 0 === data.managed) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14937,7 +14937,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "managed" } } ], !1; - if (void 0 === data.revision) return validate285.errors = [ { + if (void 0 === data.revision) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14945,7 +14945,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate285.errors = [ { + if (void 0 === data.created_at) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14953,7 +14953,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate285.errors = [ { + if (void 0 === data.updated_at) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -14963,7 +14963,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema310.properties, key0)) return validate285.errors = [ { + for (let key0 in data) if (!func7.call(schema314.properties, key0)) return validate288.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -14975,7 +14975,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate285.errors = [ { + if ("string" != typeof data0) return validate288.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/properties/uuid/type", keyword: "type", @@ -14983,7 +14983,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!formats2.test(data0)) return validate285.errors = [ { + if (!formats6.test(data0)) return validate288.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/properties/uuid/format", keyword: "format", @@ -14998,7 +14998,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.key) { let data1 = data.key, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate285.errors = [ { + if ("string" != typeof data1) return validate288.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", @@ -15006,7 +15006,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 63) return validate285.errors = [ { + if (func1(data1) > 63) return validate288.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/maxLength", keyword: "maxLength", @@ -15014,7 +15014,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data1) < 1) return validate285.errors = [ { + if (func1(data1) < 1) return validate288.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/minLength", keyword: "minLength", @@ -15029,7 +15029,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data2 = data.name, _errs6 = 0; if (0 === _errs6) { - if ("string" != typeof data2) return validate285.errors = [ { + if ("string" != typeof data2) return validate288.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -15037,7 +15037,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 127) return validate285.errors = [ { + if (func1(data2) > 127) return validate288.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -15045,7 +15045,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data2) < 1) return validate285.errors = [ { + if (func1(data2) < 1) return validate288.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -15060,7 +15060,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data3 = data.description, _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data3) return validate285.errors = [ { + if ("string" != typeof data3) return validate288.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -15068,7 +15068,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 511) return validate285.errors = [ { + if (func1(data3) > 511) return validate288.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -15082,7 +15082,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.enabled) { let _errs10 = 0; - if ("boolean" != typeof data.enabled) return validate285.errors = [ { + if ("boolean" != typeof data.enabled) return validate288.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -15095,7 +15095,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.type) { let data5 = data.type, _errs12 = 0; - if ("string" != typeof data5) return validate285.errors = [ { + if ("string" != typeof data5) return validate288.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -15103,7 +15103,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("mqtt" !== data5) return validate285.errors = [ { + if ("mqtt" !== data5) return validate288.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/const", keyword: "const", @@ -15117,7 +15117,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.broker) { let data6 = data.broker, _errs14 = 0; if (0 === _errs14) { - if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate285.errors = [ { + if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/type", keyword: "type", @@ -15125,7 +15125,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data6.host) return validate285.errors = [ { + if (void 0 === data6.host) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15133,7 +15133,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "host" } } ], !1; - if (void 0 === data6.port) return validate285.errors = [ { + if (void 0 === data6.port) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15141,7 +15141,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "port" } } ], !1; - if (void 0 === data6.client_id) return validate285.errors = [ { + if (void 0 === data6.client_id) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15149,7 +15149,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "client_id" } } ], !1; - if (void 0 === data6.topic_template) return validate285.errors = [ { + if (void 0 === data6.topic_template) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15157,7 +15157,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "topic_template" } } ], !1; - if (void 0 === data6.status_topic_template) return validate285.errors = [ { + if (void 0 === data6.status_topic_template) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15165,7 +15165,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status_topic_template" } } ], !1; - if (void 0 === data6.keepalive_seconds) return validate285.errors = [ { + if (void 0 === data6.keepalive_seconds) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15173,7 +15173,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "keepalive_seconds" } } ], !1; - if (void 0 === data6.qos) return validate285.errors = [ { + if (void 0 === data6.qos) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -15183,7 +15183,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs16 = 0; - for (let key1 in data6) if ("host" !== key1 && "port" !== key1 && "client_id" !== key1 && "topic_template" !== key1 && "status_topic_template" !== key1 && "keepalive_seconds" !== key1 && "qos" !== key1) return validate285.errors = [ { + for (let key1 in data6) if ("host" !== key1 && "port" !== key1 && "client_id" !== key1 && "topic_template" !== key1 && "status_topic_template" !== key1 && "keepalive_seconds" !== key1 && "qos" !== key1) return validate288.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/additionalProperties", keyword: "additionalProperties", @@ -15195,7 +15195,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data6.host) { let data7 = data6.host, _errs17 = 0; if (0 === _errs17) { - if ("string" != typeof data7) return validate285.errors = [ { + if ("string" != typeof data7) return validate288.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/type", keyword: "type", @@ -15203,7 +15203,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data7) > 255) return validate285.errors = [ { + if (func1(data7) > 255) return validate288.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/maxLength", keyword: "maxLength", @@ -15211,7 +15211,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 255 } } ], !1; - if (func1(data7) < 1) return validate285.errors = [ { + if (func1(data7) < 1) return validate288.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/minLength", keyword: "minLength", @@ -15225,7 +15225,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data6.port) { let data8 = data6.port, _errs19 = 0; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate285.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate288.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/type", keyword: "type", @@ -15234,7 +15234,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs19 && "number" == typeof data8 && isFinite(data8)) { - if (data8 > 65535 || isNaN(data8)) return validate285.errors = [ { + if (data8 > 65535 || isNaN(data8)) return validate288.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/maximum", keyword: "maximum", @@ -15243,7 +15243,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 65535 } } ], !1; - if (data8 < 1 || isNaN(data8)) return validate285.errors = [ { + if (data8 < 1 || isNaN(data8)) return validate288.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/minimum", keyword: "minimum", @@ -15259,7 +15259,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data6.client_id) { let data9 = data6.client_id, _errs21 = 0; if (0 === _errs21) { - if ("string" != typeof data9) return validate285.errors = [ { + if ("string" != typeof data9) return validate288.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/type", keyword: "type", @@ -15267,7 +15267,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 127) return validate285.errors = [ { + if (func1(data9) > 127) return validate288.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/maxLength", keyword: "maxLength", @@ -15275,7 +15275,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data9) < 1) return validate285.errors = [ { + if (func1(data9) < 1) return validate288.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/minLength", keyword: "minLength", @@ -15290,7 +15290,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data6.topic_template) { let data10 = data6.topic_template, _errs23 = 0; if (0 === _errs23) { - if ("string" != typeof data10) return validate285.errors = [ { + if ("string" != typeof data10) return validate288.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/type", keyword: "type", @@ -15298,7 +15298,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data10) > 511) return validate285.errors = [ { + if (func1(data10) > 511) return validate288.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/maxLength", keyword: "maxLength", @@ -15306,7 +15306,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 511 } } ], !1; - if (func1(data10) < 1) return validate285.errors = [ { + if (func1(data10) < 1) return validate288.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/minLength", keyword: "minLength", @@ -15321,7 +15321,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data6.status_topic_template) { let data11 = data6.status_topic_template, _errs25 = 0; if (0 === _errs25) { - if ("string" != typeof data11) return validate285.errors = [ { + if ("string" != typeof data11) return validate288.errors = [ { instancePath: instancePath + "/broker/status_topic_template", schemaPath: "#/properties/broker/properties/status_topic_template/type", keyword: "type", @@ -15329,7 +15329,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 511) return validate285.errors = [ { + if (func1(data11) > 511) return validate288.errors = [ { instancePath: instancePath + "/broker/status_topic_template", schemaPath: "#/properties/broker/properties/status_topic_template/maxLength", keyword: "maxLength", @@ -15343,7 +15343,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data6.keepalive_seconds) { let data12 = data6.keepalive_seconds, _errs27 = 0; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate285.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate288.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/type", keyword: "type", @@ -15352,7 +15352,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs27 && "number" == typeof data12 && isFinite(data12)) { - if (data12 > 3600 || isNaN(data12)) return validate285.errors = [ { + if (data12 > 3600 || isNaN(data12)) return validate288.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/maximum", keyword: "maximum", @@ -15361,7 +15361,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data12 < 5 || isNaN(data12)) return validate285.errors = [ { + if (data12 < 5 || isNaN(data12)) return validate288.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/minimum", keyword: "minimum", @@ -15375,7 +15375,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } else valid12 = !0; if (valid12 && void 0 !== data6.qos) { let data13 = data6.qos, _errs29 = 0; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate285.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate288.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/type", keyword: "type", @@ -15384,7 +15384,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs29 && "number" == typeof data13 && isFinite(data13)) { - if (data13 > 2 || isNaN(data13)) return validate285.errors = [ { + if (data13 > 2 || isNaN(data13)) return validate288.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/maximum", keyword: "maximum", @@ -15393,7 +15393,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 2 } } ], !1; - if (data13 < 0 || isNaN(data13)) return validate285.errors = [ { + if (data13 < 0 || isNaN(data13)) return validate288.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/minimum", keyword: "minimum", @@ -15418,7 +15418,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.authentication) { let data14 = data.authentication, _errs31 = 0; if (0 === _errs31) { - if (!data14 || "object" != typeof data14 || Array.isArray(data14)) return validate285.errors = [ { + if (!data14 || "object" != typeof data14 || Array.isArray(data14)) return validate288.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/type", keyword: "type", @@ -15426,7 +15426,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data14.username) return validate285.errors = [ { + if (void 0 === data14.username) return validate288.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/required", keyword: "required", @@ -15434,7 +15434,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "username" } } ], !1; - if (void 0 === data14.password_configured) return validate285.errors = [ { + if (void 0 === data14.password_configured) return validate288.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/required", keyword: "required", @@ -15444,7 +15444,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs33 = 0; - for (let key2 in data14) if ("username" !== key2 && "password_configured" !== key2) return validate285.errors = [ { + for (let key2 in data14) if ("username" !== key2 && "password_configured" !== key2) return validate288.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/additionalProperties", keyword: "additionalProperties", @@ -15456,7 +15456,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data14.username) { let data15 = data14.username, _errs34 = 0; if (0 === _errs34) { - if ("string" != typeof data15) return validate285.errors = [ { + if ("string" != typeof data15) return validate288.errors = [ { instancePath: instancePath + "/authentication/username", schemaPath: "#/properties/authentication/properties/username/type", keyword: "type", @@ -15464,7 +15464,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data15) > 127) return validate285.errors = [ { + if (func1(data15) > 127) return validate288.errors = [ { instancePath: instancePath + "/authentication/username", schemaPath: "#/properties/authentication/properties/username/maxLength", keyword: "maxLength", @@ -15475,7 +15475,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } var valid15 = 0 === _errs34; } else valid15 = !0; - if (valid15 && void 0 !== data14.password_configured && "boolean" != typeof data14.password_configured) return validate285.errors = [ { + if (valid15 && void 0 !== data14.password_configured && "boolean" != typeof data14.password_configured) return validate288.errors = [ { instancePath: instancePath + "/authentication/password_configured", schemaPath: "#/properties/authentication/properties/password_configured/type", keyword: "type", @@ -15492,7 +15492,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.tls) { let data17 = data.tls, _errs38 = 0; if (0 === _errs38) { - if (!data17 || "object" != typeof data17 || Array.isArray(data17)) return validate285.errors = [ { + if (!data17 || "object" != typeof data17 || Array.isArray(data17)) return validate288.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/type", keyword: "type", @@ -15500,7 +15500,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data17.mode) return validate285.errors = [ { + if (void 0 === data17.mode) return validate288.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/required", keyword: "required", @@ -15508,7 +15508,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "mode" } } ], !1; - if (void 0 === data17.ca_file) return validate285.errors = [ { + if (void 0 === data17.ca_file) return validate288.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/required", keyword: "required", @@ -15516,7 +15516,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "ca_file" } } ], !1; - if (void 0 === data17.cert_file) return validate285.errors = [ { + if (void 0 === data17.cert_file) return validate288.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/required", keyword: "required", @@ -15524,7 +15524,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cert_file" } } ], !1; - if (void 0 === data17.key_file) return validate285.errors = [ { + if (void 0 === data17.key_file) return validate288.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/required", keyword: "required", @@ -15534,7 +15534,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs40 = 0; - for (let key3 in data17) if ("mode" !== key3 && "ca_file" !== key3 && "cert_file" !== key3 && "key_file" !== key3) return validate285.errors = [ { + for (let key3 in data17) if ("mode" !== key3 && "ca_file" !== key3 && "cert_file" !== key3 && "key_file" !== key3) return validate288.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/additionalProperties", keyword: "additionalProperties", @@ -15545,7 +15545,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs40) { if (void 0 !== data17.mode) { let data18 = data17.mode, _errs41 = 0; - if ("string" != typeof data18) return validate285.errors = [ { + if ("string" != typeof data18) return validate288.errors = [ { instancePath: instancePath + "/tls/mode", schemaPath: "#/properties/tls/properties/mode/type", keyword: "type", @@ -15553,12 +15553,12 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("disabled" !== data18 && "system" !== data18 && "custom_ca" !== data18 && "mutual" !== data18) return validate285.errors = [ { + if ("disabled" !== data18 && "system" !== data18 && "custom_ca" !== data18 && "mutual" !== data18) return validate288.errors = [ { instancePath: instancePath + "/tls/mode", schemaPath: "#/properties/tls/properties/mode/enum", keyword: "enum", params: { - allowedValues: schema310.properties.tls.properties.mode.enum + allowedValues: schema314.properties.tls.properties.mode.enum } } ], !1; var valid18 = 0 === _errs41; @@ -15567,7 +15567,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data17.ca_file) { let data19 = data17.ca_file, _errs43 = 0; if (0 === _errs43) { - if ("string" != typeof data19) return validate285.errors = [ { + if ("string" != typeof data19) return validate288.errors = [ { instancePath: instancePath + "/tls/ca_file", schemaPath: "#/properties/tls/properties/ca_file/type", keyword: "type", @@ -15575,7 +15575,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data19) > 511) return validate285.errors = [ { + if (func1(data19) > 511) return validate288.errors = [ { instancePath: instancePath + "/tls/ca_file", schemaPath: "#/properties/tls/properties/ca_file/maxLength", keyword: "maxLength", @@ -15590,7 +15590,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data17.cert_file) { let data20 = data17.cert_file, _errs45 = 0; if (0 === _errs45) { - if ("string" != typeof data20) return validate285.errors = [ { + if ("string" != typeof data20) return validate288.errors = [ { instancePath: instancePath + "/tls/cert_file", schemaPath: "#/properties/tls/properties/cert_file/type", keyword: "type", @@ -15598,7 +15598,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data20) > 511) return validate285.errors = [ { + if (func1(data20) > 511) return validate288.errors = [ { instancePath: instancePath + "/tls/cert_file", schemaPath: "#/properties/tls/properties/cert_file/maxLength", keyword: "maxLength", @@ -15611,7 +15611,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } else valid20 = !0; if (valid20 && void 0 !== data17.key_file) { let data21 = data17.key_file; - if ("string" != typeof data21) return validate285.errors = [ { + if ("string" != typeof data21) return validate288.errors = [ { instancePath: instancePath + "/tls/key_file", schemaPath: "#/properties/tls/properties/key_file/type", keyword: "type", @@ -15619,7 +15619,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data21) > 511) return validate285.errors = [ { + if (func1(data21) > 511) return validate288.errors = [ { instancePath: instancePath + "/tls/key_file", schemaPath: "#/properties/tls/properties/key_file/maxLength", keyword: "maxLength", @@ -15638,7 +15638,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data.managed) { let data22 = data.managed, _errs49 = 0; - if ("boolean" != typeof data22) return validate285.errors = [ { + if ("boolean" != typeof data22) return validate288.errors = [ { instancePath: instancePath + "/managed", schemaPath: "#/properties/managed/type", keyword: "type", @@ -15646,7 +15646,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data22) return validate285.errors = [ { + if (!0 !== data22) return validate288.errors = [ { instancePath: instancePath + "/managed", schemaPath: "#/properties/managed/const", keyword: "const", @@ -15659,7 +15659,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data.revision) { let data23 = data.revision, _errs51 = 0; - if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate285.errors = [ { + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate288.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -15668,7 +15668,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs51 && "number" == typeof data23 && isFinite(data23)) { - if (data23 > 9007199254740991 || isNaN(data23)) return validate285.errors = [ { + if (data23 > 9007199254740991 || isNaN(data23)) return validate288.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -15677,7 +15677,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data23 < 1 || isNaN(data23)) return validate285.errors = [ { + if (data23 < 1 || isNaN(data23)) return validate288.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -15692,7 +15692,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data.created_at) { let data24 = data.created_at, _errs53 = 0; - if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate285.errors = [ { + if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate288.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -15700,7 +15700,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs53 && "number" == typeof data24 && isFinite(data24) && (data24 < 0 || isNaN(data24))) return validate285.errors = [ { + if (0 === _errs53 && "number" == typeof data24 && isFinite(data24) && (data24 < 0 || isNaN(data24))) return validate288.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -15713,7 +15713,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } else valid24 = !0; if (valid24 && void 0 !== data.updated_at) { let data25 = data.updated_at, _errs55 = 0; - if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate285.errors = [ { + if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate288.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -15721,7 +15721,7 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs55 && "number" == typeof data25 && isFinite(data25) && (data25 < 0 || isNaN(data25))) return validate285.errors = [ { + if (0 === _errs55 && "number" == typeof data25 && isFinite(data25) && (data25 < 0 || isNaN(data25))) return validate288.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/minimum", keyword: "minimum", @@ -15744,16 +15744,16 @@ function validate285(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate285.errors = [], !0; + return validate288.errors = [], !0; } -validate285.evaluated = { +validate288.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventDestinationCreate = validate287, schema313 = { +var __contract_EventDestinationCreate = validate290, schema317 = { type: "object", properties: { name: { @@ -15856,10 +15856,10 @@ var __contract_EventDestinationCreate = validate287, schema313 = { additionalProperties: !1 }; -function validate287(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate287.evaluated; +function validate290(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate290.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate287.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate290.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -15867,7 +15867,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate287.errors = [ { + if (void 0 === data.name) return validate290.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -15875,7 +15875,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.broker) return validate287.errors = [ { + if (void 0 === data.broker) return validate290.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -15885,7 +15885,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("name" !== key0 && "description" !== key0 && "enabled" !== key0 && "type" !== key0 && "broker" !== key0 && "authentication" !== key0 && "tls" !== key0) return validate287.errors = [ { + for (let key0 in data) if ("name" !== key0 && "description" !== key0 && "enabled" !== key0 && "type" !== key0 && "broker" !== key0 && "authentication" !== key0 && "tls" !== key0) return validate290.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -15897,7 +15897,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate287.errors = [ { + if ("string" != typeof data0) return validate290.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -15905,7 +15905,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate287.errors = [ { + if (func1(data0) > 127) return validate290.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -15913,7 +15913,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate287.errors = [ { + if (func1(data0) < 1) return validate290.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -15928,7 +15928,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data1 = data.description, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate287.errors = [ { + if ("string" != typeof data1) return validate290.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -15936,7 +15936,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 511) return validate287.errors = [ { + if (func1(data1) > 511) return validate290.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -15950,7 +15950,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = 0; - if ("boolean" != typeof data.enabled) return validate287.errors = [ { + if ("boolean" != typeof data.enabled) return validate290.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -15963,7 +15963,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.type) { let data3 = data.type, _errs8 = 0; - if ("string" != typeof data3) return validate287.errors = [ { + if ("string" != typeof data3) return validate290.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -15971,7 +15971,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("mqtt" !== data3) return validate287.errors = [ { + if ("mqtt" !== data3) return validate290.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/const", keyword: "const", @@ -15985,7 +15985,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.broker) { let data4 = data.broker, _errs10 = 0; if (0 === _errs10) { - if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate287.errors = [ { + if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate290.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/type", keyword: "type", @@ -15993,7 +15993,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data4.host) return validate287.errors = [ { + if (void 0 === data4.host) return validate290.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/required", keyword: "required", @@ -16003,7 +16003,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs12 = 0; - for (let key1 in data4) if ("host" !== key1 && "port" !== key1 && "client_id" !== key1 && "topic_template" !== key1 && "status_topic_template" !== key1 && "keepalive_seconds" !== key1 && "qos" !== key1) return validate287.errors = [ { + for (let key1 in data4) if ("host" !== key1 && "port" !== key1 && "client_id" !== key1 && "topic_template" !== key1 && "status_topic_template" !== key1 && "keepalive_seconds" !== key1 && "qos" !== key1) return validate290.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/additionalProperties", keyword: "additionalProperties", @@ -16015,7 +16015,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.host) { let data5 = data4.host, _errs13 = 0; if (0 === _errs13) { - if ("string" != typeof data5) return validate287.errors = [ { + if ("string" != typeof data5) return validate290.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/type", keyword: "type", @@ -16023,7 +16023,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 255) return validate287.errors = [ { + if (func1(data5) > 255) return validate290.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/maxLength", keyword: "maxLength", @@ -16031,7 +16031,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 255 } } ], !1; - if (func1(data5) < 1) return validate287.errors = [ { + if (func1(data5) < 1) return validate290.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/minLength", keyword: "minLength", @@ -16045,7 +16045,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data4.port) { let data6 = data4.port, _errs15 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate287.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate290.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/type", keyword: "type", @@ -16054,7 +16054,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs15 && "number" == typeof data6 && isFinite(data6)) { - if (data6 > 65535 || isNaN(data6)) return validate287.errors = [ { + if (data6 > 65535 || isNaN(data6)) return validate290.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/maximum", keyword: "maximum", @@ -16063,7 +16063,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 65535 } } ], !1; - if (data6 < 1 || isNaN(data6)) return validate287.errors = [ { + if (data6 < 1 || isNaN(data6)) return validate290.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/minimum", keyword: "minimum", @@ -16079,7 +16079,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.client_id) { let data7 = data4.client_id, _errs17 = 0; if (0 === _errs17) { - if ("string" != typeof data7) return validate287.errors = [ { + if ("string" != typeof data7) return validate290.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/type", keyword: "type", @@ -16087,7 +16087,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data7) > 127) return validate287.errors = [ { + if (func1(data7) > 127) return validate290.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/maxLength", keyword: "maxLength", @@ -16095,7 +16095,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data7) < 1) return validate287.errors = [ { + if (func1(data7) < 1) return validate290.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/minLength", keyword: "minLength", @@ -16110,7 +16110,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.topic_template) { let data8 = data4.topic_template, _errs19 = 0; if (0 === _errs19) { - if ("string" != typeof data8) return validate287.errors = [ { + if ("string" != typeof data8) return validate290.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/type", keyword: "type", @@ -16118,7 +16118,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data8) > 511) return validate287.errors = [ { + if (func1(data8) > 511) return validate290.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/maxLength", keyword: "maxLength", @@ -16126,7 +16126,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 511 } } ], !1; - if (func1(data8) < 1) return validate287.errors = [ { + if (func1(data8) < 1) return validate290.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/minLength", keyword: "minLength", @@ -16141,7 +16141,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.status_topic_template) { let data9 = data4.status_topic_template, _errs21 = 0; if (0 === _errs21) { - if ("string" != typeof data9) return validate287.errors = [ { + if ("string" != typeof data9) return validate290.errors = [ { instancePath: instancePath + "/broker/status_topic_template", schemaPath: "#/properties/broker/properties/status_topic_template/type", keyword: "type", @@ -16149,7 +16149,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 511) return validate287.errors = [ { + if (func1(data9) > 511) return validate290.errors = [ { instancePath: instancePath + "/broker/status_topic_template", schemaPath: "#/properties/broker/properties/status_topic_template/maxLength", keyword: "maxLength", @@ -16163,7 +16163,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data4.keepalive_seconds) { let data10 = data4.keepalive_seconds, _errs23 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate287.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate290.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/type", keyword: "type", @@ -16172,7 +16172,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs23 && "number" == typeof data10 && isFinite(data10)) { - if (data10 > 3600 || isNaN(data10)) return validate287.errors = [ { + if (data10 > 3600 || isNaN(data10)) return validate290.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/maximum", keyword: "maximum", @@ -16181,7 +16181,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data10 < 5 || isNaN(data10)) return validate287.errors = [ { + if (data10 < 5 || isNaN(data10)) return validate290.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/minimum", keyword: "minimum", @@ -16195,7 +16195,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } else valid10 = !0; if (valid10 && void 0 !== data4.qos) { let data11 = data4.qos, _errs25 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate287.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate290.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/type", keyword: "type", @@ -16204,7 +16204,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs25 && "number" == typeof data11 && isFinite(data11)) { - if (data11 > 2 || isNaN(data11)) return validate287.errors = [ { + if (data11 > 2 || isNaN(data11)) return validate290.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/maximum", keyword: "maximum", @@ -16213,7 +16213,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD limit: 2 } } ], !1; - if (data11 < 0 || isNaN(data11)) return validate287.errors = [ { + if (data11 < 0 || isNaN(data11)) return validate290.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/minimum", keyword: "minimum", @@ -16238,7 +16238,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.authentication) { let data12 = data.authentication, _errs27 = 0; if (0 === _errs27) { - if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate287.errors = [ { + if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate290.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/type", keyword: "type", @@ -16248,7 +16248,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs29 = 0; - for (let key2 in data12) if ("username" !== key2 && "password" !== key2) return validate287.errors = [ { + for (let key2 in data12) if ("username" !== key2 && "password" !== key2) return validate290.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/additionalProperties", keyword: "additionalProperties", @@ -16260,7 +16260,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data12.username) { let data13 = data12.username, _errs30 = 0; if (0 === _errs30) { - if ("string" != typeof data13) return validate287.errors = [ { + if ("string" != typeof data13) return validate290.errors = [ { instancePath: instancePath + "/authentication/username", schemaPath: "#/properties/authentication/properties/username/type", keyword: "type", @@ -16268,7 +16268,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate287.errors = [ { + if (func1(data13) > 127) return validate290.errors = [ { instancePath: instancePath + "/authentication/username", schemaPath: "#/properties/authentication/properties/username/maxLength", keyword: "maxLength", @@ -16281,15 +16281,15 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } else valid13 = !0; if (valid13 && void 0 !== data12.password) { let data14 = data12.password, _errs32 = 0; - if ("string" != typeof data14 && null !== data14) return validate287.errors = [ { + if ("string" != typeof data14 && null !== data14) return validate290.errors = [ { instancePath: instancePath + "/authentication/password", schemaPath: "#/properties/authentication/properties/password/type", keyword: "type", params: { - type: schema313.properties.authentication.properties.password.type + type: schema317.properties.authentication.properties.password.type } } ], !1; - if (0 === _errs32 && "string" == typeof data14 && func1(data14) > 255) return validate287.errors = [ { + if (0 === _errs32 && "string" == typeof data14 && func1(data14) > 255) return validate290.errors = [ { instancePath: instancePath + "/authentication/password", schemaPath: "#/properties/authentication/properties/password/maxLength", keyword: "maxLength", @@ -16305,7 +16305,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } else valid12 = !0; if (valid12 && void 0 !== data.tls) { let data15 = data.tls; - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate287.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate290.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/type", keyword: "type", @@ -16315,7 +16315,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs36 = 0; - for (let key3 in data15) if ("mode" !== key3 && "ca_file" !== key3 && "cert_file" !== key3 && "key_file" !== key3) return validate287.errors = [ { + for (let key3 in data15) if ("mode" !== key3 && "ca_file" !== key3 && "cert_file" !== key3 && "key_file" !== key3) return validate290.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/additionalProperties", keyword: "additionalProperties", @@ -16326,7 +16326,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs36) { if (void 0 !== data15.mode) { let data16 = data15.mode, _errs37 = 0; - if ("string" != typeof data16) return validate287.errors = [ { + if ("string" != typeof data16) return validate290.errors = [ { instancePath: instancePath + "/tls/mode", schemaPath: "#/properties/tls/properties/mode/type", keyword: "type", @@ -16334,12 +16334,12 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("disabled" !== data16 && "system" !== data16 && "custom_ca" !== data16 && "mutual" !== data16) return validate287.errors = [ { + if ("disabled" !== data16 && "system" !== data16 && "custom_ca" !== data16 && "mutual" !== data16) return validate290.errors = [ { instancePath: instancePath + "/tls/mode", schemaPath: "#/properties/tls/properties/mode/enum", keyword: "enum", params: { - allowedValues: schema313.properties.tls.properties.mode.enum + allowedValues: schema317.properties.tls.properties.mode.enum } } ], !1; var valid16 = 0 === _errs37; @@ -16348,7 +16348,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.ca_file) { let data17 = data15.ca_file, _errs39 = 0; if (0 === _errs39) { - if ("string" != typeof data17) return validate287.errors = [ { + if ("string" != typeof data17) return validate290.errors = [ { instancePath: instancePath + "/tls/ca_file", schemaPath: "#/properties/tls/properties/ca_file/type", keyword: "type", @@ -16356,7 +16356,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data17) > 511) return validate287.errors = [ { + if (func1(data17) > 511) return validate290.errors = [ { instancePath: instancePath + "/tls/ca_file", schemaPath: "#/properties/tls/properties/ca_file/maxLength", keyword: "maxLength", @@ -16371,7 +16371,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.cert_file) { let data18 = data15.cert_file, _errs41 = 0; if (0 === _errs41) { - if ("string" != typeof data18) return validate287.errors = [ { + if ("string" != typeof data18) return validate290.errors = [ { instancePath: instancePath + "/tls/cert_file", schemaPath: "#/properties/tls/properties/cert_file/type", keyword: "type", @@ -16379,7 +16379,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data18) > 511) return validate287.errors = [ { + if (func1(data18) > 511) return validate290.errors = [ { instancePath: instancePath + "/tls/cert_file", schemaPath: "#/properties/tls/properties/cert_file/maxLength", keyword: "maxLength", @@ -16392,7 +16392,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } else valid18 = !0; if (valid18 && void 0 !== data15.key_file) { let data19 = data15.key_file; - if ("string" != typeof data19) return validate287.errors = [ { + if ("string" != typeof data19) return validate290.errors = [ { instancePath: instancePath + "/tls/key_file", schemaPath: "#/properties/tls/properties/key_file/type", keyword: "type", @@ -16400,7 +16400,7 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data19) > 511) return validate287.errors = [ { + if (func1(data19) > 511) return validate290.errors = [ { instancePath: instancePath + "/tls/key_file", schemaPath: "#/properties/tls/properties/key_file/maxLength", keyword: "maxLength", @@ -16421,16 +16421,16 @@ function validate287(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate287.errors = [], !0; + return validate290.errors = [], !0; } -validate287.evaluated = { +validate290.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventDestinationUpdate = validate288, schema314 = { +var __contract_EventDestinationUpdate = validate291, schema318 = { type: "object", properties: { name: { @@ -16538,10 +16538,10 @@ var __contract_EventDestinationUpdate = validate288, schema314 = { additionalProperties: !1 }; -function validate288(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate288.evaluated; +function validate291(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate291.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate288.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate291.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -16549,7 +16549,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate288.errors = [ { + if (void 0 === data.revision) return validate291.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -16559,7 +16559,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("name" !== key0 && "description" !== key0 && "enabled" !== key0 && "type" !== key0 && "broker" !== key0 && "authentication" !== key0 && "tls" !== key0 && "revision" !== key0) return validate288.errors = [ { + for (let key0 in data) if ("name" !== key0 && "description" !== key0 && "enabled" !== key0 && "type" !== key0 && "broker" !== key0 && "authentication" !== key0 && "tls" !== key0 && "revision" !== key0) return validate291.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -16571,7 +16571,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate288.errors = [ { + if ("string" != typeof data0) return validate291.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -16579,7 +16579,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate288.errors = [ { + if (func1(data0) > 127) return validate291.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -16587,7 +16587,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate288.errors = [ { + if (func1(data0) < 1) return validate291.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -16602,7 +16602,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data1 = data.description, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate288.errors = [ { + if ("string" != typeof data1) return validate291.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -16610,7 +16610,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 511) return validate288.errors = [ { + if (func1(data1) > 511) return validate291.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -16624,7 +16624,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = 0; - if ("boolean" != typeof data.enabled) return validate288.errors = [ { + if ("boolean" != typeof data.enabled) return validate291.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -16637,7 +16637,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.type) { let data3 = data.type, _errs8 = 0; - if ("string" != typeof data3) return validate288.errors = [ { + if ("string" != typeof data3) return validate291.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -16645,7 +16645,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("mqtt" !== data3) return validate288.errors = [ { + if ("mqtt" !== data3) return validate291.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/const", keyword: "const", @@ -16659,7 +16659,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.broker) { let data4 = data.broker, _errs10 = 0; if (0 === _errs10) { - if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate288.errors = [ { + if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate291.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/type", keyword: "type", @@ -16669,7 +16669,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs12 = 0; - for (let key1 in data4) if ("host" !== key1 && "port" !== key1 && "client_id" !== key1 && "topic_template" !== key1 && "status_topic_template" !== key1 && "keepalive_seconds" !== key1 && "qos" !== key1) return validate288.errors = [ { + for (let key1 in data4) if ("host" !== key1 && "port" !== key1 && "client_id" !== key1 && "topic_template" !== key1 && "status_topic_template" !== key1 && "keepalive_seconds" !== key1 && "qos" !== key1) return validate291.errors = [ { instancePath: instancePath + "/broker", schemaPath: "#/properties/broker/additionalProperties", keyword: "additionalProperties", @@ -16681,7 +16681,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.host) { let data5 = data4.host, _errs13 = 0; if (0 === _errs13) { - if ("string" != typeof data5) return validate288.errors = [ { + if ("string" != typeof data5) return validate291.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/type", keyword: "type", @@ -16689,7 +16689,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 255) return validate288.errors = [ { + if (func1(data5) > 255) return validate291.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/maxLength", keyword: "maxLength", @@ -16697,7 +16697,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 255 } } ], !1; - if (func1(data5) < 1) return validate288.errors = [ { + if (func1(data5) < 1) return validate291.errors = [ { instancePath: instancePath + "/broker/host", schemaPath: "#/properties/broker/properties/host/minLength", keyword: "minLength", @@ -16711,7 +16711,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data4.port) { let data6 = data4.port, _errs15 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate288.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate291.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/type", keyword: "type", @@ -16720,7 +16720,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs15 && "number" == typeof data6 && isFinite(data6)) { - if (data6 > 65535 || isNaN(data6)) return validate288.errors = [ { + if (data6 > 65535 || isNaN(data6)) return validate291.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/maximum", keyword: "maximum", @@ -16729,7 +16729,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 65535 } } ], !1; - if (data6 < 1 || isNaN(data6)) return validate288.errors = [ { + if (data6 < 1 || isNaN(data6)) return validate291.errors = [ { instancePath: instancePath + "/broker/port", schemaPath: "#/properties/broker/properties/port/minimum", keyword: "minimum", @@ -16745,7 +16745,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.client_id) { let data7 = data4.client_id, _errs17 = 0; if (0 === _errs17) { - if ("string" != typeof data7) return validate288.errors = [ { + if ("string" != typeof data7) return validate291.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/type", keyword: "type", @@ -16753,7 +16753,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data7) > 127) return validate288.errors = [ { + if (func1(data7) > 127) return validate291.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/maxLength", keyword: "maxLength", @@ -16761,7 +16761,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data7) < 1) return validate288.errors = [ { + if (func1(data7) < 1) return validate291.errors = [ { instancePath: instancePath + "/broker/client_id", schemaPath: "#/properties/broker/properties/client_id/minLength", keyword: "minLength", @@ -16776,7 +16776,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.topic_template) { let data8 = data4.topic_template, _errs19 = 0; if (0 === _errs19) { - if ("string" != typeof data8) return validate288.errors = [ { + if ("string" != typeof data8) return validate291.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/type", keyword: "type", @@ -16784,7 +16784,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data8) > 511) return validate288.errors = [ { + if (func1(data8) > 511) return validate291.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/maxLength", keyword: "maxLength", @@ -16792,7 +16792,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 511 } } ], !1; - if (func1(data8) < 1) return validate288.errors = [ { + if (func1(data8) < 1) return validate291.errors = [ { instancePath: instancePath + "/broker/topic_template", schemaPath: "#/properties/broker/properties/topic_template/minLength", keyword: "minLength", @@ -16807,7 +16807,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.status_topic_template) { let data9 = data4.status_topic_template, _errs21 = 0; if (0 === _errs21) { - if ("string" != typeof data9) return validate288.errors = [ { + if ("string" != typeof data9) return validate291.errors = [ { instancePath: instancePath + "/broker/status_topic_template", schemaPath: "#/properties/broker/properties/status_topic_template/type", keyword: "type", @@ -16815,7 +16815,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 511) return validate288.errors = [ { + if (func1(data9) > 511) return validate291.errors = [ { instancePath: instancePath + "/broker/status_topic_template", schemaPath: "#/properties/broker/properties/status_topic_template/maxLength", keyword: "maxLength", @@ -16829,7 +16829,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data4.keepalive_seconds) { let data10 = data4.keepalive_seconds, _errs23 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate288.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate291.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/type", keyword: "type", @@ -16838,7 +16838,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs23 && "number" == typeof data10 && isFinite(data10)) { - if (data10 > 3600 || isNaN(data10)) return validate288.errors = [ { + if (data10 > 3600 || isNaN(data10)) return validate291.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/maximum", keyword: "maximum", @@ -16847,7 +16847,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data10 < 5 || isNaN(data10)) return validate288.errors = [ { + if (data10 < 5 || isNaN(data10)) return validate291.errors = [ { instancePath: instancePath + "/broker/keepalive_seconds", schemaPath: "#/properties/broker/properties/keepalive_seconds/minimum", keyword: "minimum", @@ -16861,7 +16861,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } else valid10 = !0; if (valid10 && void 0 !== data4.qos) { let data11 = data4.qos, _errs25 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate288.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate291.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/type", keyword: "type", @@ -16870,7 +16870,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs25 && "number" == typeof data11 && isFinite(data11)) { - if (data11 > 2 || isNaN(data11)) return validate288.errors = [ { + if (data11 > 2 || isNaN(data11)) return validate291.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/maximum", keyword: "maximum", @@ -16879,7 +16879,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 2 } } ], !1; - if (data11 < 0 || isNaN(data11)) return validate288.errors = [ { + if (data11 < 0 || isNaN(data11)) return validate291.errors = [ { instancePath: instancePath + "/broker/qos", schemaPath: "#/properties/broker/properties/qos/minimum", keyword: "minimum", @@ -16904,7 +16904,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.authentication) { let data12 = data.authentication, _errs27 = 0; if (0 === _errs27) { - if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate288.errors = [ { + if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate291.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/type", keyword: "type", @@ -16914,7 +16914,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs29 = 0; - for (let key2 in data12) if ("username" !== key2 && "password" !== key2) return validate288.errors = [ { + for (let key2 in data12) if ("username" !== key2 && "password" !== key2) return validate291.errors = [ { instancePath: instancePath + "/authentication", schemaPath: "#/properties/authentication/additionalProperties", keyword: "additionalProperties", @@ -16926,7 +16926,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data12.username) { let data13 = data12.username, _errs30 = 0; if (0 === _errs30) { - if ("string" != typeof data13) return validate288.errors = [ { + if ("string" != typeof data13) return validate291.errors = [ { instancePath: instancePath + "/authentication/username", schemaPath: "#/properties/authentication/properties/username/type", keyword: "type", @@ -16934,7 +16934,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate288.errors = [ { + if (func1(data13) > 127) return validate291.errors = [ { instancePath: instancePath + "/authentication/username", schemaPath: "#/properties/authentication/properties/username/maxLength", keyword: "maxLength", @@ -16947,15 +16947,15 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } else valid13 = !0; if (valid13 && void 0 !== data12.password) { let data14 = data12.password, _errs32 = 0; - if ("string" != typeof data14 && null !== data14) return validate288.errors = [ { + if ("string" != typeof data14 && null !== data14) return validate291.errors = [ { instancePath: instancePath + "/authentication/password", schemaPath: "#/properties/authentication/properties/password/type", keyword: "type", params: { - type: schema314.properties.authentication.properties.password.type + type: schema318.properties.authentication.properties.password.type } } ], !1; - if (0 === _errs32 && "string" == typeof data14 && func1(data14) > 255) return validate288.errors = [ { + if (0 === _errs32 && "string" == typeof data14 && func1(data14) > 255) return validate291.errors = [ { instancePath: instancePath + "/authentication/password", schemaPath: "#/properties/authentication/properties/password/maxLength", keyword: "maxLength", @@ -16973,7 +16973,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.tls) { let data15 = data.tls, _errs34 = 0; if (0 === _errs34) { - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate288.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate291.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/type", keyword: "type", @@ -16983,7 +16983,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs36 = 0; - for (let key3 in data15) if ("mode" !== key3 && "ca_file" !== key3 && "cert_file" !== key3 && "key_file" !== key3) return validate288.errors = [ { + for (let key3 in data15) if ("mode" !== key3 && "ca_file" !== key3 && "cert_file" !== key3 && "key_file" !== key3) return validate291.errors = [ { instancePath: instancePath + "/tls", schemaPath: "#/properties/tls/additionalProperties", keyword: "additionalProperties", @@ -16994,7 +16994,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs36) { if (void 0 !== data15.mode) { let data16 = data15.mode, _errs37 = 0; - if ("string" != typeof data16) return validate288.errors = [ { + if ("string" != typeof data16) return validate291.errors = [ { instancePath: instancePath + "/tls/mode", schemaPath: "#/properties/tls/properties/mode/type", keyword: "type", @@ -17002,12 +17002,12 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("disabled" !== data16 && "system" !== data16 && "custom_ca" !== data16 && "mutual" !== data16) return validate288.errors = [ { + if ("disabled" !== data16 && "system" !== data16 && "custom_ca" !== data16 && "mutual" !== data16) return validate291.errors = [ { instancePath: instancePath + "/tls/mode", schemaPath: "#/properties/tls/properties/mode/enum", keyword: "enum", params: { - allowedValues: schema314.properties.tls.properties.mode.enum + allowedValues: schema318.properties.tls.properties.mode.enum } } ], !1; var valid16 = 0 === _errs37; @@ -17016,7 +17016,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.ca_file) { let data17 = data15.ca_file, _errs39 = 0; if (0 === _errs39) { - if ("string" != typeof data17) return validate288.errors = [ { + if ("string" != typeof data17) return validate291.errors = [ { instancePath: instancePath + "/tls/ca_file", schemaPath: "#/properties/tls/properties/ca_file/type", keyword: "type", @@ -17024,7 +17024,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data17) > 511) return validate288.errors = [ { + if (func1(data17) > 511) return validate291.errors = [ { instancePath: instancePath + "/tls/ca_file", schemaPath: "#/properties/tls/properties/ca_file/maxLength", keyword: "maxLength", @@ -17039,7 +17039,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.cert_file) { let data18 = data15.cert_file, _errs41 = 0; if (0 === _errs41) { - if ("string" != typeof data18) return validate288.errors = [ { + if ("string" != typeof data18) return validate291.errors = [ { instancePath: instancePath + "/tls/cert_file", schemaPath: "#/properties/tls/properties/cert_file/type", keyword: "type", @@ -17047,7 +17047,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data18) > 511) return validate288.errors = [ { + if (func1(data18) > 511) return validate291.errors = [ { instancePath: instancePath + "/tls/cert_file", schemaPath: "#/properties/tls/properties/cert_file/maxLength", keyword: "maxLength", @@ -17060,7 +17060,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } else valid18 = !0; if (valid18 && void 0 !== data15.key_file) { let data19 = data15.key_file; - if ("string" != typeof data19) return validate288.errors = [ { + if ("string" != typeof data19) return validate291.errors = [ { instancePath: instancePath + "/tls/key_file", schemaPath: "#/properties/tls/properties/key_file/type", keyword: "type", @@ -17068,7 +17068,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data19) > 511) return validate288.errors = [ { + if (func1(data19) > 511) return validate291.errors = [ { instancePath: instancePath + "/tls/key_file", schemaPath: "#/properties/tls/properties/key_file/maxLength", keyword: "maxLength", @@ -17086,7 +17086,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } else valid15 = !0; if (valid15 && void 0 !== data.revision) { let data20 = data.revision, _errs45 = 0; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate288.errors = [ { + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate291.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -17095,7 +17095,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs45 && "number" == typeof data20 && isFinite(data20)) { - if (data20 > 9007199254740991 || isNaN(data20)) return validate288.errors = [ { + if (data20 > 9007199254740991 || isNaN(data20)) return validate291.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -17104,7 +17104,7 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data20 < 1 || isNaN(data20)) return validate288.errors = [ { + if (data20 < 1 || isNaN(data20)) return validate291.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -17123,21 +17123,21 @@ function validate288(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate288.errors = [], !0; + return validate291.errors = [], !0; } -validate288.evaluated = { +validate291.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventDestinations = validate286; +var __contract_EventDestinations = validate289; -function validate286(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate286.evaluated; +function validate289(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate289.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate286.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate289.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -17145,7 +17145,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.default_destination) return validate286.errors = [ { + if (void 0 === data.default_destination) return validate289.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -17153,7 +17153,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "default_destination" } } ], !1; - if (void 0 === data.count) return validate286.errors = [ { + if (void 0 === data.count) return validate289.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -17161,7 +17161,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "count" } } ], !1; - if (void 0 === data.destinations) return validate286.errors = [ { + if (void 0 === data.destinations) return validate289.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -17171,7 +17171,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("default_destination" !== key0 && "count" !== key0 && "destinations" !== key0) return validate286.errors = [ { + for (let key0 in data) if ("default_destination" !== key0 && "count" !== key0 && "destinations" !== key0) return validate289.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -17183,7 +17183,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.default_destination) { let data0 = data.default_destination, _errs2 = 0; if (0 === _errs2) { - if (!data0 || "object" != typeof data0 || Array.isArray(data0)) return validate286.errors = [ { + if (!data0 || "object" != typeof data0 || Array.isArray(data0)) return validate289.errors = [ { instancePath: instancePath + "/default_destination", schemaPath: "#/properties/default_destination/type", keyword: "type", @@ -17191,7 +17191,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data0.key) return validate286.errors = [ { + if (void 0 === data0.key) return validate289.errors = [ { instancePath: instancePath + "/default_destination", schemaPath: "#/properties/default_destination/required", keyword: "required", @@ -17199,7 +17199,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data0.name) return validate286.errors = [ { + if (void 0 === data0.name) return validate289.errors = [ { instancePath: instancePath + "/default_destination", schemaPath: "#/properties/default_destination/required", keyword: "required", @@ -17207,7 +17207,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data0.configuration_source) return validate286.errors = [ { + if (void 0 === data0.configuration_source) return validate289.errors = [ { instancePath: instancePath + "/default_destination", schemaPath: "#/properties/default_destination/required", keyword: "required", @@ -17215,7 +17215,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "configuration_source" } } ], !1; - if (void 0 === data0.managed) return validate286.errors = [ { + if (void 0 === data0.managed) return validate289.errors = [ { instancePath: instancePath + "/default_destination", schemaPath: "#/properties/default_destination/required", keyword: "required", @@ -17225,7 +17225,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs4 = 0; - for (let key1 in data0) if ("key" !== key1 && "name" !== key1 && "configuration_source" !== key1 && "managed" !== key1) return validate286.errors = [ { + for (let key1 in data0) if ("key" !== key1 && "name" !== key1 && "configuration_source" !== key1 && "managed" !== key1) return validate289.errors = [ { instancePath: instancePath + "/default_destination", schemaPath: "#/properties/default_destination/additionalProperties", keyword: "additionalProperties", @@ -17236,7 +17236,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs4) { if (void 0 !== data0.key) { let data1 = data0.key, _errs5 = 0; - if ("string" != typeof data1) return validate286.errors = [ { + if ("string" != typeof data1) return validate289.errors = [ { instancePath: instancePath + "/default_destination/key", schemaPath: "#/properties/default_destination/properties/key/type", keyword: "type", @@ -17244,7 +17244,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("mqtt:default" !== data1) return validate286.errors = [ { + if ("mqtt:default" !== data1) return validate289.errors = [ { instancePath: instancePath + "/default_destination/key", schemaPath: "#/properties/default_destination/properties/key/const", keyword: "const", @@ -17257,7 +17257,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data0.name) { let _errs7 = 0; - if ("string" != typeof data0.name) return validate286.errors = [ { + if ("string" != typeof data0.name) return validate289.errors = [ { instancePath: instancePath + "/default_destination/name", schemaPath: "#/properties/default_destination/properties/name/type", keyword: "type", @@ -17270,7 +17270,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data0.configuration_source) { let data3 = data0.configuration_source, _errs9 = 0; - if ("string" != typeof data3) return validate286.errors = [ { + if ("string" != typeof data3) return validate289.errors = [ { instancePath: instancePath + "/default_destination/configuration_source", schemaPath: "#/properties/default_destination/properties/configuration_source/type", keyword: "type", @@ -17278,7 +17278,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("settings" !== data3) return validate286.errors = [ { + if ("settings" !== data3) return validate289.errors = [ { instancePath: instancePath + "/default_destination/configuration_source", schemaPath: "#/properties/default_destination/properties/configuration_source/const", keyword: "const", @@ -17290,7 +17290,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } else valid3 = !0; if (valid3 && void 0 !== data0.managed) { let data4 = data0.managed; - if ("boolean" != typeof data4) return validate286.errors = [ { + if ("boolean" != typeof data4) return validate289.errors = [ { instancePath: instancePath + "/default_destination/managed", schemaPath: "#/properties/default_destination/properties/managed/type", keyword: "type", @@ -17298,7 +17298,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!1 !== data4) return validate286.errors = [ { + if (!1 !== data4) return validate289.errors = [ { instancePath: instancePath + "/default_destination/managed", schemaPath: "#/properties/default_destination/properties/managed/const", keyword: "const", @@ -17317,7 +17317,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.count) { let data5 = data.count, _errs13 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate286.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate289.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -17326,7 +17326,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs13 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 64 || isNaN(data5)) return validate286.errors = [ { + if (data5 > 64 || isNaN(data5)) return validate289.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/maximum", keyword: "maximum", @@ -17335,7 +17335,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data5 < 0 || isNaN(data5)) return validate286.errors = [ { + if (data5 < 0 || isNaN(data5)) return validate289.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -17349,7 +17349,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } else valid5 = !0; if (valid5 && void 0 !== data.destinations) { let data6 = data.destinations; - if (!Array.isArray(data6)) return validate286.errors = [ { + if (!Array.isArray(data6)) return validate289.errors = [ { instancePath: instancePath + "/destinations", schemaPath: "#/properties/destinations/type", keyword: "type", @@ -17357,7 +17357,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data6.length > 64) return validate286.errors = [ { + if (data6.length > 64) return validate289.errors = [ { instancePath: instancePath + "/destinations", schemaPath: "#/properties/destinations/maxItems", keyword: "maxItems", @@ -17369,7 +17369,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD let len0 = data6.length; for (let i0 = 0; i0 < len0; i0++) { let data7 = data6[i0], _errs17 = 0; - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate286.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/type", keyword: "type", @@ -17377,7 +17377,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data7.uuid) return validate286.errors = [ { + if (void 0 === data7.uuid) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17385,7 +17385,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data7.key) return validate286.errors = [ { + if (void 0 === data7.key) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17393,7 +17393,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data7.name) return validate286.errors = [ { + if (void 0 === data7.name) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17401,7 +17401,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data7.description) return validate286.errors = [ { + if (void 0 === data7.description) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17409,7 +17409,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "description" } } ], !1; - if (void 0 === data7.enabled) return validate286.errors = [ { + if (void 0 === data7.enabled) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17417,7 +17417,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data7.type) return validate286.errors = [ { + if (void 0 === data7.type) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17425,7 +17425,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data7.broker) return validate286.errors = [ { + if (void 0 === data7.broker) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17433,7 +17433,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "broker" } } ], !1; - if (void 0 === data7.authentication) return validate286.errors = [ { + if (void 0 === data7.authentication) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17441,7 +17441,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "authentication" } } ], !1; - if (void 0 === data7.tls) return validate286.errors = [ { + if (void 0 === data7.tls) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17449,7 +17449,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "tls" } } ], !1; - if (void 0 === data7.managed) return validate286.errors = [ { + if (void 0 === data7.managed) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17457,7 +17457,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "managed" } } ], !1; - if (void 0 === data7.revision) return validate286.errors = [ { + if (void 0 === data7.revision) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17465,7 +17465,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data7.created_at) return validate286.errors = [ { + if (void 0 === data7.created_at) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17473,7 +17473,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data7.updated_at) return validate286.errors = [ { + if (void 0 === data7.updated_at) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/required", keyword: "required", @@ -17483,7 +17483,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs20 = 0; - for (let key2 in data7) if (!func5.call(schema310.properties, key2)) return validate286.errors = [ { + for (let key2 in data7) if (!func7.call(schema314.properties, key2)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0, schemaPath: "#/$defs/EventDestination/additionalProperties", keyword: "additionalProperties", @@ -17495,7 +17495,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.uuid) { let data8 = data7.uuid, _errs21 = 0; if (0 === _errs21) { - if ("string" != typeof data8) return validate286.errors = [ { + if ("string" != typeof data8) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/uuid", schemaPath: "#/$defs/EventDestination/properties/uuid/type", keyword: "type", @@ -17503,7 +17503,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!formats2.test(data8)) return validate286.errors = [ { + if (!formats6.test(data8)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/uuid", schemaPath: "#/$defs/EventDestination/properties/uuid/format", keyword: "format", @@ -17518,7 +17518,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.key) { let data9 = data7.key, _errs23 = 0; if (0 === _errs23) { - if ("string" != typeof data9) return validate286.errors = [ { + if ("string" != typeof data9) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/key", schemaPath: "#/$defs/EventDestination/properties/key/type", keyword: "type", @@ -17526,7 +17526,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 63) return validate286.errors = [ { + if (func1(data9) > 63) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/key", schemaPath: "#/$defs/EventDestination/properties/key/maxLength", keyword: "maxLength", @@ -17534,7 +17534,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data9) < 1) return validate286.errors = [ { + if (func1(data9) < 1) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/key", schemaPath: "#/$defs/EventDestination/properties/key/minLength", keyword: "minLength", @@ -17549,7 +17549,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.name) { let data10 = data7.name, _errs25 = 0; if (0 === _errs25) { - if ("string" != typeof data10) return validate286.errors = [ { + if ("string" != typeof data10) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/name", schemaPath: "#/$defs/EventDestination/properties/name/type", keyword: "type", @@ -17557,7 +17557,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data10) > 127) return validate286.errors = [ { + if (func1(data10) > 127) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/name", schemaPath: "#/$defs/EventDestination/properties/name/maxLength", keyword: "maxLength", @@ -17565,7 +17565,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data10) < 1) return validate286.errors = [ { + if (func1(data10) < 1) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/name", schemaPath: "#/$defs/EventDestination/properties/name/minLength", keyword: "minLength", @@ -17580,7 +17580,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.description) { let data11 = data7.description, _errs27 = 0; if (0 === _errs27) { - if ("string" != typeof data11) return validate286.errors = [ { + if ("string" != typeof data11) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/description", schemaPath: "#/$defs/EventDestination/properties/description/type", keyword: "type", @@ -17588,7 +17588,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 511) return validate286.errors = [ { + if (func1(data11) > 511) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/description", schemaPath: "#/$defs/EventDestination/properties/description/maxLength", keyword: "maxLength", @@ -17602,7 +17602,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data7.enabled) { let _errs29 = 0; - if ("boolean" != typeof data7.enabled) return validate286.errors = [ { + if ("boolean" != typeof data7.enabled) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/enabled", schemaPath: "#/$defs/EventDestination/properties/enabled/type", keyword: "type", @@ -17615,7 +17615,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data7.type) { let data13 = data7.type, _errs31 = 0; - if ("string" != typeof data13) return validate286.errors = [ { + if ("string" != typeof data13) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/type", schemaPath: "#/$defs/EventDestination/properties/type/type", keyword: "type", @@ -17623,7 +17623,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("mqtt" !== data13) return validate286.errors = [ { + if ("mqtt" !== data13) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/type", schemaPath: "#/$defs/EventDestination/properties/type/const", keyword: "const", @@ -17637,7 +17637,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.broker) { let data14 = data7.broker, _errs33 = 0; if (0 === _errs33) { - if (!data14 || "object" != typeof data14 || Array.isArray(data14)) return validate286.errors = [ { + if (!data14 || "object" != typeof data14 || Array.isArray(data14)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/type", keyword: "type", @@ -17645,7 +17645,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data14.host) return validate286.errors = [ { + if (void 0 === data14.host) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17653,7 +17653,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "host" } } ], !1; - if (void 0 === data14.port) return validate286.errors = [ { + if (void 0 === data14.port) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17661,7 +17661,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "port" } } ], !1; - if (void 0 === data14.client_id) return validate286.errors = [ { + if (void 0 === data14.client_id) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17669,7 +17669,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "client_id" } } ], !1; - if (void 0 === data14.topic_template) return validate286.errors = [ { + if (void 0 === data14.topic_template) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17677,7 +17677,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "topic_template" } } ], !1; - if (void 0 === data14.status_topic_template) return validate286.errors = [ { + if (void 0 === data14.status_topic_template) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17685,7 +17685,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status_topic_template" } } ], !1; - if (void 0 === data14.keepalive_seconds) return validate286.errors = [ { + if (void 0 === data14.keepalive_seconds) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17693,7 +17693,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "keepalive_seconds" } } ], !1; - if (void 0 === data14.qos) return validate286.errors = [ { + if (void 0 === data14.qos) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/required", keyword: "required", @@ -17703,7 +17703,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs35 = 0; - for (let key3 in data14) if ("host" !== key3 && "port" !== key3 && "client_id" !== key3 && "topic_template" !== key3 && "status_topic_template" !== key3 && "keepalive_seconds" !== key3 && "qos" !== key3) return validate286.errors = [ { + for (let key3 in data14) if ("host" !== key3 && "port" !== key3 && "client_id" !== key3 && "topic_template" !== key3 && "status_topic_template" !== key3 && "keepalive_seconds" !== key3 && "qos" !== key3) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker", schemaPath: "#/$defs/EventDestination/properties/broker/additionalProperties", keyword: "additionalProperties", @@ -17715,7 +17715,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data14.host) { let data15 = data14.host, _errs36 = 0; if (0 === _errs36) { - if ("string" != typeof data15) return validate286.errors = [ { + if ("string" != typeof data15) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/host", schemaPath: "#/$defs/EventDestination/properties/broker/properties/host/type", keyword: "type", @@ -17723,7 +17723,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data15) > 255) return validate286.errors = [ { + if (func1(data15) > 255) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/host", schemaPath: "#/$defs/EventDestination/properties/broker/properties/host/maxLength", keyword: "maxLength", @@ -17731,7 +17731,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 255 } } ], !1; - if (func1(data15) < 1) return validate286.errors = [ { + if (func1(data15) < 1) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/host", schemaPath: "#/$defs/EventDestination/properties/broker/properties/host/minLength", keyword: "minLength", @@ -17745,7 +17745,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data14.port) { let data16 = data14.port, _errs38 = 0; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate286.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/port", schemaPath: "#/$defs/EventDestination/properties/broker/properties/port/type", keyword: "type", @@ -17754,7 +17754,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs38 && "number" == typeof data16 && isFinite(data16)) { - if (data16 > 65535 || isNaN(data16)) return validate286.errors = [ { + if (data16 > 65535 || isNaN(data16)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/port", schemaPath: "#/$defs/EventDestination/properties/broker/properties/port/maximum", keyword: "maximum", @@ -17763,7 +17763,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 65535 } } ], !1; - if (data16 < 1 || isNaN(data16)) return validate286.errors = [ { + if (data16 < 1 || isNaN(data16)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/port", schemaPath: "#/$defs/EventDestination/properties/broker/properties/port/minimum", keyword: "minimum", @@ -17779,7 +17779,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data14.client_id) { let data17 = data14.client_id, _errs40 = 0; if (0 === _errs40) { - if ("string" != typeof data17) return validate286.errors = [ { + if ("string" != typeof data17) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/client_id", schemaPath: "#/$defs/EventDestination/properties/broker/properties/client_id/type", keyword: "type", @@ -17787,7 +17787,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data17) > 127) return validate286.errors = [ { + if (func1(data17) > 127) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/client_id", schemaPath: "#/$defs/EventDestination/properties/broker/properties/client_id/maxLength", keyword: "maxLength", @@ -17795,7 +17795,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data17) < 1) return validate286.errors = [ { + if (func1(data17) < 1) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/client_id", schemaPath: "#/$defs/EventDestination/properties/broker/properties/client_id/minLength", keyword: "minLength", @@ -17810,7 +17810,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data14.topic_template) { let data18 = data14.topic_template, _errs42 = 0; if (0 === _errs42) { - if ("string" != typeof data18) return validate286.errors = [ { + if ("string" != typeof data18) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/topic_template", schemaPath: "#/$defs/EventDestination/properties/broker/properties/topic_template/type", keyword: "type", @@ -17818,7 +17818,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data18) > 511) return validate286.errors = [ { + if (func1(data18) > 511) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/topic_template", schemaPath: "#/$defs/EventDestination/properties/broker/properties/topic_template/maxLength", keyword: "maxLength", @@ -17826,7 +17826,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 511 } } ], !1; - if (func1(data18) < 1) return validate286.errors = [ { + if (func1(data18) < 1) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/topic_template", schemaPath: "#/$defs/EventDestination/properties/broker/properties/topic_template/minLength", keyword: "minLength", @@ -17841,7 +17841,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data14.status_topic_template) { let data19 = data14.status_topic_template, _errs44 = 0; if (0 === _errs44) { - if ("string" != typeof data19) return validate286.errors = [ { + if ("string" != typeof data19) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/status_topic_template", schemaPath: "#/$defs/EventDestination/properties/broker/properties/status_topic_template/type", keyword: "type", @@ -17849,7 +17849,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data19) > 511) return validate286.errors = [ { + if (func1(data19) > 511) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/status_topic_template", schemaPath: "#/$defs/EventDestination/properties/broker/properties/status_topic_template/maxLength", keyword: "maxLength", @@ -17863,7 +17863,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data14.keepalive_seconds) { let data20 = data14.keepalive_seconds, _errs46 = 0; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate286.errors = [ { + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/keepalive_seconds", schemaPath: "#/$defs/EventDestination/properties/broker/properties/keepalive_seconds/type", keyword: "type", @@ -17872,7 +17872,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs46 && "number" == typeof data20 && isFinite(data20)) { - if (data20 > 3600 || isNaN(data20)) return validate286.errors = [ { + if (data20 > 3600 || isNaN(data20)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/keepalive_seconds", schemaPath: "#/$defs/EventDestination/properties/broker/properties/keepalive_seconds/maximum", keyword: "maximum", @@ -17881,7 +17881,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data20 < 5 || isNaN(data20)) return validate286.errors = [ { + if (data20 < 5 || isNaN(data20)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/keepalive_seconds", schemaPath: "#/$defs/EventDestination/properties/broker/properties/keepalive_seconds/minimum", keyword: "minimum", @@ -17895,7 +17895,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } else valid21 = !0; if (valid21 && void 0 !== data14.qos) { let data21 = data14.qos, _errs48 = 0; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate286.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/qos", schemaPath: "#/$defs/EventDestination/properties/broker/properties/qos/type", keyword: "type", @@ -17904,7 +17904,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs48 && "number" == typeof data21 && isFinite(data21)) { - if (data21 > 2 || isNaN(data21)) return validate286.errors = [ { + if (data21 > 2 || isNaN(data21)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/qos", schemaPath: "#/$defs/EventDestination/properties/broker/properties/qos/maximum", keyword: "maximum", @@ -17913,7 +17913,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 2 } } ], !1; - if (data21 < 0 || isNaN(data21)) return validate286.errors = [ { + if (data21 < 0 || isNaN(data21)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/broker/qos", schemaPath: "#/$defs/EventDestination/properties/broker/properties/qos/minimum", keyword: "minimum", @@ -17938,7 +17938,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.authentication) { let data22 = data7.authentication, _errs50 = 0; if (0 === _errs50) { - if (!data22 || "object" != typeof data22 || Array.isArray(data22)) return validate286.errors = [ { + if (!data22 || "object" != typeof data22 || Array.isArray(data22)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication", schemaPath: "#/$defs/EventDestination/properties/authentication/type", keyword: "type", @@ -17946,7 +17946,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data22.username) return validate286.errors = [ { + if (void 0 === data22.username) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication", schemaPath: "#/$defs/EventDestination/properties/authentication/required", keyword: "required", @@ -17954,7 +17954,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "username" } } ], !1; - if (void 0 === data22.password_configured) return validate286.errors = [ { + if (void 0 === data22.password_configured) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication", schemaPath: "#/$defs/EventDestination/properties/authentication/required", keyword: "required", @@ -17964,7 +17964,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs52 = 0; - for (let key4 in data22) if ("username" !== key4 && "password_configured" !== key4) return validate286.errors = [ { + for (let key4 in data22) if ("username" !== key4 && "password_configured" !== key4) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication", schemaPath: "#/$defs/EventDestination/properties/authentication/additionalProperties", keyword: "additionalProperties", @@ -17976,7 +17976,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data22.username) { let data23 = data22.username, _errs53 = 0; if (0 === _errs53) { - if ("string" != typeof data23) return validate286.errors = [ { + if ("string" != typeof data23) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication/username", schemaPath: "#/$defs/EventDestination/properties/authentication/properties/username/type", keyword: "type", @@ -17984,7 +17984,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data23) > 127) return validate286.errors = [ { + if (func1(data23) > 127) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication/username", schemaPath: "#/$defs/EventDestination/properties/authentication/properties/username/maxLength", keyword: "maxLength", @@ -17995,7 +17995,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } var valid24 = 0 === _errs53; } else valid24 = !0; - if (valid24 && void 0 !== data22.password_configured && "boolean" != typeof data22.password_configured) return validate286.errors = [ { + if (valid24 && void 0 !== data22.password_configured && "boolean" != typeof data22.password_configured) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/authentication/password_configured", schemaPath: "#/$defs/EventDestination/properties/authentication/properties/password_configured/type", keyword: "type", @@ -18012,7 +18012,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.tls) { let data25 = data7.tls, _errs57 = 0; if (0 === _errs57) { - if (!data25 || "object" != typeof data25 || Array.isArray(data25)) return validate286.errors = [ { + if (!data25 || "object" != typeof data25 || Array.isArray(data25)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls", schemaPath: "#/$defs/EventDestination/properties/tls/type", keyword: "type", @@ -18020,7 +18020,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data25.mode) return validate286.errors = [ { + if (void 0 === data25.mode) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls", schemaPath: "#/$defs/EventDestination/properties/tls/required", keyword: "required", @@ -18028,7 +18028,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "mode" } } ], !1; - if (void 0 === data25.ca_file) return validate286.errors = [ { + if (void 0 === data25.ca_file) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls", schemaPath: "#/$defs/EventDestination/properties/tls/required", keyword: "required", @@ -18036,7 +18036,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "ca_file" } } ], !1; - if (void 0 === data25.cert_file) return validate286.errors = [ { + if (void 0 === data25.cert_file) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls", schemaPath: "#/$defs/EventDestination/properties/tls/required", keyword: "required", @@ -18044,7 +18044,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cert_file" } } ], !1; - if (void 0 === data25.key_file) return validate286.errors = [ { + if (void 0 === data25.key_file) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls", schemaPath: "#/$defs/EventDestination/properties/tls/required", keyword: "required", @@ -18054,7 +18054,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs59 = 0; - for (let key5 in data25) if ("mode" !== key5 && "ca_file" !== key5 && "cert_file" !== key5 && "key_file" !== key5) return validate286.errors = [ { + for (let key5 in data25) if ("mode" !== key5 && "ca_file" !== key5 && "cert_file" !== key5 && "key_file" !== key5) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls", schemaPath: "#/$defs/EventDestination/properties/tls/additionalProperties", keyword: "additionalProperties", @@ -18065,7 +18065,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs59) { if (void 0 !== data25.mode) { let data26 = data25.mode, _errs60 = 0; - if ("string" != typeof data26) return validate286.errors = [ { + if ("string" != typeof data26) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/mode", schemaPath: "#/$defs/EventDestination/properties/tls/properties/mode/type", keyword: "type", @@ -18073,12 +18073,12 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("disabled" !== data26 && "system" !== data26 && "custom_ca" !== data26 && "mutual" !== data26) return validate286.errors = [ { + if ("disabled" !== data26 && "system" !== data26 && "custom_ca" !== data26 && "mutual" !== data26) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/mode", schemaPath: "#/$defs/EventDestination/properties/tls/properties/mode/enum", keyword: "enum", params: { - allowedValues: schema310.properties.tls.properties.mode.enum + allowedValues: schema314.properties.tls.properties.mode.enum } } ], !1; var valid27 = 0 === _errs60; @@ -18087,7 +18087,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data25.ca_file) { let data27 = data25.ca_file, _errs62 = 0; if (0 === _errs62) { - if ("string" != typeof data27) return validate286.errors = [ { + if ("string" != typeof data27) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/ca_file", schemaPath: "#/$defs/EventDestination/properties/tls/properties/ca_file/type", keyword: "type", @@ -18095,7 +18095,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data27) > 511) return validate286.errors = [ { + if (func1(data27) > 511) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/ca_file", schemaPath: "#/$defs/EventDestination/properties/tls/properties/ca_file/maxLength", keyword: "maxLength", @@ -18110,7 +18110,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data25.cert_file) { let data28 = data25.cert_file, _errs64 = 0; if (0 === _errs64) { - if ("string" != typeof data28) return validate286.errors = [ { + if ("string" != typeof data28) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/cert_file", schemaPath: "#/$defs/EventDestination/properties/tls/properties/cert_file/type", keyword: "type", @@ -18118,7 +18118,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data28) > 511) return validate286.errors = [ { + if (func1(data28) > 511) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/cert_file", schemaPath: "#/$defs/EventDestination/properties/tls/properties/cert_file/maxLength", keyword: "maxLength", @@ -18131,7 +18131,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } else valid29 = !0; if (valid29 && void 0 !== data25.key_file) { let data29 = data25.key_file; - if ("string" != typeof data29) return validate286.errors = [ { + if ("string" != typeof data29) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/key_file", schemaPath: "#/$defs/EventDestination/properties/tls/properties/key_file/type", keyword: "type", @@ -18139,7 +18139,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data29) > 511) return validate286.errors = [ { + if (func1(data29) > 511) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/tls/key_file", schemaPath: "#/$defs/EventDestination/properties/tls/properties/key_file/maxLength", keyword: "maxLength", @@ -18158,7 +18158,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data7.managed) { let data30 = data7.managed, _errs68 = 0; - if ("boolean" != typeof data30) return validate286.errors = [ { + if ("boolean" != typeof data30) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/managed", schemaPath: "#/$defs/EventDestination/properties/managed/type", keyword: "type", @@ -18166,7 +18166,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data30) return validate286.errors = [ { + if (!0 !== data30) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/managed", schemaPath: "#/$defs/EventDestination/properties/managed/const", keyword: "const", @@ -18179,7 +18179,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid31) { if (void 0 !== data7.revision) { let data31 = data7.revision, _errs70 = 0; - if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate286.errors = [ { + if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/revision", schemaPath: "#/$defs/EventDestination/properties/revision/type", keyword: "type", @@ -18188,7 +18188,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs70 && "number" == typeof data31 && isFinite(data31)) { - if (data31 > 9007199254740991 || isNaN(data31)) return validate286.errors = [ { + if (data31 > 9007199254740991 || isNaN(data31)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/revision", schemaPath: "#/$defs/EventDestination/properties/revision/maximum", keyword: "maximum", @@ -18197,7 +18197,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data31 < 1 || isNaN(data31)) return validate286.errors = [ { + if (data31 < 1 || isNaN(data31)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/revision", schemaPath: "#/$defs/EventDestination/properties/revision/minimum", keyword: "minimum", @@ -18212,7 +18212,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data7.created_at) { let data32 = data7.created_at, _errs72 = 0; - if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate286.errors = [ { + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/created_at", schemaPath: "#/$defs/EventDestination/properties/created_at/type", keyword: "type", @@ -18220,7 +18220,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs72 && "number" == typeof data32 && isFinite(data32) && (data32 < 0 || isNaN(data32))) return validate286.errors = [ { + if (0 === _errs72 && "number" == typeof data32 && isFinite(data32) && (data32 < 0 || isNaN(data32))) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/created_at", schemaPath: "#/$defs/EventDestination/properties/created_at/minimum", keyword: "minimum", @@ -18233,7 +18233,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } else valid33 = !0; if (valid33 && void 0 !== data7.updated_at) { let data33 = data7.updated_at, _errs74 = 0; - if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate286.errors = [ { + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/updated_at", schemaPath: "#/$defs/EventDestination/properties/updated_at/type", keyword: "type", @@ -18241,7 +18241,7 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs74 && "number" == typeof data33 && isFinite(data33) && (data33 < 0 || isNaN(data33))) return validate286.errors = [ { + if (0 === _errs74 && "number" == typeof data33 && isFinite(data33) && (data33 < 0 || isNaN(data33))) return validate289.errors = [ { instancePath: instancePath + "/destinations/" + i0 + "/updated_at", schemaPath: "#/$defs/EventDestination/properties/updated_at/minimum", keyword: "minimum", @@ -18271,21 +18271,21 @@ function validate286(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate286.errors = [], !0; + return validate289.errors = [], !0; } -validate286.evaluated = { +validate289.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventMutationAck = validate305; +var __contract_EventMutationAck = validate308; -function validate305(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate305.evaluated; +function validate308(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate308.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate305.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate308.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -18293,7 +18293,7 @@ function validate305(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate305.errors = [ { + if (void 0 === data.success) return validate308.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18303,7 +18303,7 @@ function validate305(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0) return validate305.errors = [ { + for (let key0 in data) if ("success" !== key0) return validate308.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -18313,7 +18313,7 @@ function validate305(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (0 === _errs1 && void 0 !== data.success) { let data0 = data.success; - if ("boolean" != typeof data0) return validate305.errors = [ { + if ("boolean" != typeof data0) return validate308.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -18321,7 +18321,7 @@ function validate305(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate305.errors = [ { + if (!0 !== data0) return validate308.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -18331,16 +18331,16 @@ function validate305(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate305.errors = [], !0; + return validate308.errors = [], !0; } -validate305.evaluated = { +validate308.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventPredicate = validate289, schema315 = { +var __contract_EventPredicate = validate292, schema319 = { type: "object", properties: { version: { @@ -18412,10 +18412,10 @@ var __contract_EventPredicate = validate289, schema315 = { additionalProperties: !1 }; -function validate289(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate289.evaluated; +function validate292(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate292.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate289.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate292.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -18423,7 +18423,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.version) return validate289.errors = [ { + if (void 0 === data.version) return validate292.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18433,7 +18433,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("version" !== key0 && "detection" !== key0 && "health" !== key0) return validate289.errors = [ { + for (let key0 in data) if ("version" !== key0 && "detection" !== key0 && "health" !== key0) return validate292.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -18444,7 +18444,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.version) { let data0 = data.version, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate289.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate292.errors = [ { instancePath: instancePath + "/version", schemaPath: "#/properties/version/type", keyword: "type", @@ -18452,7 +18452,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data0) return validate289.errors = [ { + if (1 !== data0) return validate292.errors = [ { instancePath: instancePath + "/version", schemaPath: "#/properties/version/const", keyword: "const", @@ -18466,7 +18466,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.detection) { let data1 = data.detection, _errs4 = 0; if (0 === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate289.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate292.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/type", keyword: "type", @@ -18474,7 +18474,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data1).length < 1) return validate289.errors = [ { + if (Object.keys(data1).length < 1) return validate292.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/minProperties", keyword: "minProperties", @@ -18484,7 +18484,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs6 = 0; - for (let key1 in data1) if ("labels_any" !== key1 && "zone_ids_any" !== key1 && "min_confidence" !== key1) return validate289.errors = [ { + for (let key1 in data1) if ("labels_any" !== key1 && "zone_ids_any" !== key1 && "min_confidence" !== key1) return validate292.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -18496,7 +18496,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.labels_any) { let data2 = data1.labels_any, _errs7 = 0; if (0 === _errs7) { - if (!Array.isArray(data2)) return validate289.errors = [ { + if (!Array.isArray(data2)) return validate292.errors = [ { instancePath: instancePath + "/detection/labels_any", schemaPath: "#/properties/detection/properties/labels_any/type", keyword: "type", @@ -18504,7 +18504,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data2.length > 64) return validate289.errors = [ { + if (data2.length > 64) return validate292.errors = [ { instancePath: instancePath + "/detection/labels_any", schemaPath: "#/properties/detection/properties/labels_any/maxItems", keyword: "maxItems", @@ -18512,7 +18512,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data2.length < 1) return validate289.errors = [ { + if (data2.length < 1) return validate292.errors = [ { instancePath: instancePath + "/detection/labels_any", schemaPath: "#/properties/detection/properties/labels_any/minItems", keyword: "minItems", @@ -18525,7 +18525,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs9 = 0; if (0 === _errs9) { - if ("string" != typeof data3) return validate289.errors = [ { + if ("string" != typeof data3) return validate292.errors = [ { instancePath: instancePath + "/detection/labels_any/" + i0, schemaPath: "#/properties/detection/properties/labels_any/items/type", keyword: "type", @@ -18533,7 +18533,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 127) return validate289.errors = [ { + if (func1(data3) > 127) return validate292.errors = [ { instancePath: instancePath + "/detection/labels_any/" + i0, schemaPath: "#/properties/detection/properties/labels_any/items/maxLength", keyword: "maxLength", @@ -18541,7 +18541,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data3) < 1) return validate289.errors = [ { + if (func1(data3) < 1) return validate292.errors = [ { instancePath: instancePath + "/detection/labels_any/" + i0, schemaPath: "#/properties/detection/properties/labels_any/items/minLength", keyword: "minLength", @@ -18560,7 +18560,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.zone_ids_any) { let data4 = data1.zone_ids_any, _errs11 = 0; if (0 === _errs11) { - if (!Array.isArray(data4)) return validate289.errors = [ { + if (!Array.isArray(data4)) return validate292.errors = [ { instancePath: instancePath + "/detection/zone_ids_any", schemaPath: "#/properties/detection/properties/zone_ids_any/type", keyword: "type", @@ -18568,7 +18568,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 64) return validate289.errors = [ { + if (data4.length > 64) return validate292.errors = [ { instancePath: instancePath + "/detection/zone_ids_any", schemaPath: "#/properties/detection/properties/zone_ids_any/maxItems", keyword: "maxItems", @@ -18576,7 +18576,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data4.length < 1) return validate289.errors = [ { + if (data4.length < 1) return validate292.errors = [ { instancePath: instancePath + "/detection/zone_ids_any", schemaPath: "#/properties/detection/properties/zone_ids_any/minItems", keyword: "minItems", @@ -18589,7 +18589,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD for (let i1 = 0; i1 < len1; i1++) { let data5 = data4[i1], _errs13 = 0; if (0 === _errs13) { - if ("string" != typeof data5) return validate289.errors = [ { + if ("string" != typeof data5) return validate292.errors = [ { instancePath: instancePath + "/detection/zone_ids_any/" + i1, schemaPath: "#/properties/detection/properties/zone_ids_any/items/type", keyword: "type", @@ -18597,7 +18597,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 127) return validate289.errors = [ { + if (func1(data5) > 127) return validate292.errors = [ { instancePath: instancePath + "/detection/zone_ids_any/" + i1, schemaPath: "#/properties/detection/properties/zone_ids_any/items/maxLength", keyword: "maxLength", @@ -18605,7 +18605,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data5) < 1) return validate289.errors = [ { + if (func1(data5) < 1) return validate292.errors = [ { instancePath: instancePath + "/detection/zone_ids_any/" + i1, schemaPath: "#/properties/detection/properties/zone_ids_any/items/minLength", keyword: "minLength", @@ -18622,7 +18622,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } else valid4 = !0; if (valid4 && void 0 !== data1.min_confidence) { let data6 = data1.min_confidence; - if ("number" != typeof data6 || !isFinite(data6)) return validate289.errors = [ { + if ("number" != typeof data6 || !isFinite(data6)) return validate292.errors = [ { instancePath: instancePath + "/detection/min_confidence", schemaPath: "#/properties/detection/properties/min_confidence/type", keyword: "type", @@ -18630,7 +18630,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data6 > 1 || isNaN(data6)) return validate289.errors = [ { + if (data6 > 1 || isNaN(data6)) return validate292.errors = [ { instancePath: instancePath + "/detection/min_confidence", schemaPath: "#/properties/detection/properties/min_confidence/maximum", keyword: "maximum", @@ -18639,7 +18639,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data6 < 0 || isNaN(data6)) return validate289.errors = [ { + if (data6 < 0 || isNaN(data6)) return validate292.errors = [ { instancePath: instancePath + "/detection/min_confidence", schemaPath: "#/properties/detection/properties/min_confidence/minimum", keyword: "minimum", @@ -18657,7 +18657,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.health) { let data7 = data.health; - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate289.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate292.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/properties/health/type", keyword: "type", @@ -18665,7 +18665,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data7).length < 1) return validate289.errors = [ { + if (Object.keys(data7).length < 1) return validate292.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/properties/health/minProperties", keyword: "minProperties", @@ -18675,7 +18675,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs19 = 0; - for (let key2 in data7) if ("condition_codes_any" !== key2 && "severities_any" !== key2) return validate289.errors = [ { + for (let key2 in data7) if ("condition_codes_any" !== key2 && "severities_any" !== key2) return validate292.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/properties/health/additionalProperties", keyword: "additionalProperties", @@ -18687,7 +18687,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.condition_codes_any) { let data8 = data7.condition_codes_any, _errs20 = 0; if (0 === _errs20) { - if (!Array.isArray(data8)) return validate289.errors = [ { + if (!Array.isArray(data8)) return validate292.errors = [ { instancePath: instancePath + "/health/condition_codes_any", schemaPath: "#/properties/health/properties/condition_codes_any/type", keyword: "type", @@ -18695,7 +18695,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data8.length > 64) return validate289.errors = [ { + if (data8.length > 64) return validate292.errors = [ { instancePath: instancePath + "/health/condition_codes_any", schemaPath: "#/properties/health/properties/condition_codes_any/maxItems", keyword: "maxItems", @@ -18703,7 +18703,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data8.length < 1) return validate289.errors = [ { + if (data8.length < 1) return validate292.errors = [ { instancePath: instancePath + "/health/condition_codes_any", schemaPath: "#/properties/health/properties/condition_codes_any/minItems", keyword: "minItems", @@ -18716,7 +18716,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len2; i2++) { let data9 = data8[i2], _errs22 = 0; if (0 === _errs22) { - if ("string" != typeof data9) return validate289.errors = [ { + if ("string" != typeof data9) return validate292.errors = [ { instancePath: instancePath + "/health/condition_codes_any/" + i2, schemaPath: "#/properties/health/properties/condition_codes_any/items/type", keyword: "type", @@ -18724,7 +18724,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 127) return validate289.errors = [ { + if (func1(data9) > 127) return validate292.errors = [ { instancePath: instancePath + "/health/condition_codes_any/" + i2, schemaPath: "#/properties/health/properties/condition_codes_any/items/maxLength", keyword: "maxLength", @@ -18732,7 +18732,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data9) < 1) return validate289.errors = [ { + if (func1(data9) < 1) return validate292.errors = [ { instancePath: instancePath + "/health/condition_codes_any/" + i2, schemaPath: "#/properties/health/properties/condition_codes_any/items/minLength", keyword: "minLength", @@ -18749,7 +18749,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } else valid8 = !0; if (valid8 && void 0 !== data7.severities_any) { let data10 = data7.severities_any; - if (!Array.isArray(data10)) return validate289.errors = [ { + if (!Array.isArray(data10)) return validate292.errors = [ { instancePath: instancePath + "/health/severities_any", schemaPath: "#/properties/health/properties/severities_any/type", keyword: "type", @@ -18757,7 +18757,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data10.length > 3) return validate289.errors = [ { + if (data10.length > 3) return validate292.errors = [ { instancePath: instancePath + "/health/severities_any", schemaPath: "#/properties/health/properties/severities_any/maxItems", keyword: "maxItems", @@ -18765,7 +18765,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD limit: 3 } } ], !1; - if (data10.length < 1) return validate289.errors = [ { + if (data10.length < 1) return validate292.errors = [ { instancePath: instancePath + "/health/severities_any", schemaPath: "#/properties/health/properties/severities_any/minItems", keyword: "minItems", @@ -18777,7 +18777,7 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD let len3 = data10.length; for (let i3 = 0; i3 < len3; i3++) { let data11 = data10[i3], _errs26 = 0; - if ("string" != typeof data11) return validate289.errors = [ { + if ("string" != typeof data11) return validate292.errors = [ { instancePath: instancePath + "/health/severities_any/" + i3, schemaPath: "#/properties/health/properties/severities_any/items/type", keyword: "type", @@ -18785,12 +18785,12 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("warning" !== data11 && "error" !== data11 && "critical" !== data11) return validate289.errors = [ { + if ("warning" !== data11 && "error" !== data11 && "critical" !== data11) return validate292.errors = [ { instancePath: instancePath + "/health/severities_any/" + i3, schemaPath: "#/properties/health/properties/severities_any/items/enum", keyword: "enum", params: { - allowedValues: schema315.properties.health.properties.severities_any.items.enum + allowedValues: schema319.properties.health.properties.severities_any.items.enum } } ], !1; if (0 !== _errs26) break; @@ -18803,16 +18803,16 @@ function validate289(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate289.errors = [], !0; + return validate292.errors = [], !0; } -validate289.evaluated = { +validate292.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventRoute = validate293, schema318 = { +var __contract_EventRoute = validate296, schema322 = { type: "object", properties: { uuid: { @@ -18899,13 +18899,13 @@ var __contract_EventRoute = validate293, schema318 = { }, required: [ "uuid", "name", "description", "enabled", "destination", "event_types", "camera_scope", "predicate", "schedule", "suppression", "revision", "created_at", "updated_at" ], additionalProperties: !1 -}, pattern99 = new RegExp("^(?:[01][0-9]|2[0-3]):[0-5][0-9]$", "u"); +}, pattern101 = new RegExp("^(?:[01][0-9]|2[0-3]):[0-5][0-9]$", "u"); -function validate293(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate293.evaluated; +function validate296(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate296.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate293.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -18913,7 +18913,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate293.errors = [ { + if (void 0 === data.uuid) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18921,7 +18921,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.name) return validate293.errors = [ { + if (void 0 === data.name) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18929,7 +18929,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.description) return validate293.errors = [ { + if (void 0 === data.description) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18937,7 +18937,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "description" } } ], !1; - if (void 0 === data.enabled) return validate293.errors = [ { + if (void 0 === data.enabled) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18945,7 +18945,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.destination) return validate293.errors = [ { + if (void 0 === data.destination) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18953,7 +18953,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "destination" } } ], !1; - if (void 0 === data.event_types) return validate293.errors = [ { + if (void 0 === data.event_types) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18961,7 +18961,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "event_types" } } ], !1; - if (void 0 === data.camera_scope) return validate293.errors = [ { + if (void 0 === data.camera_scope) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18969,7 +18969,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_scope" } } ], !1; - if (void 0 === data.predicate) return validate293.errors = [ { + if (void 0 === data.predicate) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18977,7 +18977,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "predicate" } } ], !1; - if (void 0 === data.schedule) return validate293.errors = [ { + if (void 0 === data.schedule) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18985,7 +18985,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "schedule" } } ], !1; - if (void 0 === data.suppression) return validate293.errors = [ { + if (void 0 === data.suppression) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -18993,7 +18993,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "suppression" } } ], !1; - if (void 0 === data.revision) return validate293.errors = [ { + if (void 0 === data.revision) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -19001,7 +19001,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate293.errors = [ { + if (void 0 === data.created_at) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -19009,7 +19009,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate293.errors = [ { + if (void 0 === data.updated_at) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -19019,7 +19019,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema318.properties, key0)) return validate293.errors = [ { + for (let key0 in data) if (!func7.call(schema322.properties, key0)) return validate296.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -19031,7 +19031,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate293.errors = [ { + if ("string" != typeof data0) return validate296.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/properties/uuid/type", keyword: "type", @@ -19039,7 +19039,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!formats2.test(data0)) return validate293.errors = [ { + if (!formats6.test(data0)) return validate296.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/properties/uuid/format", keyword: "format", @@ -19054,7 +19054,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data1 = data.name, _errs4 = errors; if (errors === _errs4) { - if ("string" != typeof data1) return validate293.errors = [ { + if ("string" != typeof data1) return validate296.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -19062,7 +19062,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 127) return validate293.errors = [ { + if (func1(data1) > 127) return validate296.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -19070,7 +19070,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data1) < 1) return validate293.errors = [ { + if (func1(data1) < 1) return validate296.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -19085,7 +19085,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data2 = data.description, _errs6 = errors; if (errors === _errs6) { - if ("string" != typeof data2) return validate293.errors = [ { + if ("string" != typeof data2) return validate296.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -19093,7 +19093,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 511) return validate293.errors = [ { + if (func1(data2) > 511) return validate296.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -19107,7 +19107,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.enabled) { let _errs8 = errors; - if ("boolean" != typeof data.enabled) return validate293.errors = [ { + if ("boolean" != typeof data.enabled) return validate296.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -19121,7 +19121,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.destination) { let data4 = data.destination, _errs10 = errors; if (errors === _errs10) { - if ("string" != typeof data4) return validate293.errors = [ { + if ("string" != typeof data4) return validate296.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/type", keyword: "type", @@ -19129,7 +19129,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data4) > 127) return validate293.errors = [ { + if (func1(data4) > 127) return validate296.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/maxLength", keyword: "maxLength", @@ -19137,7 +19137,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data4) < 1) return validate293.errors = [ { + if (func1(data4) < 1) return validate296.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/minLength", keyword: "minLength", @@ -19152,7 +19152,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.event_types) { let data5 = data.event_types, _errs12 = errors; if (errors === _errs12) { - if (!Array.isArray(data5)) return validate293.errors = [ { + if (!Array.isArray(data5)) return validate296.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/type", keyword: "type", @@ -19160,7 +19160,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data5.length > 32) return validate293.errors = [ { + if (data5.length > 32) return validate296.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/maxItems", keyword: "maxItems", @@ -19168,7 +19168,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 32 } } ], !1; - if (data5.length < 1) return validate293.errors = [ { + if (data5.length < 1) return validate296.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/minItems", keyword: "minItems", @@ -19182,7 +19182,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data6 = data5[i0], _errs14 = errors; if (errors === _errs14) { - if ("string" != typeof data6) return validate293.errors = [ { + if ("string" != typeof data6) return validate296.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/type", keyword: "type", @@ -19190,7 +19190,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data6) > 127) return validate293.errors = [ { + if (func1(data6) > 127) return validate296.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/maxLength", keyword: "maxLength", @@ -19198,7 +19198,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data6) < 1) return validate293.errors = [ { + if (func1(data6) < 1) return validate296.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/minLength", keyword: "minLength", @@ -19216,7 +19216,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data5[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate293.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate296.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/uniqueItems", keyword: "uniqueItems", @@ -19237,20 +19237,20 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.camera_scope) { let _errs16 = errors; - validate290(data.camera_scope, { + validate293(data.camera_scope, { instancePath: instancePath + "/camera_scope", parentData: data, parentDataProperty: "camera_scope", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate290.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate293.errors), errors = vErrors.length); var valid8 = _errs16 === errors; } else valid8 = !0; if (valid8) { if (void 0 !== data.predicate) { let data8 = data.predicate, _errs17 = errors; if (errors == errors) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate293.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate296.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/type", keyword: "type", @@ -19258,7 +19258,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data8.version) return validate293.errors = [ { + if (void 0 === data8.version) return validate296.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/required", keyword: "required", @@ -19268,7 +19268,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs20 = errors; - for (let key1 in data8) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate293.errors = [ { + for (let key1 in data8) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate296.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/additionalProperties", keyword: "additionalProperties", @@ -19279,7 +19279,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (_errs20 === errors) { if (void 0 !== data8.version) { let data9 = data8.version, _errs21 = errors; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate293.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate296.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/type", keyword: "type", @@ -19287,7 +19287,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data9) return validate293.errors = [ { + if (1 !== data9) return validate296.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/const", keyword: "const", @@ -19301,7 +19301,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.detection) { let data10 = data8.detection, _errs23 = errors; if (errors === _errs23) { - if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate293.errors = [ { + if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/type", keyword: "type", @@ -19309,7 +19309,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data10).length < 1) return validate293.errors = [ { + if (Object.keys(data10).length < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/minProperties", keyword: "minProperties", @@ -19319,7 +19319,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs25 = errors; - for (let key2 in data10) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate293.errors = [ { + for (let key2 in data10) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -19331,7 +19331,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data10.labels_any) { let data11 = data10.labels_any, _errs26 = errors; if (errors === _errs26) { - if (!Array.isArray(data11)) return validate293.errors = [ { + if (!Array.isArray(data11)) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/type", keyword: "type", @@ -19339,7 +19339,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data11.length > 64) return validate293.errors = [ { + if (data11.length > 64) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/maxItems", keyword: "maxItems", @@ -19347,7 +19347,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data11.length < 1) return validate293.errors = [ { + if (data11.length < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/minItems", keyword: "minItems", @@ -19360,7 +19360,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len1; i2++) { let data12 = data11[i2], _errs28 = errors; if (errors === _errs28) { - if ("string" != typeof data12) return validate293.errors = [ { + if ("string" != typeof data12) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/type", keyword: "type", @@ -19368,7 +19368,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data12) > 127) return validate293.errors = [ { + if (func1(data12) > 127) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/maxLength", keyword: "maxLength", @@ -19376,7 +19376,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data12) < 1) return validate293.errors = [ { + if (func1(data12) < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/minLength", keyword: "minLength", @@ -19395,7 +19395,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data10.zone_ids_any) { let data13 = data10.zone_ids_any, _errs30 = errors; if (errors === _errs30) { - if (!Array.isArray(data13)) return validate293.errors = [ { + if (!Array.isArray(data13)) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/type", keyword: "type", @@ -19403,7 +19403,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data13.length > 64) return validate293.errors = [ { + if (data13.length > 64) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/maxItems", keyword: "maxItems", @@ -19411,7 +19411,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data13.length < 1) return validate293.errors = [ { + if (data13.length < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/minItems", keyword: "minItems", @@ -19424,7 +19424,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (let i3 = 0; i3 < len2; i3++) { let data14 = data13[i3], _errs32 = errors; if (errors === _errs32) { - if ("string" != typeof data14) return validate293.errors = [ { + if ("string" != typeof data14) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/type", keyword: "type", @@ -19432,7 +19432,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data14) > 127) return validate293.errors = [ { + if (func1(data14) > 127) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/maxLength", keyword: "maxLength", @@ -19440,7 +19440,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data14) < 1) return validate293.errors = [ { + if (func1(data14) < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/minLength", keyword: "minLength", @@ -19458,7 +19458,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid15 && void 0 !== data10.min_confidence) { let data15 = data10.min_confidence; if (errors == errors) { - if ("number" != typeof data15 || !isFinite(data15)) return validate293.errors = [ { + if ("number" != typeof data15 || !isFinite(data15)) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/type", keyword: "type", @@ -19466,7 +19466,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data15 > 1 || isNaN(data15)) return validate293.errors = [ { + if (data15 > 1 || isNaN(data15)) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/maximum", keyword: "maximum", @@ -19475,7 +19475,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data15 < 0 || isNaN(data15)) return validate293.errors = [ { + if (data15 < 0 || isNaN(data15)) return validate296.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/minimum", keyword: "minimum", @@ -19495,7 +19495,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid12 && void 0 !== data8.health) { let data16 = data8.health; if (errors == errors) { - if (!data16 || "object" != typeof data16 || Array.isArray(data16)) return validate293.errors = [ { + if (!data16 || "object" != typeof data16 || Array.isArray(data16)) return validate296.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/type", keyword: "type", @@ -19503,7 +19503,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data16).length < 1) return validate293.errors = [ { + if (Object.keys(data16).length < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/minProperties", keyword: "minProperties", @@ -19513,7 +19513,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs38 = errors; - for (let key3 in data16) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate293.errors = [ { + for (let key3 in data16) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate296.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/additionalProperties", keyword: "additionalProperties", @@ -19525,7 +19525,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data16.condition_codes_any) { let data17 = data16.condition_codes_any, _errs39 = errors; if (errors === _errs39) { - if (!Array.isArray(data17)) return validate293.errors = [ { + if (!Array.isArray(data17)) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/type", keyword: "type", @@ -19533,7 +19533,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data17.length > 64) return validate293.errors = [ { + if (data17.length > 64) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/maxItems", keyword: "maxItems", @@ -19541,7 +19541,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data17.length < 1) return validate293.errors = [ { + if (data17.length < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/minItems", keyword: "minItems", @@ -19554,7 +19554,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (let i4 = 0; i4 < len3; i4++) { let data18 = data17[i4], _errs41 = errors; if (errors === _errs41) { - if ("string" != typeof data18) return validate293.errors = [ { + if ("string" != typeof data18) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/type", keyword: "type", @@ -19562,7 +19562,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data18) > 127) return validate293.errors = [ { + if (func1(data18) > 127) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/maxLength", keyword: "maxLength", @@ -19570,7 +19570,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data18) < 1) return validate293.errors = [ { + if (func1(data18) < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/minLength", keyword: "minLength", @@ -19588,7 +19588,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid19 && void 0 !== data16.severities_any) { let data19 = data16.severities_any; if (errors == errors) { - if (!Array.isArray(data19)) return validate293.errors = [ { + if (!Array.isArray(data19)) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/type", keyword: "type", @@ -19596,7 +19596,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data19.length > 3) return validate293.errors = [ { + if (data19.length > 3) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/maxItems", keyword: "maxItems", @@ -19604,7 +19604,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 3 } } ], !1; - if (data19.length < 1) return validate293.errors = [ { + if (data19.length < 1) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/minItems", keyword: "minItems", @@ -19616,7 +19616,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD let len4 = data19.length; for (let i5 = 0; i5 < len4; i5++) { let data20 = data19[i5], _errs45 = errors; - if ("string" != typeof data20) return validate293.errors = [ { + if ("string" != typeof data20) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/type", keyword: "type", @@ -19624,12 +19624,12 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("warning" !== data20 && "error" !== data20 && "critical" !== data20) return validate293.errors = [ { + if ("warning" !== data20 && "error" !== data20 && "critical" !== data20) return validate296.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/enum", keyword: "enum", params: { - allowedValues: schema315.properties.health.properties.severities_any.items.enum + allowedValues: schema319.properties.health.properties.severities_any.items.enum } } ], !1; if (_errs45 !== errors) break; @@ -19651,7 +19651,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.schedule) { let data21 = data.schedule, _errs47 = errors; if (errors == errors) { - if (!data21 || "object" != typeof data21 || Array.isArray(data21)) return validate293.errors = [ { + if (!data21 || "object" != typeof data21 || Array.isArray(data21)) return validate296.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/type", keyword: "type", @@ -19659,7 +19659,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data21.version) return validate293.errors = [ { + if (void 0 === data21.version) return validate296.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -19667,7 +19667,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "version" } } ], !1; - if (void 0 === data21.timezone) return validate293.errors = [ { + if (void 0 === data21.timezone) return validate296.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -19675,7 +19675,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timezone" } } ], !1; - if (void 0 === data21.windows) return validate293.errors = [ { + if (void 0 === data21.windows) return validate296.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -19685,7 +19685,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs50 = errors; - for (let key4 in data21) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate293.errors = [ { + for (let key4 in data21) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate296.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/additionalProperties", keyword: "additionalProperties", @@ -19696,7 +19696,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (_errs50 === errors) { if (void 0 !== data21.version) { let data22 = data21.version, _errs51 = errors; - if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate293.errors = [ { + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate296.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/type", keyword: "type", @@ -19704,7 +19704,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data22) return validate293.errors = [ { + if (1 !== data22) return validate296.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/const", keyword: "const", @@ -19718,7 +19718,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data21.timezone) { let data23 = data21.timezone, _errs53 = errors; if (errors === _errs53) { - if ("string" != typeof data23) return validate293.errors = [ { + if ("string" != typeof data23) return validate296.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/type", keyword: "type", @@ -19726,7 +19726,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data23) > 64) return validate293.errors = [ { + if (func1(data23) > 64) return validate296.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/maxLength", keyword: "maxLength", @@ -19734,7 +19734,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (func1(data23) < 1) return validate293.errors = [ { + if (func1(data23) < 1) return validate296.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/minLength", keyword: "minLength", @@ -19748,7 +19748,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid26 && void 0 !== data21.windows) { let data24 = data21.windows; if (errors == errors) { - if (!Array.isArray(data24)) return validate293.errors = [ { + if (!Array.isArray(data24)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/type", keyword: "type", @@ -19756,7 +19756,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data24.length > 64) return validate293.errors = [ { + if (data24.length > 64) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/maxItems", keyword: "maxItems", @@ -19769,7 +19769,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (let i6 = 0; i6 < len5; i6++) { let data25 = data24[i6], _errs57 = errors; if (errors === _errs57) { - if (!data25 || "object" != typeof data25 || Array.isArray(data25)) return validate293.errors = [ { + if (!data25 || "object" != typeof data25 || Array.isArray(data25)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/type", keyword: "type", @@ -19777,7 +19777,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data25.days) return validate293.errors = [ { + if (void 0 === data25.days) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -19785,7 +19785,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "days" } } ], !1; - if (void 0 === data25.start) return validate293.errors = [ { + if (void 0 === data25.start) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -19793,7 +19793,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start" } } ], !1; - if (void 0 === data25.end) return validate293.errors = [ { + if (void 0 === data25.end) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -19803,7 +19803,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs59 = errors; - for (let key5 in data25) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate293.errors = [ { + for (let key5 in data25) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/additionalProperties", keyword: "additionalProperties", @@ -19815,7 +19815,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data25.days) { let data26 = data25.days, _errs60 = errors; if (errors === _errs60) { - if (!Array.isArray(data26)) return validate293.errors = [ { + if (!Array.isArray(data26)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/type", keyword: "type", @@ -19823,7 +19823,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data26.length > 7) return validate293.errors = [ { + if (data26.length > 7) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/maxItems", keyword: "maxItems", @@ -19831,7 +19831,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 7 } } ], !1; - if (data26.length < 1) return validate293.errors = [ { + if (data26.length < 1) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/minItems", keyword: "minItems", @@ -19844,7 +19844,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD let len6 = data26.length; for (let i7 = 0; i7 < len6; i7++) { let data27 = data26[i7], _errs62 = errors; - if ("number" != typeof data27 || data27 % 1 || isNaN(data27) || !isFinite(data27)) return validate293.errors = [ { + if ("number" != typeof data27 || data27 % 1 || isNaN(data27) || !isFinite(data27)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/type", keyword: "type", @@ -19853,7 +19853,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs62 && "number" == typeof data27 && isFinite(data27)) { - if (data27 > 6 || isNaN(data27)) return validate293.errors = [ { + if (data27 > 6 || isNaN(data27)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/maximum", keyword: "maximum", @@ -19862,7 +19862,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 6 } } ], !1; - if (data27 < 0 || isNaN(data27)) return validate293.errors = [ { + if (data27 < 0 || isNaN(data27)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/minimum", keyword: "minimum", @@ -19881,7 +19881,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD for (;i8--; ) { let item1 = data26[i8]; if ("number" == typeof item1 && !(item1 % 1) && !isNaN(item1) && isFinite(item1)) { - if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate293.errors = [ { + if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/uniqueItems", keyword: "uniqueItems", @@ -19903,7 +19903,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data25.start) { let data28 = data25.start, _errs64 = errors; if (errors === _errs64) { - if ("string" != typeof data28) return validate293.errors = [ { + if ("string" != typeof data28) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/type", keyword: "type", @@ -19911,7 +19911,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data28)) return validate293.errors = [ { + if (!pattern101.test(data28)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/pattern", keyword: "pattern", @@ -19925,7 +19925,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid32 && void 0 !== data25.end) { let data29 = data25.end; if (errors == errors) { - if ("string" != typeof data29) return validate293.errors = [ { + if ("string" != typeof data29) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/type", keyword: "type", @@ -19933,7 +19933,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data29)) return validate293.errors = [ { + if (!pattern101.test(data29)) return validate296.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/pattern", keyword: "pattern", @@ -19962,7 +19962,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.suppression) { let data30 = data.suppression, _errs68 = errors; if (errors === _errs68) { - if (!data30 || "object" != typeof data30 || Array.isArray(data30)) return validate293.errors = [ { + if (!data30 || "object" != typeof data30 || Array.isArray(data30)) return validate296.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/type", keyword: "type", @@ -19970,7 +19970,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data30.debounce_seconds) return validate293.errors = [ { + if (void 0 === data30.debounce_seconds) return validate296.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/required", keyword: "required", @@ -19978,7 +19978,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "debounce_seconds" } } ], !1; - if (void 0 === data30.cooldown_seconds) return validate293.errors = [ { + if (void 0 === data30.cooldown_seconds) return validate296.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/required", keyword: "required", @@ -19986,7 +19986,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cooldown_seconds" } } ], !1; - if (void 0 === data30.grouping_window_seconds) return validate293.errors = [ { + if (void 0 === data30.grouping_window_seconds) return validate296.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/required", keyword: "required", @@ -19994,7 +19994,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "grouping_window_seconds" } } ], !1; - if (void 0 === data30.max_events_per_minute) return validate293.errors = [ { + if (void 0 === data30.max_events_per_minute) return validate296.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/required", keyword: "required", @@ -20004,7 +20004,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs70 = errors; - for (let key6 in data30) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate293.errors = [ { + for (let key6 in data30) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate296.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/additionalProperties", keyword: "additionalProperties", @@ -20015,7 +20015,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (_errs70 === errors) { if (void 0 !== data30.debounce_seconds) { let data31 = data30.debounce_seconds, _errs71 = errors; - if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate293.errors = [ { + if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate296.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/type", keyword: "type", @@ -20024,7 +20024,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs71 && "number" == typeof data31 && isFinite(data31)) { - if (data31 > 86400 || isNaN(data31)) return validate293.errors = [ { + if (data31 > 86400 || isNaN(data31)) return validate296.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/maximum", keyword: "maximum", @@ -20033,7 +20033,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data31 < 0 || isNaN(data31)) return validate293.errors = [ { + if (data31 < 0 || isNaN(data31)) return validate296.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/minimum", keyword: "minimum", @@ -20048,7 +20048,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data30.cooldown_seconds) { let data32 = data30.cooldown_seconds, _errs73 = errors; - if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate293.errors = [ { + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate296.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/type", keyword: "type", @@ -20057,7 +20057,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs73 && "number" == typeof data32 && isFinite(data32)) { - if (data32 > 604800 || isNaN(data32)) return validate293.errors = [ { + if (data32 > 604800 || isNaN(data32)) return validate296.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/maximum", keyword: "maximum", @@ -20066,7 +20066,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 604800 } } ], !1; - if (data32 < 0 || isNaN(data32)) return validate293.errors = [ { + if (data32 < 0 || isNaN(data32)) return validate296.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/minimum", keyword: "minimum", @@ -20081,7 +20081,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid36) { if (void 0 !== data30.grouping_window_seconds) { let data33 = data30.grouping_window_seconds, _errs75 = errors; - if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate293.errors = [ { + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate296.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/type", keyword: "type", @@ -20090,7 +20090,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs75 && "number" == typeof data33 && isFinite(data33)) { - if (data33 > 3600 || isNaN(data33)) return validate293.errors = [ { + if (data33 > 3600 || isNaN(data33)) return validate296.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/maximum", keyword: "maximum", @@ -20099,7 +20099,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data33 < 0 || isNaN(data33)) return validate293.errors = [ { + if (data33 < 0 || isNaN(data33)) return validate296.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/minimum", keyword: "minimum", @@ -20113,7 +20113,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } else valid37 = !0; if (valid37 && void 0 !== data30.max_events_per_minute) { let data34 = data30.max_events_per_minute, _errs77 = errors; - if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate293.errors = [ { + if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate296.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/type", keyword: "type", @@ -20122,7 +20122,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs77 && "number" == typeof data34 && isFinite(data34)) { - if (data34 > 6e4 || isNaN(data34)) return validate293.errors = [ { + if (data34 > 6e4 || isNaN(data34)) return validate296.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/maximum", keyword: "maximum", @@ -20131,7 +20131,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 6e4 } } ], !1; - if (data34 < 0 || isNaN(data34)) return validate293.errors = [ { + if (data34 < 0 || isNaN(data34)) return validate296.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/minimum", keyword: "minimum", @@ -20152,7 +20152,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data.revision) { let data35 = data.revision, _errs79 = errors; - if ("number" != typeof data35 || data35 % 1 || isNaN(data35) || !isFinite(data35)) return validate293.errors = [ { + if ("number" != typeof data35 || data35 % 1 || isNaN(data35) || !isFinite(data35)) return validate296.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -20161,7 +20161,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs79 && "number" == typeof data35 && isFinite(data35)) { - if (data35 > 9007199254740991 || isNaN(data35)) return validate293.errors = [ { + if (data35 > 9007199254740991 || isNaN(data35)) return validate296.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -20170,7 +20170,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data35 < 1 || isNaN(data35)) return validate293.errors = [ { + if (data35 < 1 || isNaN(data35)) return validate296.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -20185,7 +20185,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD if (valid39) { if (void 0 !== data.created_at) { let data36 = data.created_at, _errs81 = errors; - if ("number" != typeof data36 || data36 % 1 || isNaN(data36) || !isFinite(data36)) return validate293.errors = [ { + if ("number" != typeof data36 || data36 % 1 || isNaN(data36) || !isFinite(data36)) return validate296.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -20193,7 +20193,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs81 && "number" == typeof data36 && isFinite(data36) && (data36 < 0 || isNaN(data36))) return validate293.errors = [ { + if (errors === _errs81 && "number" == typeof data36 && isFinite(data36) && (data36 < 0 || isNaN(data36))) return validate296.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -20206,7 +20206,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } else valid40 = !0; if (valid40 && void 0 !== data.updated_at) { let data37 = data.updated_at, _errs83 = errors; - if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate293.errors = [ { + if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate296.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -20214,7 +20214,7 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs83 && "number" == typeof data37 && isFinite(data37) && (data37 < 0 || isNaN(data37))) return validate293.errors = [ { + if (errors === _errs83 && "number" == typeof data37 && isFinite(data37) && (data37 < 0 || isNaN(data37))) return validate296.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/minimum", keyword: "minimum", @@ -20238,16 +20238,16 @@ function validate293(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate293.errors = vErrors, 0 === errors; + return validate296.errors = vErrors, 0 === errors; } -validate293.evaluated = { +validate296.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventRouteCreate = validate297, schema322 = { +var __contract_EventRouteCreate = validate300, schema326 = { type: "object", properties: { name: { @@ -20319,11 +20319,11 @@ var __contract_EventRouteCreate = validate297, schema322 = { additionalProperties: !1 }; -function validate297(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate297.evaluated; +function validate300(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate300.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate297.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate300.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -20331,7 +20331,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate297.errors = [ { + if (void 0 === data.name) return validate300.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -20339,7 +20339,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.event_types) return validate297.errors = [ { + if (void 0 === data.event_types) return validate300.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -20349,7 +20349,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema322.properties, key0)) return validate297.errors = [ { + for (let key0 in data) if (!func7.call(schema326.properties, key0)) return validate300.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -20361,7 +20361,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate297.errors = [ { + if ("string" != typeof data0) return validate300.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -20369,7 +20369,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate297.errors = [ { + if (func1(data0) > 127) return validate300.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -20377,7 +20377,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate297.errors = [ { + if (func1(data0) < 1) return validate300.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -20392,7 +20392,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data1 = data.description, _errs4 = errors; if (errors === _errs4) { - if ("string" != typeof data1) return validate297.errors = [ { + if ("string" != typeof data1) return validate300.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -20400,7 +20400,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 511) return validate297.errors = [ { + if (func1(data1) > 511) return validate300.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -20414,7 +20414,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = errors; - if ("boolean" != typeof data.enabled) return validate297.errors = [ { + if ("boolean" != typeof data.enabled) return validate300.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -20428,7 +20428,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.destination) { let data3 = data.destination, _errs8 = errors; if (errors === _errs8) { - if ("string" != typeof data3) return validate297.errors = [ { + if ("string" != typeof data3) return validate300.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/type", keyword: "type", @@ -20436,7 +20436,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 127) return validate297.errors = [ { + if (func1(data3) > 127) return validate300.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/maxLength", keyword: "maxLength", @@ -20444,7 +20444,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data3) < 1) return validate297.errors = [ { + if (func1(data3) < 1) return validate300.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/minLength", keyword: "minLength", @@ -20459,7 +20459,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.event_types) { let data4 = data.event_types, _errs10 = errors; if (errors === _errs10) { - if (!Array.isArray(data4)) return validate297.errors = [ { + if (!Array.isArray(data4)) return validate300.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/type", keyword: "type", @@ -20467,7 +20467,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 32) return validate297.errors = [ { + if (data4.length > 32) return validate300.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/maxItems", keyword: "maxItems", @@ -20475,7 +20475,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 32 } } ], !1; - if (data4.length < 1) return validate297.errors = [ { + if (data4.length < 1) return validate300.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/minItems", keyword: "minItems", @@ -20489,7 +20489,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data5 = data4[i0], _errs12 = errors; if (errors === _errs12) { - if ("string" != typeof data5) return validate297.errors = [ { + if ("string" != typeof data5) return validate300.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/type", keyword: "type", @@ -20497,7 +20497,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 127) return validate297.errors = [ { + if (func1(data5) > 127) return validate300.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/maxLength", keyword: "maxLength", @@ -20505,7 +20505,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data5) < 1) return validate297.errors = [ { + if (func1(data5) < 1) return validate300.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/minLength", keyword: "minLength", @@ -20523,7 +20523,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data4[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate297.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate300.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/uniqueItems", keyword: "uniqueItems", @@ -20544,20 +20544,20 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.camera_scope) { let _errs14 = errors; - validate290(data.camera_scope, { + validate293(data.camera_scope, { instancePath: instancePath + "/camera_scope", parentData: data, parentDataProperty: "camera_scope", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate290.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate293.errors), errors = vErrors.length); var valid7 = _errs14 === errors; } else valid7 = !0; if (valid7) { if (void 0 !== data.predicate) { let data7 = data.predicate, _errs15 = errors; if (errors == errors) { - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate297.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate300.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/type", keyword: "type", @@ -20565,7 +20565,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data7.version) return validate297.errors = [ { + if (void 0 === data7.version) return validate300.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/required", keyword: "required", @@ -20575,7 +20575,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs18 = errors; - for (let key1 in data7) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate297.errors = [ { + for (let key1 in data7) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate300.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/additionalProperties", keyword: "additionalProperties", @@ -20586,7 +20586,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (_errs18 === errors) { if (void 0 !== data7.version) { let data8 = data7.version, _errs19 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate297.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate300.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/type", keyword: "type", @@ -20594,7 +20594,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data8) return validate297.errors = [ { + if (1 !== data8) return validate300.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/const", keyword: "const", @@ -20608,7 +20608,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.detection) { let data9 = data7.detection, _errs21 = errors; if (errors === _errs21) { - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate297.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/type", keyword: "type", @@ -20616,7 +20616,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data9).length < 1) return validate297.errors = [ { + if (Object.keys(data9).length < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/minProperties", keyword: "minProperties", @@ -20626,7 +20626,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs23 = errors; - for (let key2 in data9) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate297.errors = [ { + for (let key2 in data9) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -20638,7 +20638,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.labels_any) { let data10 = data9.labels_any, _errs24 = errors; if (errors === _errs24) { - if (!Array.isArray(data10)) return validate297.errors = [ { + if (!Array.isArray(data10)) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/type", keyword: "type", @@ -20646,7 +20646,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data10.length > 64) return validate297.errors = [ { + if (data10.length > 64) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/maxItems", keyword: "maxItems", @@ -20654,7 +20654,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data10.length < 1) return validate297.errors = [ { + if (data10.length < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/minItems", keyword: "minItems", @@ -20667,7 +20667,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len1; i2++) { let data11 = data10[i2], _errs26 = errors; if (errors === _errs26) { - if ("string" != typeof data11) return validate297.errors = [ { + if ("string" != typeof data11) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/type", keyword: "type", @@ -20675,7 +20675,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 127) return validate297.errors = [ { + if (func1(data11) > 127) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/maxLength", keyword: "maxLength", @@ -20683,7 +20683,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data11) < 1) return validate297.errors = [ { + if (func1(data11) < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/minLength", keyword: "minLength", @@ -20702,7 +20702,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.zone_ids_any) { let data12 = data9.zone_ids_any, _errs28 = errors; if (errors === _errs28) { - if (!Array.isArray(data12)) return validate297.errors = [ { + if (!Array.isArray(data12)) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/type", keyword: "type", @@ -20710,7 +20710,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data12.length > 64) return validate297.errors = [ { + if (data12.length > 64) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/maxItems", keyword: "maxItems", @@ -20718,7 +20718,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data12.length < 1) return validate297.errors = [ { + if (data12.length < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/minItems", keyword: "minItems", @@ -20731,7 +20731,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (let i3 = 0; i3 < len2; i3++) { let data13 = data12[i3], _errs30 = errors; if (errors === _errs30) { - if ("string" != typeof data13) return validate297.errors = [ { + if ("string" != typeof data13) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/type", keyword: "type", @@ -20739,7 +20739,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate297.errors = [ { + if (func1(data13) > 127) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/maxLength", keyword: "maxLength", @@ -20747,7 +20747,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data13) < 1) return validate297.errors = [ { + if (func1(data13) < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/minLength", keyword: "minLength", @@ -20765,7 +20765,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid14 && void 0 !== data9.min_confidence) { let data14 = data9.min_confidence; if (errors == errors) { - if ("number" != typeof data14 || !isFinite(data14)) return validate297.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/type", keyword: "type", @@ -20773,7 +20773,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data14 > 1 || isNaN(data14)) return validate297.errors = [ { + if (data14 > 1 || isNaN(data14)) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/maximum", keyword: "maximum", @@ -20782,7 +20782,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate297.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate300.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/minimum", keyword: "minimum", @@ -20802,7 +20802,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid11 && void 0 !== data7.health) { let data15 = data7.health; if (errors == errors) { - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate297.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate300.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/type", keyword: "type", @@ -20810,7 +20810,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data15).length < 1) return validate297.errors = [ { + if (Object.keys(data15).length < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/minProperties", keyword: "minProperties", @@ -20820,7 +20820,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs36 = errors; - for (let key3 in data15) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate297.errors = [ { + for (let key3 in data15) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate300.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/additionalProperties", keyword: "additionalProperties", @@ -20832,7 +20832,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.condition_codes_any) { let data16 = data15.condition_codes_any, _errs37 = errors; if (errors === _errs37) { - if (!Array.isArray(data16)) return validate297.errors = [ { + if (!Array.isArray(data16)) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/type", keyword: "type", @@ -20840,7 +20840,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data16.length > 64) return validate297.errors = [ { + if (data16.length > 64) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/maxItems", keyword: "maxItems", @@ -20848,7 +20848,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data16.length < 1) return validate297.errors = [ { + if (data16.length < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/minItems", keyword: "minItems", @@ -20861,7 +20861,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (let i4 = 0; i4 < len3; i4++) { let data17 = data16[i4], _errs39 = errors; if (errors === _errs39) { - if ("string" != typeof data17) return validate297.errors = [ { + if ("string" != typeof data17) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/type", keyword: "type", @@ -20869,7 +20869,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data17) > 127) return validate297.errors = [ { + if (func1(data17) > 127) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/maxLength", keyword: "maxLength", @@ -20877,7 +20877,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data17) < 1) return validate297.errors = [ { + if (func1(data17) < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/minLength", keyword: "minLength", @@ -20895,7 +20895,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid18 && void 0 !== data15.severities_any) { let data18 = data15.severities_any; if (errors == errors) { - if (!Array.isArray(data18)) return validate297.errors = [ { + if (!Array.isArray(data18)) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/type", keyword: "type", @@ -20903,7 +20903,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data18.length > 3) return validate297.errors = [ { + if (data18.length > 3) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/maxItems", keyword: "maxItems", @@ -20911,7 +20911,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 3 } } ], !1; - if (data18.length < 1) return validate297.errors = [ { + if (data18.length < 1) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/minItems", keyword: "minItems", @@ -20923,7 +20923,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD let len4 = data18.length; for (let i5 = 0; i5 < len4; i5++) { let data19 = data18[i5], _errs43 = errors; - if ("string" != typeof data19) return validate297.errors = [ { + if ("string" != typeof data19) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/type", keyword: "type", @@ -20931,12 +20931,12 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("warning" !== data19 && "error" !== data19 && "critical" !== data19) return validate297.errors = [ { + if ("warning" !== data19 && "error" !== data19 && "critical" !== data19) return validate300.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/enum", keyword: "enum", params: { - allowedValues: schema315.properties.health.properties.severities_any.items.enum + allowedValues: schema319.properties.health.properties.severities_any.items.enum } } ], !1; if (_errs43 !== errors) break; @@ -20958,7 +20958,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.schedule) { let data20 = data.schedule, _errs45 = errors; if (errors == errors) { - if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate297.errors = [ { + if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate300.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/type", keyword: "type", @@ -20966,7 +20966,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data20.version) return validate297.errors = [ { + if (void 0 === data20.version) return validate300.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -20974,7 +20974,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "version" } } ], !1; - if (void 0 === data20.timezone) return validate297.errors = [ { + if (void 0 === data20.timezone) return validate300.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -20982,7 +20982,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timezone" } } ], !1; - if (void 0 === data20.windows) return validate297.errors = [ { + if (void 0 === data20.windows) return validate300.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -20992,7 +20992,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs48 = errors; - for (let key4 in data20) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate297.errors = [ { + for (let key4 in data20) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate300.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/additionalProperties", keyword: "additionalProperties", @@ -21003,7 +21003,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (_errs48 === errors) { if (void 0 !== data20.version) { let data21 = data20.version, _errs49 = errors; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate297.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate300.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/type", keyword: "type", @@ -21011,7 +21011,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data21) return validate297.errors = [ { + if (1 !== data21) return validate300.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/const", keyword: "const", @@ -21025,7 +21025,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data20.timezone) { let data22 = data20.timezone, _errs51 = errors; if (errors === _errs51) { - if ("string" != typeof data22) return validate297.errors = [ { + if ("string" != typeof data22) return validate300.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/type", keyword: "type", @@ -21033,7 +21033,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data22) > 64) return validate297.errors = [ { + if (func1(data22) > 64) return validate300.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/maxLength", keyword: "maxLength", @@ -21041,7 +21041,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (func1(data22) < 1) return validate297.errors = [ { + if (func1(data22) < 1) return validate300.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/minLength", keyword: "minLength", @@ -21055,7 +21055,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid25 && void 0 !== data20.windows) { let data23 = data20.windows; if (errors == errors) { - if (!Array.isArray(data23)) return validate297.errors = [ { + if (!Array.isArray(data23)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/type", keyword: "type", @@ -21063,7 +21063,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data23.length > 64) return validate297.errors = [ { + if (data23.length > 64) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/maxItems", keyword: "maxItems", @@ -21076,7 +21076,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (let i6 = 0; i6 < len5; i6++) { let data24 = data23[i6], _errs55 = errors; if (errors === _errs55) { - if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate297.errors = [ { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/type", keyword: "type", @@ -21084,7 +21084,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data24.days) return validate297.errors = [ { + if (void 0 === data24.days) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -21092,7 +21092,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "days" } } ], !1; - if (void 0 === data24.start) return validate297.errors = [ { + if (void 0 === data24.start) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -21100,7 +21100,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start" } } ], !1; - if (void 0 === data24.end) return validate297.errors = [ { + if (void 0 === data24.end) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -21110,7 +21110,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs57 = errors; - for (let key5 in data24) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate297.errors = [ { + for (let key5 in data24) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/additionalProperties", keyword: "additionalProperties", @@ -21122,7 +21122,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data24.days) { let data25 = data24.days, _errs58 = errors; if (errors === _errs58) { - if (!Array.isArray(data25)) return validate297.errors = [ { + if (!Array.isArray(data25)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/type", keyword: "type", @@ -21130,7 +21130,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data25.length > 7) return validate297.errors = [ { + if (data25.length > 7) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/maxItems", keyword: "maxItems", @@ -21138,7 +21138,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 7 } } ], !1; - if (data25.length < 1) return validate297.errors = [ { + if (data25.length < 1) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/minItems", keyword: "minItems", @@ -21151,7 +21151,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD let len6 = data25.length; for (let i7 = 0; i7 < len6; i7++) { let data26 = data25[i7], _errs60 = errors; - if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate297.errors = [ { + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/type", keyword: "type", @@ -21160,7 +21160,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs60 && "number" == typeof data26 && isFinite(data26)) { - if (data26 > 6 || isNaN(data26)) return validate297.errors = [ { + if (data26 > 6 || isNaN(data26)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/maximum", keyword: "maximum", @@ -21169,7 +21169,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 6 } } ], !1; - if (data26 < 0 || isNaN(data26)) return validate297.errors = [ { + if (data26 < 0 || isNaN(data26)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/minimum", keyword: "minimum", @@ -21188,7 +21188,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD for (;i8--; ) { let item1 = data25[i8]; if ("number" == typeof item1 && !(item1 % 1) && !isNaN(item1) && isFinite(item1)) { - if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate297.errors = [ { + if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/uniqueItems", keyword: "uniqueItems", @@ -21210,7 +21210,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data24.start) { let data27 = data24.start, _errs62 = errors; if (errors === _errs62) { - if ("string" != typeof data27) return validate297.errors = [ { + if ("string" != typeof data27) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/type", keyword: "type", @@ -21218,7 +21218,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data27)) return validate297.errors = [ { + if (!pattern101.test(data27)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/pattern", keyword: "pattern", @@ -21232,7 +21232,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid31 && void 0 !== data24.end) { let data28 = data24.end; if (errors == errors) { - if ("string" != typeof data28) return validate297.errors = [ { + if ("string" != typeof data28) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/type", keyword: "type", @@ -21240,7 +21240,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data28)) return validate297.errors = [ { + if (!pattern101.test(data28)) return validate300.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/pattern", keyword: "pattern", @@ -21268,7 +21268,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid22 && void 0 !== data.suppression) { let data29 = data.suppression; if (errors == errors) { - if (!data29 || "object" != typeof data29 || Array.isArray(data29)) return validate297.errors = [ { + if (!data29 || "object" != typeof data29 || Array.isArray(data29)) return validate300.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/type", keyword: "type", @@ -21278,7 +21278,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs68 = errors; - for (let key6 in data29) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate297.errors = [ { + for (let key6 in data29) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate300.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/additionalProperties", keyword: "additionalProperties", @@ -21289,7 +21289,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (_errs68 === errors) { if (void 0 !== data29.debounce_seconds) { let data30 = data29.debounce_seconds, _errs69 = errors; - if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate297.errors = [ { + if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate300.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/type", keyword: "type", @@ -21298,7 +21298,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs69 && "number" == typeof data30 && isFinite(data30)) { - if (data30 > 86400 || isNaN(data30)) return validate297.errors = [ { + if (data30 > 86400 || isNaN(data30)) return validate300.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/maximum", keyword: "maximum", @@ -21307,7 +21307,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data30 < 0 || isNaN(data30)) return validate297.errors = [ { + if (data30 < 0 || isNaN(data30)) return validate300.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/minimum", keyword: "minimum", @@ -21322,7 +21322,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data29.cooldown_seconds) { let data31 = data29.cooldown_seconds, _errs71 = errors; - if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate297.errors = [ { + if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate300.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/type", keyword: "type", @@ -21331,7 +21331,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs71 && "number" == typeof data31 && isFinite(data31)) { - if (data31 > 604800 || isNaN(data31)) return validate297.errors = [ { + if (data31 > 604800 || isNaN(data31)) return validate300.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/maximum", keyword: "maximum", @@ -21340,7 +21340,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 604800 } } ], !1; - if (data31 < 0 || isNaN(data31)) return validate297.errors = [ { + if (data31 < 0 || isNaN(data31)) return validate300.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/minimum", keyword: "minimum", @@ -21355,7 +21355,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data29.grouping_window_seconds) { let data32 = data29.grouping_window_seconds, _errs73 = errors; - if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate297.errors = [ { + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate300.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/type", keyword: "type", @@ -21364,7 +21364,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs73 && "number" == typeof data32 && isFinite(data32)) { - if (data32 > 3600 || isNaN(data32)) return validate297.errors = [ { + if (data32 > 3600 || isNaN(data32)) return validate300.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/maximum", keyword: "maximum", @@ -21373,7 +21373,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data32 < 0 || isNaN(data32)) return validate297.errors = [ { + if (data32 < 0 || isNaN(data32)) return validate300.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/minimum", keyword: "minimum", @@ -21387,7 +21387,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } else valid36 = !0; if (valid36 && void 0 !== data29.max_events_per_minute) { let data33 = data29.max_events_per_minute, _errs75 = errors; - if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate297.errors = [ { + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate300.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/type", keyword: "type", @@ -21396,7 +21396,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs75 && "number" == typeof data33 && isFinite(data33)) { - if (data33 > 6e4 || isNaN(data33)) return validate297.errors = [ { + if (data33 > 6e4 || isNaN(data33)) return validate300.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/maximum", keyword: "maximum", @@ -21405,7 +21405,7 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD limit: 6e4 } } ], !1; - if (data33 < 0 || isNaN(data33)) return validate297.errors = [ { + if (data33 < 0 || isNaN(data33)) return validate300.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/minimum", keyword: "minimum", @@ -21432,16 +21432,16 @@ function validate297(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate297.errors = vErrors, 0 === errors; + return validate300.errors = vErrors, 0 === errors; } -validate297.evaluated = { +validate300.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventRoutePreview = validate304, schema332 = { +var __contract_EventRoutePreview = validate307, schema336 = { type: "object", properties: { matched_camera_count: { @@ -21518,10 +21518,10 @@ var __contract_EventRoutePreview = validate304, schema332 = { additionalProperties: !1 }; -function validate304(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate304.evaluated; +function validate307(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate307.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate304.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -21529,7 +21529,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.matched_camera_count) return validate304.errors = [ { + if (void 0 === data.matched_camera_count) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -21537,7 +21537,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "matched_camera_count" } } ], !1; - if (void 0 === data.sample_limit) return validate304.errors = [ { + if (void 0 === data.sample_limit) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -21545,7 +21545,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample_limit" } } ], !1; - if (void 0 === data.camera_sample) return validate304.errors = [ { + if (void 0 === data.camera_sample) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -21553,7 +21553,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_sample" } } ], !1; - if (void 0 === data.event_types) return validate304.errors = [ { + if (void 0 === data.event_types) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -21561,7 +21561,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "event_types" } } ], !1; - if (void 0 === data.would_publish) return validate304.errors = [ { + if (void 0 === data.would_publish) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -21571,7 +21571,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("matched_camera_count" !== key0 && "sample_limit" !== key0 && "camera_sample" !== key0 && "event_types" !== key0 && "would_publish" !== key0) return validate304.errors = [ { + for (let key0 in data) if ("matched_camera_count" !== key0 && "sample_limit" !== key0 && "camera_sample" !== key0 && "event_types" !== key0 && "would_publish" !== key0) return validate307.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -21582,7 +21582,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.matched_camera_count) { let data0 = data.matched_camera_count, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate304.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate307.errors = [ { instancePath: instancePath + "/matched_camera_count", schemaPath: "#/properties/matched_camera_count/type", keyword: "type", @@ -21590,7 +21590,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate304.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate307.errors = [ { instancePath: instancePath + "/matched_camera_count", schemaPath: "#/properties/matched_camera_count/minimum", keyword: "minimum", @@ -21604,7 +21604,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.sample_limit) { let data1 = data.sample_limit, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate304.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate307.errors = [ { instancePath: instancePath + "/sample_limit", schemaPath: "#/properties/sample_limit/type", keyword: "type", @@ -21612,7 +21612,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (20 !== data1) return validate304.errors = [ { + if (20 !== data1) return validate307.errors = [ { instancePath: instancePath + "/sample_limit", schemaPath: "#/properties/sample_limit/const", keyword: "const", @@ -21626,7 +21626,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_sample) { let data2 = data.camera_sample, _errs6 = 0; if (0 === _errs6) { - if (!Array.isArray(data2)) return validate304.errors = [ { + if (!Array.isArray(data2)) return validate307.errors = [ { instancePath: instancePath + "/camera_sample", schemaPath: "#/properties/camera_sample/type", keyword: "type", @@ -21634,7 +21634,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data2.length > 20) return validate304.errors = [ { + if (data2.length > 20) return validate307.errors = [ { instancePath: instancePath + "/camera_sample", schemaPath: "#/properties/camera_sample/maxItems", keyword: "maxItems", @@ -21647,7 +21647,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs8 = 0; if (0 === _errs8) { - if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate304.errors = [ { + if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0, schemaPath: "#/properties/camera_sample/items/type", keyword: "type", @@ -21655,7 +21655,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data3.camera_uuid) return validate304.errors = [ { + if (void 0 === data3.camera_uuid) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0, schemaPath: "#/properties/camera_sample/items/required", keyword: "required", @@ -21663,7 +21663,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data3.name) return validate304.errors = [ { + if (void 0 === data3.name) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0, schemaPath: "#/properties/camera_sample/items/required", keyword: "required", @@ -21671,7 +21671,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data3.location_path) return validate304.errors = [ { + if (void 0 === data3.location_path) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0, schemaPath: "#/properties/camera_sample/items/required", keyword: "required", @@ -21681,7 +21681,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs10 = 0; - for (let key1 in data3) if ("camera_uuid" !== key1 && "name" !== key1 && "location_path" !== key1) return validate304.errors = [ { + for (let key1 in data3) if ("camera_uuid" !== key1 && "name" !== key1 && "location_path" !== key1) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0, schemaPath: "#/properties/camera_sample/items/additionalProperties", keyword: "additionalProperties", @@ -21693,7 +21693,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data3.camera_uuid) { let data4 = data3.camera_uuid, _errs11 = 0; if (0 === _errs11) { - if ("string" != typeof data4) return validate304.errors = [ { + if ("string" != typeof data4) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0 + "/camera_uuid", schemaPath: "#/properties/camera_sample/items/properties/camera_uuid/type", keyword: "type", @@ -21701,7 +21701,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!formats2.test(data4)) return validate304.errors = [ { + if (!formats6.test(data4)) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0 + "/camera_uuid", schemaPath: "#/properties/camera_sample/items/properties/camera_uuid/format", keyword: "format", @@ -21715,7 +21715,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data3.name) { let _errs13 = 0; - if ("string" != typeof data3.name) return validate304.errors = [ { + if ("string" != typeof data3.name) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0 + "/name", schemaPath: "#/properties/camera_sample/items/properties/name/type", keyword: "type", @@ -21725,7 +21725,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid5 = 0 === _errs13; } else valid5 = !0; - if (valid5 && void 0 !== data3.location_path && "string" != typeof data3.location_path) return validate304.errors = [ { + if (valid5 && void 0 !== data3.location_path && "string" != typeof data3.location_path) return validate307.errors = [ { instancePath: instancePath + "/camera_sample/" + i0 + "/location_path", schemaPath: "#/properties/camera_sample/items/properties/location_path/type", keyword: "type", @@ -21747,7 +21747,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.event_types) { let data7 = data.event_types, _errs17 = 0; if (0 === _errs17) { - if (!Array.isArray(data7)) return validate304.errors = [ { + if (!Array.isArray(data7)) return validate307.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/type", keyword: "type", @@ -21760,7 +21760,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD for (let i1 = 0; i1 < len1; i1++) { let data8 = data7[i1], _errs19 = 0; if (0 === _errs19) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate304.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/type", keyword: "type", @@ -21768,7 +21768,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data8.type) return validate304.errors = [ { + if (void 0 === data8.type) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21776,7 +21776,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data8.expected_rate) return validate304.errors = [ { + if (void 0 === data8.expected_rate) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21784,7 +21784,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "expected_rate" } } ], !1; - if (void 0 === data8.severity) return validate304.errors = [ { + if (void 0 === data8.severity) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21792,7 +21792,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "severity" } } ], !1; - if (void 0 === data8.dynamic_severity) return validate304.errors = [ { + if (void 0 === data8.dynamic_severity) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21800,7 +21800,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "dynamic_severity" } } ], !1; - if (void 0 === data8.minimum_severity) return validate304.errors = [ { + if (void 0 === data8.minimum_severity) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21808,7 +21808,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "minimum_severity" } } ], !1; - if (void 0 === data8.maximum_severity) return validate304.errors = [ { + if (void 0 === data8.maximum_severity) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21816,7 +21816,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "maximum_severity" } } ], !1; - if (void 0 === data8.subject_kind) return validate304.errors = [ { + if (void 0 === data8.subject_kind) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/required", keyword: "required", @@ -21826,7 +21826,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs21 = 0; - for (let key2 in data8) if ("type" !== key2 && "expected_rate" !== key2 && "severity" !== key2 && "dynamic_severity" !== key2 && "minimum_severity" !== key2 && "maximum_severity" !== key2 && "subject_kind" !== key2) return validate304.errors = [ { + for (let key2 in data8) if ("type" !== key2 && "expected_rate" !== key2 && "severity" !== key2 && "dynamic_severity" !== key2 && "minimum_severity" !== key2 && "maximum_severity" !== key2 && "subject_kind" !== key2) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1, schemaPath: "#/properties/event_types/items/additionalProperties", keyword: "additionalProperties", @@ -21837,7 +21837,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs21) { if (void 0 !== data8.type) { let _errs22 = 0; - if ("string" != typeof data8.type) return validate304.errors = [ { + if ("string" != typeof data8.type) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/type", schemaPath: "#/properties/event_types/items/properties/type/type", keyword: "type", @@ -21850,7 +21850,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data8.expected_rate) { let data10 = data8.expected_rate, _errs24 = 0; - if ("string" != typeof data10) return validate304.errors = [ { + if ("string" != typeof data10) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/expected_rate", schemaPath: "#/properties/event_types/items/properties/expected_rate/type", keyword: "type", @@ -21858,12 +21858,12 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("low" !== data10 && "medium" !== data10 && "high" !== data10) return validate304.errors = [ { + if ("low" !== data10 && "medium" !== data10 && "high" !== data10) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/expected_rate", schemaPath: "#/properties/event_types/items/properties/expected_rate/enum", keyword: "enum", params: { - allowedValues: schema332.properties.event_types.items.properties.expected_rate.enum + allowedValues: schema336.properties.event_types.items.properties.expected_rate.enum } } ], !1; var valid10 = 0 === _errs24; @@ -21871,7 +21871,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data8.severity) { let data11 = data8.severity, _errs26 = 0; - if ("string" != typeof data11) return validate304.errors = [ { + if ("string" != typeof data11) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/severity", schemaPath: "#/properties/event_types/items/properties/severity/type", keyword: "type", @@ -21879,12 +21879,12 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("debug" !== data11 && "info" !== data11 && "notice" !== data11 && "warning" !== data11 && "error" !== data11 && "critical" !== data11) return validate304.errors = [ { + if ("debug" !== data11 && "info" !== data11 && "notice" !== data11 && "warning" !== data11 && "error" !== data11 && "critical" !== data11) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/severity", schemaPath: "#/properties/event_types/items/properties/severity/enum", keyword: "enum", params: { - allowedValues: schema332.properties.event_types.items.properties.severity.enum + allowedValues: schema336.properties.event_types.items.properties.severity.enum } } ], !1; var valid11 = 0 === _errs26; @@ -21892,7 +21892,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data8.dynamic_severity) { let _errs28 = 0; - if ("boolean" != typeof data8.dynamic_severity) return validate304.errors = [ { + if ("boolean" != typeof data8.dynamic_severity) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/dynamic_severity", schemaPath: "#/properties/event_types/items/properties/dynamic_severity/type", keyword: "type", @@ -21905,7 +21905,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data8.minimum_severity) { let data13 = data8.minimum_severity, _errs30 = 0; - if ("string" != typeof data13) return validate304.errors = [ { + if ("string" != typeof data13) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/minimum_severity", schemaPath: "#/properties/event_types/items/properties/minimum_severity/type", keyword: "type", @@ -21913,12 +21913,12 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("debug" !== data13 && "info" !== data13 && "notice" !== data13 && "warning" !== data13 && "error" !== data13 && "critical" !== data13) return validate304.errors = [ { + if ("debug" !== data13 && "info" !== data13 && "notice" !== data13 && "warning" !== data13 && "error" !== data13 && "critical" !== data13) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/minimum_severity", schemaPath: "#/properties/event_types/items/properties/minimum_severity/enum", keyword: "enum", params: { - allowedValues: schema332.properties.event_types.items.properties.minimum_severity.enum + allowedValues: schema336.properties.event_types.items.properties.minimum_severity.enum } } ], !1; var valid13 = 0 === _errs30; @@ -21926,7 +21926,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data8.maximum_severity) { let data14 = data8.maximum_severity, _errs32 = 0; - if ("string" != typeof data14) return validate304.errors = [ { + if ("string" != typeof data14) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/maximum_severity", schemaPath: "#/properties/event_types/items/properties/maximum_severity/type", keyword: "type", @@ -21934,19 +21934,19 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("debug" !== data14 && "info" !== data14 && "notice" !== data14 && "warning" !== data14 && "error" !== data14 && "critical" !== data14) return validate304.errors = [ { + if ("debug" !== data14 && "info" !== data14 && "notice" !== data14 && "warning" !== data14 && "error" !== data14 && "critical" !== data14) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/maximum_severity", schemaPath: "#/properties/event_types/items/properties/maximum_severity/enum", keyword: "enum", params: { - allowedValues: schema332.properties.event_types.items.properties.maximum_severity.enum + allowedValues: schema336.properties.event_types.items.properties.maximum_severity.enum } } ], !1; var valid14 = 0 === _errs32; } else valid14 = !0; if (valid14 && void 0 !== data8.subject_kind) { let data15 = data8.subject_kind; - if ("string" != typeof data15) return validate304.errors = [ { + if ("string" != typeof data15) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/subject_kind", schemaPath: "#/properties/event_types/items/properties/subject_kind/type", keyword: "type", @@ -21954,12 +21954,12 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("camera" !== data15 && "storage" !== data15 && "system" !== data15) return validate304.errors = [ { + if ("camera" !== data15 && "storage" !== data15 && "system" !== data15) return validate307.errors = [ { instancePath: instancePath + "/event_types/" + i1 + "/subject_kind", schemaPath: "#/properties/event_types/items/properties/subject_kind/enum", keyword: "enum", params: { - allowedValues: schema332.properties.event_types.items.properties.subject_kind.enum + allowedValues: schema336.properties.event_types.items.properties.subject_kind.enum } } ], !1; } @@ -21979,7 +21979,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD } else valid7 = !0; if (valid7 && void 0 !== data.would_publish) { let data16 = data.would_publish; - if ("boolean" != typeof data16) return validate304.errors = [ { + if ("boolean" != typeof data16) return validate307.errors = [ { instancePath: instancePath + "/would_publish", schemaPath: "#/properties/would_publish/type", keyword: "type", @@ -21987,7 +21987,7 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!1 !== data16) return validate304.errors = [ { + if (!1 !== data16) return validate307.errors = [ { instancePath: instancePath + "/would_publish", schemaPath: "#/properties/would_publish/const", keyword: "const", @@ -22001,16 +22001,16 @@ function validate304(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate304.errors = [], !0; + return validate307.errors = [], !0; } -validate304.evaluated = { +validate307.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventRoutePreviewRequest = validate301, schema328 = { +var __contract_EventRoutePreviewRequest = validate304, schema332 = { type: "object", properties: { name: { @@ -22082,11 +22082,11 @@ var __contract_EventRoutePreviewRequest = validate301, schema328 = { additionalProperties: !1 }; -function validate301(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate301.evaluated; +function validate304(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate304.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate301.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate304.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -22094,7 +22094,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate301.errors = [ { + if (void 0 === data.name) return validate304.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -22102,7 +22102,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.event_types) return validate301.errors = [ { + if (void 0 === data.event_types) return validate304.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -22112,7 +22112,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema328.properties, key0)) return validate301.errors = [ { + for (let key0 in data) if (!func7.call(schema332.properties, key0)) return validate304.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -22124,7 +22124,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate301.errors = [ { + if ("string" != typeof data0) return validate304.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -22132,7 +22132,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate301.errors = [ { + if (func1(data0) > 127) return validate304.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -22140,7 +22140,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate301.errors = [ { + if (func1(data0) < 1) return validate304.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -22155,7 +22155,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data1 = data.description, _errs4 = errors; if (errors === _errs4) { - if ("string" != typeof data1) return validate301.errors = [ { + if ("string" != typeof data1) return validate304.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -22163,7 +22163,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 511) return validate301.errors = [ { + if (func1(data1) > 511) return validate304.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -22177,7 +22177,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = errors; - if ("boolean" != typeof data.enabled) return validate301.errors = [ { + if ("boolean" != typeof data.enabled) return validate304.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -22191,7 +22191,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.destination) { let data3 = data.destination, _errs8 = errors; if (errors === _errs8) { - if ("string" != typeof data3) return validate301.errors = [ { + if ("string" != typeof data3) return validate304.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/type", keyword: "type", @@ -22199,7 +22199,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 127) return validate301.errors = [ { + if (func1(data3) > 127) return validate304.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/maxLength", keyword: "maxLength", @@ -22207,7 +22207,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data3) < 1) return validate301.errors = [ { + if (func1(data3) < 1) return validate304.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/minLength", keyword: "minLength", @@ -22222,7 +22222,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.event_types) { let data4 = data.event_types, _errs10 = errors; if (errors === _errs10) { - if (!Array.isArray(data4)) return validate301.errors = [ { + if (!Array.isArray(data4)) return validate304.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/type", keyword: "type", @@ -22230,7 +22230,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 32) return validate301.errors = [ { + if (data4.length > 32) return validate304.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/maxItems", keyword: "maxItems", @@ -22238,7 +22238,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 32 } } ], !1; - if (data4.length < 1) return validate301.errors = [ { + if (data4.length < 1) return validate304.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/minItems", keyword: "minItems", @@ -22252,7 +22252,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data5 = data4[i0], _errs12 = errors; if (errors === _errs12) { - if ("string" != typeof data5) return validate301.errors = [ { + if ("string" != typeof data5) return validate304.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/type", keyword: "type", @@ -22260,7 +22260,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 127) return validate301.errors = [ { + if (func1(data5) > 127) return validate304.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/maxLength", keyword: "maxLength", @@ -22268,7 +22268,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data5) < 1) return validate301.errors = [ { + if (func1(data5) < 1) return validate304.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/minLength", keyword: "minLength", @@ -22286,7 +22286,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data4[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate301.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate304.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/uniqueItems", keyword: "uniqueItems", @@ -22307,20 +22307,20 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.camera_scope) { let _errs14 = errors; - validate290(data.camera_scope, { + validate293(data.camera_scope, { instancePath: instancePath + "/camera_scope", parentData: data, parentDataProperty: "camera_scope", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate290.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate293.errors), errors = vErrors.length); var valid7 = _errs14 === errors; } else valid7 = !0; if (valid7) { if (void 0 !== data.predicate) { let data7 = data.predicate, _errs15 = errors; if (errors == errors) { - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate301.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate304.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/type", keyword: "type", @@ -22328,7 +22328,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data7.version) return validate301.errors = [ { + if (void 0 === data7.version) return validate304.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/required", keyword: "required", @@ -22338,7 +22338,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs18 = errors; - for (let key1 in data7) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate301.errors = [ { + for (let key1 in data7) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate304.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/additionalProperties", keyword: "additionalProperties", @@ -22349,7 +22349,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (_errs18 === errors) { if (void 0 !== data7.version) { let data8 = data7.version, _errs19 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate301.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate304.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/type", keyword: "type", @@ -22357,7 +22357,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data8) return validate301.errors = [ { + if (1 !== data8) return validate304.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/const", keyword: "const", @@ -22371,7 +22371,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.detection) { let data9 = data7.detection, _errs21 = errors; if (errors === _errs21) { - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate301.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/type", keyword: "type", @@ -22379,7 +22379,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data9).length < 1) return validate301.errors = [ { + if (Object.keys(data9).length < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/minProperties", keyword: "minProperties", @@ -22389,7 +22389,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs23 = errors; - for (let key2 in data9) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate301.errors = [ { + for (let key2 in data9) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -22401,7 +22401,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.labels_any) { let data10 = data9.labels_any, _errs24 = errors; if (errors === _errs24) { - if (!Array.isArray(data10)) return validate301.errors = [ { + if (!Array.isArray(data10)) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/type", keyword: "type", @@ -22409,7 +22409,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data10.length > 64) return validate301.errors = [ { + if (data10.length > 64) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/maxItems", keyword: "maxItems", @@ -22417,7 +22417,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data10.length < 1) return validate301.errors = [ { + if (data10.length < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/minItems", keyword: "minItems", @@ -22430,7 +22430,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len1; i2++) { let data11 = data10[i2], _errs26 = errors; if (errors === _errs26) { - if ("string" != typeof data11) return validate301.errors = [ { + if ("string" != typeof data11) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/type", keyword: "type", @@ -22438,7 +22438,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 127) return validate301.errors = [ { + if (func1(data11) > 127) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/maxLength", keyword: "maxLength", @@ -22446,7 +22446,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data11) < 1) return validate301.errors = [ { + if (func1(data11) < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/minLength", keyword: "minLength", @@ -22465,7 +22465,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.zone_ids_any) { let data12 = data9.zone_ids_any, _errs28 = errors; if (errors === _errs28) { - if (!Array.isArray(data12)) return validate301.errors = [ { + if (!Array.isArray(data12)) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/type", keyword: "type", @@ -22473,7 +22473,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data12.length > 64) return validate301.errors = [ { + if (data12.length > 64) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/maxItems", keyword: "maxItems", @@ -22481,7 +22481,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data12.length < 1) return validate301.errors = [ { + if (data12.length < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/minItems", keyword: "minItems", @@ -22494,7 +22494,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (let i3 = 0; i3 < len2; i3++) { let data13 = data12[i3], _errs30 = errors; if (errors === _errs30) { - if ("string" != typeof data13) return validate301.errors = [ { + if ("string" != typeof data13) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/type", keyword: "type", @@ -22502,7 +22502,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate301.errors = [ { + if (func1(data13) > 127) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/maxLength", keyword: "maxLength", @@ -22510,7 +22510,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data13) < 1) return validate301.errors = [ { + if (func1(data13) < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/minLength", keyword: "minLength", @@ -22528,7 +22528,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid14 && void 0 !== data9.min_confidence) { let data14 = data9.min_confidence; if (errors == errors) { - if ("number" != typeof data14 || !isFinite(data14)) return validate301.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/type", keyword: "type", @@ -22536,7 +22536,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data14 > 1 || isNaN(data14)) return validate301.errors = [ { + if (data14 > 1 || isNaN(data14)) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/maximum", keyword: "maximum", @@ -22545,7 +22545,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate301.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate304.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/minimum", keyword: "minimum", @@ -22565,7 +22565,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid11 && void 0 !== data7.health) { let data15 = data7.health; if (errors == errors) { - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate301.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate304.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/type", keyword: "type", @@ -22573,7 +22573,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data15).length < 1) return validate301.errors = [ { + if (Object.keys(data15).length < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/minProperties", keyword: "minProperties", @@ -22583,7 +22583,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs36 = errors; - for (let key3 in data15) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate301.errors = [ { + for (let key3 in data15) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate304.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/additionalProperties", keyword: "additionalProperties", @@ -22595,7 +22595,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.condition_codes_any) { let data16 = data15.condition_codes_any, _errs37 = errors; if (errors === _errs37) { - if (!Array.isArray(data16)) return validate301.errors = [ { + if (!Array.isArray(data16)) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/type", keyword: "type", @@ -22603,7 +22603,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data16.length > 64) return validate301.errors = [ { + if (data16.length > 64) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/maxItems", keyword: "maxItems", @@ -22611,7 +22611,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data16.length < 1) return validate301.errors = [ { + if (data16.length < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/minItems", keyword: "minItems", @@ -22624,7 +22624,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (let i4 = 0; i4 < len3; i4++) { let data17 = data16[i4], _errs39 = errors; if (errors === _errs39) { - if ("string" != typeof data17) return validate301.errors = [ { + if ("string" != typeof data17) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/type", keyword: "type", @@ -22632,7 +22632,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data17) > 127) return validate301.errors = [ { + if (func1(data17) > 127) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/maxLength", keyword: "maxLength", @@ -22640,7 +22640,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data17) < 1) return validate301.errors = [ { + if (func1(data17) < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/minLength", keyword: "minLength", @@ -22658,7 +22658,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid18 && void 0 !== data15.severities_any) { let data18 = data15.severities_any; if (errors == errors) { - if (!Array.isArray(data18)) return validate301.errors = [ { + if (!Array.isArray(data18)) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/type", keyword: "type", @@ -22666,7 +22666,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data18.length > 3) return validate301.errors = [ { + if (data18.length > 3) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/maxItems", keyword: "maxItems", @@ -22674,7 +22674,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 3 } } ], !1; - if (data18.length < 1) return validate301.errors = [ { + if (data18.length < 1) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/minItems", keyword: "minItems", @@ -22686,7 +22686,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD let len4 = data18.length; for (let i5 = 0; i5 < len4; i5++) { let data19 = data18[i5], _errs43 = errors; - if ("string" != typeof data19) return validate301.errors = [ { + if ("string" != typeof data19) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/type", keyword: "type", @@ -22694,12 +22694,12 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("warning" !== data19 && "error" !== data19 && "critical" !== data19) return validate301.errors = [ { + if ("warning" !== data19 && "error" !== data19 && "critical" !== data19) return validate304.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/enum", keyword: "enum", params: { - allowedValues: schema315.properties.health.properties.severities_any.items.enum + allowedValues: schema319.properties.health.properties.severities_any.items.enum } } ], !1; if (_errs43 !== errors) break; @@ -22721,7 +22721,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.schedule) { let data20 = data.schedule, _errs45 = errors; if (errors == errors) { - if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate301.errors = [ { + if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate304.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/type", keyword: "type", @@ -22729,7 +22729,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data20.version) return validate301.errors = [ { + if (void 0 === data20.version) return validate304.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -22737,7 +22737,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "version" } } ], !1; - if (void 0 === data20.timezone) return validate301.errors = [ { + if (void 0 === data20.timezone) return validate304.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -22745,7 +22745,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timezone" } } ], !1; - if (void 0 === data20.windows) return validate301.errors = [ { + if (void 0 === data20.windows) return validate304.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -22755,7 +22755,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs48 = errors; - for (let key4 in data20) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate301.errors = [ { + for (let key4 in data20) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate304.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/additionalProperties", keyword: "additionalProperties", @@ -22766,7 +22766,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (_errs48 === errors) { if (void 0 !== data20.version) { let data21 = data20.version, _errs49 = errors; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate301.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate304.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/type", keyword: "type", @@ -22774,7 +22774,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data21) return validate301.errors = [ { + if (1 !== data21) return validate304.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/const", keyword: "const", @@ -22788,7 +22788,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data20.timezone) { let data22 = data20.timezone, _errs51 = errors; if (errors === _errs51) { - if ("string" != typeof data22) return validate301.errors = [ { + if ("string" != typeof data22) return validate304.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/type", keyword: "type", @@ -22796,7 +22796,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data22) > 64) return validate301.errors = [ { + if (func1(data22) > 64) return validate304.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/maxLength", keyword: "maxLength", @@ -22804,7 +22804,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (func1(data22) < 1) return validate301.errors = [ { + if (func1(data22) < 1) return validate304.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/minLength", keyword: "minLength", @@ -22818,7 +22818,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid25 && void 0 !== data20.windows) { let data23 = data20.windows; if (errors == errors) { - if (!Array.isArray(data23)) return validate301.errors = [ { + if (!Array.isArray(data23)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/type", keyword: "type", @@ -22826,7 +22826,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data23.length > 64) return validate301.errors = [ { + if (data23.length > 64) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/maxItems", keyword: "maxItems", @@ -22839,7 +22839,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (let i6 = 0; i6 < len5; i6++) { let data24 = data23[i6], _errs55 = errors; if (errors === _errs55) { - if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate301.errors = [ { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/type", keyword: "type", @@ -22847,7 +22847,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data24.days) return validate301.errors = [ { + if (void 0 === data24.days) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -22855,7 +22855,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "days" } } ], !1; - if (void 0 === data24.start) return validate301.errors = [ { + if (void 0 === data24.start) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -22863,7 +22863,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start" } } ], !1; - if (void 0 === data24.end) return validate301.errors = [ { + if (void 0 === data24.end) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -22873,7 +22873,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs57 = errors; - for (let key5 in data24) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate301.errors = [ { + for (let key5 in data24) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/additionalProperties", keyword: "additionalProperties", @@ -22885,7 +22885,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data24.days) { let data25 = data24.days, _errs58 = errors; if (errors === _errs58) { - if (!Array.isArray(data25)) return validate301.errors = [ { + if (!Array.isArray(data25)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/type", keyword: "type", @@ -22893,7 +22893,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data25.length > 7) return validate301.errors = [ { + if (data25.length > 7) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/maxItems", keyword: "maxItems", @@ -22901,7 +22901,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 7 } } ], !1; - if (data25.length < 1) return validate301.errors = [ { + if (data25.length < 1) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/minItems", keyword: "minItems", @@ -22914,7 +22914,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD let len6 = data25.length; for (let i7 = 0; i7 < len6; i7++) { let data26 = data25[i7], _errs60 = errors; - if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate301.errors = [ { + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/type", keyword: "type", @@ -22923,7 +22923,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs60 && "number" == typeof data26 && isFinite(data26)) { - if (data26 > 6 || isNaN(data26)) return validate301.errors = [ { + if (data26 > 6 || isNaN(data26)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/maximum", keyword: "maximum", @@ -22932,7 +22932,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 6 } } ], !1; - if (data26 < 0 || isNaN(data26)) return validate301.errors = [ { + if (data26 < 0 || isNaN(data26)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/minimum", keyword: "minimum", @@ -22951,7 +22951,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD for (;i8--; ) { let item1 = data25[i8]; if ("number" == typeof item1 && !(item1 % 1) && !isNaN(item1) && isFinite(item1)) { - if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate301.errors = [ { + if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/uniqueItems", keyword: "uniqueItems", @@ -22973,7 +22973,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data24.start) { let data27 = data24.start, _errs62 = errors; if (errors === _errs62) { - if ("string" != typeof data27) return validate301.errors = [ { + if ("string" != typeof data27) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/type", keyword: "type", @@ -22981,7 +22981,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data27)) return validate301.errors = [ { + if (!pattern101.test(data27)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/pattern", keyword: "pattern", @@ -22995,7 +22995,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid31 && void 0 !== data24.end) { let data28 = data24.end; if (errors == errors) { - if ("string" != typeof data28) return validate301.errors = [ { + if ("string" != typeof data28) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/type", keyword: "type", @@ -23003,7 +23003,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data28)) return validate301.errors = [ { + if (!pattern101.test(data28)) return validate304.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/pattern", keyword: "pattern", @@ -23031,7 +23031,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid22 && void 0 !== data.suppression) { let data29 = data.suppression; if (errors == errors) { - if (!data29 || "object" != typeof data29 || Array.isArray(data29)) return validate301.errors = [ { + if (!data29 || "object" != typeof data29 || Array.isArray(data29)) return validate304.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/type", keyword: "type", @@ -23041,7 +23041,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs68 = errors; - for (let key6 in data29) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate301.errors = [ { + for (let key6 in data29) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate304.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/additionalProperties", keyword: "additionalProperties", @@ -23052,7 +23052,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (_errs68 === errors) { if (void 0 !== data29.debounce_seconds) { let data30 = data29.debounce_seconds, _errs69 = errors; - if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate301.errors = [ { + if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate304.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/type", keyword: "type", @@ -23061,7 +23061,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs69 && "number" == typeof data30 && isFinite(data30)) { - if (data30 > 86400 || isNaN(data30)) return validate301.errors = [ { + if (data30 > 86400 || isNaN(data30)) return validate304.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/maximum", keyword: "maximum", @@ -23070,7 +23070,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data30 < 0 || isNaN(data30)) return validate301.errors = [ { + if (data30 < 0 || isNaN(data30)) return validate304.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/minimum", keyword: "minimum", @@ -23085,7 +23085,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data29.cooldown_seconds) { let data31 = data29.cooldown_seconds, _errs71 = errors; - if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate301.errors = [ { + if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate304.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/type", keyword: "type", @@ -23094,7 +23094,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs71 && "number" == typeof data31 && isFinite(data31)) { - if (data31 > 604800 || isNaN(data31)) return validate301.errors = [ { + if (data31 > 604800 || isNaN(data31)) return validate304.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/maximum", keyword: "maximum", @@ -23103,7 +23103,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 604800 } } ], !1; - if (data31 < 0 || isNaN(data31)) return validate301.errors = [ { + if (data31 < 0 || isNaN(data31)) return validate304.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/minimum", keyword: "minimum", @@ -23118,7 +23118,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data29.grouping_window_seconds) { let data32 = data29.grouping_window_seconds, _errs73 = errors; - if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate301.errors = [ { + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate304.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/type", keyword: "type", @@ -23127,7 +23127,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs73 && "number" == typeof data32 && isFinite(data32)) { - if (data32 > 3600 || isNaN(data32)) return validate301.errors = [ { + if (data32 > 3600 || isNaN(data32)) return validate304.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/maximum", keyword: "maximum", @@ -23136,7 +23136,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data32 < 0 || isNaN(data32)) return validate301.errors = [ { + if (data32 < 0 || isNaN(data32)) return validate304.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/minimum", keyword: "minimum", @@ -23150,7 +23150,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } else valid36 = !0; if (valid36 && void 0 !== data29.max_events_per_minute) { let data33 = data29.max_events_per_minute, _errs75 = errors; - if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate301.errors = [ { + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate304.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/type", keyword: "type", @@ -23159,7 +23159,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs75 && "number" == typeof data33 && isFinite(data33)) { - if (data33 > 6e4 || isNaN(data33)) return validate301.errors = [ { + if (data33 > 6e4 || isNaN(data33)) return validate304.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/maximum", keyword: "maximum", @@ -23168,7 +23168,7 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD limit: 6e4 } } ], !1; - if (data33 < 0 || isNaN(data33)) return validate301.errors = [ { + if (data33 < 0 || isNaN(data33)) return validate304.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/minimum", keyword: "minimum", @@ -23195,16 +23195,16 @@ function validate301(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate301.errors = vErrors, 0 === errors; + return validate304.errors = vErrors, 0 === errors; } -validate301.evaluated = { +validate304.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventRouteUpdate = validate299, schema325 = { +var __contract_EventRouteUpdate = validate302, schema329 = { type: "object", properties: { name: { @@ -23281,11 +23281,11 @@ var __contract_EventRouteUpdate = validate299, schema325 = { additionalProperties: !1 }; -function validate299(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate299.evaluated; +function validate302(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate302.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate299.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate302.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -23293,7 +23293,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate299.errors = [ { + if (void 0 === data.revision) return validate302.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -23303,7 +23303,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema325.properties, key0)) return validate299.errors = [ { + for (let key0 in data) if (!func7.call(schema329.properties, key0)) return validate302.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -23315,7 +23315,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate299.errors = [ { + if ("string" != typeof data0) return validate302.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -23323,7 +23323,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate299.errors = [ { + if (func1(data0) > 127) return validate302.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -23331,7 +23331,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate299.errors = [ { + if (func1(data0) < 1) return validate302.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -23346,7 +23346,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.description) { let data1 = data.description, _errs4 = errors; if (errors === _errs4) { - if ("string" != typeof data1) return validate299.errors = [ { + if ("string" != typeof data1) return validate302.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -23354,7 +23354,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 511) return validate299.errors = [ { + if (func1(data1) > 511) return validate302.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/maxLength", keyword: "maxLength", @@ -23368,7 +23368,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = errors; - if ("boolean" != typeof data.enabled) return validate299.errors = [ { + if ("boolean" != typeof data.enabled) return validate302.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -23382,7 +23382,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.destination) { let data3 = data.destination, _errs8 = errors; if (errors === _errs8) { - if ("string" != typeof data3) return validate299.errors = [ { + if ("string" != typeof data3) return validate302.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/type", keyword: "type", @@ -23390,7 +23390,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 127) return validate299.errors = [ { + if (func1(data3) > 127) return validate302.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/maxLength", keyword: "maxLength", @@ -23398,7 +23398,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data3) < 1) return validate299.errors = [ { + if (func1(data3) < 1) return validate302.errors = [ { instancePath: instancePath + "/destination", schemaPath: "#/properties/destination/minLength", keyword: "minLength", @@ -23413,7 +23413,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.event_types) { let data4 = data.event_types, _errs10 = errors; if (errors === _errs10) { - if (!Array.isArray(data4)) return validate299.errors = [ { + if (!Array.isArray(data4)) return validate302.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/type", keyword: "type", @@ -23421,7 +23421,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 32) return validate299.errors = [ { + if (data4.length > 32) return validate302.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/maxItems", keyword: "maxItems", @@ -23429,7 +23429,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 32 } } ], !1; - if (data4.length < 1) return validate299.errors = [ { + if (data4.length < 1) return validate302.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/minItems", keyword: "minItems", @@ -23443,7 +23443,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data5 = data4[i0], _errs12 = errors; if (errors === _errs12) { - if ("string" != typeof data5) return validate299.errors = [ { + if ("string" != typeof data5) return validate302.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/type", keyword: "type", @@ -23451,7 +23451,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 127) return validate299.errors = [ { + if (func1(data5) > 127) return validate302.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/maxLength", keyword: "maxLength", @@ -23459,7 +23459,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data5) < 1) return validate299.errors = [ { + if (func1(data5) < 1) return validate302.errors = [ { instancePath: instancePath + "/event_types/" + i0, schemaPath: "#/properties/event_types/items/minLength", keyword: "minLength", @@ -23477,7 +23477,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data4[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate299.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate302.errors = [ { instancePath: instancePath + "/event_types", schemaPath: "#/properties/event_types/uniqueItems", keyword: "uniqueItems", @@ -23498,20 +23498,20 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.camera_scope) { let _errs14 = errors; - validate290(data.camera_scope, { + validate293(data.camera_scope, { instancePath: instancePath + "/camera_scope", parentData: data, parentDataProperty: "camera_scope", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate290.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate293.errors), errors = vErrors.length); var valid7 = _errs14 === errors; } else valid7 = !0; if (valid7) { if (void 0 !== data.predicate) { let data7 = data.predicate, _errs15 = errors; if (errors == errors) { - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate299.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate302.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/type", keyword: "type", @@ -23519,7 +23519,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data7.version) return validate299.errors = [ { + if (void 0 === data7.version) return validate302.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/required", keyword: "required", @@ -23529,7 +23529,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs18 = errors; - for (let key1 in data7) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate299.errors = [ { + for (let key1 in data7) if ("version" !== key1 && "detection" !== key1 && "health" !== key1) return validate302.errors = [ { instancePath: instancePath + "/predicate", schemaPath: "#/$defs/EventPredicate/additionalProperties", keyword: "additionalProperties", @@ -23540,7 +23540,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (_errs18 === errors) { if (void 0 !== data7.version) { let data8 = data7.version, _errs19 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate299.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate302.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/type", keyword: "type", @@ -23548,7 +23548,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data8) return validate299.errors = [ { + if (1 !== data8) return validate302.errors = [ { instancePath: instancePath + "/predicate/version", schemaPath: "#/$defs/EventPredicate/properties/version/const", keyword: "const", @@ -23562,7 +23562,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data7.detection) { let data9 = data7.detection, _errs21 = errors; if (errors === _errs21) { - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate299.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/type", keyword: "type", @@ -23570,7 +23570,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data9).length < 1) return validate299.errors = [ { + if (Object.keys(data9).length < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/minProperties", keyword: "minProperties", @@ -23580,7 +23580,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs23 = errors; - for (let key2 in data9) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate299.errors = [ { + for (let key2 in data9) if ("labels_any" !== key2 && "zone_ids_any" !== key2 && "min_confidence" !== key2) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection", schemaPath: "#/$defs/EventPredicate/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -23592,7 +23592,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.labels_any) { let data10 = data9.labels_any, _errs24 = errors; if (errors === _errs24) { - if (!Array.isArray(data10)) return validate299.errors = [ { + if (!Array.isArray(data10)) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/type", keyword: "type", @@ -23600,7 +23600,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data10.length > 64) return validate299.errors = [ { + if (data10.length > 64) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/maxItems", keyword: "maxItems", @@ -23608,7 +23608,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data10.length < 1) return validate299.errors = [ { + if (data10.length < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/minItems", keyword: "minItems", @@ -23621,7 +23621,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len1; i2++) { let data11 = data10[i2], _errs26 = errors; if (errors === _errs26) { - if ("string" != typeof data11) return validate299.errors = [ { + if ("string" != typeof data11) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/type", keyword: "type", @@ -23629,7 +23629,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 127) return validate299.errors = [ { + if (func1(data11) > 127) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/maxLength", keyword: "maxLength", @@ -23637,7 +23637,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data11) < 1) return validate299.errors = [ { + if (func1(data11) < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/labels_any/" + i2, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/labels_any/items/minLength", keyword: "minLength", @@ -23656,7 +23656,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.zone_ids_any) { let data12 = data9.zone_ids_any, _errs28 = errors; if (errors === _errs28) { - if (!Array.isArray(data12)) return validate299.errors = [ { + if (!Array.isArray(data12)) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/type", keyword: "type", @@ -23664,7 +23664,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data12.length > 64) return validate299.errors = [ { + if (data12.length > 64) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/maxItems", keyword: "maxItems", @@ -23672,7 +23672,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data12.length < 1) return validate299.errors = [ { + if (data12.length < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/minItems", keyword: "minItems", @@ -23685,7 +23685,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (let i3 = 0; i3 < len2; i3++) { let data13 = data12[i3], _errs30 = errors; if (errors === _errs30) { - if ("string" != typeof data13) return validate299.errors = [ { + if ("string" != typeof data13) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/type", keyword: "type", @@ -23693,7 +23693,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate299.errors = [ { + if (func1(data13) > 127) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/maxLength", keyword: "maxLength", @@ -23701,7 +23701,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data13) < 1) return validate299.errors = [ { + if (func1(data13) < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/zone_ids_any/" + i3, schemaPath: "#/$defs/EventPredicate/properties/detection/properties/zone_ids_any/items/minLength", keyword: "minLength", @@ -23719,7 +23719,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid14 && void 0 !== data9.min_confidence) { let data14 = data9.min_confidence; if (errors == errors) { - if ("number" != typeof data14 || !isFinite(data14)) return validate299.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/type", keyword: "type", @@ -23727,7 +23727,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data14 > 1 || isNaN(data14)) return validate299.errors = [ { + if (data14 > 1 || isNaN(data14)) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/maximum", keyword: "maximum", @@ -23736,7 +23736,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate299.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate302.errors = [ { instancePath: instancePath + "/predicate/detection/min_confidence", schemaPath: "#/$defs/EventPredicate/properties/detection/properties/min_confidence/minimum", keyword: "minimum", @@ -23756,7 +23756,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid11 && void 0 !== data7.health) { let data15 = data7.health; if (errors == errors) { - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate299.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate302.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/type", keyword: "type", @@ -23764,7 +23764,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data15).length < 1) return validate299.errors = [ { + if (Object.keys(data15).length < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/minProperties", keyword: "minProperties", @@ -23774,7 +23774,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs36 = errors; - for (let key3 in data15) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate299.errors = [ { + for (let key3 in data15) if ("condition_codes_any" !== key3 && "severities_any" !== key3) return validate302.errors = [ { instancePath: instancePath + "/predicate/health", schemaPath: "#/$defs/EventPredicate/properties/health/additionalProperties", keyword: "additionalProperties", @@ -23786,7 +23786,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.condition_codes_any) { let data16 = data15.condition_codes_any, _errs37 = errors; if (errors === _errs37) { - if (!Array.isArray(data16)) return validate299.errors = [ { + if (!Array.isArray(data16)) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/type", keyword: "type", @@ -23794,7 +23794,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data16.length > 64) return validate299.errors = [ { + if (data16.length > 64) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/maxItems", keyword: "maxItems", @@ -23802,7 +23802,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (data16.length < 1) return validate299.errors = [ { + if (data16.length < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/minItems", keyword: "minItems", @@ -23815,7 +23815,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (let i4 = 0; i4 < len3; i4++) { let data17 = data16[i4], _errs39 = errors; if (errors === _errs39) { - if ("string" != typeof data17) return validate299.errors = [ { + if ("string" != typeof data17) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/type", keyword: "type", @@ -23823,7 +23823,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data17) > 127) return validate299.errors = [ { + if (func1(data17) > 127) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/maxLength", keyword: "maxLength", @@ -23831,7 +23831,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data17) < 1) return validate299.errors = [ { + if (func1(data17) < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/condition_codes_any/" + i4, schemaPath: "#/$defs/EventPredicate/properties/health/properties/condition_codes_any/items/minLength", keyword: "minLength", @@ -23849,7 +23849,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid18 && void 0 !== data15.severities_any) { let data18 = data15.severities_any; if (errors == errors) { - if (!Array.isArray(data18)) return validate299.errors = [ { + if (!Array.isArray(data18)) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/type", keyword: "type", @@ -23857,7 +23857,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data18.length > 3) return validate299.errors = [ { + if (data18.length > 3) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/maxItems", keyword: "maxItems", @@ -23865,7 +23865,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 3 } } ], !1; - if (data18.length < 1) return validate299.errors = [ { + if (data18.length < 1) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/severities_any", schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/minItems", keyword: "minItems", @@ -23877,7 +23877,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD let len4 = data18.length; for (let i5 = 0; i5 < len4; i5++) { let data19 = data18[i5], _errs43 = errors; - if ("string" != typeof data19) return validate299.errors = [ { + if ("string" != typeof data19) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/type", keyword: "type", @@ -23885,12 +23885,12 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("warning" !== data19 && "error" !== data19 && "critical" !== data19) return validate299.errors = [ { + if ("warning" !== data19 && "error" !== data19 && "critical" !== data19) return validate302.errors = [ { instancePath: instancePath + "/predicate/health/severities_any/" + i5, schemaPath: "#/$defs/EventPredicate/properties/health/properties/severities_any/items/enum", keyword: "enum", params: { - allowedValues: schema315.properties.health.properties.severities_any.items.enum + allowedValues: schema319.properties.health.properties.severities_any.items.enum } } ], !1; if (_errs43 !== errors) break; @@ -23912,7 +23912,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.schedule) { let data20 = data.schedule, _errs45 = errors; if (errors == errors) { - if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate299.errors = [ { + if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate302.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/type", keyword: "type", @@ -23920,7 +23920,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data20.version) return validate299.errors = [ { + if (void 0 === data20.version) return validate302.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -23928,7 +23928,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "version" } } ], !1; - if (void 0 === data20.timezone) return validate299.errors = [ { + if (void 0 === data20.timezone) return validate302.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -23936,7 +23936,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timezone" } } ], !1; - if (void 0 === data20.windows) return validate299.errors = [ { + if (void 0 === data20.windows) return validate302.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/required", keyword: "required", @@ -23946,7 +23946,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs48 = errors; - for (let key4 in data20) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate299.errors = [ { + for (let key4 in data20) if ("version" !== key4 && "timezone" !== key4 && "windows" !== key4) return validate302.errors = [ { instancePath: instancePath + "/schedule", schemaPath: "#/$defs/EventSchedule/additionalProperties", keyword: "additionalProperties", @@ -23957,7 +23957,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (_errs48 === errors) { if (void 0 !== data20.version) { let data21 = data20.version, _errs49 = errors; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate299.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate302.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/type", keyword: "type", @@ -23965,7 +23965,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data21) return validate299.errors = [ { + if (1 !== data21) return validate302.errors = [ { instancePath: instancePath + "/schedule/version", schemaPath: "#/$defs/EventSchedule/properties/version/const", keyword: "const", @@ -23979,7 +23979,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data20.timezone) { let data22 = data20.timezone, _errs51 = errors; if (errors === _errs51) { - if ("string" != typeof data22) return validate299.errors = [ { + if ("string" != typeof data22) return validate302.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/type", keyword: "type", @@ -23987,7 +23987,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data22) > 64) return validate299.errors = [ { + if (func1(data22) > 64) return validate302.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/maxLength", keyword: "maxLength", @@ -23995,7 +23995,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (func1(data22) < 1) return validate299.errors = [ { + if (func1(data22) < 1) return validate302.errors = [ { instancePath: instancePath + "/schedule/timezone", schemaPath: "#/$defs/EventSchedule/properties/timezone/minLength", keyword: "minLength", @@ -24009,7 +24009,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid25 && void 0 !== data20.windows) { let data23 = data20.windows; if (errors == errors) { - if (!Array.isArray(data23)) return validate299.errors = [ { + if (!Array.isArray(data23)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/type", keyword: "type", @@ -24017,7 +24017,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data23.length > 64) return validate299.errors = [ { + if (data23.length > 64) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows", schemaPath: "#/$defs/EventSchedule/properties/windows/maxItems", keyword: "maxItems", @@ -24030,7 +24030,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (let i6 = 0; i6 < len5; i6++) { let data24 = data23[i6], _errs55 = errors; if (errors === _errs55) { - if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate299.errors = [ { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/type", keyword: "type", @@ -24038,7 +24038,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data24.days) return validate299.errors = [ { + if (void 0 === data24.days) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -24046,7 +24046,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "days" } } ], !1; - if (void 0 === data24.start) return validate299.errors = [ { + if (void 0 === data24.start) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -24054,7 +24054,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start" } } ], !1; - if (void 0 === data24.end) return validate299.errors = [ { + if (void 0 === data24.end) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/required", keyword: "required", @@ -24064,7 +24064,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs57 = errors; - for (let key5 in data24) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate299.errors = [ { + for (let key5 in data24) if ("days" !== key5 && "start" !== key5 && "end" !== key5) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6, schemaPath: "#/$defs/EventSchedule/properties/windows/items/additionalProperties", keyword: "additionalProperties", @@ -24076,7 +24076,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data24.days) { let data25 = data24.days, _errs58 = errors; if (errors === _errs58) { - if (!Array.isArray(data25)) return validate299.errors = [ { + if (!Array.isArray(data25)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/type", keyword: "type", @@ -24084,7 +24084,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data25.length > 7) return validate299.errors = [ { + if (data25.length > 7) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/maxItems", keyword: "maxItems", @@ -24092,7 +24092,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 7 } } ], !1; - if (data25.length < 1) return validate299.errors = [ { + if (data25.length < 1) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/minItems", keyword: "minItems", @@ -24105,7 +24105,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD let len6 = data25.length; for (let i7 = 0; i7 < len6; i7++) { let data26 = data25[i7], _errs60 = errors; - if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate299.errors = [ { + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/type", keyword: "type", @@ -24114,7 +24114,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs60 && "number" == typeof data26 && isFinite(data26)) { - if (data26 > 6 || isNaN(data26)) return validate299.errors = [ { + if (data26 > 6 || isNaN(data26)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/maximum", keyword: "maximum", @@ -24123,7 +24123,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 6 } } ], !1; - if (data26 < 0 || isNaN(data26)) return validate299.errors = [ { + if (data26 < 0 || isNaN(data26)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days/" + i7, schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/items/minimum", keyword: "minimum", @@ -24142,7 +24142,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD for (;i8--; ) { let item1 = data25[i8]; if ("number" == typeof item1 && !(item1 % 1) && !isNaN(item1) && isFinite(item1)) { - if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate299.errors = [ { + if ("number" == typeof indices1[item1]) return j1 = indices1[item1], validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/days", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/days/uniqueItems", keyword: "uniqueItems", @@ -24164,7 +24164,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data24.start) { let data27 = data24.start, _errs62 = errors; if (errors === _errs62) { - if ("string" != typeof data27) return validate299.errors = [ { + if ("string" != typeof data27) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/type", keyword: "type", @@ -24172,7 +24172,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data27)) return validate299.errors = [ { + if (!pattern101.test(data27)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/start", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/start/pattern", keyword: "pattern", @@ -24186,7 +24186,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid31 && void 0 !== data24.end) { let data28 = data24.end; if (errors == errors) { - if ("string" != typeof data28) return validate299.errors = [ { + if ("string" != typeof data28) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/type", keyword: "type", @@ -24194,7 +24194,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data28)) return validate299.errors = [ { + if (!pattern101.test(data28)) return validate302.errors = [ { instancePath: instancePath + "/schedule/windows/" + i6 + "/end", schemaPath: "#/$defs/EventSchedule/properties/windows/items/properties/end/pattern", keyword: "pattern", @@ -24223,7 +24223,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.suppression) { let data29 = data.suppression, _errs66 = errors; if (errors === _errs66) { - if (!data29 || "object" != typeof data29 || Array.isArray(data29)) return validate299.errors = [ { + if (!data29 || "object" != typeof data29 || Array.isArray(data29)) return validate302.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/type", keyword: "type", @@ -24233,7 +24233,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs68 = errors; - for (let key6 in data29) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate299.errors = [ { + for (let key6 in data29) if ("debounce_seconds" !== key6 && "cooldown_seconds" !== key6 && "grouping_window_seconds" !== key6 && "max_events_per_minute" !== key6) return validate302.errors = [ { instancePath: instancePath + "/suppression", schemaPath: "#/properties/suppression/additionalProperties", keyword: "additionalProperties", @@ -24244,7 +24244,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (_errs68 === errors) { if (void 0 !== data29.debounce_seconds) { let data30 = data29.debounce_seconds, _errs69 = errors; - if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate299.errors = [ { + if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate302.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/type", keyword: "type", @@ -24253,7 +24253,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs69 && "number" == typeof data30 && isFinite(data30)) { - if (data30 > 86400 || isNaN(data30)) return validate299.errors = [ { + if (data30 > 86400 || isNaN(data30)) return validate302.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/maximum", keyword: "maximum", @@ -24262,7 +24262,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data30 < 0 || isNaN(data30)) return validate299.errors = [ { + if (data30 < 0 || isNaN(data30)) return validate302.errors = [ { instancePath: instancePath + "/suppression/debounce_seconds", schemaPath: "#/properties/suppression/properties/debounce_seconds/minimum", keyword: "minimum", @@ -24277,7 +24277,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data29.cooldown_seconds) { let data31 = data29.cooldown_seconds, _errs71 = errors; - if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate299.errors = [ { + if ("number" != typeof data31 || data31 % 1 || isNaN(data31) || !isFinite(data31)) return validate302.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/type", keyword: "type", @@ -24286,7 +24286,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs71 && "number" == typeof data31 && isFinite(data31)) { - if (data31 > 604800 || isNaN(data31)) return validate299.errors = [ { + if (data31 > 604800 || isNaN(data31)) return validate302.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/maximum", keyword: "maximum", @@ -24295,7 +24295,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 604800 } } ], !1; - if (data31 < 0 || isNaN(data31)) return validate299.errors = [ { + if (data31 < 0 || isNaN(data31)) return validate302.errors = [ { instancePath: instancePath + "/suppression/cooldown_seconds", schemaPath: "#/properties/suppression/properties/cooldown_seconds/minimum", keyword: "minimum", @@ -24310,7 +24310,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data29.grouping_window_seconds) { let data32 = data29.grouping_window_seconds, _errs73 = errors; - if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate299.errors = [ { + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate302.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/type", keyword: "type", @@ -24319,7 +24319,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs73 && "number" == typeof data32 && isFinite(data32)) { - if (data32 > 3600 || isNaN(data32)) return validate299.errors = [ { + if (data32 > 3600 || isNaN(data32)) return validate302.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/maximum", keyword: "maximum", @@ -24328,7 +24328,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 3600 } } ], !1; - if (data32 < 0 || isNaN(data32)) return validate299.errors = [ { + if (data32 < 0 || isNaN(data32)) return validate302.errors = [ { instancePath: instancePath + "/suppression/grouping_window_seconds", schemaPath: "#/properties/suppression/properties/grouping_window_seconds/minimum", keyword: "minimum", @@ -24342,7 +24342,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } else valid36 = !0; if (valid36 && void 0 !== data29.max_events_per_minute) { let data33 = data29.max_events_per_minute, _errs75 = errors; - if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate299.errors = [ { + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate302.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/type", keyword: "type", @@ -24351,7 +24351,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs75 && "number" == typeof data33 && isFinite(data33)) { - if (data33 > 6e4 || isNaN(data33)) return validate299.errors = [ { + if (data33 > 6e4 || isNaN(data33)) return validate302.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/maximum", keyword: "maximum", @@ -24360,7 +24360,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 6e4 } } ], !1; - if (data33 < 0 || isNaN(data33)) return validate299.errors = [ { + if (data33 < 0 || isNaN(data33)) return validate302.errors = [ { instancePath: instancePath + "/suppression/max_events_per_minute", schemaPath: "#/properties/suppression/properties/max_events_per_minute/minimum", keyword: "minimum", @@ -24380,7 +24380,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } else valid33 = !0; if (valid33 && void 0 !== data.revision) { let data34 = data.revision, _errs77 = errors; - if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate299.errors = [ { + if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate302.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -24389,7 +24389,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs77 && "number" == typeof data34 && isFinite(data34)) { - if (data34 > 9007199254740991 || isNaN(data34)) return validate299.errors = [ { + if (data34 > 9007199254740991 || isNaN(data34)) return validate302.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -24398,7 +24398,7 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data34 < 1 || isNaN(data34)) return validate299.errors = [ { + if (data34 < 1 || isNaN(data34)) return validate302.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -24420,22 +24420,22 @@ function validate299(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate299.errors = vErrors, 0 === errors; + return validate302.errors = vErrors, 0 === errors; } -validate299.evaluated = { +validate302.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventRoutes = validate295; +var __contract_EventRoutes = validate298; -function validate295(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate295.evaluated; +function validate298(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate298.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate295.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate298.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -24443,7 +24443,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.routes) return validate295.errors = [ { + if (void 0 === data.routes) return validate298.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -24451,7 +24451,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "routes" } } ], !1; - if (void 0 === data.count) return validate295.errors = [ { + if (void 0 === data.count) return validate298.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -24461,7 +24461,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("routes" !== key0 && "count" !== key0) return validate295.errors = [ { + for (let key0 in data) if ("routes" !== key0 && "count" !== key0) return validate298.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -24473,7 +24473,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.routes) { let data0 = data.routes, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate295.errors = [ { + if (!Array.isArray(data0)) return validate298.errors = [ { instancePath: instancePath + "/routes", schemaPath: "#/properties/routes/type", keyword: "type", @@ -24481,7 +24481,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data0.length > 512) return validate295.errors = [ { + if (data0.length > 512) return validate298.errors = [ { instancePath: instancePath + "/routes", schemaPath: "#/properties/routes/maxItems", keyword: "maxItems", @@ -24493,13 +24493,13 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = errors; - if (validate293(data0[i0], { + if (validate296(data0[i0], { instancePath: instancePath + "/routes/" + i0, parentData: data0, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate293.errors), errors = vErrors.length), _errs4 !== errors) break; + }) || (vErrors = vErrors.concat(validate296.errors), errors = vErrors.length), _errs4 !== errors) break; } } } @@ -24507,7 +24507,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.count) { let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate295.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate298.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -24516,7 +24516,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs5 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 512 || isNaN(data2)) return validate295.errors = [ { + if (data2 > 512 || isNaN(data2)) return validate298.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/maximum", keyword: "maximum", @@ -24525,7 +24525,7 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD limit: 512 } } ], !1; - if (data2 < 0 || isNaN(data2)) return validate295.errors = [ { + if (data2 < 0 || isNaN(data2)) return validate298.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -24539,21 +24539,21 @@ function validate295(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate295.errors = vErrors, 0 === errors; + return validate298.errors = vErrors, 0 === errors; } -validate295.evaluated = { +validate298.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EventSchedule = validate292; +var __contract_EventSchedule = validate295; -function validate292(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate292.evaluated; +function validate295(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate295.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate292.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate295.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -24561,7 +24561,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.version) return validate292.errors = [ { + if (void 0 === data.version) return validate295.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -24569,7 +24569,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "version" } } ], !1; - if (void 0 === data.timezone) return validate292.errors = [ { + if (void 0 === data.timezone) return validate295.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -24577,7 +24577,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timezone" } } ], !1; - if (void 0 === data.windows) return validate292.errors = [ { + if (void 0 === data.windows) return validate295.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -24587,7 +24587,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("version" !== key0 && "timezone" !== key0 && "windows" !== key0) return validate292.errors = [ { + for (let key0 in data) if ("version" !== key0 && "timezone" !== key0 && "windows" !== key0) return validate295.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -24598,7 +24598,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.version) { let data0 = data.version, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate292.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate295.errors = [ { instancePath: instancePath + "/version", schemaPath: "#/properties/version/type", keyword: "type", @@ -24606,7 +24606,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data0) return validate292.errors = [ { + if (1 !== data0) return validate295.errors = [ { instancePath: instancePath + "/version", schemaPath: "#/properties/version/const", keyword: "const", @@ -24620,7 +24620,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.timezone) { let data1 = data.timezone, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate292.errors = [ { + if ("string" != typeof data1) return validate295.errors = [ { instancePath: instancePath + "/timezone", schemaPath: "#/properties/timezone/type", keyword: "type", @@ -24628,7 +24628,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 64) return validate292.errors = [ { + if (func1(data1) > 64) return validate295.errors = [ { instancePath: instancePath + "/timezone", schemaPath: "#/properties/timezone/maxLength", keyword: "maxLength", @@ -24636,7 +24636,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD limit: 64 } } ], !1; - if (func1(data1) < 1) return validate292.errors = [ { + if (func1(data1) < 1) return validate295.errors = [ { instancePath: instancePath + "/timezone", schemaPath: "#/properties/timezone/minLength", keyword: "minLength", @@ -24649,7 +24649,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.windows) { let data2 = data.windows; - if (!Array.isArray(data2)) return validate292.errors = [ { + if (!Array.isArray(data2)) return validate295.errors = [ { instancePath: instancePath + "/windows", schemaPath: "#/properties/windows/type", keyword: "type", @@ -24657,7 +24657,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data2.length > 64) return validate292.errors = [ { + if (data2.length > 64) return validate295.errors = [ { instancePath: instancePath + "/windows", schemaPath: "#/properties/windows/maxItems", keyword: "maxItems", @@ -24670,7 +24670,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs8 = 0; if (0 === _errs8) { - if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate292.errors = [ { + if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0, schemaPath: "#/properties/windows/items/type", keyword: "type", @@ -24678,7 +24678,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data3.days) return validate292.errors = [ { + if (void 0 === data3.days) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0, schemaPath: "#/properties/windows/items/required", keyword: "required", @@ -24686,7 +24686,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "days" } } ], !1; - if (void 0 === data3.start) return validate292.errors = [ { + if (void 0 === data3.start) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0, schemaPath: "#/properties/windows/items/required", keyword: "required", @@ -24694,7 +24694,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start" } } ], !1; - if (void 0 === data3.end) return validate292.errors = [ { + if (void 0 === data3.end) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0, schemaPath: "#/properties/windows/items/required", keyword: "required", @@ -24704,7 +24704,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs10 = 0; - for (let key1 in data3) if ("days" !== key1 && "start" !== key1 && "end" !== key1) return validate292.errors = [ { + for (let key1 in data3) if ("days" !== key1 && "start" !== key1 && "end" !== key1) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0, schemaPath: "#/properties/windows/items/additionalProperties", keyword: "additionalProperties", @@ -24716,7 +24716,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data3.days) { let data4 = data3.days, _errs11 = 0; if (0 === _errs11) { - if (!Array.isArray(data4)) return validate292.errors = [ { + if (!Array.isArray(data4)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days", schemaPath: "#/properties/windows/items/properties/days/type", keyword: "type", @@ -24724,7 +24724,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data4.length > 7) return validate292.errors = [ { + if (data4.length > 7) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days", schemaPath: "#/properties/windows/items/properties/days/maxItems", keyword: "maxItems", @@ -24732,7 +24732,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD limit: 7 } } ], !1; - if (data4.length < 1) return validate292.errors = [ { + if (data4.length < 1) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days", schemaPath: "#/properties/windows/items/properties/days/minItems", keyword: "minItems", @@ -24745,7 +24745,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD let len1 = data4.length; for (let i1 = 0; i1 < len1; i1++) { let data5 = data4[i1], _errs13 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate292.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days/" + i1, schemaPath: "#/properties/windows/items/properties/days/items/type", keyword: "type", @@ -24754,7 +24754,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs13 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 6 || isNaN(data5)) return validate292.errors = [ { + if (data5 > 6 || isNaN(data5)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days/" + i1, schemaPath: "#/properties/windows/items/properties/days/items/maximum", keyword: "maximum", @@ -24763,7 +24763,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD limit: 6 } } ], !1; - if (data5 < 0 || isNaN(data5)) return validate292.errors = [ { + if (data5 < 0 || isNaN(data5)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days/" + i1, schemaPath: "#/properties/windows/items/properties/days/items/minimum", keyword: "minimum", @@ -24782,7 +24782,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD for (;i2--; ) { let item0 = data4[i2]; if ("number" == typeof item0 && !(item0 % 1) && !isNaN(item0) && isFinite(item0)) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate292.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/days", schemaPath: "#/properties/windows/items/properties/days/uniqueItems", keyword: "uniqueItems", @@ -24804,7 +24804,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data3.start) { let data6 = data3.start, _errs15 = 0; if (0 === _errs15) { - if ("string" != typeof data6) return validate292.errors = [ { + if ("string" != typeof data6) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/start", schemaPath: "#/properties/windows/items/properties/start/type", keyword: "type", @@ -24812,7 +24812,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data6)) return validate292.errors = [ { + if (!pattern101.test(data6)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/start", schemaPath: "#/properties/windows/items/properties/start/pattern", keyword: "pattern", @@ -24825,7 +24825,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD } else valid7 = !0; if (valid7 && void 0 !== data3.end) { let data7 = data3.end; - if ("string" != typeof data7) return validate292.errors = [ { + if ("string" != typeof data7) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/end", schemaPath: "#/properties/windows/items/properties/end/type", keyword: "type", @@ -24833,7 +24833,7 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern99.test(data7)) return validate292.errors = [ { + if (!pattern101.test(data7)) return validate295.errors = [ { instancePath: instancePath + "/windows/" + i0 + "/end", schemaPath: "#/properties/windows/items/properties/end/pattern", keyword: "pattern", @@ -24853,16 +24853,16 @@ function validate292(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate292.errors = [], !0; + return validate295.errors = [], !0; } -validate292.evaluated = { +validate295.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmark = validate390, schema452 = { +var __contract_EvidenceBookmark = validate400, schema464 = { type: "object", properties: { title: { @@ -24926,7 +24926,7 @@ var __contract_EvidenceBookmark = validate390, schema452 = { }, required: [ "title", "note", "start_time", "end_time", "cursor_time", "primary_camera_uuid", "camera_uuids", "filters", "representative_result", "uuid", "revision", "created_at", "updated_at", "holds_recordings" ], additionalProperties: !1 -}, schema450 = { +}, schema462 = { type: "object", properties: { event_type: { @@ -25009,7 +25009,7 @@ var __contract_EvidenceBookmark = validate390, schema452 = { }, required: [], additionalProperties: !1 -}, schema451 = { +}, schema463 = { anyOf: [ { type: "object", properties: { @@ -25046,11 +25046,11 @@ var __contract_EvidenceBookmark = validate390, schema452 = { } ] }; -function validate390(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate390.evaluated; +function validate400(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate400.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate390.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -25058,7 +25058,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.title) return validate390.errors = [ { + if (void 0 === data.title) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25066,7 +25066,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "title" } } ], !1; - if (void 0 === data.note) return validate390.errors = [ { + if (void 0 === data.note) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25074,7 +25074,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "note" } } ], !1; - if (void 0 === data.start_time) return validate390.errors = [ { + if (void 0 === data.start_time) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25082,7 +25082,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate390.errors = [ { + if (void 0 === data.end_time) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25090,7 +25090,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.cursor_time) return validate390.errors = [ { + if (void 0 === data.cursor_time) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25098,7 +25098,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cursor_time" } } ], !1; - if (void 0 === data.primary_camera_uuid) return validate390.errors = [ { + if (void 0 === data.primary_camera_uuid) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25106,7 +25106,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "primary_camera_uuid" } } ], !1; - if (void 0 === data.camera_uuids) return validate390.errors = [ { + if (void 0 === data.camera_uuids) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25114,7 +25114,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuids" } } ], !1; - if (void 0 === data.filters) return validate390.errors = [ { + if (void 0 === data.filters) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25122,7 +25122,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "filters" } } ], !1; - if (void 0 === data.representative_result) return validate390.errors = [ { + if (void 0 === data.representative_result) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25130,7 +25130,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "representative_result" } } ], !1; - if (void 0 === data.uuid) return validate390.errors = [ { + if (void 0 === data.uuid) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25138,7 +25138,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.revision) return validate390.errors = [ { + if (void 0 === data.revision) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25146,7 +25146,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate390.errors = [ { + if (void 0 === data.created_at) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25154,7 +25154,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate390.errors = [ { + if (void 0 === data.updated_at) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25162,7 +25162,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "updated_at" } } ], !1; - if (void 0 === data.holds_recordings) return validate390.errors = [ { + if (void 0 === data.holds_recordings) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -25172,7 +25172,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema452.properties, key0)) return validate390.errors = [ { + for (let key0 in data) if (!func7.call(schema464.properties, key0)) return validate400.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -25184,7 +25184,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.title) { let data0 = data.title, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate390.errors = [ { + if ("string" != typeof data0) return validate400.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/type", keyword: "type", @@ -25192,7 +25192,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate390.errors = [ { + if (func1(data0) > 127) return validate400.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/maxLength", keyword: "maxLength", @@ -25200,7 +25200,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate390.errors = [ { + if (func1(data0) < 1) return validate400.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/minLength", keyword: "minLength", @@ -25215,7 +25215,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.note) { let data1 = data.note, _errs4 = errors; if (errors === _errs4) { - if ("string" != typeof data1) return validate390.errors = [ { + if ("string" != typeof data1) return validate400.errors = [ { instancePath: instancePath + "/note", schemaPath: "#/properties/note/type", keyword: "type", @@ -25223,7 +25223,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 2047) return validate390.errors = [ { + if (func1(data1) > 2047) return validate400.errors = [ { instancePath: instancePath + "/note", schemaPath: "#/properties/note/maxLength", keyword: "maxLength", @@ -25237,7 +25237,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.start_time) { let data2 = data.start_time, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate390.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate400.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -25245,7 +25245,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate390.errors = [ { + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate400.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -25259,7 +25259,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.end_time) { let data3 = data.end_time, _errs8 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate390.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate400.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -25267,7 +25267,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate390.errors = [ { + if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate400.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -25281,7 +25281,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.cursor_time) { let data4 = data.cursor_time, _errs10 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate390.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate400.errors = [ { instancePath: instancePath + "/cursor_time", schemaPath: "#/properties/cursor_time/type", keyword: "type", @@ -25289,7 +25289,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate390.errors = [ { + if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate400.errors = [ { instancePath: instancePath + "/cursor_time", schemaPath: "#/properties/cursor_time/minimum", keyword: "minimum", @@ -25304,7 +25304,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_camera_uuid) { let data5 = data.primary_camera_uuid, _errs12 = errors; if (errors === _errs12) { - if ("string" != typeof data5) return validate390.errors = [ { + if ("string" != typeof data5) return validate400.errors = [ { instancePath: instancePath + "/primary_camera_uuid", schemaPath: "#/properties/primary_camera_uuid/type", keyword: "type", @@ -25312,7 +25312,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data5)) return validate390.errors = [ { + if (!pattern4.test(data5)) return validate400.errors = [ { instancePath: instancePath + "/primary_camera_uuid", schemaPath: "#/properties/primary_camera_uuid/pattern", keyword: "pattern", @@ -25327,7 +25327,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuids) { let data6 = data.camera_uuids, _errs14 = errors; if (errors === _errs14) { - if (!Array.isArray(data6)) return validate390.errors = [ { + if (!Array.isArray(data6)) return validate400.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/type", keyword: "type", @@ -25335,7 +25335,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data6.length > 16) return validate390.errors = [ { + if (data6.length > 16) return validate400.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/maxItems", keyword: "maxItems", @@ -25343,7 +25343,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 16 } } ], !1; - if (data6.length < 1) return validate390.errors = [ { + if (data6.length < 1) return validate400.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/minItems", keyword: "minItems", @@ -25357,7 +25357,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data7 = data6[i0], _errs16 = errors; if (errors === _errs16) { - if ("string" != typeof data7) return validate390.errors = [ { + if ("string" != typeof data7) return validate400.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/type", keyword: "type", @@ -25365,7 +25365,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data7)) return validate390.errors = [ { + if (!pattern4.test(data7)) return validate400.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/pattern", keyword: "pattern", @@ -25383,7 +25383,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data6[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate390.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate400.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/uniqueItems", keyword: "uniqueItems", @@ -25405,7 +25405,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.filters) { let data8 = data.filters, _errs18 = errors; if (errors == errors) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate390.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate400.errors = [ { instancePath: instancePath + "/filters", schemaPath: "#/$defs/EvidenceBookmarkFilters/type", keyword: "type", @@ -25415,7 +25415,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs21 = errors; - for (let key1 in data8) if (!func5.call(schema450.properties, key1)) return validate390.errors = [ { + for (let key1 in data8) if (!func7.call(schema462.properties, key1)) return validate400.errors = [ { instancePath: instancePath + "/filters", schemaPath: "#/$defs/EvidenceBookmarkFilters/additionalProperties", keyword: "additionalProperties", @@ -25427,7 +25427,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.event_type) { let data9 = data8.event_type, _errs22 = errors; if (errors === _errs22) { - if ("string" != typeof data9) return validate390.errors = [ { + if ("string" != typeof data9) return validate400.errors = [ { instancePath: instancePath + "/filters/event_type", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/event_type/type", keyword: "type", @@ -25435,7 +25435,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 127) return validate390.errors = [ { + if (func1(data9) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/event_type", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/event_type/maxLength", keyword: "maxLength", @@ -25450,7 +25450,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.location) { let data10 = data8.location, _errs24 = errors; if (errors === _errs24) { - if ("string" != typeof data10) return validate390.errors = [ { + if ("string" != typeof data10) return validate400.errors = [ { instancePath: instancePath + "/filters/location", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/location/type", keyword: "type", @@ -25458,7 +25458,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data10) > 127) return validate390.errors = [ { + if (func1(data10) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/location", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/location/maxLength", keyword: "maxLength", @@ -25473,7 +25473,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.label) { let data11 = data8.label, _errs26 = errors; if (errors === _errs26) { - if ("string" != typeof data11) return validate390.errors = [ { + if ("string" != typeof data11) return validate400.errors = [ { instancePath: instancePath + "/filters/label", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/label/type", keyword: "type", @@ -25481,7 +25481,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 127) return validate390.errors = [ { + if (func1(data11) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/label", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/label/maxLength", keyword: "maxLength", @@ -25496,7 +25496,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.zone) { let data12 = data8.zone, _errs28 = errors; if (errors === _errs28) { - if ("string" != typeof data12) return validate390.errors = [ { + if ("string" != typeof data12) return validate400.errors = [ { instancePath: instancePath + "/filters/zone", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/zone/type", keyword: "type", @@ -25504,7 +25504,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data12) > 127) return validate390.errors = [ { + if (func1(data12) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/zone", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/zone/maxLength", keyword: "maxLength", @@ -25519,7 +25519,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.source) { let data13 = data8.source, _errs30 = errors; if (errors === _errs30) { - if ("string" != typeof data13) return validate390.errors = [ { + if ("string" != typeof data13) return validate400.errors = [ { instancePath: instancePath + "/filters/source", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/source/type", keyword: "type", @@ -25527,7 +25527,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate390.errors = [ { + if (func1(data13) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/source", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/source/maxLength", keyword: "maxLength", @@ -25542,7 +25542,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.capture_method) { let data14 = data8.capture_method, _errs32 = errors; if (errors === _errs32) { - if ("string" != typeof data14) return validate390.errors = [ { + if ("string" != typeof data14) return validate400.errors = [ { instancePath: instancePath + "/filters/capture_method", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/capture_method/type", keyword: "type", @@ -25550,7 +25550,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data14) > 127) return validate390.errors = [ { + if (func1(data14) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/capture_method", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/capture_method/maxLength", keyword: "maxLength", @@ -25565,7 +25565,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.recording_tag) { let data15 = data8.recording_tag, _errs34 = errors; if (errors === _errs34) { - if ("string" != typeof data15) return validate390.errors = [ { + if ("string" != typeof data15) return validate400.errors = [ { instancePath: instancePath + "/filters/recording_tag", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/recording_tag/type", keyword: "type", @@ -25573,7 +25573,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data15) > 127) return validate390.errors = [ { + if (func1(data15) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/recording_tag", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/recording_tag/maxLength", keyword: "maxLength", @@ -25588,7 +25588,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.protection) { let data16 = data8.protection, _errs36 = errors; if (errors === _errs36) { - if ("string" != typeof data16) return validate390.errors = [ { + if ("string" != typeof data16) return validate400.errors = [ { instancePath: instancePath + "/filters/protection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/protection/type", keyword: "type", @@ -25596,7 +25596,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data16) > 127) return validate390.errors = [ { + if (func1(data16) > 127) return validate400.errors = [ { instancePath: instancePath + "/filters/protection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/protection/maxLength", keyword: "maxLength", @@ -25611,7 +25611,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.min_confidence) { let data17 = data8.min_confidence, _errs38 = errors; if (errors === _errs38) { - if ("number" != typeof data17 || !isFinite(data17)) return validate390.errors = [ { + if ("number" != typeof data17 || !isFinite(data17)) return validate400.errors = [ { instancePath: instancePath + "/filters/min_confidence", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/min_confidence/type", keyword: "type", @@ -25619,7 +25619,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data17 > 1 || isNaN(data17)) return validate390.errors = [ { + if (data17 > 1 || isNaN(data17)) return validate400.errors = [ { instancePath: instancePath + "/filters/min_confidence", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/min_confidence/maximum", keyword: "maximum", @@ -25628,7 +25628,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data17 < 0 || isNaN(data17)) return validate390.errors = [ { + if (data17 < 0 || isNaN(data17)) return validate400.errors = [ { instancePath: instancePath + "/filters/min_confidence", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/min_confidence/minimum", keyword: "minimum", @@ -25643,7 +25643,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid19 && void 0 !== data8.region) { let data18 = data8.region; if (errors == errors) { - if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate390.errors = [ { + if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/type", keyword: "type", @@ -25651,7 +25651,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data18.camera_uuid) return validate390.errors = [ { + if (void 0 === data18.camera_uuid) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -25659,7 +25659,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data18.x) return validate390.errors = [ { + if (void 0 === data18.x) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -25667,7 +25667,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "x" } } ], !1; - if (void 0 === data18.y) return validate390.errors = [ { + if (void 0 === data18.y) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -25675,7 +25675,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "y" } } ], !1; - if (void 0 === data18.width) return validate390.errors = [ { + if (void 0 === data18.width) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -25683,7 +25683,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "width" } } ], !1; - if (void 0 === data18.height) return validate390.errors = [ { + if (void 0 === data18.height) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -25693,7 +25693,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs42 = errors; - for (let key2 in data18) if ("x" !== key2 && "y" !== key2 && "width" !== key2 && "height" !== key2 && "camera_uuid" !== key2 && "match" !== key2 && "min_intersection" !== key2) return validate390.errors = [ { + for (let key2 in data18) if ("x" !== key2 && "y" !== key2 && "width" !== key2 && "height" !== key2 && "camera_uuid" !== key2 && "match" !== key2 && "min_intersection" !== key2) return validate400.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/additionalProperties", keyword: "additionalProperties", @@ -25705,7 +25705,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.x) { let data19 = data18.x, _errs43 = errors; if (errors === _errs43) { - if ("number" != typeof data19 || !isFinite(data19)) return validate390.errors = [ { + if ("number" != typeof data19 || !isFinite(data19)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/x", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/x/type", keyword: "type", @@ -25713,7 +25713,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data19 > 1 || isNaN(data19)) return validate390.errors = [ { + if (data19 > 1 || isNaN(data19)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/x", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/x/maximum", keyword: "maximum", @@ -25722,7 +25722,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data19 < 0 || isNaN(data19)) return validate390.errors = [ { + if (data19 < 0 || isNaN(data19)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/x", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/x/minimum", keyword: "minimum", @@ -25738,7 +25738,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.y) { let data20 = data18.y, _errs45 = errors; if (errors === _errs45) { - if ("number" != typeof data20 || !isFinite(data20)) return validate390.errors = [ { + if ("number" != typeof data20 || !isFinite(data20)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/y", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/y/type", keyword: "type", @@ -25746,7 +25746,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data20 > 1 || isNaN(data20)) return validate390.errors = [ { + if (data20 > 1 || isNaN(data20)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/y", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/y/maximum", keyword: "maximum", @@ -25755,7 +25755,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data20 < 0 || isNaN(data20)) return validate390.errors = [ { + if (data20 < 0 || isNaN(data20)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/y", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/y/minimum", keyword: "minimum", @@ -25771,7 +25771,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.width) { let data21 = data18.width, _errs47 = errors; if (errors === _errs47) { - if ("number" != typeof data21 || !isFinite(data21)) return validate390.errors = [ { + if ("number" != typeof data21 || !isFinite(data21)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/width", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/width/type", keyword: "type", @@ -25779,7 +25779,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data21 > 1 || isNaN(data21)) return validate390.errors = [ { + if (data21 > 1 || isNaN(data21)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/width", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/width/maximum", keyword: "maximum", @@ -25788,7 +25788,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data21 <= 0 || isNaN(data21)) return validate390.errors = [ { + if (data21 <= 0 || isNaN(data21)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/width", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/width/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -25804,7 +25804,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.height) { let data22 = data18.height, _errs49 = errors; if (errors === _errs49) { - if ("number" != typeof data22 || !isFinite(data22)) return validate390.errors = [ { + if ("number" != typeof data22 || !isFinite(data22)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/height", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/height/type", keyword: "type", @@ -25812,7 +25812,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data22 > 1 || isNaN(data22)) return validate390.errors = [ { + if (data22 > 1 || isNaN(data22)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/height", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/height/maximum", keyword: "maximum", @@ -25821,7 +25821,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data22 <= 0 || isNaN(data22)) return validate390.errors = [ { + if (data22 <= 0 || isNaN(data22)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/height", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/height/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -25837,7 +25837,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.camera_uuid) { let data23 = data18.camera_uuid, _errs51 = errors; if (errors === _errs51) { - if ("string" != typeof data23) return validate390.errors = [ { + if ("string" != typeof data23) return validate400.errors = [ { instancePath: instancePath + "/filters/region/camera_uuid", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/camera_uuid/type", keyword: "type", @@ -25845,7 +25845,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data23)) return validate390.errors = [ { + if (!pattern4.test(data23)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/camera_uuid", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/camera_uuid/pattern", keyword: "pattern", @@ -25859,7 +25859,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data18.match) { let data24 = data18.match, _errs53 = errors; - if ("string" != typeof data24) return validate390.errors = [ { + if ("string" != typeof data24) return validate400.errors = [ { instancePath: instancePath + "/filters/region/match", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/match/type", keyword: "type", @@ -25867,12 +25867,12 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("center" !== data24 && "intersects" !== data24 && "minimum_intersection" !== data24) return validate390.errors = [ { + if ("center" !== data24 && "intersects" !== data24 && "minimum_intersection" !== data24) return validate400.errors = [ { instancePath: instancePath + "/filters/region/match", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/match/enum", keyword: "enum", params: { - allowedValues: schema450.properties.region.properties.match.enum + allowedValues: schema462.properties.region.properties.match.enum } } ], !1; var valid26 = _errs53 === errors; @@ -25880,7 +25880,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid26 && void 0 !== data18.min_intersection) { let data25 = data18.min_intersection; if (errors == errors) { - if ("number" != typeof data25 || !isFinite(data25)) return validate390.errors = [ { + if ("number" != typeof data25 || !isFinite(data25)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/min_intersection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/min_intersection/type", keyword: "type", @@ -25888,7 +25888,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data25 > 1 || isNaN(data25)) return validate390.errors = [ { + if (data25 > 1 || isNaN(data25)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/min_intersection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/min_intersection/maximum", keyword: "maximum", @@ -25897,7 +25897,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data25 <= 0 || isNaN(data25)) return validate390.errors = [ { + if (data25 <= 0 || isNaN(data25)) return validate400.errors = [ { instancePath: instancePath + "/filters/region/min_intersection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/min_intersection/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -25956,7 +25956,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/result_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.result_id.type + type: schema463.anyOf[0].properties.result_id.type } }; vErrors.push(err1), errors++; @@ -26000,7 +26000,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/start_time/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.start_time.type + type: schema463.anyOf[0].properties.start_time.type } }; vErrors.push(err4), errors++; @@ -26016,7 +26016,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/end_time/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.end_time.type + type: schema463.anyOf[0].properties.end_time.type } }; vErrors.push(err5), errors++; @@ -26032,7 +26032,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/event_type/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.event_type.type + type: schema463.anyOf[0].properties.event_type.type } }; vErrors.push(err6), errors++; @@ -26048,7 +26048,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/recording_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.recording_id.type + type: schema463.anyOf[0].properties.recording_id.type } }; vErrors.push(err7), errors++; @@ -26064,7 +26064,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/detection_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.detection_id.type + type: schema463.anyOf[0].properties.detection_id.type } }; vErrors.push(err8), errors++; @@ -26079,7 +26079,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/label/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.label.type + type: schema463.anyOf[0].properties.label.type } }; vErrors.push(err9), errors++; @@ -26124,7 +26124,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err12), errors++, validate390.errors = vErrors, !1; + return vErrors.push(err12), errors++, validate400.errors = vErrors, !1; } errors = _errs59, vErrors.length = _errs59; var valid28 = _errs57 === errors; @@ -26133,7 +26133,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data35 = data.uuid, _errs81 = errors; if (errors === _errs81) { - if ("string" != typeof data35) return validate390.errors = [ { + if ("string" != typeof data35) return validate400.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/properties/uuid/type", keyword: "type", @@ -26141,7 +26141,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data35)) return validate390.errors = [ { + if (!pattern4.test(data35)) return validate400.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/properties/uuid/pattern", keyword: "pattern", @@ -26155,7 +26155,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid39) { if (void 0 !== data.revision) { let data36 = data.revision, _errs83 = errors; - if ("number" != typeof data36 || data36 % 1 || isNaN(data36) || !isFinite(data36)) return validate390.errors = [ { + if ("number" != typeof data36 || data36 % 1 || isNaN(data36) || !isFinite(data36)) return validate400.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -26163,7 +26163,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs83 && "number" == typeof data36 && isFinite(data36) && (data36 < 1 || isNaN(data36))) return validate390.errors = [ { + if (errors === _errs83 && "number" == typeof data36 && isFinite(data36) && (data36 < 1 || isNaN(data36))) return validate400.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -26177,7 +26177,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid40) { if (void 0 !== data.created_at) { let data37 = data.created_at, _errs85 = errors; - if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate390.errors = [ { + if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate400.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -26190,7 +26190,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD if (valid41) { if (void 0 !== data.updated_at) { let data38 = data.updated_at, _errs87 = errors; - if ("number" != typeof data38 || data38 % 1 || isNaN(data38) || !isFinite(data38)) return validate390.errors = [ { + if ("number" != typeof data38 || data38 % 1 || isNaN(data38) || !isFinite(data38)) return validate400.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -26200,7 +26200,7 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid42 = _errs87 === errors; } else valid42 = !0; - if (valid42 && void 0 !== data.holds_recordings && !1 !== data.holds_recordings) return validate390.errors = [ { + if (valid42 && void 0 !== data.holds_recordings && !1 !== data.holds_recordings) return validate400.errors = [ { instancePath: instancePath + "/holds_recordings", schemaPath: "#/properties/holds_recordings/const", keyword: "const", @@ -26223,22 +26223,22 @@ function validate390(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate390.errors = vErrors, 0 === errors; + return validate400.errors = vErrors, 0 === errors; } -validate390.evaluated = { +validate400.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmarkCreate = validate393; +var __contract_EvidenceBookmarkCreate = validate403; -function validate393(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate393.evaluated; +function validate403(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate403.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate393.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -26246,7 +26246,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.title) return validate393.errors = [ { + if (void 0 === data.title) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26254,7 +26254,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "title" } } ], !1; - if (void 0 === data.start_time) return validate393.errors = [ { + if (void 0 === data.start_time) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26262,7 +26262,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate393.errors = [ { + if (void 0 === data.end_time) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26270,7 +26270,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.cursor_time) return validate393.errors = [ { + if (void 0 === data.cursor_time) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26278,7 +26278,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cursor_time" } } ], !1; - if (void 0 === data.primary_camera_uuid) return validate393.errors = [ { + if (void 0 === data.primary_camera_uuid) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26286,7 +26286,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "primary_camera_uuid" } } ], !1; - if (void 0 === data.camera_uuids) return validate393.errors = [ { + if (void 0 === data.camera_uuids) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26294,7 +26294,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuids" } } ], !1; - if (void 0 === data.filters) return validate393.errors = [ { + if (void 0 === data.filters) return validate403.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -26305,7 +26305,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.title) { let data0 = data.title, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate393.errors = [ { + if ("string" != typeof data0) return validate403.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/type", keyword: "type", @@ -26313,7 +26313,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate393.errors = [ { + if (func1(data0) > 127) return validate403.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/maxLength", keyword: "maxLength", @@ -26321,7 +26321,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate393.errors = [ { + if (func1(data0) < 1) return validate403.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/minLength", keyword: "minLength", @@ -26336,7 +26336,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.note) { let data1 = data.note, _errs4 = errors; if (errors === _errs4) { - if ("string" != typeof data1) return validate393.errors = [ { + if ("string" != typeof data1) return validate403.errors = [ { instancePath: instancePath + "/note", schemaPath: "#/properties/note/type", keyword: "type", @@ -26344,7 +26344,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 2047) return validate393.errors = [ { + if (func1(data1) > 2047) return validate403.errors = [ { instancePath: instancePath + "/note", schemaPath: "#/properties/note/maxLength", keyword: "maxLength", @@ -26358,7 +26358,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.start_time) { let data2 = data.start_time, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate393.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate403.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -26366,7 +26366,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate393.errors = [ { + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate403.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -26380,7 +26380,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.end_time) { let data3 = data.end_time, _errs8 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate393.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate403.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -26388,7 +26388,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate393.errors = [ { + if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate403.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -26402,7 +26402,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.cursor_time) { let data4 = data.cursor_time, _errs10 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate393.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate403.errors = [ { instancePath: instancePath + "/cursor_time", schemaPath: "#/properties/cursor_time/type", keyword: "type", @@ -26410,7 +26410,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate393.errors = [ { + if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate403.errors = [ { instancePath: instancePath + "/cursor_time", schemaPath: "#/properties/cursor_time/minimum", keyword: "minimum", @@ -26425,7 +26425,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_camera_uuid) { let data5 = data.primary_camera_uuid, _errs12 = errors; if (errors === _errs12) { - if ("string" != typeof data5) return validate393.errors = [ { + if ("string" != typeof data5) return validate403.errors = [ { instancePath: instancePath + "/primary_camera_uuid", schemaPath: "#/properties/primary_camera_uuid/type", keyword: "type", @@ -26433,7 +26433,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data5)) return validate393.errors = [ { + if (!pattern4.test(data5)) return validate403.errors = [ { instancePath: instancePath + "/primary_camera_uuid", schemaPath: "#/properties/primary_camera_uuid/pattern", keyword: "pattern", @@ -26448,7 +26448,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuids) { let data6 = data.camera_uuids, _errs14 = errors; if (errors === _errs14) { - if (!Array.isArray(data6)) return validate393.errors = [ { + if (!Array.isArray(data6)) return validate403.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/type", keyword: "type", @@ -26456,7 +26456,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data6.length > 16) return validate393.errors = [ { + if (data6.length > 16) return validate403.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/maxItems", keyword: "maxItems", @@ -26464,7 +26464,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 16 } } ], !1; - if (data6.length < 1) return validate393.errors = [ { + if (data6.length < 1) return validate403.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/minItems", keyword: "minItems", @@ -26478,7 +26478,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data7 = data6[i0], _errs16 = errors; if (errors === _errs16) { - if ("string" != typeof data7) return validate393.errors = [ { + if ("string" != typeof data7) return validate403.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/type", keyword: "type", @@ -26486,7 +26486,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data7)) return validate393.errors = [ { + if (!pattern4.test(data7)) return validate403.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/pattern", keyword: "pattern", @@ -26504,7 +26504,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data6[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate393.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate403.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/uniqueItems", keyword: "uniqueItems", @@ -26526,7 +26526,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.filters) { let data8 = data.filters, _errs18 = errors; if (errors == errors) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate393.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate403.errors = [ { instancePath: instancePath + "/filters", schemaPath: "#/$defs/EvidenceBookmarkFilters/type", keyword: "type", @@ -26536,7 +26536,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs21 = errors; - for (let key0 in data8) if (!func5.call(schema450.properties, key0)) return validate393.errors = [ { + for (let key0 in data8) if (!func7.call(schema462.properties, key0)) return validate403.errors = [ { instancePath: instancePath + "/filters", schemaPath: "#/$defs/EvidenceBookmarkFilters/additionalProperties", keyword: "additionalProperties", @@ -26548,7 +26548,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.event_type) { let data9 = data8.event_type, _errs22 = errors; if (errors === _errs22) { - if ("string" != typeof data9) return validate393.errors = [ { + if ("string" != typeof data9) return validate403.errors = [ { instancePath: instancePath + "/filters/event_type", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/event_type/type", keyword: "type", @@ -26556,7 +26556,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data9) > 127) return validate393.errors = [ { + if (func1(data9) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/event_type", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/event_type/maxLength", keyword: "maxLength", @@ -26571,7 +26571,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.location) { let data10 = data8.location, _errs24 = errors; if (errors === _errs24) { - if ("string" != typeof data10) return validate393.errors = [ { + if ("string" != typeof data10) return validate403.errors = [ { instancePath: instancePath + "/filters/location", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/location/type", keyword: "type", @@ -26579,7 +26579,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data10) > 127) return validate393.errors = [ { + if (func1(data10) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/location", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/location/maxLength", keyword: "maxLength", @@ -26594,7 +26594,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.label) { let data11 = data8.label, _errs26 = errors; if (errors === _errs26) { - if ("string" != typeof data11) return validate393.errors = [ { + if ("string" != typeof data11) return validate403.errors = [ { instancePath: instancePath + "/filters/label", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/label/type", keyword: "type", @@ -26602,7 +26602,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data11) > 127) return validate393.errors = [ { + if (func1(data11) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/label", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/label/maxLength", keyword: "maxLength", @@ -26617,7 +26617,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.zone) { let data12 = data8.zone, _errs28 = errors; if (errors === _errs28) { - if ("string" != typeof data12) return validate393.errors = [ { + if ("string" != typeof data12) return validate403.errors = [ { instancePath: instancePath + "/filters/zone", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/zone/type", keyword: "type", @@ -26625,7 +26625,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data12) > 127) return validate393.errors = [ { + if (func1(data12) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/zone", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/zone/maxLength", keyword: "maxLength", @@ -26640,7 +26640,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.source) { let data13 = data8.source, _errs30 = errors; if (errors === _errs30) { - if ("string" != typeof data13) return validate393.errors = [ { + if ("string" != typeof data13) return validate403.errors = [ { instancePath: instancePath + "/filters/source", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/source/type", keyword: "type", @@ -26648,7 +26648,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data13) > 127) return validate393.errors = [ { + if (func1(data13) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/source", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/source/maxLength", keyword: "maxLength", @@ -26663,7 +26663,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.capture_method) { let data14 = data8.capture_method, _errs32 = errors; if (errors === _errs32) { - if ("string" != typeof data14) return validate393.errors = [ { + if ("string" != typeof data14) return validate403.errors = [ { instancePath: instancePath + "/filters/capture_method", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/capture_method/type", keyword: "type", @@ -26671,7 +26671,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data14) > 127) return validate393.errors = [ { + if (func1(data14) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/capture_method", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/capture_method/maxLength", keyword: "maxLength", @@ -26686,7 +26686,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.recording_tag) { let data15 = data8.recording_tag, _errs34 = errors; if (errors === _errs34) { - if ("string" != typeof data15) return validate393.errors = [ { + if ("string" != typeof data15) return validate403.errors = [ { instancePath: instancePath + "/filters/recording_tag", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/recording_tag/type", keyword: "type", @@ -26694,7 +26694,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data15) > 127) return validate393.errors = [ { + if (func1(data15) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/recording_tag", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/recording_tag/maxLength", keyword: "maxLength", @@ -26709,7 +26709,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.protection) { let data16 = data8.protection, _errs36 = errors; if (errors === _errs36) { - if ("string" != typeof data16) return validate393.errors = [ { + if ("string" != typeof data16) return validate403.errors = [ { instancePath: instancePath + "/filters/protection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/protection/type", keyword: "type", @@ -26717,7 +26717,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data16) > 127) return validate393.errors = [ { + if (func1(data16) > 127) return validate403.errors = [ { instancePath: instancePath + "/filters/protection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/protection/maxLength", keyword: "maxLength", @@ -26732,7 +26732,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.min_confidence) { let data17 = data8.min_confidence, _errs38 = errors; if (errors === _errs38) { - if ("number" != typeof data17 || !isFinite(data17)) return validate393.errors = [ { + if ("number" != typeof data17 || !isFinite(data17)) return validate403.errors = [ { instancePath: instancePath + "/filters/min_confidence", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/min_confidence/type", keyword: "type", @@ -26740,7 +26740,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data17 > 1 || isNaN(data17)) return validate393.errors = [ { + if (data17 > 1 || isNaN(data17)) return validate403.errors = [ { instancePath: instancePath + "/filters/min_confidence", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/min_confidence/maximum", keyword: "maximum", @@ -26749,7 +26749,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data17 < 0 || isNaN(data17)) return validate393.errors = [ { + if (data17 < 0 || isNaN(data17)) return validate403.errors = [ { instancePath: instancePath + "/filters/min_confidence", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/min_confidence/minimum", keyword: "minimum", @@ -26764,7 +26764,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (valid19 && void 0 !== data8.region) { let data18 = data8.region; if (errors == errors) { - if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate393.errors = [ { + if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/type", keyword: "type", @@ -26772,7 +26772,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data18.camera_uuid) return validate393.errors = [ { + if (void 0 === data18.camera_uuid) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -26780,7 +26780,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data18.x) return validate393.errors = [ { + if (void 0 === data18.x) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -26788,7 +26788,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "x" } } ], !1; - if (void 0 === data18.y) return validate393.errors = [ { + if (void 0 === data18.y) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -26796,7 +26796,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "y" } } ], !1; - if (void 0 === data18.width) return validate393.errors = [ { + if (void 0 === data18.width) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -26804,7 +26804,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "width" } } ], !1; - if (void 0 === data18.height) return validate393.errors = [ { + if (void 0 === data18.height) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/required", keyword: "required", @@ -26814,7 +26814,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs42 = errors; - for (let key1 in data18) if ("x" !== key1 && "y" !== key1 && "width" !== key1 && "height" !== key1 && "camera_uuid" !== key1 && "match" !== key1 && "min_intersection" !== key1) return validate393.errors = [ { + for (let key1 in data18) if ("x" !== key1 && "y" !== key1 && "width" !== key1 && "height" !== key1 && "camera_uuid" !== key1 && "match" !== key1 && "min_intersection" !== key1) return validate403.errors = [ { instancePath: instancePath + "/filters/region", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/additionalProperties", keyword: "additionalProperties", @@ -26826,7 +26826,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.x) { let data19 = data18.x, _errs43 = errors; if (errors === _errs43) { - if ("number" != typeof data19 || !isFinite(data19)) return validate393.errors = [ { + if ("number" != typeof data19 || !isFinite(data19)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/x", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/x/type", keyword: "type", @@ -26834,7 +26834,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data19 > 1 || isNaN(data19)) return validate393.errors = [ { + if (data19 > 1 || isNaN(data19)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/x", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/x/maximum", keyword: "maximum", @@ -26843,7 +26843,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data19 < 0 || isNaN(data19)) return validate393.errors = [ { + if (data19 < 0 || isNaN(data19)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/x", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/x/minimum", keyword: "minimum", @@ -26859,7 +26859,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.y) { let data20 = data18.y, _errs45 = errors; if (errors === _errs45) { - if ("number" != typeof data20 || !isFinite(data20)) return validate393.errors = [ { + if ("number" != typeof data20 || !isFinite(data20)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/y", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/y/type", keyword: "type", @@ -26867,7 +26867,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data20 > 1 || isNaN(data20)) return validate393.errors = [ { + if (data20 > 1 || isNaN(data20)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/y", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/y/maximum", keyword: "maximum", @@ -26876,7 +26876,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data20 < 0 || isNaN(data20)) return validate393.errors = [ { + if (data20 < 0 || isNaN(data20)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/y", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/y/minimum", keyword: "minimum", @@ -26892,7 +26892,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.width) { let data21 = data18.width, _errs47 = errors; if (errors === _errs47) { - if ("number" != typeof data21 || !isFinite(data21)) return validate393.errors = [ { + if ("number" != typeof data21 || !isFinite(data21)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/width", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/width/type", keyword: "type", @@ -26900,7 +26900,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data21 > 1 || isNaN(data21)) return validate393.errors = [ { + if (data21 > 1 || isNaN(data21)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/width", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/width/maximum", keyword: "maximum", @@ -26909,7 +26909,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data21 <= 0 || isNaN(data21)) return validate393.errors = [ { + if (data21 <= 0 || isNaN(data21)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/width", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/width/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -26925,7 +26925,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.height) { let data22 = data18.height, _errs49 = errors; if (errors === _errs49) { - if ("number" != typeof data22 || !isFinite(data22)) return validate393.errors = [ { + if ("number" != typeof data22 || !isFinite(data22)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/height", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/height/type", keyword: "type", @@ -26933,7 +26933,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data22 > 1 || isNaN(data22)) return validate393.errors = [ { + if (data22 > 1 || isNaN(data22)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/height", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/height/maximum", keyword: "maximum", @@ -26942,7 +26942,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data22 <= 0 || isNaN(data22)) return validate393.errors = [ { + if (data22 <= 0 || isNaN(data22)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/height", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/height/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -26958,7 +26958,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data18.camera_uuid) { let data23 = data18.camera_uuid, _errs51 = errors; if (errors === _errs51) { - if ("string" != typeof data23) return validate393.errors = [ { + if ("string" != typeof data23) return validate403.errors = [ { instancePath: instancePath + "/filters/region/camera_uuid", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/camera_uuid/type", keyword: "type", @@ -26966,7 +26966,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data23)) return validate393.errors = [ { + if (!pattern4.test(data23)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/camera_uuid", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/camera_uuid/pattern", keyword: "pattern", @@ -26980,7 +26980,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data18.match) { let data24 = data18.match, _errs53 = errors; - if ("string" != typeof data24) return validate393.errors = [ { + if ("string" != typeof data24) return validate403.errors = [ { instancePath: instancePath + "/filters/region/match", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/match/type", keyword: "type", @@ -26988,12 +26988,12 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("center" !== data24 && "intersects" !== data24 && "minimum_intersection" !== data24) return validate393.errors = [ { + if ("center" !== data24 && "intersects" !== data24 && "minimum_intersection" !== data24) return validate403.errors = [ { instancePath: instancePath + "/filters/region/match", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/match/enum", keyword: "enum", params: { - allowedValues: schema450.properties.region.properties.match.enum + allowedValues: schema462.properties.region.properties.match.enum } } ], !1; var valid26 = _errs53 === errors; @@ -27001,7 +27001,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD if (valid26 && void 0 !== data18.min_intersection) { let data25 = data18.min_intersection; if (errors == errors) { - if ("number" != typeof data25 || !isFinite(data25)) return validate393.errors = [ { + if ("number" != typeof data25 || !isFinite(data25)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/min_intersection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/min_intersection/type", keyword: "type", @@ -27009,7 +27009,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data25 > 1 || isNaN(data25)) return validate393.errors = [ { + if (data25 > 1 || isNaN(data25)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/min_intersection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/min_intersection/maximum", keyword: "maximum", @@ -27018,7 +27018,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data25 <= 0 || isNaN(data25)) return validate393.errors = [ { + if (data25 <= 0 || isNaN(data25)) return validate403.errors = [ { instancePath: instancePath + "/filters/region/min_intersection", schemaPath: "#/$defs/EvidenceBookmarkFilters/properties/region/properties/min_intersection/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -27076,7 +27076,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/result_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.result_id.type + type: schema463.anyOf[0].properties.result_id.type } }; vErrors.push(err1), errors++; @@ -27120,7 +27120,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/start_time/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.start_time.type + type: schema463.anyOf[0].properties.start_time.type } }; vErrors.push(err4), errors++; @@ -27136,7 +27136,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/end_time/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.end_time.type + type: schema463.anyOf[0].properties.end_time.type } }; vErrors.push(err5), errors++; @@ -27152,7 +27152,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/event_type/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.event_type.type + type: schema463.anyOf[0].properties.event_type.type } }; vErrors.push(err6), errors++; @@ -27168,7 +27168,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/recording_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.recording_id.type + type: schema463.anyOf[0].properties.recording_id.type } }; vErrors.push(err7), errors++; @@ -27184,7 +27184,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/detection_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.detection_id.type + type: schema463.anyOf[0].properties.detection_id.type } }; vErrors.push(err8), errors++; @@ -27199,7 +27199,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceBookmarkResult/anyOf/0/properties/label/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.label.type + type: schema463.anyOf[0].properties.label.type } }; vErrors.push(err9), errors++; @@ -27244,7 +27244,7 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err12), errors++, validate393.errors = vErrors, !1; + return vErrors.push(err12), errors++, validate403.errors = vErrors, !1; } errors = _errs59, vErrors.length = _errs59; } @@ -27256,21 +27256,21 @@ function validate393(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate393.errors = vErrors, 0 === errors; + return validate403.errors = vErrors, 0 === errors; } -validate393.evaluated = { +validate403.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmarkDelete = validate395; +var __contract_EvidenceBookmarkDelete = validate405; -function validate395(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate395.evaluated; +function validate405(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate405.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate395.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate405.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -27278,7 +27278,7 @@ function validate395(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate395.errors = [ { + if (void 0 === data.revision) return validate405.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -27288,7 +27288,7 @@ function validate395(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.revision) { let data0 = data.revision, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate395.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate405.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -27296,7 +27296,7 @@ function validate395(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate395.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate405.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -27306,21 +27306,21 @@ function validate395(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate395.errors = [], !0; + return validate405.errors = [], !0; } -validate395.evaluated = { +validate405.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmarkDeleted = validate401; +var __contract_EvidenceBookmarkDeleted = validate411; -function validate401(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate401.evaluated; +function validate411(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate411.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate401.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate411.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -27328,7 +27328,7 @@ function validate401(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate401.errors = [ { + if (void 0 === data.success) return validate411.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -27338,7 +27338,7 @@ function validate401(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0) return validate401.errors = [ { + for (let key0 in data) if ("success" !== key0) return validate411.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -27346,7 +27346,7 @@ function validate401(data, {instancePath: instancePath = "", parentData: parentD additionalProperty: key0 } } ], !1; - if (0 === _errs1 && void 0 !== data.success && !0 !== data.success) return validate401.errors = [ { + if (0 === _errs1 && void 0 !== data.success && !0 !== data.success) return validate411.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -27355,21 +27355,21 @@ function validate401(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate401.errors = [], !0; + return validate411.errors = [], !0; } -validate401.evaluated = { +validate411.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmarkFilters = validate388; +var __contract_EvidenceBookmarkFilters = validate398; -function validate388(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate388.evaluated; +function validate398(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate398.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate388.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate398.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -27379,7 +27379,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema450.properties, key0)) return validate388.errors = [ { + for (let key0 in data) if (!func7.call(schema462.properties, key0)) return validate398.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -27391,7 +27391,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.event_type) { let data0 = data.event_type, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate388.errors = [ { + if ("string" != typeof data0) return validate398.errors = [ { instancePath: instancePath + "/event_type", schemaPath: "#/properties/event_type/type", keyword: "type", @@ -27399,7 +27399,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate388.errors = [ { + if (func1(data0) > 127) return validate398.errors = [ { instancePath: instancePath + "/event_type", schemaPath: "#/properties/event_type/maxLength", keyword: "maxLength", @@ -27414,7 +27414,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.location) { let data1 = data.location, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate388.errors = [ { + if ("string" != typeof data1) return validate398.errors = [ { instancePath: instancePath + "/location", schemaPath: "#/properties/location/type", keyword: "type", @@ -27422,7 +27422,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 127) return validate388.errors = [ { + if (func1(data1) > 127) return validate398.errors = [ { instancePath: instancePath + "/location", schemaPath: "#/properties/location/maxLength", keyword: "maxLength", @@ -27437,7 +27437,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.label) { let data2 = data.label, _errs6 = 0; if (0 === _errs6) { - if ("string" != typeof data2) return validate388.errors = [ { + if ("string" != typeof data2) return validate398.errors = [ { instancePath: instancePath + "/label", schemaPath: "#/properties/label/type", keyword: "type", @@ -27445,7 +27445,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 127) return validate388.errors = [ { + if (func1(data2) > 127) return validate398.errors = [ { instancePath: instancePath + "/label", schemaPath: "#/properties/label/maxLength", keyword: "maxLength", @@ -27460,7 +27460,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.zone) { let data3 = data.zone, _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data3) return validate388.errors = [ { + if ("string" != typeof data3) return validate398.errors = [ { instancePath: instancePath + "/zone", schemaPath: "#/properties/zone/type", keyword: "type", @@ -27468,7 +27468,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 127) return validate388.errors = [ { + if (func1(data3) > 127) return validate398.errors = [ { instancePath: instancePath + "/zone", schemaPath: "#/properties/zone/maxLength", keyword: "maxLength", @@ -27483,7 +27483,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.source) { let data4 = data.source, _errs10 = 0; if (0 === _errs10) { - if ("string" != typeof data4) return validate388.errors = [ { + if ("string" != typeof data4) return validate398.errors = [ { instancePath: instancePath + "/source", schemaPath: "#/properties/source/type", keyword: "type", @@ -27491,7 +27491,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data4) > 127) return validate388.errors = [ { + if (func1(data4) > 127) return validate398.errors = [ { instancePath: instancePath + "/source", schemaPath: "#/properties/source/maxLength", keyword: "maxLength", @@ -27506,7 +27506,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.capture_method) { let data5 = data.capture_method, _errs12 = 0; if (0 === _errs12) { - if ("string" != typeof data5) return validate388.errors = [ { + if ("string" != typeof data5) return validate398.errors = [ { instancePath: instancePath + "/capture_method", schemaPath: "#/properties/capture_method/type", keyword: "type", @@ -27514,7 +27514,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 127) return validate388.errors = [ { + if (func1(data5) > 127) return validate398.errors = [ { instancePath: instancePath + "/capture_method", schemaPath: "#/properties/capture_method/maxLength", keyword: "maxLength", @@ -27529,7 +27529,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.recording_tag) { let data6 = data.recording_tag, _errs14 = 0; if (0 === _errs14) { - if ("string" != typeof data6) return validate388.errors = [ { + if ("string" != typeof data6) return validate398.errors = [ { instancePath: instancePath + "/recording_tag", schemaPath: "#/properties/recording_tag/type", keyword: "type", @@ -27537,7 +27537,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data6) > 127) return validate388.errors = [ { + if (func1(data6) > 127) return validate398.errors = [ { instancePath: instancePath + "/recording_tag", schemaPath: "#/properties/recording_tag/maxLength", keyword: "maxLength", @@ -27552,7 +27552,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.protection) { let data7 = data.protection, _errs16 = 0; if (0 === _errs16) { - if ("string" != typeof data7) return validate388.errors = [ { + if ("string" != typeof data7) return validate398.errors = [ { instancePath: instancePath + "/protection", schemaPath: "#/properties/protection/type", keyword: "type", @@ -27560,7 +27560,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data7) > 127) return validate388.errors = [ { + if (func1(data7) > 127) return validate398.errors = [ { instancePath: instancePath + "/protection", schemaPath: "#/properties/protection/maxLength", keyword: "maxLength", @@ -27575,7 +27575,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.min_confidence) { let data8 = data.min_confidence, _errs18 = 0; if (0 === _errs18) { - if ("number" != typeof data8 || !isFinite(data8)) return validate388.errors = [ { + if ("number" != typeof data8 || !isFinite(data8)) return validate398.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/type", keyword: "type", @@ -27583,7 +27583,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data8 > 1 || isNaN(data8)) return validate388.errors = [ { + if (data8 > 1 || isNaN(data8)) return validate398.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/maximum", keyword: "maximum", @@ -27592,7 +27592,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data8 < 0 || isNaN(data8)) return validate388.errors = [ { + if (data8 < 0 || isNaN(data8)) return validate398.errors = [ { instancePath: instancePath + "/min_confidence", schemaPath: "#/properties/min_confidence/minimum", keyword: "minimum", @@ -27606,7 +27606,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD } else valid8 = !0; if (valid8 && void 0 !== data.region) { let data9 = data.region; - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate388.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/type", keyword: "type", @@ -27614,7 +27614,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data9.camera_uuid) return validate388.errors = [ { + if (void 0 === data9.camera_uuid) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/required", keyword: "required", @@ -27622,7 +27622,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data9.x) return validate388.errors = [ { + if (void 0 === data9.x) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/required", keyword: "required", @@ -27630,7 +27630,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "x" } } ], !1; - if (void 0 === data9.y) return validate388.errors = [ { + if (void 0 === data9.y) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/required", keyword: "required", @@ -27638,7 +27638,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "y" } } ], !1; - if (void 0 === data9.width) return validate388.errors = [ { + if (void 0 === data9.width) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/required", keyword: "required", @@ -27646,7 +27646,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "width" } } ], !1; - if (void 0 === data9.height) return validate388.errors = [ { + if (void 0 === data9.height) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/required", keyword: "required", @@ -27656,7 +27656,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs22 = 0; - for (let key1 in data9) if ("x" !== key1 && "y" !== key1 && "width" !== key1 && "height" !== key1 && "camera_uuid" !== key1 && "match" !== key1 && "min_intersection" !== key1) return validate388.errors = [ { + for (let key1 in data9) if ("x" !== key1 && "y" !== key1 && "width" !== key1 && "height" !== key1 && "camera_uuid" !== key1 && "match" !== key1 && "min_intersection" !== key1) return validate398.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/additionalProperties", keyword: "additionalProperties", @@ -27668,7 +27668,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.x) { let data10 = data9.x, _errs23 = 0; if (0 === _errs23) { - if ("number" != typeof data10 || !isFinite(data10)) return validate388.errors = [ { + if ("number" != typeof data10 || !isFinite(data10)) return validate398.errors = [ { instancePath: instancePath + "/region/x", schemaPath: "#/properties/region/properties/x/type", keyword: "type", @@ -27676,7 +27676,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data10 > 1 || isNaN(data10)) return validate388.errors = [ { + if (data10 > 1 || isNaN(data10)) return validate398.errors = [ { instancePath: instancePath + "/region/x", schemaPath: "#/properties/region/properties/x/maximum", keyword: "maximum", @@ -27685,7 +27685,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data10 < 0 || isNaN(data10)) return validate388.errors = [ { + if (data10 < 0 || isNaN(data10)) return validate398.errors = [ { instancePath: instancePath + "/region/x", schemaPath: "#/properties/region/properties/x/minimum", keyword: "minimum", @@ -27701,7 +27701,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.y) { let data11 = data9.y, _errs25 = 0; if (0 === _errs25) { - if ("number" != typeof data11 || !isFinite(data11)) return validate388.errors = [ { + if ("number" != typeof data11 || !isFinite(data11)) return validate398.errors = [ { instancePath: instancePath + "/region/y", schemaPath: "#/properties/region/properties/y/type", keyword: "type", @@ -27709,7 +27709,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data11 > 1 || isNaN(data11)) return validate388.errors = [ { + if (data11 > 1 || isNaN(data11)) return validate398.errors = [ { instancePath: instancePath + "/region/y", schemaPath: "#/properties/region/properties/y/maximum", keyword: "maximum", @@ -27718,7 +27718,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data11 < 0 || isNaN(data11)) return validate388.errors = [ { + if (data11 < 0 || isNaN(data11)) return validate398.errors = [ { instancePath: instancePath + "/region/y", schemaPath: "#/properties/region/properties/y/minimum", keyword: "minimum", @@ -27734,7 +27734,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.width) { let data12 = data9.width, _errs27 = 0; if (0 === _errs27) { - if ("number" != typeof data12 || !isFinite(data12)) return validate388.errors = [ { + if ("number" != typeof data12 || !isFinite(data12)) return validate398.errors = [ { instancePath: instancePath + "/region/width", schemaPath: "#/properties/region/properties/width/type", keyword: "type", @@ -27742,7 +27742,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data12 > 1 || isNaN(data12)) return validate388.errors = [ { + if (data12 > 1 || isNaN(data12)) return validate398.errors = [ { instancePath: instancePath + "/region/width", schemaPath: "#/properties/region/properties/width/maximum", keyword: "maximum", @@ -27751,7 +27751,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data12 <= 0 || isNaN(data12)) return validate388.errors = [ { + if (data12 <= 0 || isNaN(data12)) return validate398.errors = [ { instancePath: instancePath + "/region/width", schemaPath: "#/properties/region/properties/width/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -27767,7 +27767,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.height) { let data13 = data9.height, _errs29 = 0; if (0 === _errs29) { - if ("number" != typeof data13 || !isFinite(data13)) return validate388.errors = [ { + if ("number" != typeof data13 || !isFinite(data13)) return validate398.errors = [ { instancePath: instancePath + "/region/height", schemaPath: "#/properties/region/properties/height/type", keyword: "type", @@ -27775,7 +27775,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data13 > 1 || isNaN(data13)) return validate388.errors = [ { + if (data13 > 1 || isNaN(data13)) return validate398.errors = [ { instancePath: instancePath + "/region/height", schemaPath: "#/properties/region/properties/height/maximum", keyword: "maximum", @@ -27784,7 +27784,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data13 <= 0 || isNaN(data13)) return validate388.errors = [ { + if (data13 <= 0 || isNaN(data13)) return validate398.errors = [ { instancePath: instancePath + "/region/height", schemaPath: "#/properties/region/properties/height/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -27800,7 +27800,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data9.camera_uuid) { let data14 = data9.camera_uuid, _errs31 = 0; if (0 === _errs31) { - if ("string" != typeof data14) return validate388.errors = [ { + if ("string" != typeof data14) return validate398.errors = [ { instancePath: instancePath + "/region/camera_uuid", schemaPath: "#/properties/region/properties/camera_uuid/type", keyword: "type", @@ -27808,7 +27808,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data14)) return validate388.errors = [ { + if (!pattern4.test(data14)) return validate398.errors = [ { instancePath: instancePath + "/region/camera_uuid", schemaPath: "#/properties/region/properties/camera_uuid/pattern", keyword: "pattern", @@ -27822,7 +27822,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data9.match) { let data15 = data9.match, _errs33 = 0; - if ("string" != typeof data15) return validate388.errors = [ { + if ("string" != typeof data15) return validate398.errors = [ { instancePath: instancePath + "/region/match", schemaPath: "#/properties/region/properties/match/type", keyword: "type", @@ -27830,19 +27830,19 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("center" !== data15 && "intersects" !== data15 && "minimum_intersection" !== data15) return validate388.errors = [ { + if ("center" !== data15 && "intersects" !== data15 && "minimum_intersection" !== data15) return validate398.errors = [ { instancePath: instancePath + "/region/match", schemaPath: "#/properties/region/properties/match/enum", keyword: "enum", params: { - allowedValues: schema450.properties.region.properties.match.enum + allowedValues: schema462.properties.region.properties.match.enum } } ], !1; var valid15 = 0 === _errs33; } else valid15 = !0; if (valid15 && void 0 !== data9.min_intersection) { let data16 = data9.min_intersection; - if ("number" != typeof data16 || !isFinite(data16)) return validate388.errors = [ { + if ("number" != typeof data16 || !isFinite(data16)) return validate398.errors = [ { instancePath: instancePath + "/region/min_intersection", schemaPath: "#/properties/region/properties/min_intersection/type", keyword: "type", @@ -27850,7 +27850,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data16 > 1 || isNaN(data16)) return validate388.errors = [ { + if (data16 > 1 || isNaN(data16)) return validate398.errors = [ { instancePath: instancePath + "/region/min_intersection", schemaPath: "#/properties/region/properties/min_intersection/maximum", keyword: "maximum", @@ -27859,7 +27859,7 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data16 <= 0 || isNaN(data16)) return validate388.errors = [ { + if (data16 <= 0 || isNaN(data16)) return validate398.errors = [ { instancePath: instancePath + "/region/min_intersection", schemaPath: "#/properties/region/properties/min_intersection/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -27887,19 +27887,19 @@ function validate388(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate388.errors = [], !0; + return validate398.errors = [], !0; } -validate388.evaluated = { +validate398.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmarkResult = validate389; +var __contract_EvidenceBookmarkResult = validate399; -function validate389(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate389.evaluated; +function validate399(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate399.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) { @@ -27925,7 +27925,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/result_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.result_id.type + type: schema463.anyOf[0].properties.result_id.type } }; vErrors.push(err1), errors++; @@ -27969,7 +27969,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/start_time/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.start_time.type + type: schema463.anyOf[0].properties.start_time.type } }; vErrors.push(err4), errors++; @@ -27985,7 +27985,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/end_time/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.end_time.type + type: schema463.anyOf[0].properties.end_time.type } }; vErrors.push(err5), errors++; @@ -28001,7 +28001,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/event_type/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.event_type.type + type: schema463.anyOf[0].properties.event_type.type } }; vErrors.push(err6), errors++; @@ -28017,7 +28017,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/recording_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.recording_id.type + type: schema463.anyOf[0].properties.recording_id.type } }; vErrors.push(err7), errors++; @@ -28033,7 +28033,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/detection_id/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.detection_id.type + type: schema463.anyOf[0].properties.detection_id.type } }; vErrors.push(err8), errors++; @@ -28048,7 +28048,7 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/anyOf/0/properties/label/type", keyword: "type", params: { - type: schema451.anyOf[0].properties.label.type + type: schema463.anyOf[0].properties.label.type } }; vErrors.push(err9), errors++; @@ -28093,23 +28093,23 @@ function validate389(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err12), errors++, validate389.errors = vErrors, !1; + return vErrors.push(err12), errors++, validate399.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate389.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate399.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate389.evaluated = { +validate399.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_EvidenceBookmarkUpdate = validate394; +var __contract_EvidenceBookmarkUpdate = validate404; -function validate394(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate394.evaluated; +function validate404(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate404.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate394.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate404.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -28117,7 +28117,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.title) return validate394.errors = [ { + if (void 0 === data.title) return validate404.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -28125,7 +28125,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "title" } } ], !1; - if (void 0 === data.revision) return validate394.errors = [ { + if (void 0 === data.revision) return validate404.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -28136,7 +28136,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.title) { let data0 = data.title, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate394.errors = [ { + if ("string" != typeof data0) return validate404.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/type", keyword: "type", @@ -28144,7 +28144,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate394.errors = [ { + if (func1(data0) > 127) return validate404.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/maxLength", keyword: "maxLength", @@ -28152,7 +28152,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate394.errors = [ { + if (func1(data0) < 1) return validate404.errors = [ { instancePath: instancePath + "/title", schemaPath: "#/properties/title/minLength", keyword: "minLength", @@ -28167,7 +28167,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.note) { let data1 = data.note, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate394.errors = [ { + if ("string" != typeof data1) return validate404.errors = [ { instancePath: instancePath + "/note", schemaPath: "#/properties/note/type", keyword: "type", @@ -28175,7 +28175,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 2047) return validate394.errors = [ { + if (func1(data1) > 2047) return validate404.errors = [ { instancePath: instancePath + "/note", schemaPath: "#/properties/note/maxLength", keyword: "maxLength", @@ -28188,7 +28188,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.revision) { let data2 = data.revision, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate394.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate404.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -28196,7 +28196,7 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate394.errors = [ { + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate404.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -28207,22 +28207,22 @@ function validate394(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate394.errors = [], !0; + return validate404.errors = [], !0; } -validate394.evaluated = { +validate404.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceBookmarks = validate391; +var __contract_EvidenceBookmarks = validate401; -function validate391(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate391.evaluated; +function validate401(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate401.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate391.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate401.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -28230,7 +28230,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.bookmarks) return validate391.errors = [ { + if (void 0 === data.bookmarks) return validate401.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -28238,7 +28238,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bookmarks" } } ], !1; - if (void 0 === data.count) return validate391.errors = [ { + if (void 0 === data.count) return validate401.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -28248,7 +28248,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("bookmarks" !== key0 && "count" !== key0) return validate391.errors = [ { + for (let key0 in data) if ("bookmarks" !== key0 && "count" !== key0) return validate401.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -28260,7 +28260,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.bookmarks) { let data0 = data.bookmarks, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate391.errors = [ { + if (!Array.isArray(data0)) return validate401.errors = [ { instancePath: instancePath + "/bookmarks", schemaPath: "#/properties/bookmarks/type", keyword: "type", @@ -28268,7 +28268,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data0.length > 256) return validate391.errors = [ { + if (data0.length > 256) return validate401.errors = [ { instancePath: instancePath + "/bookmarks", schemaPath: "#/properties/bookmarks/maxItems", keyword: "maxItems", @@ -28280,13 +28280,13 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = errors; - if (validate390(data0[i0], { + if (validate400(data0[i0], { instancePath: instancePath + "/bookmarks/" + i0, parentData: data0, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate390.errors), errors = vErrors.length), _errs4 !== errors) break; + }) || (vErrors = vErrors.concat(validate400.errors), errors = vErrors.length), _errs4 !== errors) break; } } } @@ -28294,7 +28294,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.count) { let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate391.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate401.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -28302,7 +28302,7 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate391.errors = [ { + if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate401.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -28315,21 +28315,116 @@ function validate391(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate391.errors = vErrors, 0 === errors; + return validate401.errors = vErrors, 0 === errors; } -validate391.evaluated = { +validate401.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceDetection = validate364; +var __contract_EvidenceDetection = validate367, schema424 = { + type: "object", + properties: { + confidence: { + type: "number" + }, + x: { + type: "number" + }, + y: { + type: "number" + }, + width: { + type: "number" + }, + height: { + type: "number" + }, + label: { + type: "string" + }, + timestamp: { + type: "integer" + }, + end_timestamp: { + type: "integer" + }, + observation: { + $ref: "#/$defs/EvidenceDetectionObservation" + } + }, + required: [ "confidence", "x", "y", "width", "height", "label", "timestamp", "end_timestamp" ], + additionalProperties: !1 +}, pattern159 = new RegExp("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "u"), schema427 = { + type: "object", + properties: { + key: { + type: "string", + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$" + }, + writer_namespace: { + type: "string", + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}$" + }, + classifier: { + type: "string", + minLength: 1, + maxLength: 64 + }, + model_version: { + type: [ "string", "null" ], + maxLength: 64 + }, + label: { + type: "string", + minLength: 1, + maxLength: 128 + }, + taxonomy_id: { + type: [ "string", "null" ], + maxLength: 64 + }, + confidence: { + type: "number", + minimum: 0, + maximum: 1 + }, + classified_at: { + $ref: "#/$defs/ObservationTime" + }, + input_asset_id: { + $ref: "#/$defs/ObservationUuid" + }, + input_revision: { + type: "integer", + minimum: 1 + }, + kind: { + type: "string", + enum: [ "automated", "owner_confirmed" ] + }, + annotation_revision: { + type: "integer", + minimum: 1 + }, + created_at: { + $ref: "#/$defs/ObservationTime" + }, + updated_at: { + $ref: "#/$defs/ObservationTime" + } + }, + required: [ "key", "writer_namespace", "classifier", "model_version", "label", "taxonomy_id", "confidence", "classified_at", "input_asset_id", "input_revision", "kind", "annotation_revision", "created_at", "updated_at" ], + additionalProperties: !0, + description: "Stored classification annotation (contract §10.3). P2." +}, pattern160 = new RegExp("^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$", "u"), pattern161 = new RegExp("^[a-z0-9][a-z0-9_.-]{0,31}$", "u"), formats2 = require_formats().fullFormats["date-time"]; -function validate364(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate364.evaluated; +function validate369(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate369.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate364.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -28337,450 +28432,446 @@ function validate364(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.confidence) return validate364.errors = [ { + if (void 0 === data.key) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "confidence" + missingProperty: "key" } } ], !1; - if (void 0 === data.x) return validate364.errors = [ { + if (void 0 === data.writer_namespace) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "x" + missingProperty: "writer_namespace" } } ], !1; - if (void 0 === data.y) return validate364.errors = [ { + if (void 0 === data.classifier) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "y" + missingProperty: "classifier" } } ], !1; - if (void 0 === data.width) return validate364.errors = [ { + if (void 0 === data.model_version) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "width" + missingProperty: "model_version" } } ], !1; - if (void 0 === data.height) return validate364.errors = [ { + if (void 0 === data.label) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "height" + missingProperty: "label" } } ], !1; - if (void 0 === data.label) return validate364.errors = [ { + if (void 0 === data.taxonomy_id) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "label" + missingProperty: "taxonomy_id" } } ], !1; - if (void 0 === data.timestamp) return validate364.errors = [ { + if (void 0 === data.confidence) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "timestamp" + missingProperty: "confidence" } } ], !1; - if (void 0 === data.end_timestamp) return validate364.errors = [ { + if (void 0 === data.classified_at) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "end_timestamp" + missingProperty: "classified_at" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("confidence" !== key0 && "x" !== key0 && "y" !== key0 && "width" !== key0 && "height" !== key0 && "label" !== key0 && "timestamp" !== key0 && "end_timestamp" !== key0) return validate364.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.confidence) { - let data0 = data.confidence, _errs2 = 0; - if ("number" != typeof data0 || !isFinite(data0)) return validate364.errors = [ { - instancePath: instancePath + "/confidence", - schemaPath: "#/properties/confidence/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.x) { - let data1 = data.x, _errs4 = 0; - if ("number" != typeof data1 || !isFinite(data1)) return validate364.errors = [ { - instancePath: instancePath + "/x", - schemaPath: "#/properties/x/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.y) { - let data2 = data.y, _errs6 = 0; - if ("number" != typeof data2 || !isFinite(data2)) return validate364.errors = [ { - instancePath: instancePath + "/y", - schemaPath: "#/properties/y/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.width) { - let data3 = data.width, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate364.errors = [ { - instancePath: instancePath + "/width", - schemaPath: "#/properties/width/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.height) { - let data4 = data.height, _errs10 = 0; - if ("number" != typeof data4 || !isFinite(data4)) return validate364.errors = [ { - instancePath: instancePath + "/height", - schemaPath: "#/properties/height/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.label) { - let _errs12 = 0; - if ("string" != typeof data.label) return validate364.errors = [ { - instancePath: instancePath + "/label", - schemaPath: "#/properties/label/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.timestamp) { - let data6 = data.timestamp, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate364.errors = [ { - instancePath: instancePath + "/timestamp", - schemaPath: "#/properties/timestamp/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6 && void 0 !== data.end_timestamp) { - let data7 = data.end_timestamp; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate364.errors = [ { - instancePath: instancePath + "/end_timestamp", - schemaPath: "#/properties/end_timestamp/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - } - } - } - } - } - } - } + if (void 0 === data.input_asset_id) return validate369.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "input_asset_id" } - } - return validate364.errors = [], !0; -} - -validate364.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_EvidenceDetections = validate365, schema32 = { - type: "string", - enum: [ "Stopped", "Starting", "Running", "Stopping", "Error", "Reconnecting", "Degraded", "Unknown" ] -}; - -function validate365(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate365.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate365.errors = [ { + } ], !1; + if (void 0 === data.input_revision) return validate369.errors = [ { instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "input_revision" } } ], !1; - if (void 0 === data.detections) return validate365.errors = [ { + if (void 0 === data.kind) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "detections" + missingProperty: "kind" } } ], !1; - if (void 0 === data.timestamp) return validate365.errors = [ { + if (void 0 === data.annotation_revision) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "timestamp" + missingProperty: "annotation_revision" } } ], !1; - if (void 0 === data.stream_status) return validate365.errors = [ { + if (void 0 === data.created_at) return validate369.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "stream_status" + missingProperty: "created_at" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("detections" !== key0 && "timestamp" !== key0 && "stream_status" !== key0) return validate365.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 + if (void 0 === data.updated_at) return validate369.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "updated_at" + } + } ], !1; + if (void 0 !== data.key) { + let data0 = data.key, _errs2 = 0; + if (0 === _errs2) { + if ("string" != typeof data0) return validate369.errors = [ { + instancePath: instancePath + "/key", + schemaPath: "#/properties/key/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern160.test(data0)) return validate369.errors = [ { + instancePath: instancePath + "/key", + schemaPath: "#/properties/key/pattern", + keyword: "pattern", + params: { + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$" + } + } ], !1; + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.writer_namespace) { + let data1 = data.writer_namespace, _errs4 = 0; + if (0 === _errs4) { + if ("string" != typeof data1) return validate369.errors = [ { + instancePath: instancePath + "/writer_namespace", + schemaPath: "#/properties/writer_namespace/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern161.test(data1)) return validate369.errors = [ { + instancePath: instancePath + "/writer_namespace", + schemaPath: "#/properties/writer_namespace/pattern", + keyword: "pattern", + params: { + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + } ], !1; } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.detections) { - let data0 = data.detections, _errs2 = 0; - if (0 === _errs2) { - if (!Array.isArray(data0)) return validate365.errors = [ { - instancePath: instancePath + "/detections", - schemaPath: "#/properties/detections/type", + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.classifier) { + let data2 = data.classifier, _errs6 = 0; + if (0 === _errs6) { + if ("string" != typeof data2) return validate369.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/type", keyword: "type", params: { - type: "array" + type: "string" } } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/type", + if (func1(data2) > 64) return validate369.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + if (func1(data2) < 1) return validate369.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.model_version) { + let data3 = data.model_version, _errs8 = 0; + if ("string" != typeof data3 && null !== data3) return validate369.errors = [ { + instancePath: instancePath + "/model_version", + schemaPath: "#/properties/model_version/type", + keyword: "type", + params: { + type: schema427.properties.model_version.type + } + } ], !1; + if (0 === _errs8 && "string" == typeof data3 && func1(data3) > 64) return validate369.errors = [ { + instancePath: instancePath + "/model_version", + schemaPath: "#/properties/model_version/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.label) { + let data4 = data.label, _errs10 = 0; + if (0 === _errs10) { + if ("string" != typeof data4) return validate369.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", keyword: "type", params: { - type: "object" - } - } ], !1; - if (void 0 === data1.confidence) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", - params: { - missingProperty: "confidence" - } - } ], !1; - if (void 0 === data1.x) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", - params: { - missingProperty: "x" + type: "string" } } ], !1; - if (void 0 === data1.y) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", + if (func1(data4) > 128) return validate369.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/maxLength", + keyword: "maxLength", params: { - missingProperty: "y" + limit: 128 } } ], !1; - if (void 0 === data1.width) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", + if (func1(data4) < 1) return validate369.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/minLength", + keyword: "minLength", params: { - missingProperty: "width" + limit: 1 } } ], !1; - if (void 0 === data1.height) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", + } + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.taxonomy_id) { + let data5 = data.taxonomy_id, _errs12 = 0; + if ("string" != typeof data5 && null !== data5) return validate369.errors = [ { + instancePath: instancePath + "/taxonomy_id", + schemaPath: "#/properties/taxonomy_id/type", + keyword: "type", params: { - missingProperty: "height" + type: schema427.properties.taxonomy_id.type } } ], !1; - if (void 0 === data1.label) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", + if (0 === _errs12 && "string" == typeof data5 && func1(data5) > 64) return validate369.errors = [ { + instancePath: instancePath + "/taxonomy_id", + schemaPath: "#/properties/taxonomy_id/maxLength", + keyword: "maxLength", params: { - missingProperty: "label" + limit: 64 } } ], !1; - if (void 0 === data1.timestamp) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", - params: { - missingProperty: "timestamp" - } - } ], !1; - if (void 0 === data1.end_timestamp) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/required", - keyword: "required", - params: { - missingProperty: "end_timestamp" + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.confidence) { + let data6 = data.confidence, _errs14 = 0; + if (0 === _errs14) { + if ("number" != typeof data6 || !isFinite(data6)) return validate369.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data6 > 1 || isNaN(data6)) return validate369.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data6 < 0 || isNaN(data6)) return validate369.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; } - } ], !1; - { - let _errs7 = 0; - for (let key1 in data1) if ("confidence" !== key1 && "x" !== key1 && "y" !== key1 && "width" !== key1 && "height" !== key1 && "label" !== key1 && "timestamp" !== key1 && "end_timestamp" !== key1) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0, - schemaPath: "#/$defs/EvidenceDetection/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key1 - } - } ], !1; - if (0 === _errs7) { - if (void 0 !== data1.confidence) { - let data2 = data1.confidence, _errs8 = 0; - if ("number" != typeof data2 || !isFinite(data2)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/confidence", - schemaPath: "#/$defs/EvidenceDetection/properties/confidence/type", + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.classified_at) { + let data7 = data.classified_at, _errs16 = 0; + if ("string" != typeof data7) return validate369.errors = [ { + instancePath: instancePath + "/classified_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!formats2.validate(data7)) return validate369.errors = [ { + instancePath: instancePath + "/classified_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.input_asset_id) { + let data8 = data.input_asset_id, _errs19 = 0; + if ("string" != typeof data8) return validate369.errors = [ { + instancePath: instancePath + "/input_asset_id", + schemaPath: "#/$defs/ObservationUuid/type", keyword: "type", params: { - type: "number" + type: "string" } } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data1.x) { - let data3 = data1.x, _errs10 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/x", - schemaPath: "#/$defs/EvidenceDetection/properties/x/type", + if (!pattern159.test(data8)) return validate369.errors = [ { + instancePath: instancePath + "/input_asset_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + var valid9 = 0 === _errs19; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.input_revision) { + let data9 = data.input_revision, _errs22 = 0; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate369.errors = [ { + instancePath: instancePath + "/input_revision", + schemaPath: "#/properties/input_revision/type", keyword: "type", params: { - type: "number" + type: "integer" } } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data1.y) { - let data4 = data1.y, _errs12 = 0; - if ("number" != typeof data4 || !isFinite(data4)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/y", - schemaPath: "#/$defs/EvidenceDetection/properties/y/type", + if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 1 || isNaN(data9))) return validate369.errors = [ { + instancePath: instancePath + "/input_revision", + schemaPath: "#/properties/input_revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid11 = 0 === _errs22; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.kind) { + let data10 = data.kind, _errs24 = 0; + if ("string" != typeof data10) return validate369.errors = [ { + instancePath: instancePath + "/kind", + schemaPath: "#/properties/kind/type", keyword: "type", params: { - type: "number" + type: "string" } } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data1.width) { - let data5 = data1.width, _errs14 = 0; - if ("number" != typeof data5 || !isFinite(data5)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/width", - schemaPath: "#/$defs/EvidenceDetection/properties/width/type", + if ("automated" !== data10 && "owner_confirmed" !== data10) return validate369.errors = [ { + instancePath: instancePath + "/kind", + schemaPath: "#/properties/kind/enum", + keyword: "enum", + params: { + allowedValues: schema427.properties.kind.enum + } + } ], !1; + var valid12 = 0 === _errs24; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data.annotation_revision) { + let data11 = data.annotation_revision, _errs26 = 0; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate369.errors = [ { + instancePath: instancePath + "/annotation_revision", + schemaPath: "#/properties/annotation_revision/type", keyword: "type", params: { - type: "number" + type: "integer" } } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data1.height) { - let data6 = data1.height, _errs16 = 0; - if ("number" != typeof data6 || !isFinite(data6)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/height", - schemaPath: "#/$defs/EvidenceDetection/properties/height/type", + if (0 === _errs26 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate369.errors = [ { + instancePath: instancePath + "/annotation_revision", + schemaPath: "#/properties/annotation_revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid13 = 0 === _errs26; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.created_at) { + let data12 = data.created_at, _errs28 = 0; + if ("string" != typeof data12) return validate369.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { - type: "number" + type: "string" } } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data1.label) { - let _errs18 = 0; - if ("string" != typeof data1.label) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/label", - schemaPath: "#/$defs/EvidenceDetection/properties/label/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data1.timestamp) { - let data8 = data1.timestamp, _errs20 = 0; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/timestamp", - schemaPath: "#/$defs/EvidenceDetection/properties/timestamp/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9 && void 0 !== data1.end_timestamp) { - let data9 = data1.end_timestamp; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate365.errors = [ { - instancePath: instancePath + "/detections/" + i0 + "/end_timestamp", - schemaPath: "#/$defs/EvidenceDetection/properties/end_timestamp/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; + if (!formats2.validate(data12)) return validate369.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" } - } + } ], !1; + var valid14 = 0 === _errs28; + } else valid14 = !0; + if (valid14 && void 0 !== data.updated_at) { + let data13 = data.updated_at; + if ("string" != typeof data13) return validate369.errors = [ { + instancePath: instancePath + "/updated_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!formats2.validate(data13)) return validate369.errors = [ { + instancePath: instancePath + "/updated_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; } } } @@ -28788,60 +28879,490 @@ function validate365(data, {instancePath: instancePath = "", parentData: parentD } } } - if (0 !== _errs4) break; } } } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.timestamp) { - let _errs24 = 0; - if ("string" != typeof data.timestamp) return validate365.errors = [ { - instancePath: instancePath + "/timestamp", - schemaPath: "#/properties/timestamp/type", - keyword: "type", - params: { - type: "string" + } + } + } + return validate369.errors = [], !0; +} + +function validate368(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate368.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate368.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.id) return validate368.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "id" + } + } ], !1; + if (void 0 === data.owner_confirmed) return validate368.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "owner_confirmed" + } + } ], !1; + if (void 0 === data.classification) return validate368.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "classification" + } + } ], !1; + { + let _errs1 = errors; + for (let key0 in data) if ("id" !== key0 && "owner_confirmed" !== key0 && "classification" !== key0) return validate368.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (_errs1 === errors) { + if (void 0 !== data.id) { + let data0 = data.id, _errs2 = errors; + if (errors == errors) { + if ("string" != typeof data0) return validate368.errors = [ { + instancePath: instancePath + "/id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data0)) return validate368.errors = [ { + instancePath: instancePath + "/id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.owner_confirmed) { + let _errs5 = errors; + if ("boolean" != typeof data.owner_confirmed) return validate368.errors = [ { + instancePath: instancePath + "/owner_confirmed", + schemaPath: "#/properties/owner_confirmed/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2 && void 0 !== data.classification) { + let data2 = data.classification, _errs8 = errors, valid4 = !1, _errs9 = errors; + validate369(data2, { + instancePath: instancePath + "/classification", + parentData: data, + parentDataProperty: "classification", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate369.errors), errors = vErrors.length); + var _valid0 = _errs9 === errors; + valid4 = valid4 || _valid0; + let _errs10 = errors; + if (null !== data2) { + let err0 = { + instancePath: instancePath + "/classification", + schemaPath: "#/properties/classification/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err0), errors++; } - } ], !1; - var valid11 = 0 === _errs24; - } else valid11 = !0; - if (valid11 && void 0 !== data.stream_status) { - let data11 = data.stream_status; - if ("string" != typeof data11) return validate365.errors = [ { - instancePath: instancePath + "/stream_status", - schemaPath: "#/$defs/StreamStatus/type", + if (_valid0 = _errs10 === errors, valid4 = valid4 || _valid0, !valid4) { + let err1 = { + instancePath: instancePath + "/classification", + schemaPath: "#/properties/classification/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err1), errors++, validate368.errors = vErrors, !1; + } + errors = _errs8, vErrors.length = _errs8; + } + } + } + } + } + return validate368.errors = vErrors, 0 === errors; +} + +function validate367(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate367.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.confidence) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "confidence" + } + } ], !1; + if (void 0 === data.x) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "x" + } + } ], !1; + if (void 0 === data.y) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "y" + } + } ], !1; + if (void 0 === data.width) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "width" + } + } ], !1; + if (void 0 === data.height) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "height" + } + } ], !1; + if (void 0 === data.label) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "label" + } + } ], !1; + if (void 0 === data.timestamp) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "timestamp" + } + } ], !1; + if (void 0 === data.end_timestamp) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "end_timestamp" + } + } ], !1; + { + let _errs1 = errors; + for (let key0 in data) if (!func7.call(schema424.properties, key0)) return validate367.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (_errs1 === errors) { + if (void 0 !== data.confidence) { + let data0 = data.confidence, _errs2 = errors; + if ("number" != typeof data0 || !isFinite(data0)) return validate367.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/type", keyword: "type", params: { - type: "string" + type: "number" } } ], !1; - if ("Stopped" !== data11 && "Starting" !== data11 && "Running" !== data11 && "Stopping" !== data11 && "Error" !== data11 && "Reconnecting" !== data11 && "Degraded" !== data11 && "Unknown" !== data11) return validate365.errors = [ { - instancePath: instancePath + "/stream_status", - schemaPath: "#/$defs/StreamStatus/enum", - keyword: "enum", - params: { - allowedValues: schema32.enum + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.x) { + let data1 = data.x, _errs4 = errors; + if ("number" != typeof data1 || !isFinite(data1)) return validate367.errors = [ { + instancePath: instancePath + "/x", + schemaPath: "#/properties/x/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.y) { + let data2 = data.y, _errs6 = errors; + if ("number" != typeof data2 || !isFinite(data2)) return validate367.errors = [ { + instancePath: instancePath + "/y", + schemaPath: "#/properties/y/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.width) { + let data3 = data.width, _errs8 = errors; + if ("number" != typeof data3 || !isFinite(data3)) return validate367.errors = [ { + instancePath: instancePath + "/width", + schemaPath: "#/properties/width/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid3 = _errs8 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.height) { + let data4 = data.height, _errs10 = errors; + if ("number" != typeof data4 || !isFinite(data4)) return validate367.errors = [ { + instancePath: instancePath + "/height", + schemaPath: "#/properties/height/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid4 = _errs10 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.label) { + let _errs12 = errors; + if ("string" != typeof data.label) return validate367.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid5 = _errs12 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.timestamp) { + let data6 = data.timestamp, _errs14 = errors; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate367.errors = [ { + instancePath: instancePath + "/timestamp", + schemaPath: "#/properties/timestamp/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid6 = _errs14 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.end_timestamp) { + let data7 = data.end_timestamp, _errs16 = errors; + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate367.errors = [ { + instancePath: instancePath + "/end_timestamp", + schemaPath: "#/properties/end_timestamp/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid7 = _errs16 === errors; + } else valid7 = !0; + valid7 && void 0 !== data.observation && (validate368(data.observation, { + instancePath: instancePath + "/observation", + parentData: data, + parentDataProperty: "observation", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate368.errors), errors = vErrors.length)); + } + } + } + } } - } ], !1; + } } } } } - return validate365.errors = [], !0; + return validate367.errors = vErrors, 0 === errors; } -validate365.evaluated = { +validate369.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate368.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate367.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceFileCheck = validate368; +var __contract_EvidenceDetections = validate374, schema32 = { + type: "string", + enum: [ "Stopped", "Starting", "Running", "Stopping", "Error", "Reconnecting", "Degraded", "Unknown" ] +}; -function validate368(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate368.evaluated; +function validate374(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate374.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate374.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.detections) return validate374.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "detections" + } + } ], !1; + if (void 0 === data.timestamp) return validate374.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "timestamp" + } + } ], !1; + if (void 0 === data.stream_status) return validate374.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream_status" + } + } ], !1; + { + let _errs1 = errors; + for (let key0 in data) if ("detections" !== key0 && "timestamp" !== key0 && "stream_status" !== key0) return validate374.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (_errs1 === errors) { + if (void 0 !== data.detections) { + let data0 = data.detections, _errs2 = errors; + if (errors === _errs2) { + if (!Array.isArray(data0)) return validate374.errors = [ { + instancePath: instancePath + "/detections", + schemaPath: "#/properties/detections/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs4 = errors; + if (validate367(data0[i0], { + instancePath: instancePath + "/detections/" + i0, + parentData: data0, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate367.errors), errors = vErrors.length), _errs4 !== errors) break; + } + } + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.timestamp) { + let _errs5 = errors; + if ("string" != typeof data.timestamp) return validate374.errors = [ { + instancePath: instancePath + "/timestamp", + schemaPath: "#/properties/timestamp/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2 && void 0 !== data.stream_status) { + let data3 = data.stream_status; + if ("string" != typeof data3) return validate374.errors = [ { + instancePath: instancePath + "/stream_status", + schemaPath: "#/$defs/StreamStatus/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("Stopped" !== data3 && "Starting" !== data3 && "Running" !== data3 && "Stopping" !== data3 && "Error" !== data3 && "Reconnecting" !== data3 && "Degraded" !== data3 && "Unknown" !== data3) return validate374.errors = [ { + instancePath: instancePath + "/stream_status", + schemaPath: "#/$defs/StreamStatus/enum", + keyword: "enum", + params: { + allowedValues: schema32.enum + } + } ], !1; + } + } + } + } + } + return validate374.errors = vErrors, 0 === errors; +} + +validate374.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_EvidenceFileCheck = validate378; + +function validate378(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate378.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.exists) { @@ -29021,18 +29542,18 @@ function validate368(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err13), errors++, validate368.errors = vErrors, !1; + return vErrors.push(err13), errors++, validate378.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate368.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate378.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate368.evaluated = { +validate378.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_EvidenceFileDeleted = validate369, schema428 = { +var __contract_EvidenceFileDeleted = validate379, schema440 = { type: "object", properties: { success: { @@ -29050,10 +29571,10 @@ var __contract_EvidenceFileDeleted = validate369, schema428 = { additionalProperties: !1 }; -function validate369(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate369.evaluated; +function validate379(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate379.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate369.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate379.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -29061,7 +29582,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate369.errors = [ { + if (void 0 === data.success) return validate379.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29069,7 +29590,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.existed) return validate369.errors = [ { + if (void 0 === data.existed) return validate379.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29077,7 +29598,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "existed" } } ], !1; - if (void 0 === data.status) return validate369.errors = [ { + if (void 0 === data.status) return validate379.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29087,7 +29608,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "existed" !== key0 && "status" !== key0) return validate369.errors = [ { + for (let key0 in data) if ("success" !== key0 && "existed" !== key0 && "status" !== key0) return validate379.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -29098,7 +29619,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.success) { let _errs2 = 0; - if (!0 !== data.success) return validate369.errors = [ { + if (!0 !== data.success) return validate379.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -29111,7 +29632,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.existed) { let _errs3 = 0; - if ("boolean" != typeof data.existed) return validate369.errors = [ { + if ("boolean" != typeof data.existed) return validate379.errors = [ { instancePath: instancePath + "/existed", schemaPath: "#/properties/existed/type", keyword: "type", @@ -29123,7 +29644,7 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.status) { let data2 = data.status; - if ("string" != typeof data2) return validate369.errors = [ { + if ("string" != typeof data2) return validate379.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -29131,33 +29652,33 @@ function validate369(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("deleted" !== data2 && "deletion_pending" !== data2) return validate369.errors = [ { + if ("deleted" !== data2 && "deletion_pending" !== data2) return validate379.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/enum", keyword: "enum", params: { - allowedValues: schema428.properties.status.enum + allowedValues: schema440.properties.status.enum } } ], !1; } } } } - return validate369.errors = [], !0; + return validate379.errors = [], !0; } -validate369.evaluated = { +validate379.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceLprDeleted = validate400; +var __contract_EvidenceLprDeleted = validate410; -function validate400(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate400.evaluated; +function validate410(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate410.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate400.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate410.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -29165,7 +29686,7 @@ function validate400(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.deleted) return validate400.errors = [ { + if (void 0 === data.deleted) return validate410.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29175,7 +29696,7 @@ function validate400(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("deleted" !== key0) return validate400.errors = [ { + for (let key0 in data) if ("deleted" !== key0) return validate410.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -29183,7 +29704,7 @@ function validate400(data, {instancePath: instancePath = "", parentData: parentD additionalProperty: key0 } } ], !1; - if (0 === _errs1 && void 0 !== data.deleted && !0 !== data.deleted) return validate400.errors = [ { + if (0 === _errs1 && void 0 !== data.deleted && !0 !== data.deleted) return validate410.errors = [ { instancePath: instancePath + "/deleted", schemaPath: "#/properties/deleted/const", keyword: "const", @@ -29192,16 +29713,16 @@ function validate400(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate400.errors = [], !0; + return validate410.errors = [], !0; } -validate400.evaluated = { +validate410.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceLprExportRequest = validate399, schema465 = { +var __contract_EvidenceLprExportRequest = validate409, schema477 = { type: "object", properties: { camera_uuid: { @@ -29233,10 +29754,10 @@ var __contract_EvidenceLprExportRequest = validate399, schema465 = { additionalProperties: !0 }; -function validate399(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate399.evaluated; +function validate409(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate409.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate399.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate409.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -29244,7 +29765,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.camera_uuid) return validate399.errors = [ { + if (void 0 === data.camera_uuid) return validate409.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29252,7 +29773,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.start_at) return validate399.errors = [ { + if (void 0 === data.start_at) return validate409.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29260,7 +29781,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_at" } } ], !1; - if (void 0 === data.end_at) return validate399.errors = [ { + if (void 0 === data.end_at) return validate409.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29271,7 +29792,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data0 = data.camera_uuid, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate399.errors = [ { + if ("string" != typeof data0) return validate409.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -29279,7 +29800,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate399.errors = [ { + if (!pattern4.test(data0)) return validate409.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/pattern", keyword: "pattern", @@ -29293,7 +29814,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.start_at) { let data1 = data.start_at, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate399.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate409.errors = [ { instancePath: instancePath + "/start_at", schemaPath: "#/properties/start_at/type", keyword: "type", @@ -29301,7 +29822,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate399.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate409.errors = [ { instancePath: instancePath + "/start_at", schemaPath: "#/properties/start_at/minimum", keyword: "minimum", @@ -29315,7 +29836,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.end_at) { let data2 = data.end_at, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate399.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate409.errors = [ { instancePath: instancePath + "/end_at", schemaPath: "#/properties/end_at/type", keyword: "type", @@ -29323,7 +29844,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate399.errors = [ { + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate409.errors = [ { instancePath: instancePath + "/end_at", schemaPath: "#/properties/end_at/minimum", keyword: "minimum", @@ -29337,7 +29858,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.limit) { let data3 = data.limit, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate399.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate409.errors = [ { instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/type", keyword: "type", @@ -29346,7 +29867,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs8 && "number" == typeof data3 && isFinite(data3)) { - if (data3 > 1e3 || isNaN(data3)) return validate399.errors = [ { + if (data3 > 1e3 || isNaN(data3)) return validate409.errors = [ { instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/maximum", keyword: "maximum", @@ -29355,7 +29876,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD limit: 1e3 } } ], !1; - if (data3 < 1 || isNaN(data3)) return validate399.errors = [ { + if (data3 < 1 || isNaN(data3)) return validate409.errors = [ { instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/minimum", keyword: "minimum", @@ -29370,7 +29891,7 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.match) { let data4 = data.match, _errs10 = 0; - if ("string" != typeof data4) return validate399.errors = [ { + if ("string" != typeof data4) return validate409.errors = [ { instancePath: instancePath + "/match", schemaPath: "#/properties/match/type", keyword: "type", @@ -29378,17 +29899,17 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("exact" !== data4 && "partial" !== data4) return validate399.errors = [ { + if ("exact" !== data4 && "partial" !== data4) return validate409.errors = [ { instancePath: instancePath + "/match", schemaPath: "#/properties/match/enum", keyword: "enum", params: { - allowedValues: schema465.properties.match.enum + allowedValues: schema477.properties.match.enum } } ], !1; var valid4 = 0 === _errs10; } else valid4 = !0; - if (valid4 && void 0 !== data.plate && "string" != typeof data.plate) return validate399.errors = [ { + if (valid4 && void 0 !== data.plate && "string" != typeof data.plate) return validate409.errors = [ { instancePath: instancePath + "/plate", schemaPath: "#/properties/plate/type", keyword: "type", @@ -29400,16 +29921,16 @@ function validate399(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate399.errors = [], !0; + return validate409.errors = [], !0; } -validate399.evaluated = { +validate409.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceLprRead = validate396, schema461 = { +var __contract_EvidenceLprRead = validate406, schema473 = { type: "object", properties: { id: { @@ -29522,11 +30043,11 @@ var __contract_EvidenceLprRead = validate396, schema461 = { additionalProperties: !1 }; -function validate396(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate396.evaluated; +function validate406(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate406.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate396.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -29534,7 +30055,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.id) return validate396.errors = [ { + if (void 0 === data.id) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29542,7 +30063,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data.type) return validate396.errors = [ { + if (void 0 === data.type) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29550,7 +30071,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data.camera_uuid) return validate396.errors = [ { + if (void 0 === data.camera_uuid) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29558,7 +30079,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.stream_name) return validate396.errors = [ { + if (void 0 === data.stream_name) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29566,7 +30087,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data.observed_at) return validate396.errors = [ { + if (void 0 === data.observed_at) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29574,7 +30095,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observed_at" } } ], !1; - if (void 0 === data.received_at) return validate396.errors = [ { + if (void 0 === data.received_at) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29582,7 +30103,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "received_at" } } ], !1; - if (void 0 === data.source) return validate396.errors = [ { + if (void 0 === data.source) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29590,7 +30111,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source" } } ], !1; - if (void 0 === data.plate) return validate396.errors = [ { + if (void 0 === data.plate) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29598,7 +30119,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "plate" } } ], !1; - if (void 0 === data.confidence) return validate396.errors = [ { + if (void 0 === data.confidence) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29606,7 +30127,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "confidence" } } ], !1; - if (void 0 === data.recording_id) return validate396.errors = [ { + if (void 0 === data.recording_id) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29614,7 +30135,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.attributes) return validate396.errors = [ { + if (void 0 === data.attributes) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -29624,7 +30145,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema461.properties, key0)) return validate396.errors = [ { + for (let key0 in data) if (!func7.call(schema473.properties, key0)) return validate406.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -29636,7 +30157,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.id) { let data0 = data.id, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate396.errors = [ { + if ("string" != typeof data0) return validate406.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", @@ -29644,7 +30165,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate396.errors = [ { + if (!pattern4.test(data0)) return validate406.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/pattern", keyword: "pattern", @@ -29658,7 +30179,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.type) { let _errs4 = errors; - if ("license_plate" !== data.type) return validate396.errors = [ { + if ("license_plate" !== data.type) return validate406.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/const", keyword: "const", @@ -29672,7 +30193,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data2 = data.camera_uuid, _errs5 = errors; if (errors === _errs5) { - if ("string" != typeof data2) return validate396.errors = [ { + if ("string" != typeof data2) return validate406.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -29680,7 +30201,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data2)) return validate396.errors = [ { + if (!pattern4.test(data2)) return validate406.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/pattern", keyword: "pattern", @@ -29694,7 +30215,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.stream_name) { let _errs7 = errors; - if ("string" != typeof data.stream_name) return validate396.errors = [ { + if ("string" != typeof data.stream_name) return validate406.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -29707,7 +30228,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.observed_at) { let data4 = data.observed_at, _errs9 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate396.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate406.errors = [ { instancePath: instancePath + "/observed_at", schemaPath: "#/properties/observed_at/type", keyword: "type", @@ -29715,7 +30236,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs9 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate396.errors = [ { + if (errors === _errs9 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate406.errors = [ { instancePath: instancePath + "/observed_at", schemaPath: "#/properties/observed_at/minimum", keyword: "minimum", @@ -29729,7 +30250,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.received_at) { let data5 = data.received_at, _errs11 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate396.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate406.errors = [ { instancePath: instancePath + "/received_at", schemaPath: "#/properties/received_at/type", keyword: "type", @@ -29737,7 +30258,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs11 && "number" == typeof data5 && isFinite(data5) && (data5 < 1 || isNaN(data5))) return validate396.errors = [ { + if (errors === _errs11 && "number" == typeof data5 && isFinite(data5) && (data5 < 1 || isNaN(data5))) return validate406.errors = [ { instancePath: instancePath + "/received_at", schemaPath: "#/properties/received_at/minimum", keyword: "minimum", @@ -29751,7 +30272,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.source) { let _errs13 = errors; - if ("string" != typeof data.source) return validate396.errors = [ { + if ("string" != typeof data.source) return validate406.errors = [ { instancePath: instancePath + "/source", schemaPath: "#/properties/source/type", keyword: "type", @@ -29764,7 +30285,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.plate) { let _errs15 = errors; - if ("string" != typeof data.plate) return validate396.errors = [ { + if ("string" != typeof data.plate) return validate406.errors = [ { instancePath: instancePath + "/plate", schemaPath: "#/properties/plate/type", keyword: "type", @@ -29833,7 +30354,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err4), errors++, validate396.errors = vErrors, !1; + return vErrors.push(err4), errors++, validate406.errors = vErrors, !1; } errors = _errs18, vErrors.length = _errs18; var valid8 = _errs17 === errors; @@ -29885,7 +30406,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err8), errors++, validate396.errors = vErrors, !1; + return vErrors.push(err8), errors++, validate406.errors = vErrors, !1; } errors = _errs24, vErrors.length = _errs24; var valid10 = _errs23 === errors; @@ -29893,7 +30414,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid10 && void 0 !== data.attributes) { let data10 = data.attributes; if (errors == errors) { - if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate396.errors = [ { + if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/type", keyword: "type", @@ -29901,7 +30422,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data10.country) return validate396.errors = [ { + if (void 0 === data10.country) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29909,7 +30430,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "country" } } ], !1; - if (void 0 === data10.region) return validate396.errors = [ { + if (void 0 === data10.region) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29917,7 +30438,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "region" } } ], !1; - if (void 0 === data10.plate_type) return validate396.errors = [ { + if (void 0 === data10.plate_type) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29925,7 +30446,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "plate_type" } } ], !1; - if (void 0 === data10.direction) return validate396.errors = [ { + if (void 0 === data10.direction) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29933,7 +30454,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "direction" } } ], !1; - if (void 0 === data10.lane) return validate396.errors = [ { + if (void 0 === data10.lane) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29941,7 +30462,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "lane" } } ], !1; - if (void 0 === data10.vehicle_type) return validate396.errors = [ { + if (void 0 === data10.vehicle_type) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29949,7 +30470,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "vehicle_type" } } ], !1; - if (void 0 === data10.vehicle_color) return validate396.errors = [ { + if (void 0 === data10.vehicle_color) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29957,7 +30478,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "vehicle_color" } } ], !1; - if (void 0 === data10.object_id) return validate396.errors = [ { + if (void 0 === data10.object_id) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29965,7 +30486,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "object_id" } } ], !1; - if (void 0 === data10.correlation_id) return validate396.errors = [ { + if (void 0 === data10.correlation_id) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29973,7 +30494,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "correlation_id" } } ], !1; - if (void 0 === data10.vendor_topic) return validate396.errors = [ { + if (void 0 === data10.vendor_topic) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29981,7 +30502,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "vendor_topic" } } ], !1; - if (void 0 === data10.bounding_box) return validate396.errors = [ { + if (void 0 === data10.bounding_box) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/required", keyword: "required", @@ -29991,7 +30512,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs31 = errors; - for (let key1 in data10) if (!func5.call(schema461.properties.attributes.properties, key1)) return validate396.errors = [ { + for (let key1 in data10) if (!func7.call(schema473.properties.attributes.properties, key1)) return validate406.errors = [ { instancePath: instancePath + "/attributes", schemaPath: "#/properties/attributes/additionalProperties", keyword: "additionalProperties", @@ -30002,12 +30523,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (_errs31 === errors) { if (void 0 !== data10.country) { let data11 = data10.country, _errs32 = errors; - if ("string" != typeof data11 && null !== data11) return validate396.errors = [ { + if ("string" != typeof data11 && null !== data11) return validate406.errors = [ { instancePath: instancePath + "/attributes/country", schemaPath: "#/properties/attributes/properties/country/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.country.type + type: schema473.properties.attributes.properties.country.type } } ], !1; var valid13 = _errs32 === errors; @@ -30015,12 +30536,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data10.region) { let data12 = data10.region, _errs34 = errors; - if ("string" != typeof data12 && null !== data12) return validate396.errors = [ { + if ("string" != typeof data12 && null !== data12) return validate406.errors = [ { instancePath: instancePath + "/attributes/region", schemaPath: "#/properties/attributes/properties/region/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.region.type + type: schema473.properties.attributes.properties.region.type } } ], !1; var valid14 = _errs34 === errors; @@ -30028,12 +30549,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data10.plate_type) { let data13 = data10.plate_type, _errs36 = errors; - if ("string" != typeof data13 && null !== data13) return validate396.errors = [ { + if ("string" != typeof data13 && null !== data13) return validate406.errors = [ { instancePath: instancePath + "/attributes/plate_type", schemaPath: "#/properties/attributes/properties/plate_type/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.plate_type.type + type: schema473.properties.attributes.properties.plate_type.type } } ], !1; var valid15 = _errs36 === errors; @@ -30041,12 +30562,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data10.direction) { let data14 = data10.direction, _errs38 = errors; - if ("string" != typeof data14 && null !== data14) return validate396.errors = [ { + if ("string" != typeof data14 && null !== data14) return validate406.errors = [ { instancePath: instancePath + "/attributes/direction", schemaPath: "#/properties/attributes/properties/direction/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.direction.type + type: schema473.properties.attributes.properties.direction.type } } ], !1; var valid16 = _errs38 === errors; @@ -30054,12 +30575,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data10.lane) { let data15 = data10.lane, _errs40 = errors; - if ("string" != typeof data15 && null !== data15) return validate396.errors = [ { + if ("string" != typeof data15 && null !== data15) return validate406.errors = [ { instancePath: instancePath + "/attributes/lane", schemaPath: "#/properties/attributes/properties/lane/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.lane.type + type: schema473.properties.attributes.properties.lane.type } } ], !1; var valid17 = _errs40 === errors; @@ -30067,12 +30588,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data10.vehicle_type) { let data16 = data10.vehicle_type, _errs42 = errors; - if ("string" != typeof data16 && null !== data16) return validate396.errors = [ { + if ("string" != typeof data16 && null !== data16) return validate406.errors = [ { instancePath: instancePath + "/attributes/vehicle_type", schemaPath: "#/properties/attributes/properties/vehicle_type/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.vehicle_type.type + type: schema473.properties.attributes.properties.vehicle_type.type } } ], !1; var valid18 = _errs42 === errors; @@ -30080,12 +30601,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data10.vehicle_color) { let data17 = data10.vehicle_color, _errs44 = errors; - if ("string" != typeof data17 && null !== data17) return validate396.errors = [ { + if ("string" != typeof data17 && null !== data17) return validate406.errors = [ { instancePath: instancePath + "/attributes/vehicle_color", schemaPath: "#/properties/attributes/properties/vehicle_color/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.vehicle_color.type + type: schema473.properties.attributes.properties.vehicle_color.type } } ], !1; var valid19 = _errs44 === errors; @@ -30093,12 +30614,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data10.object_id) { let data18 = data10.object_id, _errs46 = errors; - if ("string" != typeof data18 && null !== data18) return validate396.errors = [ { + if ("string" != typeof data18 && null !== data18) return validate406.errors = [ { instancePath: instancePath + "/attributes/object_id", schemaPath: "#/properties/attributes/properties/object_id/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.object_id.type + type: schema473.properties.attributes.properties.object_id.type } } ], !1; var valid20 = _errs46 === errors; @@ -30106,12 +30627,12 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data10.correlation_id) { let data19 = data10.correlation_id, _errs48 = errors; - if ("string" != typeof data19 && null !== data19) return validate396.errors = [ { + if ("string" != typeof data19 && null !== data19) return validate406.errors = [ { instancePath: instancePath + "/attributes/correlation_id", schemaPath: "#/properties/attributes/properties/correlation_id/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.correlation_id.type + type: schema473.properties.attributes.properties.correlation_id.type } } ], !1; var valid21 = _errs48 === errors; @@ -30119,7 +30640,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data10.vendor_topic) { let _errs50 = errors; - if ("string" != typeof data10.vendor_topic) return validate396.errors = [ { + if ("string" != typeof data10.vendor_topic) return validate406.errors = [ { instancePath: instancePath + "/attributes/vendor_topic", schemaPath: "#/properties/attributes/properties/vendor_topic/type", keyword: "type", @@ -30282,7 +30803,7 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err20), errors++, validate396.errors = vErrors, !1; + return vErrors.push(err20), errors++, validate406.errors = vErrors, !1; } errors = _errs53, vErrors.length = _errs53; } @@ -30311,22 +30832,22 @@ function validate396(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate396.errors = vErrors, 0 === errors; + return validate406.errors = vErrors, 0 === errors; } -validate396.evaluated = { +validate406.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceLprReads = validate397; +var __contract_EvidenceLprReads = validate407; -function validate397(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate397.evaluated; +function validate407(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate407.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate397.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate407.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -30334,7 +30855,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.reads) return validate397.errors = [ { + if (void 0 === data.reads) return validate407.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -30342,7 +30863,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "reads" } } ], !1; - if (void 0 === data.count) return validate397.errors = [ { + if (void 0 === data.count) return validate407.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -30352,7 +30873,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("reads" !== key0 && "count" !== key0) return validate397.errors = [ { + for (let key0 in data) if ("reads" !== key0 && "count" !== key0) return validate407.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -30364,7 +30885,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.reads) { let data0 = data.reads, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate397.errors = [ { + if (!Array.isArray(data0)) return validate407.errors = [ { instancePath: instancePath + "/reads", schemaPath: "#/properties/reads/type", keyword: "type", @@ -30372,7 +30893,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data0.length > 1e3) return validate397.errors = [ { + if (data0.length > 1e3) return validate407.errors = [ { instancePath: instancePath + "/reads", schemaPath: "#/properties/reads/maxItems", keyword: "maxItems", @@ -30385,7 +30906,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data1 = data0[i0], _errs4 = errors; if (errors == errors) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate397.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/type", keyword: "type", @@ -30393,7 +30914,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.id) return validate397.errors = [ { + if (void 0 === data1.id) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30401,7 +30922,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data1.type) return validate397.errors = [ { + if (void 0 === data1.type) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30409,7 +30930,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data1.camera_uuid) return validate397.errors = [ { + if (void 0 === data1.camera_uuid) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30417,7 +30938,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data1.stream_name) return validate397.errors = [ { + if (void 0 === data1.stream_name) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30425,7 +30946,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data1.observed_at) return validate397.errors = [ { + if (void 0 === data1.observed_at) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30433,7 +30954,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observed_at" } } ], !1; - if (void 0 === data1.received_at) return validate397.errors = [ { + if (void 0 === data1.received_at) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30441,7 +30962,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "received_at" } } ], !1; - if (void 0 === data1.source) return validate397.errors = [ { + if (void 0 === data1.source) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30449,7 +30970,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source" } } ], !1; - if (void 0 === data1.plate) return validate397.errors = [ { + if (void 0 === data1.plate) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30457,7 +30978,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "plate" } } ], !1; - if (void 0 === data1.confidence) return validate397.errors = [ { + if (void 0 === data1.confidence) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30465,7 +30986,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "confidence" } } ], !1; - if (void 0 === data1.recording_id) return validate397.errors = [ { + if (void 0 === data1.recording_id) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30473,7 +30994,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data1.attributes) return validate397.errors = [ { + if (void 0 === data1.attributes) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/required", keyword: "required", @@ -30483,7 +31004,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs7 = errors; - for (let key1 in data1) if (!func5.call(schema461.properties, key1)) return validate397.errors = [ { + for (let key1 in data1) if (!func7.call(schema473.properties, key1)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0, schemaPath: "#/$defs/EvidenceLprRead/additionalProperties", keyword: "additionalProperties", @@ -30495,7 +31016,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.id) { let data2 = data1.id, _errs8 = errors; if (errors === _errs8) { - if ("string" != typeof data2) return validate397.errors = [ { + if ("string" != typeof data2) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/id", schemaPath: "#/$defs/EvidenceLprRead/properties/id/type", keyword: "type", @@ -30503,7 +31024,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data2)) return validate397.errors = [ { + if (!pattern4.test(data2)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/id", schemaPath: "#/$defs/EvidenceLprRead/properties/id/pattern", keyword: "pattern", @@ -30517,7 +31038,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data1.type) { let _errs10 = errors; - if ("license_plate" !== data1.type) return validate397.errors = [ { + if ("license_plate" !== data1.type) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/type", schemaPath: "#/$defs/EvidenceLprRead/properties/type/const", keyword: "const", @@ -30531,7 +31052,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.camera_uuid) { let data4 = data1.camera_uuid, _errs11 = errors; if (errors === _errs11) { - if ("string" != typeof data4) return validate397.errors = [ { + if ("string" != typeof data4) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/camera_uuid", schemaPath: "#/$defs/EvidenceLprRead/properties/camera_uuid/type", keyword: "type", @@ -30539,7 +31060,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data4)) return validate397.errors = [ { + if (!pattern4.test(data4)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/camera_uuid", schemaPath: "#/$defs/EvidenceLprRead/properties/camera_uuid/pattern", keyword: "pattern", @@ -30553,7 +31074,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data1.stream_name) { let _errs13 = errors; - if ("string" != typeof data1.stream_name) return validate397.errors = [ { + if ("string" != typeof data1.stream_name) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/stream_name", schemaPath: "#/$defs/EvidenceLprRead/properties/stream_name/type", keyword: "type", @@ -30566,7 +31087,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data1.observed_at) { let data6 = data1.observed_at, _errs15 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate397.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/observed_at", schemaPath: "#/$defs/EvidenceLprRead/properties/observed_at/type", keyword: "type", @@ -30574,7 +31095,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 1 || isNaN(data6))) return validate397.errors = [ { + if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 1 || isNaN(data6))) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/observed_at", schemaPath: "#/$defs/EvidenceLprRead/properties/observed_at/minimum", keyword: "minimum", @@ -30588,7 +31109,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data1.received_at) { let data7 = data1.received_at, _errs17 = errors; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate397.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/received_at", schemaPath: "#/$defs/EvidenceLprRead/properties/received_at/type", keyword: "type", @@ -30596,7 +31117,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs17 && "number" == typeof data7 && isFinite(data7) && (data7 < 1 || isNaN(data7))) return validate397.errors = [ { + if (errors === _errs17 && "number" == typeof data7 && isFinite(data7) && (data7 < 1 || isNaN(data7))) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/received_at", schemaPath: "#/$defs/EvidenceLprRead/properties/received_at/minimum", keyword: "minimum", @@ -30610,7 +31131,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data1.source) { let _errs19 = errors; - if ("string" != typeof data1.source) return validate397.errors = [ { + if ("string" != typeof data1.source) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/source", schemaPath: "#/$defs/EvidenceLprRead/properties/source/type", keyword: "type", @@ -30623,7 +31144,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data1.plate) { let _errs21 = errors; - if ("string" != typeof data1.plate) return validate397.errors = [ { + if ("string" != typeof data1.plate) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/plate", schemaPath: "#/$defs/EvidenceLprRead/properties/plate/type", keyword: "type", @@ -30692,7 +31213,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err4), errors++, validate397.errors = vErrors, !1; + return vErrors.push(err4), errors++, validate407.errors = vErrors, !1; } errors = _errs24, vErrors.length = _errs24; var valid11 = _errs23 === errors; @@ -30744,7 +31265,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err8), errors++, validate397.errors = vErrors, !1; + return vErrors.push(err8), errors++, validate407.errors = vErrors, !1; } errors = _errs30, vErrors.length = _errs30; var valid13 = _errs29 === errors; @@ -30752,7 +31273,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid13 && void 0 !== data1.attributes) { let data12 = data1.attributes; if (errors == errors) { - if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate397.errors = [ { + if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/type", keyword: "type", @@ -30760,7 +31281,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data12.country) return validate397.errors = [ { + if (void 0 === data12.country) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30768,7 +31289,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "country" } } ], !1; - if (void 0 === data12.region) return validate397.errors = [ { + if (void 0 === data12.region) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30776,7 +31297,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "region" } } ], !1; - if (void 0 === data12.plate_type) return validate397.errors = [ { + if (void 0 === data12.plate_type) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30784,7 +31305,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "plate_type" } } ], !1; - if (void 0 === data12.direction) return validate397.errors = [ { + if (void 0 === data12.direction) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30792,7 +31313,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "direction" } } ], !1; - if (void 0 === data12.lane) return validate397.errors = [ { + if (void 0 === data12.lane) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30800,7 +31321,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "lane" } } ], !1; - if (void 0 === data12.vehicle_type) return validate397.errors = [ { + if (void 0 === data12.vehicle_type) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30808,7 +31329,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "vehicle_type" } } ], !1; - if (void 0 === data12.vehicle_color) return validate397.errors = [ { + if (void 0 === data12.vehicle_color) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30816,7 +31337,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "vehicle_color" } } ], !1; - if (void 0 === data12.object_id) return validate397.errors = [ { + if (void 0 === data12.object_id) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30824,7 +31345,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "object_id" } } ], !1; - if (void 0 === data12.correlation_id) return validate397.errors = [ { + if (void 0 === data12.correlation_id) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30832,7 +31353,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "correlation_id" } } ], !1; - if (void 0 === data12.vendor_topic) return validate397.errors = [ { + if (void 0 === data12.vendor_topic) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30840,7 +31361,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "vendor_topic" } } ], !1; - if (void 0 === data12.bounding_box) return validate397.errors = [ { + if (void 0 === data12.bounding_box) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/required", keyword: "required", @@ -30850,7 +31371,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs37 = errors; - for (let key2 in data12) if (!func5.call(schema461.properties.attributes.properties, key2)) return validate397.errors = [ { + for (let key2 in data12) if (!func7.call(schema473.properties.attributes.properties, key2)) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/additionalProperties", keyword: "additionalProperties", @@ -30861,12 +31382,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (_errs37 === errors) { if (void 0 !== data12.country) { let data13 = data12.country, _errs38 = errors; - if ("string" != typeof data13 && null !== data13) return validate397.errors = [ { + if ("string" != typeof data13 && null !== data13) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/country", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/country/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.country.type + type: schema473.properties.attributes.properties.country.type } } ], !1; var valid16 = _errs38 === errors; @@ -30874,12 +31395,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data12.region) { let data14 = data12.region, _errs40 = errors; - if ("string" != typeof data14 && null !== data14) return validate397.errors = [ { + if ("string" != typeof data14 && null !== data14) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/region", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/region/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.region.type + type: schema473.properties.attributes.properties.region.type } } ], !1; var valid17 = _errs40 === errors; @@ -30887,12 +31408,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data12.plate_type) { let data15 = data12.plate_type, _errs42 = errors; - if ("string" != typeof data15 && null !== data15) return validate397.errors = [ { + if ("string" != typeof data15 && null !== data15) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/plate_type", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/plate_type/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.plate_type.type + type: schema473.properties.attributes.properties.plate_type.type } } ], !1; var valid18 = _errs42 === errors; @@ -30900,12 +31421,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data12.direction) { let data16 = data12.direction, _errs44 = errors; - if ("string" != typeof data16 && null !== data16) return validate397.errors = [ { + if ("string" != typeof data16 && null !== data16) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/direction", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/direction/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.direction.type + type: schema473.properties.attributes.properties.direction.type } } ], !1; var valid19 = _errs44 === errors; @@ -30913,12 +31434,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data12.lane) { let data17 = data12.lane, _errs46 = errors; - if ("string" != typeof data17 && null !== data17) return validate397.errors = [ { + if ("string" != typeof data17 && null !== data17) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/lane", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/lane/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.lane.type + type: schema473.properties.attributes.properties.lane.type } } ], !1; var valid20 = _errs46 === errors; @@ -30926,12 +31447,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data12.vehicle_type) { let data18 = data12.vehicle_type, _errs48 = errors; - if ("string" != typeof data18 && null !== data18) return validate397.errors = [ { + if ("string" != typeof data18 && null !== data18) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/vehicle_type", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/vehicle_type/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.vehicle_type.type + type: schema473.properties.attributes.properties.vehicle_type.type } } ], !1; var valid21 = _errs48 === errors; @@ -30939,12 +31460,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data12.vehicle_color) { let data19 = data12.vehicle_color, _errs50 = errors; - if ("string" != typeof data19 && null !== data19) return validate397.errors = [ { + if ("string" != typeof data19 && null !== data19) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/vehicle_color", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/vehicle_color/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.vehicle_color.type + type: schema473.properties.attributes.properties.vehicle_color.type } } ], !1; var valid22 = _errs50 === errors; @@ -30952,12 +31473,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data12.object_id) { let data20 = data12.object_id, _errs52 = errors; - if ("string" != typeof data20 && null !== data20) return validate397.errors = [ { + if ("string" != typeof data20 && null !== data20) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/object_id", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/object_id/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.object_id.type + type: schema473.properties.attributes.properties.object_id.type } } ], !1; var valid23 = _errs52 === errors; @@ -30965,12 +31486,12 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data12.correlation_id) { let data21 = data12.correlation_id, _errs54 = errors; - if ("string" != typeof data21 && null !== data21) return validate397.errors = [ { + if ("string" != typeof data21 && null !== data21) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/correlation_id", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/correlation_id/type", keyword: "type", params: { - type: schema461.properties.attributes.properties.correlation_id.type + type: schema473.properties.attributes.properties.correlation_id.type } } ], !1; var valid24 = _errs54 === errors; @@ -30978,7 +31499,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data12.vendor_topic) { let _errs56 = errors; - if ("string" != typeof data12.vendor_topic) return validate397.errors = [ { + if ("string" != typeof data12.vendor_topic) return validate407.errors = [ { instancePath: instancePath + "/reads/" + i0 + "/attributes/vendor_topic", schemaPath: "#/$defs/EvidenceLprRead/properties/attributes/properties/vendor_topic/type", keyword: "type", @@ -31141,7 +31662,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err20), errors++, validate397.errors = vErrors, !1; + return vErrors.push(err20), errors++, validate407.errors = vErrors, !1; } errors = _errs59, vErrors.length = _errs59; } @@ -31178,7 +31699,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.count) { let data28 = data.count, _errs73 = errors; - if ("number" != typeof data28 || data28 % 1 || isNaN(data28) || !isFinite(data28)) return validate397.errors = [ { + if ("number" != typeof data28 || data28 % 1 || isNaN(data28) || !isFinite(data28)) return validate407.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -31186,7 +31707,7 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs73 && "number" == typeof data28 && isFinite(data28) && (data28 < 0 || isNaN(data28))) return validate397.errors = [ { + if (errors === _errs73 && "number" == typeof data28 && isFinite(data28) && (data28 < 0 || isNaN(data28))) return validate407.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -31199,16 +31720,16 @@ function validate397(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate397.errors = vErrors, 0 === errors; + return validate407.errors = vErrors, 0 === errors; } -validate397.evaluated = { +validate407.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceLprSearchRequest = validate398, schema464 = { +var __contract_EvidenceLprSearchRequest = validate408, schema476 = { type: "object", properties: { camera_uuid: { @@ -31241,10 +31762,10 @@ var __contract_EvidenceLprSearchRequest = validate398, schema464 = { description: "Epoch milliseconds, end at/after start within 366 days; match requires a canonicalizable plate (partial at least 3 canonical characters). Search returns at most 100 rows." }; -function validate398(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate398.evaluated; +function validate408(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate408.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate398.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate408.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -31252,7 +31773,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.camera_uuid) return validate398.errors = [ { + if (void 0 === data.camera_uuid) return validate408.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31260,7 +31781,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.start_at) return validate398.errors = [ { + if (void 0 === data.start_at) return validate408.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31268,7 +31789,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_at" } } ], !1; - if (void 0 === data.end_at) return validate398.errors = [ { + if (void 0 === data.end_at) return validate408.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31279,7 +31800,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data0 = data.camera_uuid, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate398.errors = [ { + if ("string" != typeof data0) return validate408.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -31287,7 +31808,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate398.errors = [ { + if (!pattern4.test(data0)) return validate408.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/pattern", keyword: "pattern", @@ -31301,7 +31822,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.start_at) { let data1 = data.start_at, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate398.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate408.errors = [ { instancePath: instancePath + "/start_at", schemaPath: "#/properties/start_at/type", keyword: "type", @@ -31309,7 +31830,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate398.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate408.errors = [ { instancePath: instancePath + "/start_at", schemaPath: "#/properties/start_at/minimum", keyword: "minimum", @@ -31323,7 +31844,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.end_at) { let data2 = data.end_at, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate398.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate408.errors = [ { instancePath: instancePath + "/end_at", schemaPath: "#/properties/end_at/type", keyword: "type", @@ -31331,7 +31852,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate398.errors = [ { + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate408.errors = [ { instancePath: instancePath + "/end_at", schemaPath: "#/properties/end_at/minimum", keyword: "minimum", @@ -31345,7 +31866,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.limit) { let data3 = data.limit, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate398.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate408.errors = [ { instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/type", keyword: "type", @@ -31354,7 +31875,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs8 && "number" == typeof data3 && isFinite(data3)) { - if (data3 > 100 || isNaN(data3)) return validate398.errors = [ { + if (data3 > 100 || isNaN(data3)) return validate408.errors = [ { instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/maximum", keyword: "maximum", @@ -31363,7 +31884,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD limit: 100 } } ], !1; - if (data3 < 1 || isNaN(data3)) return validate398.errors = [ { + if (data3 < 1 || isNaN(data3)) return validate408.errors = [ { instancePath: instancePath + "/limit", schemaPath: "#/properties/limit/minimum", keyword: "minimum", @@ -31378,7 +31899,7 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.match) { let data4 = data.match, _errs10 = 0; - if ("string" != typeof data4) return validate398.errors = [ { + if ("string" != typeof data4) return validate408.errors = [ { instancePath: instancePath + "/match", schemaPath: "#/properties/match/type", keyword: "type", @@ -31386,17 +31907,17 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("exact" !== data4 && "partial" !== data4) return validate398.errors = [ { + if ("exact" !== data4 && "partial" !== data4) return validate408.errors = [ { instancePath: instancePath + "/match", schemaPath: "#/properties/match/enum", keyword: "enum", params: { - allowedValues: schema464.properties.match.enum + allowedValues: schema476.properties.match.enum } } ], !1; var valid4 = 0 === _errs10; } else valid4 = !0; - if (valid4 && void 0 !== data.plate && "string" != typeof data.plate) return validate398.errors = [ { + if (valid4 && void 0 !== data.plate && "string" != typeof data.plate) return validate408.errors = [ { instancePath: instancePath + "/plate", schemaPath: "#/properties/plate/type", keyword: "type", @@ -31408,21 +31929,21 @@ function validate398(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate398.errors = [], !0; + return validate408.errors = [], !0; } -validate398.evaluated = { +validate408.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceModel = validate366; +var __contract_EvidenceModel = validate376; -function validate366(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate366.evaluated; +function validate376(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate376.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate366.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -31430,7 +31951,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.id) return validate366.errors = [ { + if (void 0 === data.id) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31438,7 +31959,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data.name) return validate366.errors = [ { + if (void 0 === data.name) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31446,7 +31967,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.path) return validate366.errors = [ { + if (void 0 === data.path) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31454,7 +31975,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "path" } } ], !1; - if (void 0 === data.type) return validate366.errors = [ { + if (void 0 === data.type) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31462,7 +31983,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data.supported) return validate366.errors = [ { + if (void 0 === data.supported) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31472,7 +31993,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("id" !== key0 && "name" !== key0 && "path" !== key0 && "type" !== key0 && "supported" !== key0 && "description" !== key0 && "unsupported_reason" !== key0 && "size" !== key0) return validate366.errors = [ { + for (let key0 in data) if ("id" !== key0 && "name" !== key0 && "path" !== key0 && "type" !== key0 && "supported" !== key0 && "description" !== key0 && "unsupported_reason" !== key0 && "size" !== key0) return validate376.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -31483,7 +32004,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.id) { let _errs2 = 0; - if ("string" != typeof data.id) return validate366.errors = [ { + if ("string" != typeof data.id) return validate376.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", @@ -31496,7 +32017,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.name) { let _errs4 = 0; - if ("string" != typeof data.name) return validate366.errors = [ { + if ("string" != typeof data.name) return validate376.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -31509,7 +32030,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.path) { let _errs6 = 0; - if ("string" != typeof data.path) return validate366.errors = [ { + if ("string" != typeof data.path) return validate376.errors = [ { instancePath: instancePath + "/path", schemaPath: "#/properties/path/type", keyword: "type", @@ -31522,7 +32043,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.type) { let _errs8 = 0; - if ("string" != typeof data.type) return validate366.errors = [ { + if ("string" != typeof data.type) return validate376.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -31535,7 +32056,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.supported) { let _errs10 = 0; - if ("boolean" != typeof data.supported) return validate366.errors = [ { + if ("boolean" != typeof data.supported) return validate376.errors = [ { instancePath: instancePath + "/supported", schemaPath: "#/properties/supported/type", keyword: "type", @@ -31548,7 +32069,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.description) { let _errs12 = 0; - if ("string" != typeof data.description) return validate366.errors = [ { + if ("string" != typeof data.description) return validate376.errors = [ { instancePath: instancePath + "/description", schemaPath: "#/properties/description/type", keyword: "type", @@ -31561,7 +32082,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.unsupported_reason) { let _errs14 = 0; - if ("string" != typeof data.unsupported_reason) return validate366.errors = [ { + if ("string" != typeof data.unsupported_reason) return validate376.errors = [ { instancePath: instancePath + "/unsupported_reason", schemaPath: "#/properties/unsupported_reason/type", keyword: "type", @@ -31573,7 +32094,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD } else valid6 = !0; if (valid6 && void 0 !== data.size) { let data7 = data.size, _errs16 = 0; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate366.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate376.errors = [ { instancePath: instancePath + "/size", schemaPath: "#/properties/size/type", keyword: "type", @@ -31581,7 +32102,7 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs16 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate366.errors = [ { + if (0 === _errs16 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate376.errors = [ { instancePath: instancePath + "/size", schemaPath: "#/properties/size/minimum", keyword: "minimum", @@ -31599,21 +32120,21 @@ function validate366(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate366.errors = [], !0; + return validate376.errors = [], !0; } -validate366.evaluated = { +validate376.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceModels = validate367; +var __contract_EvidenceModels = validate377; -function validate367(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate367.evaluated; +function validate377(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate377.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate367.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate377.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -31621,7 +32142,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.models) return validate367.errors = [ { + if (void 0 === data.models) return validate377.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31629,7 +32150,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "models" } } ], !1; - if (void 0 === data.count) return validate367.errors = [ { + if (void 0 === data.count) return validate377.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31637,7 +32158,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "count" } } ], !1; - if (void 0 === data.litert_available) return validate367.errors = [ { + if (void 0 === data.litert_available) return validate377.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31645,7 +32166,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "litert_available" } } ], !1; - if (void 0 === data.sod_available) return validate367.errors = [ { + if (void 0 === data.sod_available) return validate377.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -31655,7 +32176,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("models" !== key0 && "count" !== key0 && "litert_available" !== key0 && "sod_available" !== key0 && "message" !== key0) return validate367.errors = [ { + for (let key0 in data) if ("models" !== key0 && "count" !== key0 && "litert_available" !== key0 && "sod_available" !== key0 && "message" !== key0) return validate377.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -31667,7 +32188,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.models) { let data0 = data.models, _errs2 = 0; if (0 === _errs2) { - if (!Array.isArray(data0)) return validate367.errors = [ { + if (!Array.isArray(data0)) return validate377.errors = [ { instancePath: instancePath + "/models", schemaPath: "#/properties/models/type", keyword: "type", @@ -31679,7 +32200,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate367.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/type", keyword: "type", @@ -31687,7 +32208,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.id) return validate367.errors = [ { + if (void 0 === data1.id) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/required", keyword: "required", @@ -31695,7 +32216,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data1.name) return validate367.errors = [ { + if (void 0 === data1.name) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/required", keyword: "required", @@ -31703,7 +32224,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data1.path) return validate367.errors = [ { + if (void 0 === data1.path) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/required", keyword: "required", @@ -31711,7 +32232,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "path" } } ], !1; - if (void 0 === data1.type) return validate367.errors = [ { + if (void 0 === data1.type) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/required", keyword: "required", @@ -31719,7 +32240,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data1.supported) return validate367.errors = [ { + if (void 0 === data1.supported) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/required", keyword: "required", @@ -31729,7 +32250,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs7 = 0; - for (let key1 in data1) if ("id" !== key1 && "name" !== key1 && "path" !== key1 && "type" !== key1 && "supported" !== key1 && "description" !== key1 && "unsupported_reason" !== key1 && "size" !== key1) return validate367.errors = [ { + for (let key1 in data1) if ("id" !== key1 && "name" !== key1 && "path" !== key1 && "type" !== key1 && "supported" !== key1 && "description" !== key1 && "unsupported_reason" !== key1 && "size" !== key1) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0, schemaPath: "#/$defs/EvidenceModel/additionalProperties", keyword: "additionalProperties", @@ -31740,7 +32261,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs7) { if (void 0 !== data1.id) { let _errs8 = 0; - if ("string" != typeof data1.id) return validate367.errors = [ { + if ("string" != typeof data1.id) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/id", schemaPath: "#/$defs/EvidenceModel/properties/id/type", keyword: "type", @@ -31753,7 +32274,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data1.name) { let _errs10 = 0; - if ("string" != typeof data1.name) return validate367.errors = [ { + if ("string" != typeof data1.name) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/name", schemaPath: "#/$defs/EvidenceModel/properties/name/type", keyword: "type", @@ -31766,7 +32287,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data1.path) { let _errs12 = 0; - if ("string" != typeof data1.path) return validate367.errors = [ { + if ("string" != typeof data1.path) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/path", schemaPath: "#/$defs/EvidenceModel/properties/path/type", keyword: "type", @@ -31779,7 +32300,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data1.type) { let _errs14 = 0; - if ("string" != typeof data1.type) return validate367.errors = [ { + if ("string" != typeof data1.type) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/type", schemaPath: "#/$defs/EvidenceModel/properties/type/type", keyword: "type", @@ -31792,7 +32313,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data1.supported) { let _errs16 = 0; - if ("boolean" != typeof data1.supported) return validate367.errors = [ { + if ("boolean" != typeof data1.supported) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/supported", schemaPath: "#/$defs/EvidenceModel/properties/supported/type", keyword: "type", @@ -31805,7 +32326,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data1.description) { let _errs18 = 0; - if ("string" != typeof data1.description) return validate367.errors = [ { + if ("string" != typeof data1.description) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/description", schemaPath: "#/$defs/EvidenceModel/properties/description/type", keyword: "type", @@ -31818,7 +32339,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data1.unsupported_reason) { let _errs20 = 0; - if ("string" != typeof data1.unsupported_reason) return validate367.errors = [ { + if ("string" != typeof data1.unsupported_reason) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/unsupported_reason", schemaPath: "#/$defs/EvidenceModel/properties/unsupported_reason/type", keyword: "type", @@ -31830,7 +32351,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD } else valid9 = !0; if (valid9 && void 0 !== data1.size) { let data9 = data1.size, _errs22 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate367.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/size", schemaPath: "#/$defs/EvidenceModel/properties/size/type", keyword: "type", @@ -31838,7 +32359,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate367.errors = [ { + if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate377.errors = [ { instancePath: instancePath + "/models/" + i0 + "/size", schemaPath: "#/$defs/EvidenceModel/properties/size/minimum", keyword: "minimum", @@ -31865,7 +32386,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.count) { let data10 = data.count, _errs24 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate367.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate377.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -31873,7 +32394,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs24 && "number" == typeof data10 && isFinite(data10) && (data10 < 0 || isNaN(data10))) return validate367.errors = [ { + if (0 === _errs24 && "number" == typeof data10 && isFinite(data10) && (data10 < 0 || isNaN(data10))) return validate377.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -31887,7 +32408,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.litert_available) { let _errs26 = 0; - if ("boolean" != typeof data.litert_available) return validate367.errors = [ { + if ("boolean" != typeof data.litert_available) return validate377.errors = [ { instancePath: instancePath + "/litert_available", schemaPath: "#/properties/litert_available/type", keyword: "type", @@ -31900,7 +32421,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data.sod_available) { let _errs28 = 0; - if ("boolean" != typeof data.sod_available) return validate367.errors = [ { + if ("boolean" != typeof data.sod_available) return validate377.errors = [ { instancePath: instancePath + "/sod_available", schemaPath: "#/properties/sod_available/type", keyword: "type", @@ -31910,7 +32431,7 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid13 = 0 === _errs28; } else valid13 = !0; - if (valid13 && void 0 !== data.message && "string" != typeof data.message) return validate367.errors = [ { + if (valid13 && void 0 !== data.message && "string" != typeof data.message) return validate377.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", @@ -31923,16 +32444,16 @@ function validate367(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate367.errors = [], !0; + return validate377.errors = [], !0; } -validate367.evaluated = { +validate377.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidencePreview = validate381, schema441 = { +var __contract_EvidencePreview = validate391, schema453 = { type: "object", properties: { start_time: { @@ -32017,7 +32538,7 @@ var __contract_EvidencePreview = validate381, schema441 = { }, required: [ "start_time", "end_time", "camera_count", "recording_count", "total_bytes", "protected_count", "unprotected_count", "recordings", "permissions" ], additionalProperties: !1 -}, schema440 = { +}, schema452 = { type: "object", properties: { id: { @@ -32058,10 +32579,10 @@ var __contract_EvidencePreview = validate381, schema441 = { additionalProperties: !1 }; -function validate381(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate381.evaluated; +function validate391(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate391.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate381.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -32069,7 +32590,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.start_time) return validate381.errors = [ { + if (void 0 === data.start_time) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32077,7 +32598,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate381.errors = [ { + if (void 0 === data.end_time) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32085,7 +32606,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.camera_count) return validate381.errors = [ { + if (void 0 === data.camera_count) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32093,7 +32614,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_count" } } ], !1; - if (void 0 === data.recording_count) return validate381.errors = [ { + if (void 0 === data.recording_count) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32101,7 +32622,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_count" } } ], !1; - if (void 0 === data.total_bytes) return validate381.errors = [ { + if (void 0 === data.total_bytes) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32109,7 +32630,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "total_bytes" } } ], !1; - if (void 0 === data.protected_count) return validate381.errors = [ { + if (void 0 === data.protected_count) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32117,7 +32638,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "protected_count" } } ], !1; - if (void 0 === data.unprotected_count) return validate381.errors = [ { + if (void 0 === data.unprotected_count) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32125,7 +32646,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unprotected_count" } } ], !1; - if (void 0 === data.recordings) return validate381.errors = [ { + if (void 0 === data.recordings) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32133,7 +32654,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recordings" } } ], !1; - if (void 0 === data.permissions) return validate381.errors = [ { + if (void 0 === data.permissions) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32143,7 +32664,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema441.properties, key0)) return validate381.errors = [ { + for (let key0 in data) if (!func7.call(schema453.properties, key0)) return validate391.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -32154,7 +32675,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.start_time) { let data0 = data.start_time, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate381.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate391.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -32162,7 +32683,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate381.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate391.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -32176,7 +32697,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.end_time) { let data1 = data.end_time, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate381.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate391.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -32184,7 +32705,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate381.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate391.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -32198,7 +32719,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.camera_count) { let data2 = data.camera_count, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate381.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate391.errors = [ { instancePath: instancePath + "/camera_count", schemaPath: "#/properties/camera_count/type", keyword: "type", @@ -32206,7 +32727,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate381.errors = [ { + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate391.errors = [ { instancePath: instancePath + "/camera_count", schemaPath: "#/properties/camera_count/minimum", keyword: "minimum", @@ -32220,7 +32741,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.recording_count) { let data3 = data.recording_count, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate381.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate391.errors = [ { instancePath: instancePath + "/recording_count", schemaPath: "#/properties/recording_count/type", keyword: "type", @@ -32228,7 +32749,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate381.errors = [ { + if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate391.errors = [ { instancePath: instancePath + "/recording_count", schemaPath: "#/properties/recording_count/minimum", keyword: "minimum", @@ -32242,7 +32763,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.total_bytes) { let data4 = data.total_bytes, _errs10 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate381.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate391.errors = [ { instancePath: instancePath + "/total_bytes", schemaPath: "#/properties/total_bytes/type", keyword: "type", @@ -32250,7 +32771,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate381.errors = [ { + if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate391.errors = [ { instancePath: instancePath + "/total_bytes", schemaPath: "#/properties/total_bytes/minimum", keyword: "minimum", @@ -32264,7 +32785,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.protected_count) { let data5 = data.protected_count, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate381.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate391.errors = [ { instancePath: instancePath + "/protected_count", schemaPath: "#/properties/protected_count/type", keyword: "type", @@ -32272,7 +32793,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate381.errors = [ { + if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate391.errors = [ { instancePath: instancePath + "/protected_count", schemaPath: "#/properties/protected_count/minimum", keyword: "minimum", @@ -32286,7 +32807,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.unprotected_count) { let data6 = data.unprotected_count, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate381.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate391.errors = [ { instancePath: instancePath + "/unprotected_count", schemaPath: "#/properties/unprotected_count/type", keyword: "type", @@ -32294,7 +32815,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate381.errors = [ { + if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate391.errors = [ { instancePath: instancePath + "/unprotected_count", schemaPath: "#/properties/unprotected_count/minimum", keyword: "minimum", @@ -32309,7 +32830,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.recordings) { let data7 = data.recordings, _errs16 = 0; if (0 === _errs16) { - if (!Array.isArray(data7)) return validate381.errors = [ { + if (!Array.isArray(data7)) return validate391.errors = [ { instancePath: instancePath + "/recordings", schemaPath: "#/properties/recordings/type", keyword: "type", @@ -32317,7 +32838,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data7.length > 200) return validate381.errors = [ { + if (data7.length > 200) return validate391.errors = [ { instancePath: instancePath + "/recordings", schemaPath: "#/properties/recordings/maxItems", keyword: "maxItems", @@ -32329,7 +32850,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD let len0 = data7.length; for (let i0 = 0; i0 < len0; i0++) { let data8 = data7[i0], _errs18 = 0; - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate381.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/type", keyword: "type", @@ -32337,7 +32858,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data8.id) return validate381.errors = [ { + if (void 0 === data8.id) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32345,7 +32866,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data8.camera_uuid) return validate381.errors = [ { + if (void 0 === data8.camera_uuid) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32353,7 +32874,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data8.camera_name) return validate381.errors = [ { + if (void 0 === data8.camera_name) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32361,7 +32882,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_name" } } ], !1; - if (void 0 === data8.stream_name) return validate381.errors = [ { + if (void 0 === data8.stream_name) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32369,7 +32890,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data8.start_time) return validate381.errors = [ { + if (void 0 === data8.start_time) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32377,7 +32898,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data8.end_time) return validate381.errors = [ { + if (void 0 === data8.end_time) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32385,7 +32906,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data8.size_bytes) return validate381.errors = [ { + if (void 0 === data8.size_bytes) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32393,7 +32914,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "size_bytes" } } ], !1; - if (void 0 === data8.protected) return validate381.errors = [ { + if (void 0 === data8.protected) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32401,7 +32922,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "protected" } } ], !1; - if (void 0 === data8.can_protect) return validate381.errors = [ { + if (void 0 === data8.can_protect) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32409,7 +32930,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "can_protect" } } ], !1; - if (void 0 === data8.can_export) return validate381.errors = [ { + if (void 0 === data8.can_export) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/required", keyword: "required", @@ -32419,7 +32940,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs21 = 0; - for (let key1 in data8) if (!func5.call(schema440.properties, key1)) return validate381.errors = [ { + for (let key1 in data8) if (!func7.call(schema452.properties, key1)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0, schemaPath: "#/$defs/EvidencePreviewRecording/additionalProperties", keyword: "additionalProperties", @@ -32430,7 +32951,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs21) { if (void 0 !== data8.id) { let data9 = data8.id, _errs22 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate381.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/id", schemaPath: "#/$defs/EvidencePreviewRecording/properties/id/type", keyword: "type", @@ -32438,7 +32959,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 1 || isNaN(data9))) return validate381.errors = [ { + if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 1 || isNaN(data9))) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/id", schemaPath: "#/$defs/EvidencePreviewRecording/properties/id/minimum", keyword: "minimum", @@ -32453,7 +32974,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data8.camera_uuid) { let data10 = data8.camera_uuid, _errs24 = 0; if (0 === _errs24) { - if ("string" != typeof data10) return validate381.errors = [ { + if ("string" != typeof data10) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/camera_uuid", schemaPath: "#/$defs/EvidencePreviewRecording/properties/camera_uuid/type", keyword: "type", @@ -32461,7 +32982,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data10)) return validate381.errors = [ { + if (!pattern4.test(data10)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/camera_uuid", schemaPath: "#/$defs/EvidencePreviewRecording/properties/camera_uuid/pattern", keyword: "pattern", @@ -32475,7 +32996,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data8.camera_name) { let _errs26 = 0; - if ("string" != typeof data8.camera_name) return validate381.errors = [ { + if ("string" != typeof data8.camera_name) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/camera_name", schemaPath: "#/$defs/EvidencePreviewRecording/properties/camera_name/type", keyword: "type", @@ -32488,7 +33009,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data8.stream_name) { let _errs28 = 0; - if ("string" != typeof data8.stream_name) return validate381.errors = [ { + if ("string" != typeof data8.stream_name) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/stream_name", schemaPath: "#/$defs/EvidencePreviewRecording/properties/stream_name/type", keyword: "type", @@ -32501,7 +33022,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data8.start_time) { let data13 = data8.start_time, _errs30 = 0; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate381.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/start_time", schemaPath: "#/$defs/EvidencePreviewRecording/properties/start_time/type", keyword: "type", @@ -32514,7 +33035,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data8.end_time) { let data14 = data8.end_time, _errs32 = 0; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate381.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/end_time", schemaPath: "#/$defs/EvidencePreviewRecording/properties/end_time/type", keyword: "type", @@ -32527,7 +33048,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data8.size_bytes) { let data15 = data8.size_bytes, _errs34 = 0; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate381.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/size_bytes", schemaPath: "#/$defs/EvidencePreviewRecording/properties/size_bytes/type", keyword: "type", @@ -32535,7 +33056,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs34 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate381.errors = [ { + if (0 === _errs34 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/size_bytes", schemaPath: "#/$defs/EvidencePreviewRecording/properties/size_bytes/minimum", keyword: "minimum", @@ -32549,7 +33070,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data8.protected) { let _errs36 = 0; - if ("boolean" != typeof data8.protected) return validate381.errors = [ { + if ("boolean" != typeof data8.protected) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/protected", schemaPath: "#/$defs/EvidencePreviewRecording/properties/protected/type", keyword: "type", @@ -32562,7 +33083,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data8.can_protect) { let _errs38 = 0; - if ("boolean" != typeof data8.can_protect) return validate381.errors = [ { + if ("boolean" != typeof data8.can_protect) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/can_protect", schemaPath: "#/$defs/EvidencePreviewRecording/properties/can_protect/type", keyword: "type", @@ -32572,7 +33093,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid18 = 0 === _errs38; } else valid18 = !0; - if (valid18 && void 0 !== data8.can_export && "boolean" != typeof data8.can_export) return validate381.errors = [ { + if (valid18 && void 0 !== data8.can_export && "boolean" != typeof data8.can_export) return validate391.errors = [ { instancePath: instancePath + "/recordings/" + i0 + "/can_export", schemaPath: "#/$defs/EvidencePreviewRecording/properties/can_export/type", keyword: "type", @@ -32598,7 +33119,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } else valid7 = !0; if (valid7 && void 0 !== data.permissions) { let data19 = data.permissions; - if (!data19 || "object" != typeof data19 || Array.isArray(data19)) return validate381.errors = [ { + if (!data19 || "object" != typeof data19 || Array.isArray(data19)) return validate391.errors = [ { instancePath: instancePath + "/permissions", schemaPath: "#/properties/permissions/type", keyword: "type", @@ -32606,7 +33127,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data19.protect) return validate381.errors = [ { + if (void 0 === data19.protect) return validate391.errors = [ { instancePath: instancePath + "/permissions", schemaPath: "#/properties/permissions/required", keyword: "required", @@ -32614,7 +33135,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "protect" } } ], !1; - if (void 0 === data19.export) return validate381.errors = [ { + if (void 0 === data19.export) return validate391.errors = [ { instancePath: instancePath + "/permissions", schemaPath: "#/properties/permissions/required", keyword: "required", @@ -32624,7 +33145,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs44 = 0; - for (let key2 in data19) if ("protect" !== key2 && "export" !== key2) return validate381.errors = [ { + for (let key2 in data19) if ("protect" !== key2 && "export" !== key2) return validate391.errors = [ { instancePath: instancePath + "/permissions", schemaPath: "#/properties/permissions/additionalProperties", keyword: "additionalProperties", @@ -32636,7 +33157,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data19.protect) { let data20 = data19.protect, _errs45 = 0; if (0 === _errs45) { - if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate381.errors = [ { + if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect", schemaPath: "#/properties/permissions/properties/protect/type", keyword: "type", @@ -32644,7 +33165,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data20.allowed_count) return validate381.errors = [ { + if (void 0 === data20.allowed_count) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect", schemaPath: "#/properties/permissions/properties/protect/required", keyword: "required", @@ -32652,7 +33173,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "allowed_count" } } ], !1; - if (void 0 === data20.denied_count) return validate381.errors = [ { + if (void 0 === data20.denied_count) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect", schemaPath: "#/properties/permissions/properties/protect/required", keyword: "required", @@ -32660,7 +33181,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "denied_count" } } ], !1; - if (void 0 === data20.actionable_count) return validate381.errors = [ { + if (void 0 === data20.actionable_count) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect", schemaPath: "#/properties/permissions/properties/protect/required", keyword: "required", @@ -32670,7 +33191,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs47 = 0; - for (let key3 in data20) if ("allowed_count" !== key3 && "denied_count" !== key3 && "actionable_count" !== key3) return validate381.errors = [ { + for (let key3 in data20) if ("allowed_count" !== key3 && "denied_count" !== key3 && "actionable_count" !== key3) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect", schemaPath: "#/properties/permissions/properties/protect/additionalProperties", keyword: "additionalProperties", @@ -32681,7 +33202,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs47) { if (void 0 !== data20.allowed_count) { let data21 = data20.allowed_count, _errs48 = 0; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate381.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect/allowed_count", schemaPath: "#/properties/permissions/properties/protect/properties/allowed_count/type", keyword: "type", @@ -32689,7 +33210,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs48 && "number" == typeof data21 && isFinite(data21) && (data21 < 0 || isNaN(data21))) return validate381.errors = [ { + if (0 === _errs48 && "number" == typeof data21 && isFinite(data21) && (data21 < 0 || isNaN(data21))) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect/allowed_count", schemaPath: "#/properties/permissions/properties/protect/properties/allowed_count/minimum", keyword: "minimum", @@ -32703,7 +33224,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data20.denied_count) { let data22 = data20.denied_count, _errs50 = 0; - if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate381.errors = [ { + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect/denied_count", schemaPath: "#/properties/permissions/properties/protect/properties/denied_count/type", keyword: "type", @@ -32711,7 +33232,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs50 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate381.errors = [ { + if (0 === _errs50 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect/denied_count", schemaPath: "#/properties/permissions/properties/protect/properties/denied_count/minimum", keyword: "minimum", @@ -32724,7 +33245,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } else valid23 = !0; if (valid23 && void 0 !== data20.actionable_count) { let data23 = data20.actionable_count, _errs52 = 0; - if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate381.errors = [ { + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect/actionable_count", schemaPath: "#/properties/permissions/properties/protect/properties/actionable_count/type", keyword: "type", @@ -32732,7 +33253,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs52 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate381.errors = [ { + if (0 === _errs52 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate391.errors = [ { instancePath: instancePath + "/permissions/protect/actionable_count", schemaPath: "#/properties/permissions/properties/protect/properties/actionable_count/minimum", keyword: "minimum", @@ -32750,7 +33271,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } else valid21 = !0; if (valid21 && void 0 !== data19.export) { let data24 = data19.export; - if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate381.errors = [ { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate391.errors = [ { instancePath: instancePath + "/permissions/export", schemaPath: "#/properties/permissions/properties/export/type", keyword: "type", @@ -32758,7 +33279,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data24.allowed_count) return validate381.errors = [ { + if (void 0 === data24.allowed_count) return validate391.errors = [ { instancePath: instancePath + "/permissions/export", schemaPath: "#/properties/permissions/properties/export/required", keyword: "required", @@ -32766,7 +33287,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "allowed_count" } } ], !1; - if (void 0 === data24.denied_count) return validate381.errors = [ { + if (void 0 === data24.denied_count) return validate391.errors = [ { instancePath: instancePath + "/permissions/export", schemaPath: "#/properties/permissions/properties/export/required", keyword: "required", @@ -32774,7 +33295,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "denied_count" } } ], !1; - if (void 0 === data24.all_allowed) return validate381.errors = [ { + if (void 0 === data24.all_allowed) return validate391.errors = [ { instancePath: instancePath + "/permissions/export", schemaPath: "#/properties/permissions/properties/export/required", keyword: "required", @@ -32784,7 +33305,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs56 = 0; - for (let key4 in data24) if ("allowed_count" !== key4 && "denied_count" !== key4 && "all_allowed" !== key4) return validate381.errors = [ { + for (let key4 in data24) if ("allowed_count" !== key4 && "denied_count" !== key4 && "all_allowed" !== key4) return validate391.errors = [ { instancePath: instancePath + "/permissions/export", schemaPath: "#/properties/permissions/properties/export/additionalProperties", keyword: "additionalProperties", @@ -32795,7 +33316,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs56) { if (void 0 !== data24.allowed_count) { let data25 = data24.allowed_count, _errs57 = 0; - if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate381.errors = [ { + if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate391.errors = [ { instancePath: instancePath + "/permissions/export/allowed_count", schemaPath: "#/properties/permissions/properties/export/properties/allowed_count/type", keyword: "type", @@ -32803,7 +33324,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs57 && "number" == typeof data25 && isFinite(data25) && (data25 < 0 || isNaN(data25))) return validate381.errors = [ { + if (0 === _errs57 && "number" == typeof data25 && isFinite(data25) && (data25 < 0 || isNaN(data25))) return validate391.errors = [ { instancePath: instancePath + "/permissions/export/allowed_count", schemaPath: "#/properties/permissions/properties/export/properties/allowed_count/minimum", keyword: "minimum", @@ -32817,7 +33338,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data24.denied_count) { let data26 = data24.denied_count, _errs59 = 0; - if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate381.errors = [ { + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate391.errors = [ { instancePath: instancePath + "/permissions/export/denied_count", schemaPath: "#/properties/permissions/properties/export/properties/denied_count/type", keyword: "type", @@ -32825,7 +33346,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs59 && "number" == typeof data26 && isFinite(data26) && (data26 < 0 || isNaN(data26))) return validate381.errors = [ { + if (0 === _errs59 && "number" == typeof data26 && isFinite(data26) && (data26 < 0 || isNaN(data26))) return validate391.errors = [ { instancePath: instancePath + "/permissions/export/denied_count", schemaPath: "#/properties/permissions/properties/export/properties/denied_count/minimum", keyword: "minimum", @@ -32836,7 +33357,7 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid27 = 0 === _errs59; } else valid27 = !0; - if (valid27 && void 0 !== data24.all_allowed && "boolean" != typeof data24.all_allowed) return validate381.errors = [ { + if (valid27 && void 0 !== data24.all_allowed && "boolean" != typeof data24.all_allowed) return validate391.errors = [ { instancePath: instancePath + "/permissions/export/all_allowed", schemaPath: "#/properties/permissions/properties/export/properties/all_allowed/type", keyword: "type", @@ -32860,21 +33381,21 @@ function validate381(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate381.errors = [], !0; + return validate391.errors = [], !0; } -validate381.evaluated = { +validate391.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidencePreviewRecording = validate380; +var __contract_EvidencePreviewRecording = validate390; -function validate380(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate380.evaluated; +function validate390(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate390.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate380.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -32882,7 +33403,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.id) return validate380.errors = [ { + if (void 0 === data.id) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32890,7 +33411,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data.camera_uuid) return validate380.errors = [ { + if (void 0 === data.camera_uuid) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32898,7 +33419,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.camera_name) return validate380.errors = [ { + if (void 0 === data.camera_name) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32906,7 +33427,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_name" } } ], !1; - if (void 0 === data.stream_name) return validate380.errors = [ { + if (void 0 === data.stream_name) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32914,7 +33435,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data.start_time) return validate380.errors = [ { + if (void 0 === data.start_time) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32922,7 +33443,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate380.errors = [ { + if (void 0 === data.end_time) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32930,7 +33451,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.size_bytes) return validate380.errors = [ { + if (void 0 === data.size_bytes) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32938,7 +33459,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "size_bytes" } } ], !1; - if (void 0 === data.protected) return validate380.errors = [ { + if (void 0 === data.protected) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32946,7 +33467,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "protected" } } ], !1; - if (void 0 === data.can_protect) return validate380.errors = [ { + if (void 0 === data.can_protect) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32954,7 +33475,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "can_protect" } } ], !1; - if (void 0 === data.can_export) return validate380.errors = [ { + if (void 0 === data.can_export) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -32964,7 +33485,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema440.properties, key0)) return validate380.errors = [ { + for (let key0 in data) if (!func7.call(schema452.properties, key0)) return validate390.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -32975,7 +33496,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.id) { let data0 = data.id, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate380.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate390.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/type", keyword: "type", @@ -32983,7 +33504,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate380.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate390.errors = [ { instancePath: instancePath + "/id", schemaPath: "#/properties/id/minimum", keyword: "minimum", @@ -32998,7 +33519,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data1 = data.camera_uuid, _errs4 = 0; if (0 === _errs4) { - if ("string" != typeof data1) return validate380.errors = [ { + if ("string" != typeof data1) return validate390.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -33006,7 +33527,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data1)) return validate380.errors = [ { + if (!pattern4.test(data1)) return validate390.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/pattern", keyword: "pattern", @@ -33020,7 +33541,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.camera_name) { let _errs6 = 0; - if ("string" != typeof data.camera_name) return validate380.errors = [ { + if ("string" != typeof data.camera_name) return validate390.errors = [ { instancePath: instancePath + "/camera_name", schemaPath: "#/properties/camera_name/type", keyword: "type", @@ -33033,7 +33554,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.stream_name) { let _errs8 = 0; - if ("string" != typeof data.stream_name) return validate380.errors = [ { + if ("string" != typeof data.stream_name) return validate390.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -33046,7 +33567,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.start_time) { let data4 = data.start_time, _errs10 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate380.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate390.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -33059,7 +33580,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.end_time) { let data5 = data.end_time, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate380.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate390.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -33072,7 +33593,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.size_bytes) { let data6 = data.size_bytes, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate380.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate390.errors = [ { instancePath: instancePath + "/size_bytes", schemaPath: "#/properties/size_bytes/type", keyword: "type", @@ -33080,7 +33601,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate380.errors = [ { + if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate390.errors = [ { instancePath: instancePath + "/size_bytes", schemaPath: "#/properties/size_bytes/minimum", keyword: "minimum", @@ -33094,7 +33615,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.protected) { let _errs16 = 0; - if ("boolean" != typeof data.protected) return validate380.errors = [ { + if ("boolean" != typeof data.protected) return validate390.errors = [ { instancePath: instancePath + "/protected", schemaPath: "#/properties/protected/type", keyword: "type", @@ -33107,7 +33628,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.can_protect) { let _errs18 = 0; - if ("boolean" != typeof data.can_protect) return validate380.errors = [ { + if ("boolean" != typeof data.can_protect) return validate390.errors = [ { instancePath: instancePath + "/can_protect", schemaPath: "#/properties/can_protect/type", keyword: "type", @@ -33117,7 +33638,7 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid8 = 0 === _errs18; } else valid8 = !0; - if (valid8 && void 0 !== data.can_export && "boolean" != typeof data.can_export) return validate380.errors = [ { + if (valid8 && void 0 !== data.can_export && "boolean" != typeof data.can_export) return validate390.errors = [ { instancePath: instancePath + "/can_export", schemaPath: "#/properties/can_export/type", keyword: "type", @@ -33135,21 +33656,21 @@ function validate380(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate380.errors = [], !0; + return validate390.errors = [], !0; } -validate380.evaluated = { +validate390.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceProtectedCount = validate370; +var __contract_EvidenceProtectedCount = validate380; -function validate370(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate370.evaluated; +function validate380(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate380.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate370.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate380.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -33157,7 +33678,7 @@ function validate370(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.protected_count) return validate370.errors = [ { + if (void 0 === data.protected_count) return validate380.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33167,7 +33688,7 @@ function validate370(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("protected_count" !== key0 && "stream_name" !== key0) return validate370.errors = [ { + for (let key0 in data) if ("protected_count" !== key0 && "stream_name" !== key0) return validate380.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -33178,7 +33699,7 @@ function validate370(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.protected_count) { let data0 = data.protected_count, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate370.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate380.errors = [ { instancePath: instancePath + "/protected_count", schemaPath: "#/properties/protected_count/type", keyword: "type", @@ -33186,7 +33707,7 @@ function validate370(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate370.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate380.errors = [ { instancePath: instancePath + "/protected_count", schemaPath: "#/properties/protected_count/minimum", keyword: "minimum", @@ -33197,7 +33718,7 @@ function validate370(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid0 = 0 === _errs2; } else valid0 = !0; - if (valid0 && void 0 !== data.stream_name && "string" != typeof data.stream_name) return validate370.errors = [ { + if (valid0 && void 0 !== data.stream_name && "string" != typeof data.stream_name) return validate380.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -33207,21 +33728,21 @@ function validate370(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate370.errors = [], !0; + return validate380.errors = [], !0; } -validate370.evaluated = { +validate380.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceRangeRequest = validate377; +var __contract_EvidenceRangeRequest = validate387; -function validate377(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate377.evaluated; +function validate387(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate387.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate377.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate387.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -33229,7 +33750,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.start_time) return validate377.errors = [ { + if (void 0 === data.start_time) return validate387.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33237,7 +33758,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate377.errors = [ { + if (void 0 === data.end_time) return validate387.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33245,7 +33766,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.camera_uuids) return validate377.errors = [ { + if (void 0 === data.camera_uuids) return validate387.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33255,7 +33776,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.start_time) { let data0 = data.start_time, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate377.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate387.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -33263,7 +33784,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate377.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate387.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -33277,7 +33798,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.end_time) { let data1 = data.end_time, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate377.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate387.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -33285,7 +33806,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate377.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate387.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -33298,7 +33819,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.camera_uuids) { let data2 = data.camera_uuids; - if (!Array.isArray(data2)) return validate377.errors = [ { + if (!Array.isArray(data2)) return validate387.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/type", keyword: "type", @@ -33306,7 +33827,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data2.length > 16) return validate377.errors = [ { + if (data2.length > 16) return validate387.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/maxItems", keyword: "maxItems", @@ -33314,7 +33835,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD limit: 16 } } ], !1; - if (data2.length < 1) return validate377.errors = [ { + if (data2.length < 1) return validate387.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/minItems", keyword: "minItems", @@ -33328,7 +33849,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data3) return validate377.errors = [ { + if ("string" != typeof data3) return validate387.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/type", keyword: "type", @@ -33336,7 +33857,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data3)) return validate377.errors = [ { + if (!pattern4.test(data3)) return validate387.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/pattern", keyword: "pattern", @@ -33354,7 +33875,7 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD for (;i1--; ) { let item0 = data2[i1]; if ("string" == typeof item0) { - if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate377.errors = [ { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate387.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/uniqueItems", keyword: "uniqueItems", @@ -33371,16 +33892,16 @@ function validate377(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate377.errors = [], !0; + return validate387.errors = [], !0; } -validate377.evaluated = { +validate387.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceSearch = validate385, schema447 = { +var __contract_EvidenceSearch = validate395, schema459 = { type: "object", properties: { camera_count: { @@ -33759,7 +34280,7 @@ var __contract_EvidenceSearch = validate385, schema447 = { }, required: [ "camera_count", "selector_applied", "summary_complete", "results", "page", "facets", "histogram", "coverage" ], additionalProperties: !1 -}, schema446 = { +}, schema458 = { type: "object", properties: { result_id: { @@ -33916,11 +34437,11 @@ var __contract_EvidenceSearch = validate385, schema447 = { additionalProperties: !1 }; -function validate385(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate385.evaluated; +function validate395(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate395.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate385.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -33928,7 +34449,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.camera_count) return validate385.errors = [ { + if (void 0 === data.camera_count) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33936,7 +34457,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_count" } } ], !1; - if (void 0 === data.selector_applied) return validate385.errors = [ { + if (void 0 === data.selector_applied) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33944,7 +34465,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "selector_applied" } } ], !1; - if (void 0 === data.summary_complete) return validate385.errors = [ { + if (void 0 === data.summary_complete) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33952,7 +34473,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "summary_complete" } } ], !1; - if (void 0 === data.results) return validate385.errors = [ { + if (void 0 === data.results) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33960,7 +34481,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "results" } } ], !1; - if (void 0 === data.page) return validate385.errors = [ { + if (void 0 === data.page) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33968,7 +34489,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "page" } } ], !1; - if (void 0 === data.facets) return validate385.errors = [ { + if (void 0 === data.facets) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33976,7 +34497,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "facets" } } ], !1; - if (void 0 === data.histogram) return validate385.errors = [ { + if (void 0 === data.histogram) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33984,7 +34505,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "histogram" } } ], !1; - if (void 0 === data.coverage) return validate385.errors = [ { + if (void 0 === data.coverage) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -33994,7 +34515,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("camera_count" !== key0 && "selector_applied" !== key0 && "summary_complete" !== key0 && "results" !== key0 && "page" !== key0 && "facets" !== key0 && "histogram" !== key0 && "coverage" !== key0) return validate385.errors = [ { + for (let key0 in data) if ("camera_count" !== key0 && "selector_applied" !== key0 && "summary_complete" !== key0 && "results" !== key0 && "page" !== key0 && "facets" !== key0 && "histogram" !== key0 && "coverage" !== key0) return validate395.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -34005,7 +34526,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.camera_count) { let data0 = data.camera_count, _errs2 = errors; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate385.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate395.errors = [ { instancePath: instancePath + "/camera_count", schemaPath: "#/properties/camera_count/type", keyword: "type", @@ -34013,7 +34534,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate385.errors = [ { + if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate395.errors = [ { instancePath: instancePath + "/camera_count", schemaPath: "#/properties/camera_count/minimum", keyword: "minimum", @@ -34027,7 +34548,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.selector_applied) { let _errs4 = errors; - if ("boolean" != typeof data.selector_applied) return validate385.errors = [ { + if ("boolean" != typeof data.selector_applied) return validate395.errors = [ { instancePath: instancePath + "/selector_applied", schemaPath: "#/properties/selector_applied/type", keyword: "type", @@ -34040,7 +34561,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.summary_complete) { let _errs6 = errors; - if ("boolean" != typeof data.summary_complete) return validate385.errors = [ { + if ("boolean" != typeof data.summary_complete) return validate395.errors = [ { instancePath: instancePath + "/summary_complete", schemaPath: "#/properties/summary_complete/type", keyword: "type", @@ -34054,7 +34575,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.results) { let data3 = data.results, _errs8 = errors; if (errors === _errs8) { - if (!Array.isArray(data3)) return validate385.errors = [ { + if (!Array.isArray(data3)) return validate395.errors = [ { instancePath: instancePath + "/results", schemaPath: "#/properties/results/type", keyword: "type", @@ -34067,7 +34588,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data4 = data3[i0], _errs10 = errors; if (errors == errors) { - if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate385.errors = [ { + if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/type", keyword: "type", @@ -34075,7 +34596,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data4.result_id) return validate385.errors = [ { + if (void 0 === data4.result_id) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34083,7 +34604,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "result_id" } } ], !1; - if (void 0 === data4.event_type) return validate385.errors = [ { + if (void 0 === data4.event_type) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34091,7 +34612,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "event_type" } } ], !1; - if (void 0 === data4.camera_uuid) return validate385.errors = [ { + if (void 0 === data4.camera_uuid) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34099,7 +34620,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data4.start_time) return validate385.errors = [ { + if (void 0 === data4.start_time) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34107,7 +34628,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data4.end_time) return validate385.errors = [ { + if (void 0 === data4.end_time) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34115,7 +34636,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data4.media_available) return validate385.errors = [ { + if (void 0 === data4.media_available) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34123,7 +34644,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "media_available" } } ], !1; - if (void 0 === data4.known_gap) return validate385.errors = [ { + if (void 0 === data4.known_gap) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34131,7 +34652,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "known_gap" } } ], !1; - if (void 0 === data4.recording_id) return validate385.errors = [ { + if (void 0 === data4.recording_id) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34139,7 +34660,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data4.camera) return validate385.errors = [ { + if (void 0 === data4.camera) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34147,7 +34668,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera" } } ], !1; - if (void 0 === data4.detection) return validate385.errors = [ { + if (void 0 === data4.detection) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34155,7 +34676,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "detection" } } ], !1; - if (void 0 === data4.thumbnail) return validate385.errors = [ { + if (void 0 === data4.thumbnail) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34163,7 +34684,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "thumbnail" } } ], !1; - if (void 0 === data4.recording) return validate385.errors = [ { + if (void 0 === data4.recording) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/required", keyword: "required", @@ -34173,7 +34694,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs13 = errors; - for (let key1 in data4) if (!func5.call(schema446.properties, key1)) return validate385.errors = [ { + for (let key1 in data4) if (!func7.call(schema458.properties, key1)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0, schemaPath: "#/$defs/EvidenceSearchResult/additionalProperties", keyword: "additionalProperties", @@ -34184,7 +34705,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs13 === errors) { if (void 0 !== data4.result_id) { let _errs14 = errors; - if ("string" != typeof data4.result_id) return validate385.errors = [ { + if ("string" != typeof data4.result_id) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/result_id", schemaPath: "#/$defs/EvidenceSearchResult/properties/result_id/type", keyword: "type", @@ -34197,7 +34718,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data4.event_type) { let data6 = data4.event_type, _errs16 = errors; - if ("string" != typeof data6) return validate385.errors = [ { + if ("string" != typeof data6) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/event_type", schemaPath: "#/$defs/EvidenceSearchResult/properties/event_type/type", keyword: "type", @@ -34205,12 +34726,12 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("motion" !== data6 && "detection" !== data6) return validate385.errors = [ { + if ("motion" !== data6 && "detection" !== data6) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/event_type", schemaPath: "#/$defs/EvidenceSearchResult/properties/event_type/enum", keyword: "enum", params: { - allowedValues: schema446.properties.event_type.enum + allowedValues: schema458.properties.event_type.enum } } ], !1; var valid7 = _errs16 === errors; @@ -34219,7 +34740,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.camera_uuid) { let data7 = data4.camera_uuid, _errs18 = errors; if (errors === _errs18) { - if ("string" != typeof data7) return validate385.errors = [ { + if ("string" != typeof data7) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera_uuid", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera_uuid/type", keyword: "type", @@ -34227,7 +34748,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data7)) return validate385.errors = [ { + if (!pattern4.test(data7)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera_uuid", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera_uuid/pattern", keyword: "pattern", @@ -34241,7 +34762,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data4.start_time) { let data8 = data4.start_time, _errs20 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate385.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/start_time", schemaPath: "#/$defs/EvidenceSearchResult/properties/start_time/type", keyword: "type", @@ -34254,7 +34775,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data4.end_time) { let data9 = data4.end_time, _errs22 = errors; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate385.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/end_time", schemaPath: "#/$defs/EvidenceSearchResult/properties/end_time/type", keyword: "type", @@ -34267,7 +34788,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data4.media_available) { let _errs24 = errors; - if ("boolean" != typeof data4.media_available) return validate385.errors = [ { + if ("boolean" != typeof data4.media_available) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/media_available", schemaPath: "#/$defs/EvidenceSearchResult/properties/media_available/type", keyword: "type", @@ -34280,7 +34801,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data4.known_gap) { let _errs26 = errors; - if ("boolean" != typeof data4.known_gap) return validate385.errors = [ { + if ("boolean" != typeof data4.known_gap) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/known_gap", schemaPath: "#/$defs/EvidenceSearchResult/properties/known_gap/type", keyword: "type", @@ -34337,7 +34858,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate385.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate395.errors = vErrors, !1; } errors = _errs29, vErrors.length = _errs29; var valid13 = _errs28 === errors; @@ -34346,7 +34867,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.camera) { let data13 = data4.camera, _errs34 = errors; if (errors === _errs34) { - if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate385.errors = [ { + if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/type", keyword: "type", @@ -34354,7 +34875,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data13.name) return validate385.errors = [ { + if (void 0 === data13.name) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/required", keyword: "required", @@ -34362,7 +34883,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data13.location_uuid) return validate385.errors = [ { + if (void 0 === data13.location_uuid) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/required", keyword: "required", @@ -34370,7 +34891,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "location_uuid" } } ], !1; - if (void 0 === data13.location_name) return validate385.errors = [ { + if (void 0 === data13.location_name) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/required", keyword: "required", @@ -34378,7 +34899,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "location_name" } } ], !1; - if (void 0 === data13.location_path) return validate385.errors = [ { + if (void 0 === data13.location_path) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/required", keyword: "required", @@ -34388,7 +34909,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs36 = errors; - for (let key2 in data13) if ("name" !== key2 && "location_uuid" !== key2 && "location_name" !== key2 && "location_path" !== key2) return validate385.errors = [ { + for (let key2 in data13) if ("name" !== key2 && "location_uuid" !== key2 && "location_name" !== key2 && "location_path" !== key2) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/additionalProperties", keyword: "additionalProperties", @@ -34399,7 +34920,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs36 === errors) { if (void 0 !== data13.name) { let _errs37 = errors; - if ("string" != typeof data13.name) return validate385.errors = [ { + if ("string" != typeof data13.name) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera/name", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/properties/name/type", keyword: "type", @@ -34412,12 +34933,12 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data13.location_uuid) { let data15 = data13.location_uuid, _errs39 = errors; - if ("string" != typeof data15 && null !== data15) return validate385.errors = [ { + if ("string" != typeof data15 && null !== data15) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera/location_uuid", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/properties/location_uuid/type", keyword: "type", params: { - type: schema446.properties.camera.properties.location_uuid.type + type: schema458.properties.camera.properties.location_uuid.type } } ], !1; var valid17 = _errs39 === errors; @@ -34425,24 +34946,24 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data13.location_name) { let data16 = data13.location_name, _errs41 = errors; - if ("string" != typeof data16 && null !== data16) return validate385.errors = [ { + if ("string" != typeof data16 && null !== data16) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera/location_name", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/properties/location_name/type", keyword: "type", params: { - type: schema446.properties.camera.properties.location_name.type + type: schema458.properties.camera.properties.location_name.type } } ], !1; var valid18 = _errs41 === errors; } else valid18 = !0; if (valid18 && void 0 !== data13.location_path) { let data17 = data13.location_path; - if ("string" != typeof data17 && null !== data17) return validate385.errors = [ { + if ("string" != typeof data17 && null !== data17) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/camera/location_path", schemaPath: "#/$defs/EvidenceSearchResult/properties/camera/properties/location_path/type", keyword: "type", params: { - type: schema446.properties.camera.properties.location_path.type + type: schema458.properties.camera.properties.location_path.type } } ], !1; } @@ -34457,7 +34978,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data4.detection) { let data18 = data4.detection, _errs45 = errors; if (errors === _errs45) { - if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate385.errors = [ { + if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/type", keyword: "type", @@ -34465,7 +34986,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data18.id) return validate385.errors = [ { + if (void 0 === data18.id) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34473,7 +34994,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data18.label) return validate385.errors = [ { + if (void 0 === data18.label) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34481,7 +35002,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "label" } } ], !1; - if (void 0 === data18.confidence) return validate385.errors = [ { + if (void 0 === data18.confidence) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34489,7 +35010,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "confidence" } } ], !1; - if (void 0 === data18.source) return validate385.errors = [ { + if (void 0 === data18.source) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34497,7 +35018,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source" } } ], !1; - if (void 0 === data18.zone_uuid) return validate385.errors = [ { + if (void 0 === data18.zone_uuid) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34505,7 +35026,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "zone_uuid" } } ], !1; - if (void 0 === data18.track_id) return validate385.errors = [ { + if (void 0 === data18.track_id) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34513,7 +35034,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "track_id" } } ], !1; - if (void 0 === data18.bounding_box) return validate385.errors = [ { + if (void 0 === data18.bounding_box) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/required", keyword: "required", @@ -34523,7 +35044,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs47 = errors; - for (let key3 in data18) if ("id" !== key3 && "label" !== key3 && "confidence" !== key3 && "source" !== key3 && "zone_uuid" !== key3 && "track_id" !== key3 && "bounding_box" !== key3) return validate385.errors = [ { + for (let key3 in data18) if ("id" !== key3 && "label" !== key3 && "confidence" !== key3 && "source" !== key3 && "zone_uuid" !== key3 && "track_id" !== key3 && "bounding_box" !== key3) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -34534,7 +35055,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs47 === errors) { if (void 0 !== data18.id) { let data19 = data18.id, _errs48 = errors; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate385.errors = [ { + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection/id", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/properties/id/type", keyword: "type", @@ -34542,7 +35063,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs48 && "number" == typeof data19 && isFinite(data19) && (data19 < 1 || isNaN(data19))) return validate385.errors = [ { + if (errors === _errs48 && "number" == typeof data19 && isFinite(data19) && (data19 < 1 || isNaN(data19))) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection/id", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/properties/id/minimum", keyword: "minimum", @@ -34556,7 +35077,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data18.label) { let _errs50 = errors; - if ("string" != typeof data18.label) return validate385.errors = [ { + if ("string" != typeof data18.label) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection/label", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/properties/label/type", keyword: "type", @@ -34569,7 +35090,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data18.confidence) { let data21 = data18.confidence, _errs52 = errors; - if ("number" != typeof data21 || !isFinite(data21)) return validate385.errors = [ { + if ("number" != typeof data21 || !isFinite(data21)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection/confidence", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/properties/confidence/type", keyword: "type", @@ -34582,7 +35103,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data18.source) { let _errs54 = errors; - if ("string" != typeof data18.source) return validate385.errors = [ { + if ("string" != typeof data18.source) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection/source", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/properties/source/type", keyword: "type", @@ -34595,12 +35116,12 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data18.zone_uuid) { let data23 = data18.zone_uuid, _errs56 = errors; - if ("string" != typeof data23 && null !== data23) return validate385.errors = [ { + if ("string" != typeof data23 && null !== data23) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/detection/zone_uuid", schemaPath: "#/$defs/EvidenceSearchResult/properties/detection/properties/zone_uuid/type", keyword: "type", params: { - type: schema446.properties.detection.properties.zone_uuid.type + type: schema458.properties.detection.properties.zone_uuid.type } } ], !1; var valid25 = _errs56 === errors; @@ -34652,7 +35173,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate385.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate395.errors = vErrors, !1; } errors = _errs59, vErrors.length = _errs59; var valid26 = _errs58 === errors; @@ -34834,7 +35355,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err21), errors++, validate385.errors = vErrors, !1; + return vErrors.push(err21), errors++, validate395.errors = vErrors, !1; } errors = _errs65, vErrors.length = _errs65; } @@ -34969,7 +35490,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceSearchResult/properties/thumbnail/oneOf/1/properties/status/enum", keyword: "enum", params: { - allowedValues: schema446.properties.thumbnail.oneOf[1].properties.status.enum + allowedValues: schema458.properties.thumbnail.oneOf[1].properties.status.enum } }; vErrors.push(err31), errors++; @@ -34997,7 +35518,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err33), errors++, validate385.errors = vErrors, !1; + return vErrors.push(err33), errors++, validate395.errors = vErrors, !1; } errors = _errs81, vErrors.length = _errs81; var valid35 = _errs80 === errors; @@ -35005,7 +35526,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid35 && void 0 !== data4.recording) { let data35 = data4.recording; if (errors == errors) { - if (!data35 || "object" != typeof data35 || Array.isArray(data35)) return validate385.errors = [ { + if (!data35 || "object" != typeof data35 || Array.isArray(data35)) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/recording", schemaPath: "#/$defs/EvidenceSearchResult/properties/recording/type", keyword: "type", @@ -35013,7 +35534,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data35.capture_method) return validate385.errors = [ { + if (void 0 === data35.capture_method) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/recording", schemaPath: "#/$defs/EvidenceSearchResult/properties/recording/required", keyword: "required", @@ -35021,7 +35542,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capture_method" } } ], !1; - if (void 0 === data35.protected) return validate385.errors = [ { + if (void 0 === data35.protected) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/recording", schemaPath: "#/$defs/EvidenceSearchResult/properties/recording/required", keyword: "required", @@ -35031,7 +35552,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs95 = errors; - for (let key7 in data35) if ("capture_method" !== key7 && "protected" !== key7) return validate385.errors = [ { + for (let key7 in data35) if ("capture_method" !== key7 && "protected" !== key7) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/recording", schemaPath: "#/$defs/EvidenceSearchResult/properties/recording/additionalProperties", keyword: "additionalProperties", @@ -35042,12 +35563,12 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs95 === errors) { if (void 0 !== data35.capture_method) { let data36 = data35.capture_method, _errs96 = errors; - if ("string" != typeof data36 && null !== data36) return validate385.errors = [ { + if ("string" != typeof data36 && null !== data36) return validate395.errors = [ { instancePath: instancePath + "/results/" + i0 + "/recording/capture_method", schemaPath: "#/$defs/EvidenceSearchResult/properties/recording/properties/capture_method/type", keyword: "type", params: { - type: schema446.properties.recording.properties.capture_method.type + type: schema458.properties.recording.properties.capture_method.type } } ], !1; var valid41 = _errs96 === errors; @@ -35086,7 +35607,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err36), errors++, validate385.errors = vErrors, !1; + return vErrors.push(err36), errors++, validate395.errors = vErrors, !1; } errors = _errs99, vErrors.length = _errs99; } @@ -35117,7 +35638,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.page) { let data38 = data.page, _errs104 = errors; if (errors === _errs104) { - if (!data38 || "object" != typeof data38 || Array.isArray(data38)) return validate385.errors = [ { + if (!data38 || "object" != typeof data38 || Array.isArray(data38)) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/type", keyword: "type", @@ -35125,7 +35646,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data38.limit) return validate385.errors = [ { + if (void 0 === data38.limit) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/required", keyword: "required", @@ -35133,7 +35654,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "limit" } } ], !1; - if (void 0 === data38.returned) return validate385.errors = [ { + if (void 0 === data38.returned) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/required", keyword: "required", @@ -35141,7 +35662,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "returned" } } ], !1; - if (void 0 === data38.total) return validate385.errors = [ { + if (void 0 === data38.total) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/required", keyword: "required", @@ -35149,7 +35670,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "total" } } ], !1; - if (void 0 === data38.has_more) return validate385.errors = [ { + if (void 0 === data38.has_more) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/required", keyword: "required", @@ -35157,7 +35678,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "has_more" } } ], !1; - if (void 0 === data38.next_cursor) return validate385.errors = [ { + if (void 0 === data38.next_cursor) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/required", keyword: "required", @@ -35167,7 +35688,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs106 = errors; - for (let key8 in data38) if ("limit" !== key8 && "returned" !== key8 && "total" !== key8 && "has_more" !== key8 && "next_cursor" !== key8) return validate385.errors = [ { + for (let key8 in data38) if ("limit" !== key8 && "returned" !== key8 && "total" !== key8 && "has_more" !== key8 && "next_cursor" !== key8) return validate395.errors = [ { instancePath: instancePath + "/page", schemaPath: "#/properties/page/additionalProperties", keyword: "additionalProperties", @@ -35178,7 +35699,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs106 === errors) { if (void 0 !== data38.limit) { let data39 = data38.limit, _errs107 = errors; - if ("number" != typeof data39 || data39 % 1 || isNaN(data39) || !isFinite(data39)) return validate385.errors = [ { + if ("number" != typeof data39 || data39 % 1 || isNaN(data39) || !isFinite(data39)) return validate395.errors = [ { instancePath: instancePath + "/page/limit", schemaPath: "#/properties/page/properties/limit/type", keyword: "type", @@ -35186,7 +35707,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs107 && "number" == typeof data39 && isFinite(data39) && (data39 < 1 || isNaN(data39))) return validate385.errors = [ { + if (errors === _errs107 && "number" == typeof data39 && isFinite(data39) && (data39 < 1 || isNaN(data39))) return validate395.errors = [ { instancePath: instancePath + "/page/limit", schemaPath: "#/properties/page/properties/limit/minimum", keyword: "minimum", @@ -35200,7 +35721,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid45) { if (void 0 !== data38.returned) { let data40 = data38.returned, _errs109 = errors; - if ("number" != typeof data40 || data40 % 1 || isNaN(data40) || !isFinite(data40)) return validate385.errors = [ { + if ("number" != typeof data40 || data40 % 1 || isNaN(data40) || !isFinite(data40)) return validate395.errors = [ { instancePath: instancePath + "/page/returned", schemaPath: "#/properties/page/properties/returned/type", keyword: "type", @@ -35208,7 +35729,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs109 && "number" == typeof data40 && isFinite(data40) && (data40 < 0 || isNaN(data40))) return validate385.errors = [ { + if (errors === _errs109 && "number" == typeof data40 && isFinite(data40) && (data40 < 0 || isNaN(data40))) return validate395.errors = [ { instancePath: instancePath + "/page/returned", schemaPath: "#/properties/page/properties/returned/minimum", keyword: "minimum", @@ -35222,7 +35743,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid46) { if (void 0 !== data38.total) { let data41 = data38.total, _errs111 = errors; - if ("number" != typeof data41 || data41 % 1 || isNaN(data41) || !isFinite(data41)) return validate385.errors = [ { + if ("number" != typeof data41 || data41 % 1 || isNaN(data41) || !isFinite(data41)) return validate395.errors = [ { instancePath: instancePath + "/page/total", schemaPath: "#/properties/page/properties/total/type", keyword: "type", @@ -35230,7 +35751,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs111 && "number" == typeof data41 && isFinite(data41) && (data41 < 0 || isNaN(data41))) return validate385.errors = [ { + if (errors === _errs111 && "number" == typeof data41 && isFinite(data41) && (data41 < 0 || isNaN(data41))) return validate395.errors = [ { instancePath: instancePath + "/page/total", schemaPath: "#/properties/page/properties/total/minimum", keyword: "minimum", @@ -35244,7 +35765,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid47) { if (void 0 !== data38.has_more) { let _errs113 = errors; - if ("boolean" != typeof data38.has_more) return validate385.errors = [ { + if ("boolean" != typeof data38.has_more) return validate395.errors = [ { instancePath: instancePath + "/page/has_more", schemaPath: "#/properties/page/properties/has_more/type", keyword: "type", @@ -35256,12 +35777,12 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid48 = !0; if (valid48 && void 0 !== data38.next_cursor) { let data43 = data38.next_cursor; - if ("string" != typeof data43 && null !== data43) return validate385.errors = [ { + if ("string" != typeof data43 && null !== data43) return validate395.errors = [ { instancePath: instancePath + "/page/next_cursor", schemaPath: "#/properties/page/properties/next_cursor/type", keyword: "type", params: { - type: schema447.properties.page.properties.next_cursor.type + type: schema459.properties.page.properties.next_cursor.type } } ], !1; } @@ -35277,7 +35798,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.facets) { let data44 = data.facets, _errs117 = errors; if (errors === _errs117) { - if (!data44 || "object" != typeof data44 || Array.isArray(data44)) return validate385.errors = [ { + if (!data44 || "object" != typeof data44 || Array.isArray(data44)) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/type", keyword: "type", @@ -35285,7 +35806,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data44.cameras) return validate385.errors = [ { + if (void 0 === data44.cameras) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35293,7 +35814,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cameras" } } ], !1; - if (void 0 === data44.labels) return validate385.errors = [ { + if (void 0 === data44.labels) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35301,7 +35822,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "labels" } } ], !1; - if (void 0 === data44.zones) return validate385.errors = [ { + if (void 0 === data44.zones) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35309,7 +35830,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "zones" } } ], !1; - if (void 0 === data44.sources) return validate385.errors = [ { + if (void 0 === data44.sources) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35317,7 +35838,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sources" } } ], !1; - if (void 0 === data44.event_types) return validate385.errors = [ { + if (void 0 === data44.event_types) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35325,7 +35846,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "event_types" } } ], !1; - if (void 0 === data44.locations) return validate385.errors = [ { + if (void 0 === data44.locations) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35333,7 +35854,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "locations" } } ], !1; - if (void 0 === data44.capture_methods) return validate385.errors = [ { + if (void 0 === data44.capture_methods) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35341,7 +35862,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capture_methods" } } ], !1; - if (void 0 === data44.recording_tags) return validate385.errors = [ { + if (void 0 === data44.recording_tags) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35349,7 +35870,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_tags" } } ], !1; - if (void 0 === data44.protection) return validate385.errors = [ { + if (void 0 === data44.protection) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/required", keyword: "required", @@ -35359,7 +35880,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs119 = errors; - for (let key9 in data44) if (!func5.call(schema447.properties.facets.properties, key9)) return validate385.errors = [ { + for (let key9 in data44) if (!func7.call(schema459.properties.facets.properties, key9)) return validate395.errors = [ { instancePath: instancePath + "/facets", schemaPath: "#/properties/facets/additionalProperties", keyword: "additionalProperties", @@ -35371,7 +35892,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.cameras) { let data45 = data44.cameras, _errs120 = errors; if (errors === _errs120) { - if (!Array.isArray(data45)) return validate385.errors = [ { + if (!Array.isArray(data45)) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras", schemaPath: "#/properties/facets/properties/cameras/type", keyword: "type", @@ -35384,7 +35905,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i1 = 0; i1 < len1; i1++) { let data46 = data45[i1], _errs122 = errors; if (errors === _errs122) { - if (!data46 || "object" != typeof data46 || Array.isArray(data46)) return validate385.errors = [ { + if (!data46 || "object" != typeof data46 || Array.isArray(data46)) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1, schemaPath: "#/properties/facets/properties/cameras/items/type", keyword: "type", @@ -35392,7 +35913,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data46.value) return validate385.errors = [ { + if (void 0 === data46.value) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1, schemaPath: "#/properties/facets/properties/cameras/items/required", keyword: "required", @@ -35400,7 +35921,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data46.count) return validate385.errors = [ { + if (void 0 === data46.count) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1, schemaPath: "#/properties/facets/properties/cameras/items/required", keyword: "required", @@ -35410,7 +35931,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs124 = errors; - for (let key10 in data46) if ("value" !== key10 && "label" !== key10 && "count" !== key10) return validate385.errors = [ { + for (let key10 in data46) if ("value" !== key10 && "label" !== key10 && "count" !== key10) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1, schemaPath: "#/properties/facets/properties/cameras/items/additionalProperties", keyword: "additionalProperties", @@ -35421,7 +35942,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs124 === errors) { if (void 0 !== data46.value) { let _errs125 = errors; - if ("string" != typeof data46.value) return validate385.errors = [ { + if ("string" != typeof data46.value) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1 + "/value", schemaPath: "#/properties/facets/properties/cameras/items/properties/value/type", keyword: "type", @@ -35434,7 +35955,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid53) { if (void 0 !== data46.label) { let _errs127 = errors; - if ("string" != typeof data46.label) return validate385.errors = [ { + if ("string" != typeof data46.label) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1 + "/label", schemaPath: "#/properties/facets/properties/cameras/items/properties/label/type", keyword: "type", @@ -35446,7 +35967,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid54 = !0; if (valid54 && void 0 !== data46.count) { let data49 = data46.count, _errs129 = errors; - if ("number" != typeof data49 || data49 % 1 || isNaN(data49) || !isFinite(data49)) return validate385.errors = [ { + if ("number" != typeof data49 || data49 % 1 || isNaN(data49) || !isFinite(data49)) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1 + "/count", schemaPath: "#/properties/facets/properties/cameras/items/properties/count/type", keyword: "type", @@ -35454,7 +35975,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs129 && "number" == typeof data49 && isFinite(data49) && (data49 < 0 || isNaN(data49))) return validate385.errors = [ { + if (errors === _errs129 && "number" == typeof data49 && isFinite(data49) && (data49 < 0 || isNaN(data49))) return validate395.errors = [ { instancePath: instancePath + "/facets/cameras/" + i1 + "/count", schemaPath: "#/properties/facets/properties/cameras/items/properties/count/minimum", keyword: "minimum", @@ -35478,7 +35999,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.labels) { let data50 = data44.labels, _errs131 = errors; if (errors === _errs131) { - if (!Array.isArray(data50)) return validate385.errors = [ { + if (!Array.isArray(data50)) return validate395.errors = [ { instancePath: instancePath + "/facets/labels", schemaPath: "#/properties/facets/properties/labels/type", keyword: "type", @@ -35491,7 +36012,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len2; i2++) { let data51 = data50[i2], _errs133 = errors; if (errors === _errs133) { - if (!data51 || "object" != typeof data51 || Array.isArray(data51)) return validate385.errors = [ { + if (!data51 || "object" != typeof data51 || Array.isArray(data51)) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2, schemaPath: "#/properties/facets/properties/labels/items/type", keyword: "type", @@ -35499,7 +36020,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data51.value) return validate385.errors = [ { + if (void 0 === data51.value) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2, schemaPath: "#/properties/facets/properties/labels/items/required", keyword: "required", @@ -35507,7 +36028,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data51.count) return validate385.errors = [ { + if (void 0 === data51.count) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2, schemaPath: "#/properties/facets/properties/labels/items/required", keyword: "required", @@ -35517,7 +36038,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs135 = errors; - for (let key11 in data51) if ("value" !== key11 && "label" !== key11 && "count" !== key11) return validate385.errors = [ { + for (let key11 in data51) if ("value" !== key11 && "label" !== key11 && "count" !== key11) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2, schemaPath: "#/properties/facets/properties/labels/items/additionalProperties", keyword: "additionalProperties", @@ -35528,7 +36049,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs135 === errors) { if (void 0 !== data51.value) { let _errs136 = errors; - if ("string" != typeof data51.value) return validate385.errors = [ { + if ("string" != typeof data51.value) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2 + "/value", schemaPath: "#/properties/facets/properties/labels/items/properties/value/type", keyword: "type", @@ -35541,7 +36062,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid58) { if (void 0 !== data51.label) { let _errs138 = errors; - if ("string" != typeof data51.label) return validate385.errors = [ { + if ("string" != typeof data51.label) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2 + "/label", schemaPath: "#/properties/facets/properties/labels/items/properties/label/type", keyword: "type", @@ -35553,7 +36074,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid59 = !0; if (valid59 && void 0 !== data51.count) { let data54 = data51.count, _errs140 = errors; - if ("number" != typeof data54 || data54 % 1 || isNaN(data54) || !isFinite(data54)) return validate385.errors = [ { + if ("number" != typeof data54 || data54 % 1 || isNaN(data54) || !isFinite(data54)) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2 + "/count", schemaPath: "#/properties/facets/properties/labels/items/properties/count/type", keyword: "type", @@ -35561,7 +36082,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs140 && "number" == typeof data54 && isFinite(data54) && (data54 < 0 || isNaN(data54))) return validate385.errors = [ { + if (errors === _errs140 && "number" == typeof data54 && isFinite(data54) && (data54 < 0 || isNaN(data54))) return validate395.errors = [ { instancePath: instancePath + "/facets/labels/" + i2 + "/count", schemaPath: "#/properties/facets/properties/labels/items/properties/count/minimum", keyword: "minimum", @@ -35585,7 +36106,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.zones) { let data55 = data44.zones, _errs142 = errors; if (errors === _errs142) { - if (!Array.isArray(data55)) return validate385.errors = [ { + if (!Array.isArray(data55)) return validate395.errors = [ { instancePath: instancePath + "/facets/zones", schemaPath: "#/properties/facets/properties/zones/type", keyword: "type", @@ -35598,7 +36119,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i3 = 0; i3 < len3; i3++) { let data56 = data55[i3], _errs144 = errors; if (errors === _errs144) { - if (!data56 || "object" != typeof data56 || Array.isArray(data56)) return validate385.errors = [ { + if (!data56 || "object" != typeof data56 || Array.isArray(data56)) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3, schemaPath: "#/properties/facets/properties/zones/items/type", keyword: "type", @@ -35606,7 +36127,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data56.value) return validate385.errors = [ { + if (void 0 === data56.value) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3, schemaPath: "#/properties/facets/properties/zones/items/required", keyword: "required", @@ -35614,7 +36135,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data56.count) return validate385.errors = [ { + if (void 0 === data56.count) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3, schemaPath: "#/properties/facets/properties/zones/items/required", keyword: "required", @@ -35624,7 +36145,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs146 = errors; - for (let key12 in data56) if ("value" !== key12 && "label" !== key12 && "count" !== key12) return validate385.errors = [ { + for (let key12 in data56) if ("value" !== key12 && "label" !== key12 && "count" !== key12) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3, schemaPath: "#/properties/facets/properties/zones/items/additionalProperties", keyword: "additionalProperties", @@ -35635,7 +36156,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs146 === errors) { if (void 0 !== data56.value) { let _errs147 = errors; - if ("string" != typeof data56.value) return validate385.errors = [ { + if ("string" != typeof data56.value) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3 + "/value", schemaPath: "#/properties/facets/properties/zones/items/properties/value/type", keyword: "type", @@ -35648,7 +36169,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid63) { if (void 0 !== data56.label) { let _errs149 = errors; - if ("string" != typeof data56.label) return validate385.errors = [ { + if ("string" != typeof data56.label) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3 + "/label", schemaPath: "#/properties/facets/properties/zones/items/properties/label/type", keyword: "type", @@ -35660,7 +36181,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid64 = !0; if (valid64 && void 0 !== data56.count) { let data59 = data56.count, _errs151 = errors; - if ("number" != typeof data59 || data59 % 1 || isNaN(data59) || !isFinite(data59)) return validate385.errors = [ { + if ("number" != typeof data59 || data59 % 1 || isNaN(data59) || !isFinite(data59)) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3 + "/count", schemaPath: "#/properties/facets/properties/zones/items/properties/count/type", keyword: "type", @@ -35668,7 +36189,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs151 && "number" == typeof data59 && isFinite(data59) && (data59 < 0 || isNaN(data59))) return validate385.errors = [ { + if (errors === _errs151 && "number" == typeof data59 && isFinite(data59) && (data59 < 0 || isNaN(data59))) return validate395.errors = [ { instancePath: instancePath + "/facets/zones/" + i3 + "/count", schemaPath: "#/properties/facets/properties/zones/items/properties/count/minimum", keyword: "minimum", @@ -35692,7 +36213,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.sources) { let data60 = data44.sources, _errs153 = errors; if (errors === _errs153) { - if (!Array.isArray(data60)) return validate385.errors = [ { + if (!Array.isArray(data60)) return validate395.errors = [ { instancePath: instancePath + "/facets/sources", schemaPath: "#/properties/facets/properties/sources/type", keyword: "type", @@ -35705,7 +36226,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i4 = 0; i4 < len4; i4++) { let data61 = data60[i4], _errs155 = errors; if (errors === _errs155) { - if (!data61 || "object" != typeof data61 || Array.isArray(data61)) return validate385.errors = [ { + if (!data61 || "object" != typeof data61 || Array.isArray(data61)) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4, schemaPath: "#/properties/facets/properties/sources/items/type", keyword: "type", @@ -35713,7 +36234,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data61.value) return validate385.errors = [ { + if (void 0 === data61.value) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4, schemaPath: "#/properties/facets/properties/sources/items/required", keyword: "required", @@ -35721,7 +36242,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data61.count) return validate385.errors = [ { + if (void 0 === data61.count) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4, schemaPath: "#/properties/facets/properties/sources/items/required", keyword: "required", @@ -35731,7 +36252,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs157 = errors; - for (let key13 in data61) if ("value" !== key13 && "label" !== key13 && "count" !== key13) return validate385.errors = [ { + for (let key13 in data61) if ("value" !== key13 && "label" !== key13 && "count" !== key13) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4, schemaPath: "#/properties/facets/properties/sources/items/additionalProperties", keyword: "additionalProperties", @@ -35742,7 +36263,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs157 === errors) { if (void 0 !== data61.value) { let _errs158 = errors; - if ("string" != typeof data61.value) return validate385.errors = [ { + if ("string" != typeof data61.value) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4 + "/value", schemaPath: "#/properties/facets/properties/sources/items/properties/value/type", keyword: "type", @@ -35755,7 +36276,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid68) { if (void 0 !== data61.label) { let _errs160 = errors; - if ("string" != typeof data61.label) return validate385.errors = [ { + if ("string" != typeof data61.label) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4 + "/label", schemaPath: "#/properties/facets/properties/sources/items/properties/label/type", keyword: "type", @@ -35767,7 +36288,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid69 = !0; if (valid69 && void 0 !== data61.count) { let data64 = data61.count, _errs162 = errors; - if ("number" != typeof data64 || data64 % 1 || isNaN(data64) || !isFinite(data64)) return validate385.errors = [ { + if ("number" != typeof data64 || data64 % 1 || isNaN(data64) || !isFinite(data64)) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4 + "/count", schemaPath: "#/properties/facets/properties/sources/items/properties/count/type", keyword: "type", @@ -35775,7 +36296,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs162 && "number" == typeof data64 && isFinite(data64) && (data64 < 0 || isNaN(data64))) return validate385.errors = [ { + if (errors === _errs162 && "number" == typeof data64 && isFinite(data64) && (data64 < 0 || isNaN(data64))) return validate395.errors = [ { instancePath: instancePath + "/facets/sources/" + i4 + "/count", schemaPath: "#/properties/facets/properties/sources/items/properties/count/minimum", keyword: "minimum", @@ -35799,7 +36320,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.event_types) { let data65 = data44.event_types, _errs164 = errors; if (errors === _errs164) { - if (!Array.isArray(data65)) return validate385.errors = [ { + if (!Array.isArray(data65)) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types", schemaPath: "#/properties/facets/properties/event_types/type", keyword: "type", @@ -35812,7 +36333,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i5 = 0; i5 < len5; i5++) { let data66 = data65[i5], _errs166 = errors; if (errors === _errs166) { - if (!data66 || "object" != typeof data66 || Array.isArray(data66)) return validate385.errors = [ { + if (!data66 || "object" != typeof data66 || Array.isArray(data66)) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5, schemaPath: "#/properties/facets/properties/event_types/items/type", keyword: "type", @@ -35820,7 +36341,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data66.value) return validate385.errors = [ { + if (void 0 === data66.value) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5, schemaPath: "#/properties/facets/properties/event_types/items/required", keyword: "required", @@ -35828,7 +36349,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data66.count) return validate385.errors = [ { + if (void 0 === data66.count) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5, schemaPath: "#/properties/facets/properties/event_types/items/required", keyword: "required", @@ -35838,7 +36359,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs168 = errors; - for (let key14 in data66) if ("value" !== key14 && "label" !== key14 && "count" !== key14) return validate385.errors = [ { + for (let key14 in data66) if ("value" !== key14 && "label" !== key14 && "count" !== key14) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5, schemaPath: "#/properties/facets/properties/event_types/items/additionalProperties", keyword: "additionalProperties", @@ -35849,7 +36370,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs168 === errors) { if (void 0 !== data66.value) { let _errs169 = errors; - if ("string" != typeof data66.value) return validate385.errors = [ { + if ("string" != typeof data66.value) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5 + "/value", schemaPath: "#/properties/facets/properties/event_types/items/properties/value/type", keyword: "type", @@ -35862,7 +36383,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid73) { if (void 0 !== data66.label) { let _errs171 = errors; - if ("string" != typeof data66.label) return validate385.errors = [ { + if ("string" != typeof data66.label) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5 + "/label", schemaPath: "#/properties/facets/properties/event_types/items/properties/label/type", keyword: "type", @@ -35874,7 +36395,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid74 = !0; if (valid74 && void 0 !== data66.count) { let data69 = data66.count, _errs173 = errors; - if ("number" != typeof data69 || data69 % 1 || isNaN(data69) || !isFinite(data69)) return validate385.errors = [ { + if ("number" != typeof data69 || data69 % 1 || isNaN(data69) || !isFinite(data69)) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5 + "/count", schemaPath: "#/properties/facets/properties/event_types/items/properties/count/type", keyword: "type", @@ -35882,7 +36403,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs173 && "number" == typeof data69 && isFinite(data69) && (data69 < 0 || isNaN(data69))) return validate385.errors = [ { + if (errors === _errs173 && "number" == typeof data69 && isFinite(data69) && (data69 < 0 || isNaN(data69))) return validate395.errors = [ { instancePath: instancePath + "/facets/event_types/" + i5 + "/count", schemaPath: "#/properties/facets/properties/event_types/items/properties/count/minimum", keyword: "minimum", @@ -35906,7 +36427,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.locations) { let data70 = data44.locations, _errs175 = errors; if (errors === _errs175) { - if (!Array.isArray(data70)) return validate385.errors = [ { + if (!Array.isArray(data70)) return validate395.errors = [ { instancePath: instancePath + "/facets/locations", schemaPath: "#/properties/facets/properties/locations/type", keyword: "type", @@ -35919,7 +36440,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i6 = 0; i6 < len6; i6++) { let data71 = data70[i6], _errs177 = errors; if (errors === _errs177) { - if (!data71 || "object" != typeof data71 || Array.isArray(data71)) return validate385.errors = [ { + if (!data71 || "object" != typeof data71 || Array.isArray(data71)) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6, schemaPath: "#/properties/facets/properties/locations/items/type", keyword: "type", @@ -35927,7 +36448,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data71.value) return validate385.errors = [ { + if (void 0 === data71.value) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6, schemaPath: "#/properties/facets/properties/locations/items/required", keyword: "required", @@ -35935,7 +36456,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data71.count) return validate385.errors = [ { + if (void 0 === data71.count) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6, schemaPath: "#/properties/facets/properties/locations/items/required", keyword: "required", @@ -35945,7 +36466,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs179 = errors; - for (let key15 in data71) if ("value" !== key15 && "label" !== key15 && "count" !== key15) return validate385.errors = [ { + for (let key15 in data71) if ("value" !== key15 && "label" !== key15 && "count" !== key15) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6, schemaPath: "#/properties/facets/properties/locations/items/additionalProperties", keyword: "additionalProperties", @@ -35956,7 +36477,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs179 === errors) { if (void 0 !== data71.value) { let _errs180 = errors; - if ("string" != typeof data71.value) return validate385.errors = [ { + if ("string" != typeof data71.value) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6 + "/value", schemaPath: "#/properties/facets/properties/locations/items/properties/value/type", keyword: "type", @@ -35969,7 +36490,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid78) { if (void 0 !== data71.label) { let _errs182 = errors; - if ("string" != typeof data71.label) return validate385.errors = [ { + if ("string" != typeof data71.label) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6 + "/label", schemaPath: "#/properties/facets/properties/locations/items/properties/label/type", keyword: "type", @@ -35981,7 +36502,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid79 = !0; if (valid79 && void 0 !== data71.count) { let data74 = data71.count, _errs184 = errors; - if ("number" != typeof data74 || data74 % 1 || isNaN(data74) || !isFinite(data74)) return validate385.errors = [ { + if ("number" != typeof data74 || data74 % 1 || isNaN(data74) || !isFinite(data74)) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6 + "/count", schemaPath: "#/properties/facets/properties/locations/items/properties/count/type", keyword: "type", @@ -35989,7 +36510,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs184 && "number" == typeof data74 && isFinite(data74) && (data74 < 0 || isNaN(data74))) return validate385.errors = [ { + if (errors === _errs184 && "number" == typeof data74 && isFinite(data74) && (data74 < 0 || isNaN(data74))) return validate395.errors = [ { instancePath: instancePath + "/facets/locations/" + i6 + "/count", schemaPath: "#/properties/facets/properties/locations/items/properties/count/minimum", keyword: "minimum", @@ -36013,7 +36534,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.capture_methods) { let data75 = data44.capture_methods, _errs186 = errors; if (errors === _errs186) { - if (!Array.isArray(data75)) return validate385.errors = [ { + if (!Array.isArray(data75)) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods", schemaPath: "#/properties/facets/properties/capture_methods/type", keyword: "type", @@ -36026,7 +36547,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i7 = 0; i7 < len7; i7++) { let data76 = data75[i7], _errs188 = errors; if (errors === _errs188) { - if (!data76 || "object" != typeof data76 || Array.isArray(data76)) return validate385.errors = [ { + if (!data76 || "object" != typeof data76 || Array.isArray(data76)) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7, schemaPath: "#/properties/facets/properties/capture_methods/items/type", keyword: "type", @@ -36034,7 +36555,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data76.value) return validate385.errors = [ { + if (void 0 === data76.value) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7, schemaPath: "#/properties/facets/properties/capture_methods/items/required", keyword: "required", @@ -36042,7 +36563,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data76.count) return validate385.errors = [ { + if (void 0 === data76.count) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7, schemaPath: "#/properties/facets/properties/capture_methods/items/required", keyword: "required", @@ -36052,7 +36573,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs190 = errors; - for (let key16 in data76) if ("value" !== key16 && "label" !== key16 && "count" !== key16) return validate385.errors = [ { + for (let key16 in data76) if ("value" !== key16 && "label" !== key16 && "count" !== key16) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7, schemaPath: "#/properties/facets/properties/capture_methods/items/additionalProperties", keyword: "additionalProperties", @@ -36063,7 +36584,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs190 === errors) { if (void 0 !== data76.value) { let _errs191 = errors; - if ("string" != typeof data76.value) return validate385.errors = [ { + if ("string" != typeof data76.value) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7 + "/value", schemaPath: "#/properties/facets/properties/capture_methods/items/properties/value/type", keyword: "type", @@ -36076,7 +36597,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid83) { if (void 0 !== data76.label) { let _errs193 = errors; - if ("string" != typeof data76.label) return validate385.errors = [ { + if ("string" != typeof data76.label) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7 + "/label", schemaPath: "#/properties/facets/properties/capture_methods/items/properties/label/type", keyword: "type", @@ -36088,7 +36609,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid84 = !0; if (valid84 && void 0 !== data76.count) { let data79 = data76.count, _errs195 = errors; - if ("number" != typeof data79 || data79 % 1 || isNaN(data79) || !isFinite(data79)) return validate385.errors = [ { + if ("number" != typeof data79 || data79 % 1 || isNaN(data79) || !isFinite(data79)) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7 + "/count", schemaPath: "#/properties/facets/properties/capture_methods/items/properties/count/type", keyword: "type", @@ -36096,7 +36617,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs195 && "number" == typeof data79 && isFinite(data79) && (data79 < 0 || isNaN(data79))) return validate385.errors = [ { + if (errors === _errs195 && "number" == typeof data79 && isFinite(data79) && (data79 < 0 || isNaN(data79))) return validate395.errors = [ { instancePath: instancePath + "/facets/capture_methods/" + i7 + "/count", schemaPath: "#/properties/facets/properties/capture_methods/items/properties/count/minimum", keyword: "minimum", @@ -36120,7 +36641,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data44.recording_tags) { let data80 = data44.recording_tags, _errs197 = errors; if (errors === _errs197) { - if (!Array.isArray(data80)) return validate385.errors = [ { + if (!Array.isArray(data80)) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags", schemaPath: "#/properties/facets/properties/recording_tags/type", keyword: "type", @@ -36133,7 +36654,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i8 = 0; i8 < len8; i8++) { let data81 = data80[i8], _errs199 = errors; if (errors === _errs199) { - if (!data81 || "object" != typeof data81 || Array.isArray(data81)) return validate385.errors = [ { + if (!data81 || "object" != typeof data81 || Array.isArray(data81)) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8, schemaPath: "#/properties/facets/properties/recording_tags/items/type", keyword: "type", @@ -36141,7 +36662,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data81.value) return validate385.errors = [ { + if (void 0 === data81.value) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8, schemaPath: "#/properties/facets/properties/recording_tags/items/required", keyword: "required", @@ -36149,7 +36670,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data81.count) return validate385.errors = [ { + if (void 0 === data81.count) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8, schemaPath: "#/properties/facets/properties/recording_tags/items/required", keyword: "required", @@ -36159,7 +36680,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs201 = errors; - for (let key17 in data81) if ("value" !== key17 && "label" !== key17 && "count" !== key17) return validate385.errors = [ { + for (let key17 in data81) if ("value" !== key17 && "label" !== key17 && "count" !== key17) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8, schemaPath: "#/properties/facets/properties/recording_tags/items/additionalProperties", keyword: "additionalProperties", @@ -36170,7 +36691,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs201 === errors) { if (void 0 !== data81.value) { let _errs202 = errors; - if ("string" != typeof data81.value) return validate385.errors = [ { + if ("string" != typeof data81.value) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8 + "/value", schemaPath: "#/properties/facets/properties/recording_tags/items/properties/value/type", keyword: "type", @@ -36183,7 +36704,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid88) { if (void 0 !== data81.label) { let _errs204 = errors; - if ("string" != typeof data81.label) return validate385.errors = [ { + if ("string" != typeof data81.label) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8 + "/label", schemaPath: "#/properties/facets/properties/recording_tags/items/properties/label/type", keyword: "type", @@ -36195,7 +36716,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid89 = !0; if (valid89 && void 0 !== data81.count) { let data84 = data81.count, _errs206 = errors; - if ("number" != typeof data84 || data84 % 1 || isNaN(data84) || !isFinite(data84)) return validate385.errors = [ { + if ("number" != typeof data84 || data84 % 1 || isNaN(data84) || !isFinite(data84)) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8 + "/count", schemaPath: "#/properties/facets/properties/recording_tags/items/properties/count/type", keyword: "type", @@ -36203,7 +36724,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs206 && "number" == typeof data84 && isFinite(data84) && (data84 < 0 || isNaN(data84))) return validate385.errors = [ { + if (errors === _errs206 && "number" == typeof data84 && isFinite(data84) && (data84 < 0 || isNaN(data84))) return validate395.errors = [ { instancePath: instancePath + "/facets/recording_tags/" + i8 + "/count", schemaPath: "#/properties/facets/properties/recording_tags/items/properties/count/minimum", keyword: "minimum", @@ -36226,7 +36747,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid86 && void 0 !== data44.protection) { let data85 = data44.protection; if (errors == errors) { - if (!Array.isArray(data85)) return validate385.errors = [ { + if (!Array.isArray(data85)) return validate395.errors = [ { instancePath: instancePath + "/facets/protection", schemaPath: "#/properties/facets/properties/protection/type", keyword: "type", @@ -36239,7 +36760,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i9 = 0; i9 < len9; i9++) { let data86 = data85[i9], _errs210 = errors; if (errors === _errs210) { - if (!data86 || "object" != typeof data86 || Array.isArray(data86)) return validate385.errors = [ { + if (!data86 || "object" != typeof data86 || Array.isArray(data86)) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9, schemaPath: "#/properties/facets/properties/protection/items/type", keyword: "type", @@ -36247,7 +36768,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data86.value) return validate385.errors = [ { + if (void 0 === data86.value) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9, schemaPath: "#/properties/facets/properties/protection/items/required", keyword: "required", @@ -36255,7 +36776,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "value" } } ], !1; - if (void 0 === data86.count) return validate385.errors = [ { + if (void 0 === data86.count) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9, schemaPath: "#/properties/facets/properties/protection/items/required", keyword: "required", @@ -36265,7 +36786,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs212 = errors; - for (let key18 in data86) if ("value" !== key18 && "label" !== key18 && "count" !== key18) return validate385.errors = [ { + for (let key18 in data86) if ("value" !== key18 && "label" !== key18 && "count" !== key18) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9, schemaPath: "#/properties/facets/properties/protection/items/additionalProperties", keyword: "additionalProperties", @@ -36276,7 +36797,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs212 === errors) { if (void 0 !== data86.value) { let _errs213 = errors; - if ("string" != typeof data86.value) return validate385.errors = [ { + if ("string" != typeof data86.value) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9 + "/value", schemaPath: "#/properties/facets/properties/protection/items/properties/value/type", keyword: "type", @@ -36289,7 +36810,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid93) { if (void 0 !== data86.label) { let _errs215 = errors; - if ("string" != typeof data86.label) return validate385.errors = [ { + if ("string" != typeof data86.label) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9 + "/label", schemaPath: "#/properties/facets/properties/protection/items/properties/label/type", keyword: "type", @@ -36301,7 +36822,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid94 = !0; if (valid94 && void 0 !== data86.count) { let data89 = data86.count, _errs217 = errors; - if ("number" != typeof data89 || data89 % 1 || isNaN(data89) || !isFinite(data89)) return validate385.errors = [ { + if ("number" != typeof data89 || data89 % 1 || isNaN(data89) || !isFinite(data89)) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9 + "/count", schemaPath: "#/properties/facets/properties/protection/items/properties/count/type", keyword: "type", @@ -36309,7 +36830,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs217 && "number" == typeof data89 && isFinite(data89) && (data89 < 0 || isNaN(data89))) return validate385.errors = [ { + if (errors === _errs217 && "number" == typeof data89 && isFinite(data89) && (data89 < 0 || isNaN(data89))) return validate395.errors = [ { instancePath: instancePath + "/facets/protection/" + i9 + "/count", schemaPath: "#/properties/facets/properties/protection/items/properties/count/minimum", keyword: "minimum", @@ -36344,7 +36865,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.histogram) { let data90 = data.histogram, _errs219 = errors; if (errors === _errs219) { - if (!data90 || "object" != typeof data90 || Array.isArray(data90)) return validate385.errors = [ { + if (!data90 || "object" != typeof data90 || Array.isArray(data90)) return validate395.errors = [ { instancePath: instancePath + "/histogram", schemaPath: "#/properties/histogram/type", keyword: "type", @@ -36352,7 +36873,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data90.bucket_seconds) return validate385.errors = [ { + if (void 0 === data90.bucket_seconds) return validate395.errors = [ { instancePath: instancePath + "/histogram", schemaPath: "#/properties/histogram/required", keyword: "required", @@ -36360,7 +36881,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bucket_seconds" } } ], !1; - if (void 0 === data90.buckets) return validate385.errors = [ { + if (void 0 === data90.buckets) return validate395.errors = [ { instancePath: instancePath + "/histogram", schemaPath: "#/properties/histogram/required", keyword: "required", @@ -36370,7 +36891,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs221 = errors; - for (let key19 in data90) if ("bucket_seconds" !== key19 && "buckets" !== key19) return validate385.errors = [ { + for (let key19 in data90) if ("bucket_seconds" !== key19 && "buckets" !== key19) return validate395.errors = [ { instancePath: instancePath + "/histogram", schemaPath: "#/properties/histogram/additionalProperties", keyword: "additionalProperties", @@ -36381,7 +36902,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs221 === errors) { if (void 0 !== data90.bucket_seconds) { let data91 = data90.bucket_seconds, _errs222 = errors; - if ("number" != typeof data91 || data91 % 1 || isNaN(data91) || !isFinite(data91)) return validate385.errors = [ { + if ("number" != typeof data91 || data91 % 1 || isNaN(data91) || !isFinite(data91)) return validate395.errors = [ { instancePath: instancePath + "/histogram/bucket_seconds", schemaPath: "#/properties/histogram/properties/bucket_seconds/type", keyword: "type", @@ -36389,7 +36910,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs222 && "number" == typeof data91 && isFinite(data91) && (data91 < 0 || isNaN(data91))) return validate385.errors = [ { + if (errors === _errs222 && "number" == typeof data91 && isFinite(data91) && (data91 < 0 || isNaN(data91))) return validate395.errors = [ { instancePath: instancePath + "/histogram/bucket_seconds", schemaPath: "#/properties/histogram/properties/bucket_seconds/minimum", keyword: "minimum", @@ -36403,7 +36924,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid97 && void 0 !== data90.buckets) { let data92 = data90.buckets; if (errors == errors) { - if (!Array.isArray(data92)) return validate385.errors = [ { + if (!Array.isArray(data92)) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets", schemaPath: "#/properties/histogram/properties/buckets/type", keyword: "type", @@ -36416,7 +36937,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD for (let i10 = 0; i10 < len10; i10++) { let data93 = data92[i10], _errs226 = errors; if (errors === _errs226) { - if (!data93 || "object" != typeof data93 || Array.isArray(data93)) return validate385.errors = [ { + if (!data93 || "object" != typeof data93 || Array.isArray(data93)) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10, schemaPath: "#/properties/histogram/properties/buckets/items/type", keyword: "type", @@ -36424,7 +36945,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data93.start_time) return validate385.errors = [ { + if (void 0 === data93.start_time) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10, schemaPath: "#/properties/histogram/properties/buckets/items/required", keyword: "required", @@ -36432,7 +36953,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data93.end_time) return validate385.errors = [ { + if (void 0 === data93.end_time) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10, schemaPath: "#/properties/histogram/properties/buckets/items/required", keyword: "required", @@ -36440,7 +36961,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data93.event_time) return validate385.errors = [ { + if (void 0 === data93.event_time) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10, schemaPath: "#/properties/histogram/properties/buckets/items/required", keyword: "required", @@ -36448,7 +36969,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "event_time" } } ], !1; - if (void 0 === data93.count) return validate385.errors = [ { + if (void 0 === data93.count) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10, schemaPath: "#/properties/histogram/properties/buckets/items/required", keyword: "required", @@ -36458,7 +36979,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs228 = errors; - for (let key20 in data93) if ("start_time" !== key20 && "end_time" !== key20 && "event_time" !== key20 && "count" !== key20) return validate385.errors = [ { + for (let key20 in data93) if ("start_time" !== key20 && "end_time" !== key20 && "event_time" !== key20 && "count" !== key20) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10, schemaPath: "#/properties/histogram/properties/buckets/items/additionalProperties", keyword: "additionalProperties", @@ -36469,7 +36990,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs228 === errors) { if (void 0 !== data93.start_time) { let data94 = data93.start_time, _errs229 = errors; - if ("number" != typeof data94 || data94 % 1 || isNaN(data94) || !isFinite(data94)) return validate385.errors = [ { + if ("number" != typeof data94 || data94 % 1 || isNaN(data94) || !isFinite(data94)) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10 + "/start_time", schemaPath: "#/properties/histogram/properties/buckets/items/properties/start_time/type", keyword: "type", @@ -36482,7 +37003,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid100) { if (void 0 !== data93.end_time) { let data95 = data93.end_time, _errs231 = errors; - if ("number" != typeof data95 || data95 % 1 || isNaN(data95) || !isFinite(data95)) return validate385.errors = [ { + if ("number" != typeof data95 || data95 % 1 || isNaN(data95) || !isFinite(data95)) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10 + "/end_time", schemaPath: "#/properties/histogram/properties/buckets/items/properties/end_time/type", keyword: "type", @@ -36495,7 +37016,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid101) { if (void 0 !== data93.event_time) { let data96 = data93.event_time, _errs233 = errors; - if ("number" != typeof data96 || data96 % 1 || isNaN(data96) || !isFinite(data96)) return validate385.errors = [ { + if ("number" != typeof data96 || data96 % 1 || isNaN(data96) || !isFinite(data96)) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10 + "/event_time", schemaPath: "#/properties/histogram/properties/buckets/items/properties/event_time/type", keyword: "type", @@ -36507,7 +37028,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } else valid102 = !0; if (valid102 && void 0 !== data93.count) { let data97 = data93.count, _errs235 = errors; - if ("number" != typeof data97 || data97 % 1 || isNaN(data97) || !isFinite(data97)) return validate385.errors = [ { + if ("number" != typeof data97 || data97 % 1 || isNaN(data97) || !isFinite(data97)) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10 + "/count", schemaPath: "#/properties/histogram/properties/buckets/items/properties/count/type", keyword: "type", @@ -36515,7 +37036,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs235 && "number" == typeof data97 && isFinite(data97) && (data97 < 0 || isNaN(data97))) return validate385.errors = [ { + if (errors === _errs235 && "number" == typeof data97 && isFinite(data97) && (data97 < 0 || isNaN(data97))) return validate395.errors = [ { instancePath: instancePath + "/histogram/buckets/" + i10 + "/count", schemaPath: "#/properties/histogram/properties/buckets/items/properties/count/minimum", keyword: "minimum", @@ -36543,7 +37064,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid96 && void 0 !== data.coverage) { let data98 = data.coverage; if (errors == errors) { - if (!data98 || "object" != typeof data98 || Array.isArray(data98)) return validate385.errors = [ { + if (!data98 || "object" != typeof data98 || Array.isArray(data98)) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/type", keyword: "type", @@ -36551,7 +37072,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data98.requested_start) return validate385.errors = [ { + if (void 0 === data98.requested_start) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36559,7 +37080,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "requested_start" } } ], !1; - if (void 0 === data98.requested_end) return validate385.errors = [ { + if (void 0 === data98.requested_end) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36567,7 +37088,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "requested_end" } } ], !1; - if (void 0 === data98.searched_start) return validate385.errors = [ { + if (void 0 === data98.searched_start) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36575,7 +37096,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "searched_start" } } ], !1; - if (void 0 === data98.searched_end) return validate385.errors = [ { + if (void 0 === data98.searched_end) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36583,7 +37104,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "searched_end" } } ], !1; - if (void 0 === data98.unresolved_legacy_rows) return validate385.errors = [ { + if (void 0 === data98.unresolved_legacy_rows) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36591,7 +37112,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unresolved_legacy_rows" } } ], !1; - if (void 0 === data98.spatial_metadata) return validate385.errors = [ { + if (void 0 === data98.spatial_metadata) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36599,7 +37120,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "spatial_metadata" } } ], !1; - if (void 0 === data98.incomplete_reasons) return validate385.errors = [ { + if (void 0 === data98.incomplete_reasons) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36607,7 +37128,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "incomplete_reasons" } } ], !1; - if (void 0 === data98.complete) return validate385.errors = [ { + if (void 0 === data98.complete) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -36617,7 +37138,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs239 = errors; - for (let key21 in data98) if ("requested_start" !== key21 && "requested_end" !== key21 && "searched_start" !== key21 && "searched_end" !== key21 && "unresolved_legacy_rows" !== key21 && "spatial_metadata" !== key21 && "incomplete_reasons" !== key21 && "complete" !== key21) return validate385.errors = [ { + for (let key21 in data98) if ("requested_start" !== key21 && "requested_end" !== key21 && "searched_start" !== key21 && "searched_end" !== key21 && "unresolved_legacy_rows" !== key21 && "spatial_metadata" !== key21 && "incomplete_reasons" !== key21 && "complete" !== key21) return validate395.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/additionalProperties", keyword: "additionalProperties", @@ -36628,7 +37149,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (_errs239 === errors) { if (void 0 !== data98.requested_start) { let data99 = data98.requested_start, _errs240 = errors; - if ("number" != typeof data99 || data99 % 1 || isNaN(data99) || !isFinite(data99)) return validate385.errors = [ { + if ("number" != typeof data99 || data99 % 1 || isNaN(data99) || !isFinite(data99)) return validate395.errors = [ { instancePath: instancePath + "/coverage/requested_start", schemaPath: "#/properties/coverage/properties/requested_start/type", keyword: "type", @@ -36636,7 +37157,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs240 && "number" == typeof data99 && isFinite(data99) && (data99 < 1 || isNaN(data99))) return validate385.errors = [ { + if (errors === _errs240 && "number" == typeof data99 && isFinite(data99) && (data99 < 1 || isNaN(data99))) return validate395.errors = [ { instancePath: instancePath + "/coverage/requested_start", schemaPath: "#/properties/coverage/properties/requested_start/minimum", keyword: "minimum", @@ -36650,7 +37171,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid105) { if (void 0 !== data98.requested_end) { let data100 = data98.requested_end, _errs242 = errors; - if ("number" != typeof data100 || data100 % 1 || isNaN(data100) || !isFinite(data100)) return validate385.errors = [ { + if ("number" != typeof data100 || data100 % 1 || isNaN(data100) || !isFinite(data100)) return validate395.errors = [ { instancePath: instancePath + "/coverage/requested_end", schemaPath: "#/properties/coverage/properties/requested_end/type", keyword: "type", @@ -36658,7 +37179,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs242 && "number" == typeof data100 && isFinite(data100) && (data100 < 1 || isNaN(data100))) return validate385.errors = [ { + if (errors === _errs242 && "number" == typeof data100 && isFinite(data100) && (data100 < 1 || isNaN(data100))) return validate395.errors = [ { instancePath: instancePath + "/coverage/requested_end", schemaPath: "#/properties/coverage/properties/requested_end/minimum", keyword: "minimum", @@ -36672,7 +37193,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid106) { if (void 0 !== data98.searched_start) { let data101 = data98.searched_start, _errs244 = errors; - if ("number" != typeof data101 || data101 % 1 || isNaN(data101) || !isFinite(data101)) return validate385.errors = [ { + if ("number" != typeof data101 || data101 % 1 || isNaN(data101) || !isFinite(data101)) return validate395.errors = [ { instancePath: instancePath + "/coverage/searched_start", schemaPath: "#/properties/coverage/properties/searched_start/type", keyword: "type", @@ -36680,7 +37201,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs244 && "number" == typeof data101 && isFinite(data101) && (data101 < 1 || isNaN(data101))) return validate385.errors = [ { + if (errors === _errs244 && "number" == typeof data101 && isFinite(data101) && (data101 < 1 || isNaN(data101))) return validate395.errors = [ { instancePath: instancePath + "/coverage/searched_start", schemaPath: "#/properties/coverage/properties/searched_start/minimum", keyword: "minimum", @@ -36694,7 +37215,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid107) { if (void 0 !== data98.searched_end) { let data102 = data98.searched_end, _errs246 = errors; - if ("number" != typeof data102 || data102 % 1 || isNaN(data102) || !isFinite(data102)) return validate385.errors = [ { + if ("number" != typeof data102 || data102 % 1 || isNaN(data102) || !isFinite(data102)) return validate395.errors = [ { instancePath: instancePath + "/coverage/searched_end", schemaPath: "#/properties/coverage/properties/searched_end/type", keyword: "type", @@ -36702,7 +37223,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs246 && "number" == typeof data102 && isFinite(data102) && (data102 < 1 || isNaN(data102))) return validate385.errors = [ { + if (errors === _errs246 && "number" == typeof data102 && isFinite(data102) && (data102 < 1 || isNaN(data102))) return validate395.errors = [ { instancePath: instancePath + "/coverage/searched_end", schemaPath: "#/properties/coverage/properties/searched_end/minimum", keyword: "minimum", @@ -36716,7 +37237,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (valid108) { if (void 0 !== data98.unresolved_legacy_rows) { let data103 = data98.unresolved_legacy_rows, _errs248 = errors; - if ("number" != typeof data103 || data103 % 1 || isNaN(data103) || !isFinite(data103)) return validate385.errors = [ { + if ("number" != typeof data103 || data103 % 1 || isNaN(data103) || !isFinite(data103)) return validate395.errors = [ { instancePath: instancePath + "/coverage/unresolved_legacy_rows", schemaPath: "#/properties/coverage/properties/unresolved_legacy_rows/type", keyword: "type", @@ -36724,7 +37245,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs248 && "number" == typeof data103 && isFinite(data103) && (data103 < 0 || isNaN(data103))) return validate385.errors = [ { + if (errors === _errs248 && "number" == typeof data103 && isFinite(data103) && (data103 < 0 || isNaN(data103))) return validate395.errors = [ { instancePath: instancePath + "/coverage/unresolved_legacy_rows", schemaPath: "#/properties/coverage/properties/unresolved_legacy_rows/minimum", keyword: "minimum", @@ -36868,7 +37389,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err48), errors++; } else { let _errs258 = errors; - for (let key23 in data104) if (!func5.call(schema447.properties.coverage.properties.spatial_metadata.oneOf[1].properties, key23)) { + for (let key23 in data104) if (!func7.call(schema459.properties.coverage.properties.spatial_metadata.oneOf[1].properties, key23)) { let err49 = { instancePath: instancePath + "/coverage/spatial_metadata", schemaPath: "#/properties/coverage/properties/spatial_metadata/oneOf/1/additionalProperties", @@ -36960,7 +37481,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/properties/coverage/properties/spatial_metadata/oneOf/1/properties/match/enum", keyword: "enum", params: { - allowedValues: schema447.properties.coverage.properties.spatial_metadata.oneOf[1].properties.match.enum + allowedValues: schema459.properties.coverage.properties.spatial_metadata.oneOf[1].properties.match.enum } }; vErrors.push(err55), errors++; @@ -37338,7 +37859,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing1 } }; - return vErrors.push(err83), errors++, validate385.errors = vErrors, !1; + return vErrors.push(err83), errors++, validate395.errors = vErrors, !1; } errors = _errs251, vErrors.length = _errs251; var valid110 = _errs250 === errors; @@ -37347,7 +37868,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data98.incomplete_reasons) { let data119 = data98.incomplete_reasons, _errs284 = errors; if (errors === _errs284) { - if (!Array.isArray(data119)) return validate385.errors = [ { + if (!Array.isArray(data119)) return validate395.errors = [ { instancePath: instancePath + "/coverage/incomplete_reasons", schemaPath: "#/properties/coverage/properties/incomplete_reasons/type", keyword: "type", @@ -37359,7 +37880,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD let len11 = data119.length; for (let i11 = 0; i11 < len11; i11++) { let _errs286 = errors; - if ("string" != typeof data119[i11]) return validate385.errors = [ { + if ("string" != typeof data119[i11]) return validate395.errors = [ { instancePath: instancePath + "/coverage/incomplete_reasons/" + i11, schemaPath: "#/properties/coverage/properties/incomplete_reasons/items/type", keyword: "type", @@ -37373,7 +37894,7 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } var valid126 = _errs284 === errors; } else valid126 = !0; - if (valid126 && void 0 !== data98.complete && "boolean" != typeof data98.complete) return validate385.errors = [ { + if (valid126 && void 0 !== data98.complete && "boolean" != typeof data98.complete) return validate395.errors = [ { instancePath: instancePath + "/coverage/complete", schemaPath: "#/properties/coverage/properties/complete/type", keyword: "type", @@ -37400,16 +37921,16 @@ function validate385(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate385.errors = vErrors, 0 === errors; + return validate395.errors = vErrors, 0 === errors; } -validate385.evaluated = { +validate395.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceSearchRequest = validate386, schema449 = { +var __contract_EvidenceSearchRequest = validate396, schema461 = { oneOf: [ { type: "object", properties: { @@ -37738,10 +38259,10 @@ var __contract_EvidenceSearchRequest = validate386, schema449 = { required: [ "start_time", "end_time", "selector" ], additionalProperties: !0 } ] -}, pattern168 = new RegExp("^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$", "u"); +}, pattern175 = new RegExp("^v1-[0-9a-fA-F]+-[0-9a-fA-F]+$", "u"); -function validate386(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate386.evaluated; +function validate396(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate396.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.start_time) { @@ -37976,7 +38497,7 @@ function validate386(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.cursor) { let data6 = data.cursor, _errs14 = errors, _errs15 = errors, valid9 = !1, _errs16 = errors; if (errors === _errs16) if ("string" == typeof data6) { - if (!pattern168.test(data6)) { + if (!pattern175.test(data6)) { let err17 = { instancePath: instancePath + "/cursor", schemaPath: "#/oneOf/0/properties/cursor/anyOf/0/pattern", @@ -38889,7 +39410,7 @@ function validate386(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/oneOf/0/properties/region/properties/match/enum", keyword: "enum", params: { - allowedValues: schema449.oneOf[0].properties.region.properties.match.enum + allowedValues: schema461.oneOf[0].properties.region.properties.match.enum } }; vErrors.push(err86), errors++; @@ -39127,7 +39648,7 @@ function validate386(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.cursor) { let data40 = data.cursor, _errs88 = errors, _errs89 = errors, valid44 = !1, _errs90 = errors; if (errors === _errs90) if ("string" == typeof data40) { - if (!pattern168.test(data40)) { + if (!pattern175.test(data40)) { let err103 = { instancePath: instancePath + "/cursor", schemaPath: "#/oneOf/1/properties/cursor/anyOf/0/pattern", @@ -40040,7 +40561,7 @@ function validate386(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/oneOf/1/properties/region/properties/match/enum", keyword: "enum", params: { - allowedValues: schema449.oneOf[1].properties.region.properties.match.enum + allowedValues: schema461.oneOf[1].properties.region.properties.match.enum } }; vErrors.push(err172), errors++; @@ -40132,24 +40653,24 @@ function validate386(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err178), errors++, validate386.errors = vErrors, !1; + return vErrors.push(err178), errors++, validate396.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate386.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate396.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate386.evaluated = { +validate396.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_EvidenceSearchResult = validate384; +var __contract_EvidenceSearchResult = validate394; -function validate384(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate384.evaluated; +function validate394(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate394.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate384.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -40157,7 +40678,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.result_id) return validate384.errors = [ { + if (void 0 === data.result_id) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40165,7 +40686,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "result_id" } } ], !1; - if (void 0 === data.event_type) return validate384.errors = [ { + if (void 0 === data.event_type) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40173,7 +40694,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "event_type" } } ], !1; - if (void 0 === data.camera_uuid) return validate384.errors = [ { + if (void 0 === data.camera_uuid) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40181,7 +40702,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.start_time) return validate384.errors = [ { + if (void 0 === data.start_time) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40189,7 +40710,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate384.errors = [ { + if (void 0 === data.end_time) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40197,7 +40718,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.media_available) return validate384.errors = [ { + if (void 0 === data.media_available) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40205,7 +40726,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "media_available" } } ], !1; - if (void 0 === data.known_gap) return validate384.errors = [ { + if (void 0 === data.known_gap) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40213,7 +40734,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "known_gap" } } ], !1; - if (void 0 === data.recording_id) return validate384.errors = [ { + if (void 0 === data.recording_id) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40221,7 +40742,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.camera) return validate384.errors = [ { + if (void 0 === data.camera) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40229,7 +40750,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera" } } ], !1; - if (void 0 === data.detection) return validate384.errors = [ { + if (void 0 === data.detection) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40237,7 +40758,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "detection" } } ], !1; - if (void 0 === data.thumbnail) return validate384.errors = [ { + if (void 0 === data.thumbnail) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40245,7 +40766,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "thumbnail" } } ], !1; - if (void 0 === data.recording) return validate384.errors = [ { + if (void 0 === data.recording) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -40255,7 +40776,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema446.properties, key0)) return validate384.errors = [ { + for (let key0 in data) if (!func7.call(schema458.properties, key0)) return validate394.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -40266,7 +40787,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.result_id) { let _errs2 = errors; - if ("string" != typeof data.result_id) return validate384.errors = [ { + if ("string" != typeof data.result_id) return validate394.errors = [ { instancePath: instancePath + "/result_id", schemaPath: "#/properties/result_id/type", keyword: "type", @@ -40279,7 +40800,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.event_type) { let data1 = data.event_type, _errs4 = errors; - if ("string" != typeof data1) return validate384.errors = [ { + if ("string" != typeof data1) return validate394.errors = [ { instancePath: instancePath + "/event_type", schemaPath: "#/properties/event_type/type", keyword: "type", @@ -40287,12 +40808,12 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("motion" !== data1 && "detection" !== data1) return validate384.errors = [ { + if ("motion" !== data1 && "detection" !== data1) return validate394.errors = [ { instancePath: instancePath + "/event_type", schemaPath: "#/properties/event_type/enum", keyword: "enum", params: { - allowedValues: schema446.properties.event_type.enum + allowedValues: schema458.properties.event_type.enum } } ], !1; var valid1 = _errs4 === errors; @@ -40301,7 +40822,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data2 = data.camera_uuid, _errs6 = errors; if (errors === _errs6) { - if ("string" != typeof data2) return validate384.errors = [ { + if ("string" != typeof data2) return validate394.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -40309,7 +40830,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data2)) return validate384.errors = [ { + if (!pattern4.test(data2)) return validate394.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/pattern", keyword: "pattern", @@ -40323,7 +40844,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.start_time) { let data3 = data.start_time, _errs8 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate384.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate394.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -40336,7 +40857,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.end_time) { let data4 = data.end_time, _errs10 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate384.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate394.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -40349,7 +40870,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.media_available) { let _errs12 = errors; - if ("boolean" != typeof data.media_available) return validate384.errors = [ { + if ("boolean" != typeof data.media_available) return validate394.errors = [ { instancePath: instancePath + "/media_available", schemaPath: "#/properties/media_available/type", keyword: "type", @@ -40362,7 +40883,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.known_gap) { let _errs14 = errors; - if ("boolean" != typeof data.known_gap) return validate384.errors = [ { + if ("boolean" != typeof data.known_gap) return validate394.errors = [ { instancePath: instancePath + "/known_gap", schemaPath: "#/properties/known_gap/type", keyword: "type", @@ -40419,7 +40940,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate384.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate394.errors = vErrors, !1; } errors = _errs17, vErrors.length = _errs17; var valid7 = _errs16 === errors; @@ -40428,7 +40949,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera) { let data8 = data.camera, _errs22 = errors; if (errors === _errs22) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate384.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate394.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/type", keyword: "type", @@ -40436,7 +40957,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data8.name) return validate384.errors = [ { + if (void 0 === data8.name) return validate394.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/required", keyword: "required", @@ -40444,7 +40965,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data8.location_uuid) return validate384.errors = [ { + if (void 0 === data8.location_uuid) return validate394.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/required", keyword: "required", @@ -40452,7 +40973,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "location_uuid" } } ], !1; - if (void 0 === data8.location_name) return validate384.errors = [ { + if (void 0 === data8.location_name) return validate394.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/required", keyword: "required", @@ -40460,7 +40981,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "location_name" } } ], !1; - if (void 0 === data8.location_path) return validate384.errors = [ { + if (void 0 === data8.location_path) return validate394.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/required", keyword: "required", @@ -40470,7 +40991,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs24 = errors; - for (let key1 in data8) if ("name" !== key1 && "location_uuid" !== key1 && "location_name" !== key1 && "location_path" !== key1) return validate384.errors = [ { + for (let key1 in data8) if ("name" !== key1 && "location_uuid" !== key1 && "location_name" !== key1 && "location_path" !== key1) return validate394.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/additionalProperties", keyword: "additionalProperties", @@ -40481,7 +41002,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (_errs24 === errors) { if (void 0 !== data8.name) { let _errs25 = errors; - if ("string" != typeof data8.name) return validate384.errors = [ { + if ("string" != typeof data8.name) return validate394.errors = [ { instancePath: instancePath + "/camera/name", schemaPath: "#/properties/camera/properties/name/type", keyword: "type", @@ -40494,12 +41015,12 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data8.location_uuid) { let data10 = data8.location_uuid, _errs27 = errors; - if ("string" != typeof data10 && null !== data10) return validate384.errors = [ { + if ("string" != typeof data10 && null !== data10) return validate394.errors = [ { instancePath: instancePath + "/camera/location_uuid", schemaPath: "#/properties/camera/properties/location_uuid/type", keyword: "type", params: { - type: schema446.properties.camera.properties.location_uuid.type + type: schema458.properties.camera.properties.location_uuid.type } } ], !1; var valid11 = _errs27 === errors; @@ -40507,24 +41028,24 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data8.location_name) { let data11 = data8.location_name, _errs29 = errors; - if ("string" != typeof data11 && null !== data11) return validate384.errors = [ { + if ("string" != typeof data11 && null !== data11) return validate394.errors = [ { instancePath: instancePath + "/camera/location_name", schemaPath: "#/properties/camera/properties/location_name/type", keyword: "type", params: { - type: schema446.properties.camera.properties.location_name.type + type: schema458.properties.camera.properties.location_name.type } } ], !1; var valid12 = _errs29 === errors; } else valid12 = !0; if (valid12 && void 0 !== data8.location_path) { let data12 = data8.location_path; - if ("string" != typeof data12 && null !== data12) return validate384.errors = [ { + if ("string" != typeof data12 && null !== data12) return validate394.errors = [ { instancePath: instancePath + "/camera/location_path", schemaPath: "#/properties/camera/properties/location_path/type", keyword: "type", params: { - type: schema446.properties.camera.properties.location_path.type + type: schema458.properties.camera.properties.location_path.type } } ], !1; } @@ -40539,7 +41060,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.detection) { let data13 = data.detection, _errs33 = errors; if (errors === _errs33) { - if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate384.errors = [ { + if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/type", keyword: "type", @@ -40547,7 +41068,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data13.id) return validate384.errors = [ { + if (void 0 === data13.id) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40555,7 +41076,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "id" } } ], !1; - if (void 0 === data13.label) return validate384.errors = [ { + if (void 0 === data13.label) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40563,7 +41084,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "label" } } ], !1; - if (void 0 === data13.confidence) return validate384.errors = [ { + if (void 0 === data13.confidence) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40571,7 +41092,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "confidence" } } ], !1; - if (void 0 === data13.source) return validate384.errors = [ { + if (void 0 === data13.source) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40579,7 +41100,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source" } } ], !1; - if (void 0 === data13.zone_uuid) return validate384.errors = [ { + if (void 0 === data13.zone_uuid) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40587,7 +41108,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "zone_uuid" } } ], !1; - if (void 0 === data13.track_id) return validate384.errors = [ { + if (void 0 === data13.track_id) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40595,7 +41116,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "track_id" } } ], !1; - if (void 0 === data13.bounding_box) return validate384.errors = [ { + if (void 0 === data13.bounding_box) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/required", keyword: "required", @@ -40605,7 +41126,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs35 = errors; - for (let key2 in data13) if ("id" !== key2 && "label" !== key2 && "confidence" !== key2 && "source" !== key2 && "zone_uuid" !== key2 && "track_id" !== key2 && "bounding_box" !== key2) return validate384.errors = [ { + for (let key2 in data13) if ("id" !== key2 && "label" !== key2 && "confidence" !== key2 && "source" !== key2 && "zone_uuid" !== key2 && "track_id" !== key2 && "bounding_box" !== key2) return validate394.errors = [ { instancePath: instancePath + "/detection", schemaPath: "#/properties/detection/additionalProperties", keyword: "additionalProperties", @@ -40616,7 +41137,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (_errs35 === errors) { if (void 0 !== data13.id) { let data14 = data13.id, _errs36 = errors; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate384.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate394.errors = [ { instancePath: instancePath + "/detection/id", schemaPath: "#/properties/detection/properties/id/type", keyword: "type", @@ -40624,7 +41145,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs36 && "number" == typeof data14 && isFinite(data14) && (data14 < 1 || isNaN(data14))) return validate384.errors = [ { + if (errors === _errs36 && "number" == typeof data14 && isFinite(data14) && (data14 < 1 || isNaN(data14))) return validate394.errors = [ { instancePath: instancePath + "/detection/id", schemaPath: "#/properties/detection/properties/id/minimum", keyword: "minimum", @@ -40638,7 +41159,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data13.label) { let _errs38 = errors; - if ("string" != typeof data13.label) return validate384.errors = [ { + if ("string" != typeof data13.label) return validate394.errors = [ { instancePath: instancePath + "/detection/label", schemaPath: "#/properties/detection/properties/label/type", keyword: "type", @@ -40651,7 +41172,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data13.confidence) { let data16 = data13.confidence, _errs40 = errors; - if ("number" != typeof data16 || !isFinite(data16)) return validate384.errors = [ { + if ("number" != typeof data16 || !isFinite(data16)) return validate394.errors = [ { instancePath: instancePath + "/detection/confidence", schemaPath: "#/properties/detection/properties/confidence/type", keyword: "type", @@ -40664,7 +41185,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data13.source) { let _errs42 = errors; - if ("string" != typeof data13.source) return validate384.errors = [ { + if ("string" != typeof data13.source) return validate394.errors = [ { instancePath: instancePath + "/detection/source", schemaPath: "#/properties/detection/properties/source/type", keyword: "type", @@ -40677,12 +41198,12 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data13.zone_uuid) { let data18 = data13.zone_uuid, _errs44 = errors; - if ("string" != typeof data18 && null !== data18) return validate384.errors = [ { + if ("string" != typeof data18 && null !== data18) return validate394.errors = [ { instancePath: instancePath + "/detection/zone_uuid", schemaPath: "#/properties/detection/properties/zone_uuid/type", keyword: "type", params: { - type: schema446.properties.detection.properties.zone_uuid.type + type: schema458.properties.detection.properties.zone_uuid.type } } ], !1; var valid19 = _errs44 === errors; @@ -40734,7 +41255,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate384.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate394.errors = vErrors, !1; } errors = _errs47, vErrors.length = _errs47; var valid20 = _errs46 === errors; @@ -40916,7 +41437,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err21), errors++, validate384.errors = vErrors, !1; + return vErrors.push(err21), errors++, validate394.errors = vErrors, !1; } errors = _errs53, vErrors.length = _errs53; } @@ -41051,7 +41572,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/properties/thumbnail/oneOf/1/properties/status/enum", keyword: "enum", params: { - allowedValues: schema446.properties.thumbnail.oneOf[1].properties.status.enum + allowedValues: schema458.properties.thumbnail.oneOf[1].properties.status.enum } }; vErrors.push(err31), errors++; @@ -41079,7 +41600,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err33), errors++, validate384.errors = vErrors, !1; + return vErrors.push(err33), errors++, validate394.errors = vErrors, !1; } errors = _errs69, vErrors.length = _errs69; var valid29 = _errs68 === errors; @@ -41087,7 +41608,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (valid29 && void 0 !== data.recording) { let data30 = data.recording; if (errors == errors) { - if (!data30 || "object" != typeof data30 || Array.isArray(data30)) return validate384.errors = [ { + if (!data30 || "object" != typeof data30 || Array.isArray(data30)) return validate394.errors = [ { instancePath: instancePath + "/recording", schemaPath: "#/properties/recording/type", keyword: "type", @@ -41095,7 +41616,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data30.capture_method) return validate384.errors = [ { + if (void 0 === data30.capture_method) return validate394.errors = [ { instancePath: instancePath + "/recording", schemaPath: "#/properties/recording/required", keyword: "required", @@ -41103,7 +41624,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capture_method" } } ], !1; - if (void 0 === data30.protected) return validate384.errors = [ { + if (void 0 === data30.protected) return validate394.errors = [ { instancePath: instancePath + "/recording", schemaPath: "#/properties/recording/required", keyword: "required", @@ -41113,7 +41634,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs83 = errors; - for (let key6 in data30) if ("capture_method" !== key6 && "protected" !== key6) return validate384.errors = [ { + for (let key6 in data30) if ("capture_method" !== key6 && "protected" !== key6) return validate394.errors = [ { instancePath: instancePath + "/recording", schemaPath: "#/properties/recording/additionalProperties", keyword: "additionalProperties", @@ -41124,12 +41645,12 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD if (_errs83 === errors) { if (void 0 !== data30.capture_method) { let data31 = data30.capture_method, _errs84 = errors; - if ("string" != typeof data31 && null !== data31) return validate384.errors = [ { + if ("string" != typeof data31 && null !== data31) return validate394.errors = [ { instancePath: instancePath + "/recording/capture_method", schemaPath: "#/properties/recording/properties/capture_method/type", keyword: "type", params: { - type: schema446.properties.recording.properties.capture_method.type + type: schema458.properties.recording.properties.capture_method.type } } ], !1; var valid35 = _errs84 === errors; @@ -41168,7 +41689,7 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err36), errors++, validate384.errors = vErrors, !1; + return vErrors.push(err36), errors++, validate394.errors = vErrors, !1; } errors = _errs87, vErrors.length = _errs87; } @@ -41189,16 +41710,16 @@ function validate384(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate384.errors = vErrors, 0 === errors; + return validate394.errors = vErrors, 0 === errors; } -validate384.evaluated = { +validate394.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceSegment = validate372, schema431 = { +var __contract_EvidenceSegment = validate382, schema443 = { oneOf: [ { type: "object", properties: { @@ -41276,8 +41797,8 @@ var __contract_EvidenceSegment = validate372, schema431 = { } ] }; -function validate372(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate372.evaluated; +function validate382(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate382.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.id) { @@ -41372,7 +41893,7 @@ function validate372(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err8), errors++; } else { let _errs3 = errors; - for (let key0 in data) if (!func5.call(schema431.oneOf[0].properties, key0)) { + for (let key0 in data) if (!func7.call(schema443.oneOf[0].properties, key0)) { let err9 = { instancePath: instancePath, schemaPath: "#/oneOf/0/additionalProperties", @@ -41673,7 +42194,7 @@ function validate372(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err31), errors++; } else { let _errs23 = errors; - for (let key1 in data) if (!func5.call(schema431.oneOf[1].properties, key1)) { + for (let key1 in data) if (!func7.call(schema443.oneOf[1].properties, key1)) { let err32 = { instancePath: instancePath, schemaPath: "#/oneOf/1/additionalProperties", @@ -41907,24 +42428,24 @@ function validate372(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err47), errors++, validate372.errors = vErrors, !1; + return vErrors.push(err47), errors++, validate382.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate372.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate382.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate372.evaluated = { +validate382.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_EvidenceSegmentAt = validate376; +var __contract_EvidenceSegmentAt = validate386; -function validate376(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate376.evaluated; +function validate386(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate386.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate376.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate386.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -41932,7 +42453,7 @@ function validate376(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.segment) return validate376.errors = [ { + if (void 0 === data.segment) return validate386.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -41942,7 +42463,7 @@ function validate376(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("segment" !== key0) return validate376.errors = [ { + for (let key0 in data) if ("segment" !== key0) return validate386.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -42044,7 +42565,7 @@ function validate376(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err8), errors++; } else { let _errs9 = errors; - for (let key1 in data0) if (!func5.call(schema431.oneOf[0].properties, key1)) { + for (let key1 in data0) if (!func7.call(schema443.oneOf[0].properties, key1)) { let err9 = { instancePath: instancePath + "/segment", schemaPath: "#/$defs/EvidenceSegment/oneOf/0/additionalProperties", @@ -42342,7 +42863,7 @@ function validate376(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err31), errors++; } else { let _errs29 = errors; - for (let key2 in data0) if (!func5.call(schema431.oneOf[1].properties, key2)) { + for (let key2 in data0) if (!func7.call(schema443.oneOf[1].properties, key2)) { let err32 = { instancePath: instancePath + "/segment", schemaPath: "#/$defs/EvidenceSegment/oneOf/1/additionalProperties", @@ -42599,27 +43120,27 @@ function validate376(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err49), errors++, validate376.errors = vErrors, !1; + return vErrors.push(err49), errors++, validate386.errors = vErrors, !1; } errors = _errs3, vErrors.length = _errs3; } } } - return validate376.errors = vErrors, 0 === errors; + return validate386.errors = vErrors, 0 === errors; } -validate376.evaluated = { +validate386.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceSegmentAtRequest = validate378; +var __contract_EvidenceSegmentAtRequest = validate388; -function validate378(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate378.evaluated; +function validate388(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate388.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate378.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate388.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -42627,7 +43148,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.timestamp) return validate378.errors = [ { + if (void 0 === data.timestamp) return validate388.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42635,7 +43156,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timestamp" } } ], !1; - if (void 0 === data.camera_uuids) return validate378.errors = [ { + if (void 0 === data.camera_uuids) return validate388.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42645,7 +43166,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.timestamp) { let data0 = data.timestamp, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate378.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate388.errors = [ { instancePath: instancePath + "/timestamp", schemaPath: "#/properties/timestamp/type", keyword: "type", @@ -42653,7 +43174,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate378.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate388.errors = [ { instancePath: instancePath + "/timestamp", schemaPath: "#/properties/timestamp/minimum", keyword: "minimum", @@ -42666,7 +43187,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.camera_uuids) { let data1 = data.camera_uuids; - if (!Array.isArray(data1)) return validate378.errors = [ { + if (!Array.isArray(data1)) return validate388.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/type", keyword: "type", @@ -42674,7 +43195,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data1.length > 1) return validate378.errors = [ { + if (data1.length > 1) return validate388.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/maxItems", keyword: "maxItems", @@ -42682,7 +43203,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data1.length < 1) return validate378.errors = [ { + if (data1.length < 1) return validate388.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/minItems", keyword: "minItems", @@ -42695,7 +43216,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data2 = data1[i0], _errs6 = 0; if (0 === _errs6) { - if ("string" != typeof data2) return validate378.errors = [ { + if ("string" != typeof data2) return validate388.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/type", keyword: "type", @@ -42703,7 +43224,7 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data2)) return validate378.errors = [ { + if (!pattern4.test(data2)) return validate388.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/pattern", keyword: "pattern", @@ -42716,21 +43237,21 @@ function validate378(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate378.errors = [], !0; + return validate388.errors = [], !0; } -validate378.evaluated = { +validate388.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceSyncComplete = validate371; +var __contract_EvidenceSyncComplete = validate381; -function validate371(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate371.evaluated; +function validate381(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate381.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate371.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate381.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -42738,7 +43259,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate371.errors = [ { + if (void 0 === data.success) return validate381.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42746,7 +43267,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.message) return validate371.errors = [ { + if (void 0 === data.message) return validate381.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42754,7 +43275,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "message" } } ], !1; - if (void 0 === data.updated) return validate371.errors = [ { + if (void 0 === data.updated) return validate381.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42764,7 +43285,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "message" !== key0 && "updated" !== key0) return validate371.errors = [ { + for (let key0 in data) if ("success" !== key0 && "message" !== key0 && "updated" !== key0) return validate381.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -42775,7 +43296,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.success) { let _errs2 = 0; - if (!0 !== data.success) return validate371.errors = [ { + if (!0 !== data.success) return validate381.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -42788,7 +43309,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.message) { let _errs3 = 0; - if ("string" != typeof data.message) return validate371.errors = [ { + if ("string" != typeof data.message) return validate381.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", @@ -42800,7 +43321,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.updated) { let data2 = data.updated, _errs5 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate371.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate381.errors = [ { instancePath: instancePath + "/updated", schemaPath: "#/properties/updated/type", keyword: "type", @@ -42808,7 +43329,7 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate371.errors = [ { + if (0 === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate381.errors = [ { instancePath: instancePath + "/updated", schemaPath: "#/properties/updated/minimum", keyword: "minimum", @@ -42821,21 +43342,21 @@ function validate371(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate371.errors = [], !0; + return validate381.errors = [], !0; } -validate371.evaluated = { +validate381.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceThumbnailRequest = validate379; +var __contract_EvidenceThumbnailRequest = validate389; -function validate379(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate379.evaluated; +function validate389(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate389.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate379.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate389.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -42843,7 +43364,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.start_time) return validate379.errors = [ { + if (void 0 === data.start_time) return validate389.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42851,7 +43372,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate379.errors = [ { + if (void 0 === data.end_time) return validate389.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42859,7 +43380,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.camera_uuids) return validate379.errors = [ { + if (void 0 === data.camera_uuids) return validate389.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -42869,7 +43390,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.start_time) { let data0 = data.start_time, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate379.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate389.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -42877,7 +43398,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate379.errors = [ { + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate389.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -42891,7 +43412,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.end_time) { let data1 = data.end_time, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate379.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate389.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -42899,7 +43420,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate379.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate389.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -42914,7 +43435,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuids) { let data2 = data.camera_uuids, _errs6 = 0; if (0 === _errs6) { - if (!Array.isArray(data2)) return validate379.errors = [ { + if (!Array.isArray(data2)) return validate389.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/type", keyword: "type", @@ -42922,7 +43443,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data2.length > 1) return validate379.errors = [ { + if (data2.length > 1) return validate389.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/maxItems", keyword: "maxItems", @@ -42930,7 +43451,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data2.length < 1) return validate379.errors = [ { + if (data2.length < 1) return validate389.errors = [ { instancePath: instancePath + "/camera_uuids", schemaPath: "#/properties/camera_uuids/minItems", keyword: "minItems", @@ -42943,7 +43464,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data3) return validate379.errors = [ { + if ("string" != typeof data3) return validate389.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/type", keyword: "type", @@ -42951,7 +43472,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data3)) return validate379.errors = [ { + if (!pattern4.test(data3)) return validate389.errors = [ { instancePath: instancePath + "/camera_uuids/" + i0, schemaPath: "#/properties/camera_uuids/items/pattern", keyword: "pattern", @@ -42968,7 +43489,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD } else valid2 = !0; if (valid2 && void 0 !== data.sample_count) { let data4 = data.sample_count, _errs10 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate379.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate389.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/type", keyword: "type", @@ -42977,7 +43498,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs10 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 12 || isNaN(data4)) return validate379.errors = [ { + if (data4 > 12 || isNaN(data4)) return validate389.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/maximum", keyword: "maximum", @@ -42986,7 +43507,7 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD limit: 12 } } ], !1; - if (data4 < 3 || isNaN(data4)) return validate379.errors = [ { + if (data4 < 3 || isNaN(data4)) return validate389.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/minimum", keyword: "minimum", @@ -42999,16 +43520,16 @@ function validate379(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate379.errors = [], !0; + return validate389.errors = [], !0; } -validate379.evaluated = { +validate389.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceThumbnailSample = validate382, schema443 = { +var __contract_EvidenceThumbnailSample = validate392, schema455 = { oneOf: [ { type: "object", properties: { @@ -43087,8 +43608,8 @@ var __contract_EvidenceThumbnailSample = validate382, schema443 = { } ] }; -function validate382(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate382.evaluated; +function validate392(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate392.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.timestamp) { @@ -43495,7 +44016,7 @@ function validate382(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/oneOf/1/properties/thumbnail/oneOf/1/properties/status/enum", keyword: "enum", params: { - allowedValues: schema443.oneOf[1].properties.thumbnail.oneOf[1].properties.status.enum + allowedValues: schema455.oneOf[1].properties.thumbnail.oneOf[1].properties.status.enum } }; vErrors.push(err32), errors++; @@ -43641,24 +44162,24 @@ function validate382(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err42), errors++, validate382.errors = vErrors, !1; + return vErrors.push(err42), errors++, validate392.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate382.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate392.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate382.evaluated = { +validate392.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_EvidenceThumbnails = validate383; +var __contract_EvidenceThumbnails = validate393; -function validate383(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate383.evaluated; +function validate393(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate393.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate383.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -43666,7 +44187,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.start_time) return validate383.errors = [ { + if (void 0 === data.start_time) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -43674,7 +44195,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate383.errors = [ { + if (void 0 === data.end_time) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -43682,7 +44203,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.sample_count) return validate383.errors = [ { + if (void 0 === data.sample_count) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -43690,7 +44211,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample_count" } } ], !1; - if (void 0 === data.camera) return validate383.errors = [ { + if (void 0 === data.camera) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -43698,7 +44219,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera" } } ], !1; - if (void 0 === data.samples) return validate383.errors = [ { + if (void 0 === data.samples) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -43706,7 +44227,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "samples" } } ], !1; - if (void 0 === data.coverage) return validate383.errors = [ { + if (void 0 === data.coverage) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -43716,7 +44237,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("start_time" !== key0 && "end_time" !== key0 && "sample_count" !== key0 && "camera" !== key0 && "samples" !== key0 && "coverage" !== key0) return validate383.errors = [ { + for (let key0 in data) if ("start_time" !== key0 && "end_time" !== key0 && "sample_count" !== key0 && "camera" !== key0 && "samples" !== key0 && "coverage" !== key0) return validate393.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -43727,7 +44248,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.start_time) { let data0 = data.start_time, _errs2 = errors; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate383.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate393.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -43735,7 +44256,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate383.errors = [ { + if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate393.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -43749,7 +44270,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.end_time) { let data1 = data.end_time, _errs4 = errors; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate383.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate393.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -43757,7 +44278,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate383.errors = [ { + if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate393.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -43771,7 +44292,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.sample_count) { let data2 = data.sample_count, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate383.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate393.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/type", keyword: "type", @@ -43779,7 +44300,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate383.errors = [ { + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate393.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/minimum", keyword: "minimum", @@ -43794,7 +44315,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera) { let data3 = data.camera, _errs8 = errors; if (errors === _errs8) { - if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate383.errors = [ { + if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate393.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/type", keyword: "type", @@ -43802,7 +44323,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data3.camera_uuid) return validate383.errors = [ { + if (void 0 === data3.camera_uuid) return validate393.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/required", keyword: "required", @@ -43810,7 +44331,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data3.name) return validate383.errors = [ { + if (void 0 === data3.name) return validate393.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/required", keyword: "required", @@ -43820,7 +44341,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs10 = errors; - for (let key1 in data3) if ("camera_uuid" !== key1 && "name" !== key1) return validate383.errors = [ { + for (let key1 in data3) if ("camera_uuid" !== key1 && "name" !== key1) return validate393.errors = [ { instancePath: instancePath + "/camera", schemaPath: "#/properties/camera/additionalProperties", keyword: "additionalProperties", @@ -43832,7 +44353,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data3.camera_uuid) { let data4 = data3.camera_uuid, _errs11 = errors; if (errors === _errs11) { - if ("string" != typeof data4) return validate383.errors = [ { + if ("string" != typeof data4) return validate393.errors = [ { instancePath: instancePath + "/camera/camera_uuid", schemaPath: "#/properties/camera/properties/camera_uuid/type", keyword: "type", @@ -43840,7 +44361,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data4)) return validate383.errors = [ { + if (!pattern4.test(data4)) return validate393.errors = [ { instancePath: instancePath + "/camera/camera_uuid", schemaPath: "#/properties/camera/properties/camera_uuid/pattern", keyword: "pattern", @@ -43851,7 +44372,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD } var valid4 = _errs11 === errors; } else valid4 = !0; - if (valid4 && void 0 !== data3.name && "string" != typeof data3.name) return validate383.errors = [ { + if (valid4 && void 0 !== data3.name && "string" != typeof data3.name) return validate393.errors = [ { instancePath: instancePath + "/camera/name", schemaPath: "#/properties/camera/properties/name/type", keyword: "type", @@ -43868,7 +44389,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.samples) { let data6 = data.samples, _errs15 = errors; if (errors === _errs15) { - if (!Array.isArray(data6)) return validate383.errors = [ { + if (!Array.isArray(data6)) return validate393.errors = [ { instancePath: instancePath + "/samples", schemaPath: "#/properties/samples/type", keyword: "type", @@ -43876,7 +44397,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data6.length > 12) return validate383.errors = [ { + if (data6.length > 12) return validate393.errors = [ { instancePath: instancePath + "/samples", schemaPath: "#/properties/samples/maxItems", keyword: "maxItems", @@ -44289,7 +44810,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD schemaPath: "#/$defs/EvidenceThumbnailSample/oneOf/1/properties/thumbnail/oneOf/1/properties/status/enum", keyword: "enum", params: { - allowedValues: schema443.oneOf[1].properties.thumbnail.oneOf[1].properties.status.enum + allowedValues: schema455.oneOf[1].properties.thumbnail.oneOf[1].properties.status.enum } }; vErrors.push(err32), errors++; @@ -44435,7 +44956,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err42), errors++, validate383.errors = vErrors, !1; + return vErrors.push(err42), errors++, validate393.errors = vErrors, !1; } if (errors = _errs19, vErrors.length = _errs19, _errs17 !== errors) break; } @@ -44446,7 +44967,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (valid6 && void 0 !== data.coverage) { let data22 = data.coverage; if (errors == errors) { - if (!data22 || "object" != typeof data22 || Array.isArray(data22)) return validate383.errors = [ { + if (!data22 || "object" != typeof data22 || Array.isArray(data22)) return validate393.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/type", keyword: "type", @@ -44454,7 +44975,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data22.segments_truncated) return validate383.errors = [ { + if (void 0 === data22.segments_truncated) return validate393.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -44462,7 +44983,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "segments_truncated" } } ], !1; - if (void 0 === data22.available_samples) return validate383.errors = [ { + if (void 0 === data22.available_samples) return validate393.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -44470,7 +44991,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "available_samples" } } ], !1; - if (void 0 === data22.gap_samples) return validate383.errors = [ { + if (void 0 === data22.gap_samples) return validate393.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -44480,7 +45001,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs59 = errors; - for (let key7 in data22) if ("segments_truncated" !== key7 && "available_samples" !== key7 && "gap_samples" !== key7) return validate383.errors = [ { + for (let key7 in data22) if ("segments_truncated" !== key7 && "available_samples" !== key7 && "gap_samples" !== key7) return validate393.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/additionalProperties", keyword: "additionalProperties", @@ -44491,7 +45012,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (_errs59 === errors) { if (void 0 !== data22.segments_truncated) { let _errs60 = errors; - if (!1 !== data22.segments_truncated) return validate383.errors = [ { + if (!1 !== data22.segments_truncated) return validate393.errors = [ { instancePath: instancePath + "/coverage/segments_truncated", schemaPath: "#/properties/coverage/properties/segments_truncated/const", keyword: "const", @@ -44504,7 +45025,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data22.available_samples) { let data24 = data22.available_samples, _errs61 = errors; - if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate383.errors = [ { + if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate393.errors = [ { instancePath: instancePath + "/coverage/available_samples", schemaPath: "#/properties/coverage/properties/available_samples/type", keyword: "type", @@ -44512,7 +45033,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs61 && "number" == typeof data24 && isFinite(data24) && (data24 < 0 || isNaN(data24))) return validate383.errors = [ { + if (errors === _errs61 && "number" == typeof data24 && isFinite(data24) && (data24 < 0 || isNaN(data24))) return validate393.errors = [ { instancePath: instancePath + "/coverage/available_samples", schemaPath: "#/properties/coverage/properties/available_samples/minimum", keyword: "minimum", @@ -44525,7 +45046,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD } else valid27 = !0; if (valid27 && void 0 !== data22.gap_samples) { let data25 = data22.gap_samples, _errs63 = errors; - if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate383.errors = [ { + if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate393.errors = [ { instancePath: instancePath + "/coverage/gap_samples", schemaPath: "#/properties/coverage/properties/gap_samples/type", keyword: "type", @@ -44533,7 +45054,7 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs63 && "number" == typeof data25 && isFinite(data25) && (data25 < 0 || isNaN(data25))) return validate383.errors = [ { + if (errors === _errs63 && "number" == typeof data25 && isFinite(data25) && (data25 < 0 || isNaN(data25))) return validate393.errors = [ { instancePath: instancePath + "/coverage/gap_samples", schemaPath: "#/properties/coverage/properties/gap_samples/minimum", keyword: "minimum", @@ -44555,16 +45076,16 @@ function validate383(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate383.errors = vErrors, 0 === errors; + return validate393.errors = vErrors, 0 === errors; } -validate383.evaluated = { +validate393.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceTimeline = validate374, schema432 = { +var __contract_EvidenceTimeline = validate384, schema444 = { type: "object", properties: { camera_uuid: { @@ -44624,11 +45145,11 @@ var __contract_EvidenceTimeline = validate374, schema432 = { additionalProperties: !1 }; -function validate373(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate373.evaluated; +function validate383(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate383.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate373.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -44636,7 +45157,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.camera_uuid) return validate373.errors = [ { + if (void 0 === data.camera_uuid) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44644,7 +45165,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.name) return validate373.errors = [ { + if (void 0 === data.name) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44652,7 +45173,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.stream_name) return validate373.errors = [ { + if (void 0 === data.stream_name) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44660,7 +45181,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data.segment_count) return validate373.errors = [ { + if (void 0 === data.segment_count) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44668,7 +45189,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "segment_count" } } ], !1; - if (void 0 === data.returned_interval_count) return validate373.errors = [ { + if (void 0 === data.returned_interval_count) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44676,7 +45197,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "returned_interval_count" } } ], !1; - if (void 0 === data.aggregated) return validate373.errors = [ { + if (void 0 === data.aggregated) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44684,7 +45205,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "aggregated" } } ], !1; - if (void 0 === data.aggregation_bucket_seconds) return validate373.errors = [ { + if (void 0 === data.aggregation_bucket_seconds) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44692,7 +45213,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "aggregation_bucket_seconds" } } ], !1; - if (void 0 === data.truncated) return validate373.errors = [ { + if (void 0 === data.truncated) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44700,7 +45221,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "truncated" } } ], !1; - if (void 0 === data.coverage) return validate373.errors = [ { + if (void 0 === data.coverage) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44708,7 +45229,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "coverage" } } ], !1; - if (void 0 === data.segments) return validate373.errors = [ { + if (void 0 === data.segments) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -44718,7 +45239,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema432.properties, key0)) return validate373.errors = [ { + for (let key0 in data) if (!func7.call(schema444.properties, key0)) return validate383.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -44730,7 +45251,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data0 = data.camera_uuid, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate373.errors = [ { + if ("string" != typeof data0) return validate383.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -44738,7 +45259,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate373.errors = [ { + if (!pattern4.test(data0)) return validate383.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/pattern", keyword: "pattern", @@ -44752,7 +45273,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.name) { let _errs4 = errors; - if ("string" != typeof data.name) return validate373.errors = [ { + if ("string" != typeof data.name) return validate383.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -44765,7 +45286,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.stream_name) { let _errs6 = errors; - if ("string" != typeof data.stream_name) return validate373.errors = [ { + if ("string" != typeof data.stream_name) return validate383.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -44778,7 +45299,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.segment_count) { let data3 = data.segment_count, _errs8 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate373.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate383.errors = [ { instancePath: instancePath + "/segment_count", schemaPath: "#/properties/segment_count/type", keyword: "type", @@ -44786,7 +45307,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate373.errors = [ { + if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate383.errors = [ { instancePath: instancePath + "/segment_count", schemaPath: "#/properties/segment_count/minimum", keyword: "minimum", @@ -44800,7 +45321,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.returned_interval_count) { let data4 = data.returned_interval_count, _errs10 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate373.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate383.errors = [ { instancePath: instancePath + "/returned_interval_count", schemaPath: "#/properties/returned_interval_count/type", keyword: "type", @@ -44808,7 +45329,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate373.errors = [ { + if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate383.errors = [ { instancePath: instancePath + "/returned_interval_count", schemaPath: "#/properties/returned_interval_count/minimum", keyword: "minimum", @@ -44822,7 +45343,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.aggregated) { let _errs12 = errors; - if ("boolean" != typeof data.aggregated) return validate373.errors = [ { + if ("boolean" != typeof data.aggregated) return validate383.errors = [ { instancePath: instancePath + "/aggregated", schemaPath: "#/properties/aggregated/type", keyword: "type", @@ -44835,7 +45356,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.aggregation_bucket_seconds) { let data6 = data.aggregation_bucket_seconds, _errs14 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate373.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate383.errors = [ { instancePath: instancePath + "/aggregation_bucket_seconds", schemaPath: "#/properties/aggregation_bucket_seconds/type", keyword: "type", @@ -44843,7 +45364,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 1 || isNaN(data6))) return validate373.errors = [ { + if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 1 || isNaN(data6))) return validate383.errors = [ { instancePath: instancePath + "/aggregation_bucket_seconds", schemaPath: "#/properties/aggregation_bucket_seconds/minimum", keyword: "minimum", @@ -44857,7 +45378,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.truncated) { let _errs16 = errors; - if (!1 !== data.truncated) return validate373.errors = [ { + if (!1 !== data.truncated) return validate383.errors = [ { instancePath: instancePath + "/truncated", schemaPath: "#/properties/truncated/const", keyword: "const", @@ -44871,7 +45392,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.coverage) { let data8 = data.coverage, _errs17 = errors; if (errors === _errs17) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate373.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate383.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/type", keyword: "type", @@ -44879,7 +45400,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data8.identity_resolved) return validate373.errors = [ { + if (void 0 === data8.identity_resolved) return validate383.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -44887,7 +45408,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "identity_resolved" } } ], !1; - if (void 0 === data8.requested_start) return validate373.errors = [ { + if (void 0 === data8.requested_start) return validate383.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -44895,7 +45416,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "requested_start" } } ], !1; - if (void 0 === data8.requested_end) return validate373.errors = [ { + if (void 0 === data8.requested_end) return validate383.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -44905,7 +45426,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs19 = errors; - for (let key1 in data8) if ("identity_resolved" !== key1 && "requested_start" !== key1 && "requested_end" !== key1) return validate373.errors = [ { + for (let key1 in data8) if ("identity_resolved" !== key1 && "requested_start" !== key1 && "requested_end" !== key1) return validate383.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/additionalProperties", keyword: "additionalProperties", @@ -44916,7 +45437,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (_errs19 === errors) { if (void 0 !== data8.identity_resolved) { let _errs20 = errors; - if (!0 !== data8.identity_resolved) return validate373.errors = [ { + if (!0 !== data8.identity_resolved) return validate383.errors = [ { instancePath: instancePath + "/coverage/identity_resolved", schemaPath: "#/properties/coverage/properties/identity_resolved/const", keyword: "const", @@ -44929,7 +45450,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data8.requested_start) { let data10 = data8.requested_start, _errs21 = errors; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate373.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate383.errors = [ { instancePath: instancePath + "/coverage/requested_start", schemaPath: "#/properties/coverage/properties/requested_start/type", keyword: "type", @@ -44937,7 +45458,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs21 && "number" == typeof data10 && isFinite(data10) && (data10 < 1 || isNaN(data10))) return validate373.errors = [ { + if (errors === _errs21 && "number" == typeof data10 && isFinite(data10) && (data10 < 1 || isNaN(data10))) return validate383.errors = [ { instancePath: instancePath + "/coverage/requested_start", schemaPath: "#/properties/coverage/properties/requested_start/minimum", keyword: "minimum", @@ -44950,7 +45471,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD } else valid10 = !0; if (valid10 && void 0 !== data8.requested_end) { let data11 = data8.requested_end, _errs23 = errors; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate373.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate383.errors = [ { instancePath: instancePath + "/coverage/requested_end", schemaPath: "#/properties/coverage/properties/requested_end/type", keyword: "type", @@ -44958,7 +45479,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs23 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate373.errors = [ { + if (errors === _errs23 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate383.errors = [ { instancePath: instancePath + "/coverage/requested_end", schemaPath: "#/properties/coverage/properties/requested_end/minimum", keyword: "minimum", @@ -44977,7 +45498,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD if (valid8 && void 0 !== data.segments) { let data12 = data.segments; if (errors == errors) { - if (!Array.isArray(data12)) return validate373.errors = [ { + if (!Array.isArray(data12)) return validate383.errors = [ { instancePath: instancePath + "/segments", schemaPath: "#/properties/segments/type", keyword: "type", @@ -45081,7 +45602,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err8), errors++; } else { let _errs32 = errors; - for (let key2 in data13) if (!func5.call(schema431.oneOf[0].properties, key2)) { + for (let key2 in data13) if (!func7.call(schema443.oneOf[0].properties, key2)) { let err9 = { instancePath: instancePath + "/segments/" + i0, schemaPath: "#/$defs/EvidenceSegment/oneOf/0/additionalProperties", @@ -45379,7 +45900,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err31), errors++; } else { let _errs52 = errors; - for (let key3 in data13) if (!func5.call(schema431.oneOf[1].properties, key3)) { + for (let key3 in data13) if (!func7.call(schema443.oneOf[1].properties, key3)) { let err32 = { instancePath: instancePath + "/segments/" + i0, schemaPath: "#/$defs/EvidenceSegment/oneOf/1/additionalProperties", @@ -45613,7 +46134,7 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err47), errors++, validate373.errors = vErrors, !1; + return vErrors.push(err47), errors++, validate383.errors = vErrors, !1; } if (errors = _errs29, vErrors.length = _errs29, _errs27 !== errors) break; } @@ -45631,14 +46152,14 @@ function validate373(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate373.errors = vErrors, 0 === errors; + return validate383.errors = vErrors, 0 === errors; } -function validate374(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate374.evaluated; +function validate384(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate384.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate374.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -45646,7 +46167,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.start_time) return validate374.errors = [ { + if (void 0 === data.start_time) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -45654,7 +46175,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "start_time" } } ], !1; - if (void 0 === data.end_time) return validate374.errors = [ { + if (void 0 === data.end_time) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -45662,7 +46183,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "end_time" } } ], !1; - if (void 0 === data.camera_count) return validate374.errors = [ { + if (void 0 === data.camera_count) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -45670,7 +46191,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_count" } } ], !1; - if (void 0 === data.max_active_decoders) return validate374.errors = [ { + if (void 0 === data.max_active_decoders) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -45678,7 +46199,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "max_active_decoders" } } ], !1; - if (void 0 === data.tracks) return validate374.errors = [ { + if (void 0 === data.tracks) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -45688,7 +46209,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("start_time" !== key0 && "end_time" !== key0 && "camera_count" !== key0 && "max_active_decoders" !== key0 && "tracks" !== key0) return validate374.errors = [ { + for (let key0 in data) if ("start_time" !== key0 && "end_time" !== key0 && "camera_count" !== key0 && "max_active_decoders" !== key0 && "tracks" !== key0) return validate384.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -45699,7 +46220,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.start_time) { let data0 = data.start_time, _errs2 = errors; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate374.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate384.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/type", keyword: "type", @@ -45707,7 +46228,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate374.errors = [ { + if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 1 || isNaN(data0))) return validate384.errors = [ { instancePath: instancePath + "/start_time", schemaPath: "#/properties/start_time/minimum", keyword: "minimum", @@ -45721,7 +46242,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.end_time) { let data1 = data.end_time, _errs4 = errors; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate374.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate384.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/type", keyword: "type", @@ -45729,7 +46250,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate374.errors = [ { + if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate384.errors = [ { instancePath: instancePath + "/end_time", schemaPath: "#/properties/end_time/minimum", keyword: "minimum", @@ -45743,7 +46264,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.camera_count) { let data2 = data.camera_count, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate374.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate384.errors = [ { instancePath: instancePath + "/camera_count", schemaPath: "#/properties/camera_count/type", keyword: "type", @@ -45751,7 +46272,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate374.errors = [ { + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate384.errors = [ { instancePath: instancePath + "/camera_count", schemaPath: "#/properties/camera_count/minimum", keyword: "minimum", @@ -45765,7 +46286,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.max_active_decoders) { let _errs8 = errors; - if (4 !== data.max_active_decoders) return validate374.errors = [ { + if (4 !== data.max_active_decoders) return validate384.errors = [ { instancePath: instancePath + "/max_active_decoders", schemaPath: "#/properties/max_active_decoders/const", keyword: "const", @@ -45778,7 +46299,7 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD if (valid3 && void 0 !== data.tracks) { let data4 = data.tracks; if (errors == errors) { - if (!Array.isArray(data4)) return validate374.errors = [ { + if (!Array.isArray(data4)) return validate384.errors = [ { instancePath: instancePath + "/tracks", schemaPath: "#/properties/tracks/type", keyword: "type", @@ -45790,13 +46311,13 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD let len0 = data4.length; for (let i0 = 0; i0 < len0; i0++) { let _errs11 = errors; - if (validate373(data4[i0], { + if (validate383(data4[i0], { instancePath: instancePath + "/tracks/" + i0, parentData: data4, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate373.errors), errors = vErrors.length), _errs11 !== errors) break; + }) || (vErrors = vErrors.concat(validate383.errors), errors = vErrors.length), _errs11 !== errors) break; } } } @@ -45807,20 +46328,20 @@ function validate374(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate374.errors = vErrors, 0 === errors; + return validate384.errors = vErrors, 0 === errors; } -validate373.evaluated = { +validate383.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate374.evaluated = { +}, validate384.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_EvidenceTimelineTrack = validate373, __contract_FleetLocation = validate163, schema169 = { +var __contract_EvidenceTimelineTrack = validate383, __contract_FleetLocation = validate163, schema169 = { type: "object", properties: { uuid: { @@ -45974,7 +46495,7 @@ function validate163(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema169.properties, key0)) return validate163.errors = [ { + for (let key0 in data) if (!func7.call(schema169.properties, key0)) return validate163.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -46746,7 +47267,7 @@ function validate164(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs7 = errors; - for (let key1 in data1) if (!func5.call(schema169.properties, key1)) return validate164.errors = [ { + for (let key1 in data1) if (!func7.call(schema169.properties, key1)) return validate164.errors = [ { instancePath: instancePath + "/locations/" + i0, schemaPath: "#/$defs/FleetLocation/additionalProperties", keyword: "additionalProperties", @@ -47177,7 +47698,7 @@ function validate146(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema156.properties, key0)) return validate146.errors = [ { + for (let key0 in data) if (!func7.call(schema156.properties, key0)) return validate146.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -48652,7 +49173,7 @@ function validate156(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema165.properties, key0)) return validate156.errors = [ { + for (let key0 in data) if (!func7.call(schema165.properties, key0)) return validate156.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -49034,7 +49555,7 @@ function validate156(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs22 = errors; - for (let key1 in data11) if (!func5.call(schema157.properties, key1)) return validate156.errors = [ { + for (let key1 in data11) if (!func7.call(schema157.properties, key1)) return validate156.errors = [ { instancePath: instancePath + "/cameras/" + i0, schemaPath: "#/$defs/FleetCamera/additionalProperties", keyword: "additionalProperties", @@ -49683,7 +50204,7 @@ function validate144(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema155.properties, key0)) return validate144.errors = [ { + for (let key0 in data) if (!func7.call(schema155.properties, key0)) return validate144.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -50084,7 +50605,7 @@ function validate153(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema163.properties, key0)) return validate153.errors = [ { + for (let key0 in data) if (!func7.call(schema163.properties, key0)) return validate153.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -50466,7 +50987,7 @@ function validate153(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs23 = errors; - for (let key1 in data11) if (!func5.call(schema157.properties, key1)) return validate153.errors = [ { + for (let key1 in data11) if (!func7.call(schema157.properties, key1)) return validate153.errors = [ { instancePath: instancePath + "/cameras/" + i0, schemaPath: "#/$defs/FleetCamera/additionalProperties", keyword: "additionalProperties", @@ -51286,7 +51807,7 @@ function validate159(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema167.properties, key0)) return validate159.errors = [ { + for (let key0 in data) if (!func7.call(schema167.properties, key0)) return validate159.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -52922,7 +53443,7 @@ function validate224(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema240.properties, key0)) return validate224.errors = [ { + for (let key0 in data) if (!func7.call(schema240.properties, key0)) return validate224.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -53489,7 +54010,7 @@ function validate244(data, {instancePath: instancePath = "", parentData: parentD vErrors.push(err5), errors++; } else { let _errs11 = errors; - for (let key1 in data2) if (!func5.call(schema256.properties, key1)) { + for (let key1 in data2) if (!func7.call(schema256.properties, key1)) { let err6 = { instancePath: instancePath + "/shapes/" + i0, schemaPath: "#/$defs/FloorSketchRect/additionalProperties", @@ -55189,7 +55710,7 @@ function validate253(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema275.properties, key0)) return validate253.errors = [ { + for (let key0 in data) if (!func7.call(schema275.properties, key0)) return validate253.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -57279,7 +57800,7 @@ function validate240(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema256.properties, key0)) return validate240.errors = [ { + for (let key0 in data) if (!func7.call(schema256.properties, key0)) return validate240.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -57836,12 +58357,12 @@ validate241.evaluated = { dynamicItems: !1 }; -var __contract_Go2rtcConfigurationInput = validate268; +var __contract_Go2rtcConfigurationInput = validate271; -function validate268(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate268.evaluated; +function validate271(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate271.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate268.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate271.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -57851,7 +58372,7 @@ function validate268(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.override) { let data0 = data.override; - if ("string" != typeof data0) return validate268.errors = [ { + if ("string" != typeof data0) return validate271.errors = [ { instancePath: instancePath + "/override", schemaPath: "#/properties/override/type", keyword: "type", @@ -57859,7 +58380,7 @@ function validate268(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 65535) return validate268.errors = [ { + if (func1(data0) > 65535) return validate271.errors = [ { instancePath: instancePath + "/override", schemaPath: "#/properties/override/maxLength", keyword: "maxLength", @@ -57868,22 +58389,22 @@ function validate268(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate268.errors = [], !0; + return validate271.errors = [], !0; } -validate268.evaluated = { +validate271.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_Go2rtcConfigurationValidation = validate267; +var __contract_Go2rtcConfigurationValidation = validate270; -function validate267(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate267.evaluated; +function validate270(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate270.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate267.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate270.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -57891,7 +58412,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.valid) return validate267.errors = [ { + if (void 0 === data.valid) return validate270.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -57899,7 +58420,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "valid" } } ], !1; - if (void 0 === data.libyaml_available) return validate267.errors = [ { + if (void 0 === data.libyaml_available) return validate270.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -57907,7 +58428,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "libyaml_available" } } ], !1; - if (void 0 === data.skipped) return validate267.errors = [ { + if (void 0 === data.skipped) return validate270.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -57915,7 +58436,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "skipped" } } ], !1; - if (void 0 === data.warnings) return validate267.errors = [ { + if (void 0 === data.warnings) return validate270.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -57925,7 +58446,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.valid) { let _errs2 = errors; - if ("boolean" != typeof data.valid) return validate267.errors = [ { + if ("boolean" != typeof data.valid) return validate270.errors = [ { instancePath: instancePath + "/valid", schemaPath: "#/properties/valid/type", keyword: "type", @@ -57938,7 +58459,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.libyaml_available) { let _errs4 = errors; - if ("boolean" != typeof data.libyaml_available) return validate267.errors = [ { + if ("boolean" != typeof data.libyaml_available) return validate270.errors = [ { instancePath: instancePath + "/libyaml_available", schemaPath: "#/properties/libyaml_available/type", keyword: "type", @@ -57951,7 +58472,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.skipped) { let _errs6 = errors; - if ("boolean" != typeof data.skipped) return validate267.errors = [ { + if ("boolean" != typeof data.skipped) return validate270.errors = [ { instancePath: instancePath + "/skipped", schemaPath: "#/properties/skipped/type", keyword: "type", @@ -58040,7 +58561,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err5), errors++, validate267.errors = vErrors, !1; + return vErrors.push(err5), errors++, validate270.errors = vErrors, !1; } errors = _errs9, vErrors.length = _errs9; var valid3 = _errs8 === errors; @@ -58049,7 +58570,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.warnings) { let data7 = data.warnings, _errs21 = errors; if (errors === _errs21) { - if (!Array.isArray(data7)) return validate267.errors = [ { + if (!Array.isArray(data7)) return validate270.errors = [ { instancePath: instancePath + "/warnings", schemaPath: "#/properties/warnings/type", keyword: "type", @@ -58061,7 +58582,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD let len0 = data7.length; for (let i0 = 0; i0 < len0; i0++) { let _errs23 = errors; - if ("string" != typeof data7[i0]) return validate267.errors = [ { + if ("string" != typeof data7[i0]) return validate270.errors = [ { instancePath: instancePath + "/warnings/" + i0, schemaPath: "#/properties/warnings/items/type", keyword: "type", @@ -58078,7 +58599,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD if (valid8 && void 0 !== data.skip_reason) { let data9 = data.skip_reason; if (errors == errors) { - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate267.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate270.errors = [ { instancePath: instancePath + "/skip_reason", schemaPath: "#/properties/skip_reason/type", keyword: "type", @@ -58086,7 +58607,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data9.message) return validate267.errors = [ { + if (void 0 === data9.message) return validate270.errors = [ { instancePath: instancePath + "/skip_reason", schemaPath: "#/properties/skip_reason/required", keyword: "required", @@ -58094,7 +58615,7 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "message" } } ], !1; - if (void 0 !== data9.message && "string" != typeof data9.message) return validate267.errors = [ { + if (void 0 !== data9.message && "string" != typeof data9.message) return validate270.errors = [ { instancePath: instancePath + "/skip_reason/message", schemaPath: "#/properties/skip_reason/properties/message/type", keyword: "type", @@ -58109,21 +58630,21 @@ function validate267(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate267.errors = vErrors, 0 === errors; + return validate270.errors = vErrors, 0 === errors; } -validate267.evaluated = { +validate270.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_Go2rtcEffectiveConfiguration = validate269; +var __contract_Go2rtcEffectiveConfiguration = validate272; -function validate269(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate269.evaluated; +function validate272(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate272.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate269.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -58131,7 +58652,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.go2rtc_initialized) return validate269.errors = [ { + if (void 0 === data.go2rtc_initialized) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58139,7 +58660,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "go2rtc_initialized" } } ], !1; - if (void 0 === data.redaction_available) return validate269.errors = [ { + if (void 0 === data.redaction_available) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58147,7 +58668,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "redaction_available" } } ], !1; - if (void 0 === data.base) return validate269.errors = [ { + if (void 0 === data.base) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58155,7 +58676,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "base" } } ], !1; - if (void 0 === data.override) return validate269.errors = [ { + if (void 0 === data.override) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58163,7 +58684,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "override" } } ], !1; - if (void 0 === data.managed_listener_policy) return validate269.errors = [ { + if (void 0 === data.managed_listener_policy) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58171,7 +58692,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "managed_listener_policy" } } ], !1; - if (void 0 === data.merged_source_order) return validate269.errors = [ { + if (void 0 === data.merged_source_order) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58179,7 +58700,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "merged_source_order" } } ], !1; - if (void 0 === data.override_in_use) return validate269.errors = [ { + if (void 0 === data.override_in_use) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58187,7 +58708,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "override_in_use" } } ], !1; - if (void 0 === data.warnings) return validate269.errors = [ { + if (void 0 === data.warnings) return validate272.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58197,7 +58718,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.go2rtc_initialized) { let _errs2 = 0; - if ("boolean" != typeof data.go2rtc_initialized) return validate269.errors = [ { + if ("boolean" != typeof data.go2rtc_initialized) return validate272.errors = [ { instancePath: instancePath + "/go2rtc_initialized", schemaPath: "#/properties/go2rtc_initialized/type", keyword: "type", @@ -58210,7 +58731,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.redaction_available) { let _errs4 = 0; - if ("boolean" != typeof data.redaction_available) return validate269.errors = [ { + if ("boolean" != typeof data.redaction_available) return validate272.errors = [ { instancePath: instancePath + "/redaction_available", schemaPath: "#/properties/redaction_available/type", keyword: "type", @@ -58223,7 +58744,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.base) { let _errs6 = 0; - if ("string" != typeof data.base) return validate269.errors = [ { + if ("string" != typeof data.base) return validate272.errors = [ { instancePath: instancePath + "/base", schemaPath: "#/properties/base/type", keyword: "type", @@ -58236,7 +58757,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.override) { let _errs8 = 0; - if ("string" != typeof data.override) return validate269.errors = [ { + if ("string" != typeof data.override) return validate272.errors = [ { instancePath: instancePath + "/override", schemaPath: "#/properties/override/type", keyword: "type", @@ -58249,7 +58770,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.managed_listener_policy) { let _errs10 = 0; - if ("string" != typeof data.managed_listener_policy) return validate269.errors = [ { + if ("string" != typeof data.managed_listener_policy) return validate272.errors = [ { instancePath: instancePath + "/managed_listener_policy", schemaPath: "#/properties/managed_listener_policy/type", keyword: "type", @@ -58263,7 +58784,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.merged_source_order) { let data5 = data.merged_source_order, _errs12 = 0; if (0 === _errs12) { - if (!Array.isArray(data5)) return validate269.errors = [ { + if (!Array.isArray(data5)) return validate272.errors = [ { instancePath: instancePath + "/merged_source_order", schemaPath: "#/properties/merged_source_order/type", keyword: "type", @@ -58275,7 +58796,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD let len0 = data5.length; for (let i0 = 0; i0 < len0; i0++) { let _errs14 = 0; - if ("string" != typeof data5[i0]) return validate269.errors = [ { + if ("string" != typeof data5[i0]) return validate272.errors = [ { instancePath: instancePath + "/merged_source_order/" + i0, schemaPath: "#/properties/merged_source_order/items/type", keyword: "type", @@ -58292,7 +58813,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.override_in_use) { let _errs16 = 0; - if ("boolean" != typeof data.override_in_use) return validate269.errors = [ { + if ("boolean" != typeof data.override_in_use) return validate272.errors = [ { instancePath: instancePath + "/override_in_use", schemaPath: "#/properties/override_in_use/type", keyword: "type", @@ -58304,7 +58825,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD } else valid7 = !0; if (valid7 && void 0 !== data.warnings) { let data8 = data.warnings; - if (!Array.isArray(data8)) return validate269.errors = [ { + if (!Array.isArray(data8)) return validate272.errors = [ { instancePath: instancePath + "/warnings", schemaPath: "#/properties/warnings/type", keyword: "type", @@ -58316,7 +58837,7 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD let len1 = data8.length; for (let i1 = 0; i1 < len1; i1++) { let _errs20 = 0; - if ("string" != typeof data8[i1]) return validate269.errors = [ { + if ("string" != typeof data8[i1]) return validate272.errors = [ { instancePath: instancePath + "/warnings/" + i1, schemaPath: "#/properties/warnings/items/type", keyword: "type", @@ -58334,21 +58855,21 @@ function validate269(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate269.errors = [], !0; + return validate272.errors = [], !0; } -validate269.evaluated = { +validate272.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_Go2rtcOverrideDiagnostics = validate270; +var __contract_Go2rtcOverrideDiagnostics = validate273; -function validate270(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate270.evaluated; +function validate273(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate273.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate270.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -58356,7 +58877,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.expected_file_path) return validate270.errors = [ { + if (void 0 === data.expected_file_path) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58364,7 +58885,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "expected_file_path" } } ], !1; - if (void 0 === data.db_bytes) return validate270.errors = [ { + if (void 0 === data.db_bytes) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58372,7 +58893,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "db_bytes" } } ], !1; - if (void 0 === data.file_present) return validate270.errors = [ { + if (void 0 === data.file_present) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58380,7 +58901,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "file_present" } } ], !1; - if (void 0 === data.file_bytes) return validate270.errors = [ { + if (void 0 === data.file_bytes) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58388,7 +58909,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "file_bytes" } } ], !1; - if (void 0 === data.content_matches) return validate270.errors = [ { + if (void 0 === data.content_matches) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58396,7 +58917,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "content_matches" } } ], !1; - if (void 0 === data.quarantine_active) return validate270.errors = [ { + if (void 0 === data.quarantine_active) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58404,7 +58925,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "quarantine_active" } } ], !1; - if (void 0 === data.process_state_known) return validate270.errors = [ { + if (void 0 === data.process_state_known) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58412,7 +58933,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "process_state_known" } } ], !1; - if (void 0 === data.process_running) return validate270.errors = [ { + if (void 0 === data.process_running) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58420,7 +58941,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "process_running" } } ], !1; - if (void 0 === data.process_pid) return validate270.errors = [ { + if (void 0 === data.process_pid) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58428,7 +58949,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "process_pid" } } ], !1; - if (void 0 === data.quarantined_copy_bytes) return validate270.errors = [ { + if (void 0 === data.quarantined_copy_bytes) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58436,7 +58957,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "quarantined_copy_bytes" } } ], !1; - if (void 0 === data.validated_version) return validate270.errors = [ { + if (void 0 === data.validated_version) return validate273.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58446,7 +58967,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.expected_file_path) { let _errs2 = 0; - if ("string" != typeof data.expected_file_path) return validate270.errors = [ { + if ("string" != typeof data.expected_file_path) return validate273.errors = [ { instancePath: instancePath + "/expected_file_path", schemaPath: "#/properties/expected_file_path/type", keyword: "type", @@ -58459,7 +58980,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.db_bytes) { let data1 = data.db_bytes, _errs4 = 0; - if ("number" != typeof data1 || !isFinite(data1)) return validate270.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate273.errors = [ { instancePath: instancePath + "/db_bytes", schemaPath: "#/properties/db_bytes/type", keyword: "type", @@ -58472,7 +58993,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.file_present) { let _errs6 = 0; - if ("boolean" != typeof data.file_present) return validate270.errors = [ { + if ("boolean" != typeof data.file_present) return validate273.errors = [ { instancePath: instancePath + "/file_present", schemaPath: "#/properties/file_present/type", keyword: "type", @@ -58485,7 +59006,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.file_bytes) { let data3 = data.file_bytes, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate270.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate273.errors = [ { instancePath: instancePath + "/file_bytes", schemaPath: "#/properties/file_bytes/type", keyword: "type", @@ -58498,7 +59019,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.content_matches) { let _errs10 = 0; - if ("boolean" != typeof data.content_matches) return validate270.errors = [ { + if ("boolean" != typeof data.content_matches) return validate273.errors = [ { instancePath: instancePath + "/content_matches", schemaPath: "#/properties/content_matches/type", keyword: "type", @@ -58511,7 +59032,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.quarantine_active) { let _errs12 = 0; - if ("boolean" != typeof data.quarantine_active) return validate270.errors = [ { + if ("boolean" != typeof data.quarantine_active) return validate273.errors = [ { instancePath: instancePath + "/quarantine_active", schemaPath: "#/properties/quarantine_active/type", keyword: "type", @@ -58524,7 +59045,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.quarantine_reason) { let _errs14 = 0; - if ("string" != typeof data.quarantine_reason) return validate270.errors = [ { + if ("string" != typeof data.quarantine_reason) return validate273.errors = [ { instancePath: instancePath + "/quarantine_reason", schemaPath: "#/properties/quarantine_reason/type", keyword: "type", @@ -58537,7 +59058,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.process_state_known) { let _errs16 = 0; - if ("boolean" != typeof data.process_state_known) return validate270.errors = [ { + if ("boolean" != typeof data.process_state_known) return validate273.errors = [ { instancePath: instancePath + "/process_state_known", schemaPath: "#/properties/process_state_known/type", keyword: "type", @@ -58550,7 +59071,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.process_running) { let _errs18 = 0; - if ("boolean" != typeof data.process_running) return validate270.errors = [ { + if ("boolean" != typeof data.process_running) return validate273.errors = [ { instancePath: instancePath + "/process_running", schemaPath: "#/properties/process_running/type", keyword: "type", @@ -58563,7 +59084,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.process_pid) { let data9 = data.process_pid, _errs20 = 0; - if ("number" != typeof data9 || !isFinite(data9)) return validate270.errors = [ { + if ("number" != typeof data9 || !isFinite(data9)) return validate273.errors = [ { instancePath: instancePath + "/process_pid", schemaPath: "#/properties/process_pid/type", keyword: "type", @@ -58576,7 +59097,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.file_read_error) { let _errs22 = 0; - if ("string" != typeof data.file_read_error) return validate270.errors = [ { + if ("string" != typeof data.file_read_error) return validate273.errors = [ { instancePath: instancePath + "/file_read_error", schemaPath: "#/properties/file_read_error/type", keyword: "type", @@ -58589,7 +59110,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.quarantined_copy_bytes) { let data11 = data.quarantined_copy_bytes, _errs24 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate270.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate273.errors = [ { instancePath: instancePath + "/quarantined_copy_bytes", schemaPath: "#/properties/quarantined_copy_bytes/type", keyword: "type", @@ -58599,7 +59120,7 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid11 = 0 === _errs24; } else valid11 = !0; - if (valid11 && void 0 !== data.validated_version && "string" != typeof data.validated_version) return validate270.errors = [ { + if (valid11 && void 0 !== data.validated_version && "string" != typeof data.validated_version) return validate273.errors = [ { instancePath: instancePath + "/validated_version", schemaPath: "#/properties/validated_version/type", keyword: "type", @@ -58618,21 +59139,21 @@ function validate270(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate270.errors = [], !0; + return validate273.errors = [], !0; } -validate270.evaluated = { +validate273.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_HlsHealth = validate283; +var __contract_HlsHealth = validate286; -function validate283(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate283.evaluated; +function validate286(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate286.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate283.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate286.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -58640,7 +59161,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.healthy) return validate283.errors = [ { + if (void 0 === data.healthy) return validate286.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58648,7 +59169,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "healthy" } } ], !1; - if (void 0 === data.status) return validate283.errors = [ { + if (void 0 === data.status) return validate286.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58656,7 +59177,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status" } } ], !1; - if (void 0 === data.watchdogRestartCount) return validate283.errors = [ { + if (void 0 === data.watchdogRestartCount) return validate286.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58664,7 +59185,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "watchdogRestartCount" } } ], !1; - if (void 0 === data.timestamp) return validate283.errors = [ { + if (void 0 === data.timestamp) return validate286.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58674,7 +59195,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.healthy) { let _errs2 = 0; - if ("boolean" != typeof data.healthy) return validate283.errors = [ { + if ("boolean" != typeof data.healthy) return validate286.errors = [ { instancePath: instancePath + "/healthy", schemaPath: "#/properties/healthy/type", keyword: "type", @@ -58687,7 +59208,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.status) { let _errs4 = 0; - if ("string" != typeof data.status) return validate283.errors = [ { + if ("string" != typeof data.status) return validate286.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -58700,7 +59221,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.watchdogRestartCount) { let data2 = data.watchdogRestartCount, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate283.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate286.errors = [ { instancePath: instancePath + "/watchdogRestartCount", schemaPath: "#/properties/watchdogRestartCount/type", keyword: "type", @@ -58710,7 +59231,7 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid2 = 0 === _errs6; } else valid2 = !0; - if (valid2 && void 0 !== data.timestamp && "string" != typeof data.timestamp) return validate283.errors = [ { + if (valid2 && void 0 !== data.timestamp && "string" != typeof data.timestamp) return validate286.errors = [ { instancePath: instancePath + "/timestamp", schemaPath: "#/properties/timestamp/type", keyword: "type", @@ -58720,22 +59241,22 @@ function validate283(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate283.errors = [], !0; + return validate286.errors = [], !0; } -validate283.evaluated = { +validate286.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_HostHealth = validate265; +var __contract_HostHealth = validate268; -function validate265(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate265.evaluated; +function validate268(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate268.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate265.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -58743,7 +59264,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.schema_version) return validate265.errors = [ { + if (void 0 === data.schema_version) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58751,7 +59272,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "schema_version" } } ], !1; - if (void 0 === data.monitoring) return validate265.errors = [ { + if (void 0 === data.monitoring) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58759,7 +59280,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "monitoring" } } ], !1; - if (void 0 === data.overall_state) return validate265.errors = [ { + if (void 0 === data.overall_state) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58767,7 +59288,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "overall_state" } } ], !1; - if (void 0 === data.visibility) return validate265.errors = [ { + if (void 0 === data.visibility) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58775,7 +59296,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "visibility" } } ], !1; - if (void 0 === data.snapshot) return validate265.errors = [ { + if (void 0 === data.snapshot) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58783,7 +59304,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "snapshot" } } ], !1; - if (void 0 === data.coverage) return validate265.errors = [ { + if (void 0 === data.coverage) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58791,7 +59312,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "coverage" } } ], !1; - if (void 0 === data.observations) return validate265.errors = [ { + if (void 0 === data.observations) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58799,7 +59320,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observations" } } ], !1; - if (void 0 === data.thresholds) return validate265.errors = [ { + if (void 0 === data.thresholds) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58807,7 +59328,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "thresholds" } } ], !1; - if (void 0 === data.active_incidents) return validate265.errors = [ { + if (void 0 === data.active_incidents) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58815,7 +59336,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "active_incidents" } } ], !1; - if (void 0 === data.recent_samples) return validate265.errors = [ { + if (void 0 === data.recent_samples) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58823,7 +59344,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recent_samples" } } ], !1; - if (void 0 === data.self_observability) return validate265.errors = [ { + if (void 0 === data.self_observability) return validate268.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -58833,7 +59354,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.schema_version) { let data0 = data.schema_version, _errs2 = errors; - if ("number" != typeof data0 || !isFinite(data0)) return validate265.errors = [ { + if ("number" != typeof data0 || !isFinite(data0)) return validate268.errors = [ { instancePath: instancePath + "/schema_version", schemaPath: "#/properties/schema_version/type", keyword: "type", @@ -58847,7 +59368,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.monitoring) { let data1 = data.monitoring, _errs4 = errors; if (errors === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate265.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate268.errors = [ { instancePath: instancePath + "/monitoring", schemaPath: "#/properties/monitoring/type", keyword: "type", @@ -58855,7 +59376,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.configured_enabled) return validate265.errors = [ { + if (void 0 === data1.configured_enabled) return validate268.errors = [ { instancePath: instancePath + "/monitoring", schemaPath: "#/properties/monitoring/required", keyword: "required", @@ -58863,7 +59384,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "configured_enabled" } } ], !1; - if (void 0 === data1.initialized) return validate265.errors = [ { + if (void 0 === data1.initialized) return validate268.errors = [ { instancePath: instancePath + "/monitoring", schemaPath: "#/properties/monitoring/required", keyword: "required", @@ -58871,7 +59392,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "initialized" } } ], !1; - if (void 0 === data1.enabled) return validate265.errors = [ { + if (void 0 === data1.enabled) return validate268.errors = [ { instancePath: instancePath + "/monitoring", schemaPath: "#/properties/monitoring/required", keyword: "required", @@ -58879,7 +59400,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data1.worker_threads) return validate265.errors = [ { + if (void 0 === data1.worker_threads) return validate268.errors = [ { instancePath: instancePath + "/monitoring", schemaPath: "#/properties/monitoring/required", keyword: "required", @@ -58887,7 +59408,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "worker_threads" } } ], !1; - if (void 0 === data1.restart_required) return validate265.errors = [ { + if (void 0 === data1.restart_required) return validate268.errors = [ { instancePath: instancePath + "/monitoring", schemaPath: "#/properties/monitoring/required", keyword: "required", @@ -58897,7 +59418,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data1.configured_enabled) { let _errs7 = errors; - if ("boolean" != typeof data1.configured_enabled) return validate265.errors = [ { + if ("boolean" != typeof data1.configured_enabled) return validate268.errors = [ { instancePath: instancePath + "/monitoring/configured_enabled", schemaPath: "#/properties/monitoring/properties/configured_enabled/type", keyword: "type", @@ -58910,7 +59431,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data1.initialized) { let _errs9 = errors; - if ("boolean" != typeof data1.initialized) return validate265.errors = [ { + if ("boolean" != typeof data1.initialized) return validate268.errors = [ { instancePath: instancePath + "/monitoring/initialized", schemaPath: "#/properties/monitoring/properties/initialized/type", keyword: "type", @@ -58923,7 +59444,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data1.enabled) { let _errs11 = errors; - if ("boolean" != typeof data1.enabled) return validate265.errors = [ { + if ("boolean" != typeof data1.enabled) return validate268.errors = [ { instancePath: instancePath + "/monitoring/enabled", schemaPath: "#/properties/monitoring/properties/enabled/type", keyword: "type", @@ -58936,7 +59457,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data1.worker_threads) { let data5 = data1.worker_threads, _errs13 = errors; - if ("number" != typeof data5 || !isFinite(data5)) return validate265.errors = [ { + if ("number" != typeof data5 || !isFinite(data5)) return validate268.errors = [ { instancePath: instancePath + "/monitoring/worker_threads", schemaPath: "#/properties/monitoring/properties/worker_threads/type", keyword: "type", @@ -58946,7 +59467,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid5 = _errs13 === errors; } else valid5 = !0; - if (valid5 && void 0 !== data1.restart_required && "boolean" != typeof data1.restart_required) return validate265.errors = [ { + if (valid5 && void 0 !== data1.restart_required && "boolean" != typeof data1.restart_required) return validate268.errors = [ { instancePath: instancePath + "/monitoring/restart_required", schemaPath: "#/properties/monitoring/properties/restart_required/type", keyword: "type", @@ -58963,7 +59484,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.overall_state) { let _errs17 = errors; - if ("string" != typeof data.overall_state) return validate265.errors = [ { + if ("string" != typeof data.overall_state) return validate268.errors = [ { instancePath: instancePath + "/overall_state", schemaPath: "#/properties/overall_state/type", keyword: "type", @@ -58977,7 +59498,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.visibility) { let data8 = data.visibility, _errs19 = errors; if (errors === _errs19) { - if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate265.errors = [ { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate268.errors = [ { instancePath: instancePath + "/visibility", schemaPath: "#/properties/visibility/type", keyword: "type", @@ -58985,7 +59506,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data8.effective_scope) return validate265.errors = [ { + if (void 0 === data8.effective_scope) return validate268.errors = [ { instancePath: instancePath + "/visibility", schemaPath: "#/properties/visibility/required", keyword: "required", @@ -58993,7 +59514,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "effective_scope" } } ], !1; - if (void 0 === data8.host_hardware_visible) return validate265.errors = [ { + if (void 0 === data8.host_hardware_visible) return validate268.errors = [ { instancePath: instancePath + "/visibility", schemaPath: "#/properties/visibility/required", keyword: "required", @@ -59001,7 +59522,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "host_hardware_visible" } } ], !1; - if (void 0 === data8.coverage_boundary) return validate265.errors = [ { + if (void 0 === data8.coverage_boundary) return validate268.errors = [ { instancePath: instancePath + "/visibility", schemaPath: "#/properties/visibility/required", keyword: "required", @@ -59011,7 +59532,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data8.effective_scope) { let _errs22 = errors; - if ("string" != typeof data8.effective_scope) return validate265.errors = [ { + if ("string" != typeof data8.effective_scope) return validate268.errors = [ { instancePath: instancePath + "/visibility/effective_scope", schemaPath: "#/properties/visibility/properties/effective_scope/type", keyword: "type", @@ -59024,7 +59545,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data8.host_hardware_visible) { let _errs24 = errors; - if ("boolean" != typeof data8.host_hardware_visible) return validate265.errors = [ { + if ("boolean" != typeof data8.host_hardware_visible) return validate268.errors = [ { instancePath: instancePath + "/visibility/host_hardware_visible", schemaPath: "#/properties/visibility/properties/host_hardware_visible/type", keyword: "type", @@ -59034,7 +59555,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid10 = _errs24 === errors; } else valid10 = !0; - if (valid10 && void 0 !== data8.coverage_boundary && "string" != typeof data8.coverage_boundary) return validate265.errors = [ { + if (valid10 && void 0 !== data8.coverage_boundary && "string" != typeof data8.coverage_boundary) return validate268.errors = [ { instancePath: instancePath + "/visibility/coverage_boundary", schemaPath: "#/properties/visibility/properties/coverage_boundary/type", keyword: "type", @@ -59050,7 +59571,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.snapshot) { let data12 = data.snapshot, _errs28 = errors; if (errors === _errs28) { - if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate265.errors = [ { + if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate268.errors = [ { instancePath: instancePath + "/snapshot", schemaPath: "#/properties/snapshot/type", keyword: "type", @@ -59058,7 +59579,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data12.available) return validate265.errors = [ { + if (void 0 === data12.available) return validate268.errors = [ { instancePath: instancePath + "/snapshot", schemaPath: "#/properties/snapshot/required", keyword: "required", @@ -59066,7 +59587,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "available" } } ], !1; - if (void 0 === data12.sequence) return validate265.errors = [ { + if (void 0 === data12.sequence) return validate268.errors = [ { instancePath: instancePath + "/snapshot", schemaPath: "#/properties/snapshot/required", keyword: "required", @@ -59074,7 +59595,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sequence" } } ], !1; - if (void 0 === data12.completed_at_ms) return validate265.errors = [ { + if (void 0 === data12.completed_at_ms) return validate268.errors = [ { instancePath: instancePath + "/snapshot", schemaPath: "#/properties/snapshot/required", keyword: "required", @@ -59082,7 +59603,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "completed_at_ms" } } ], !1; - if (void 0 === data12.age_ms) return validate265.errors = [ { + if (void 0 === data12.age_ms) return validate268.errors = [ { instancePath: instancePath + "/snapshot", schemaPath: "#/properties/snapshot/required", keyword: "required", @@ -59090,7 +59611,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "age_ms" } } ], !1; - if (void 0 === data12.freshness) return validate265.errors = [ { + if (void 0 === data12.freshness) return validate268.errors = [ { instancePath: instancePath + "/snapshot", schemaPath: "#/properties/snapshot/required", keyword: "required", @@ -59100,7 +59621,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data12.available) { let _errs31 = errors; - if ("boolean" != typeof data12.available) return validate265.errors = [ { + if ("boolean" != typeof data12.available) return validate268.errors = [ { instancePath: instancePath + "/snapshot/available", schemaPath: "#/properties/snapshot/properties/available/type", keyword: "type", @@ -59145,7 +59666,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err2), errors++, validate265.errors = vErrors, !1; + return vErrors.push(err2), errors++, validate268.errors = vErrors, !1; } errors = _errs34, vErrors.length = _errs34; var valid14 = _errs33 === errors; @@ -59185,7 +59706,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err5), errors++, validate265.errors = vErrors, !1; + return vErrors.push(err5), errors++, validate268.errors = vErrors, !1; } errors = _errs40, vErrors.length = _errs40; var valid16 = _errs39 === errors; @@ -59225,12 +59746,12 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err8), errors++, validate265.errors = vErrors, !1; + return vErrors.push(err8), errors++, validate268.errors = vErrors, !1; } errors = _errs46, vErrors.length = _errs46; var valid18 = _errs45 === errors; } else valid18 = !0; - if (valid18 && void 0 !== data12.freshness && "string" != typeof data12.freshness) return validate265.errors = [ { + if (valid18 && void 0 !== data12.freshness && "string" != typeof data12.freshness) return validate268.errors = [ { instancePath: instancePath + "/snapshot/freshness", schemaPath: "#/properties/snapshot/properties/freshness/type", keyword: "type", @@ -59248,7 +59769,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.coverage) { let data18 = data.coverage, _errs53 = errors; if (errors === _errs53) { - if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate265.errors = [ { + if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/type", keyword: "type", @@ -59256,7 +59777,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data18.complete) return validate265.errors = [ { + if (void 0 === data18.complete) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59264,7 +59785,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "complete" } } ], !1; - if (void 0 === data18.incomplete_count) return validate265.errors = [ { + if (void 0 === data18.incomplete_count) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59272,7 +59793,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "incomplete_count" } } ], !1; - if (void 0 === data18.unavailable_observations) return validate265.errors = [ { + if (void 0 === data18.unavailable_observations) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59280,7 +59801,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unavailable_observations" } } ], !1; - if (void 0 === data18.observations_dropped) return validate265.errors = [ { + if (void 0 === data18.observations_dropped) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59288,7 +59809,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observations_dropped" } } ], !1; - if (void 0 === data18.collection_errors) return validate265.errors = [ { + if (void 0 === data18.collection_errors) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59296,7 +59817,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "collection_errors" } } ], !1; - if (void 0 === data18.collection_timeouts) return validate265.errors = [ { + if (void 0 === data18.collection_timeouts) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59304,7 +59825,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "collection_timeouts" } } ], !1; - if (void 0 === data18.abandoned_helpers) return validate265.errors = [ { + if (void 0 === data18.abandoned_helpers) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59312,7 +59833,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "abandoned_helpers" } } ], !1; - if (void 0 === data18.coverage_overflows) return validate265.errors = [ { + if (void 0 === data18.coverage_overflows) return validate268.errors = [ { instancePath: instancePath + "/coverage", schemaPath: "#/properties/coverage/required", keyword: "required", @@ -59322,7 +59843,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data18.complete) { let _errs56 = errors; - if ("boolean" != typeof data18.complete) return validate265.errors = [ { + if ("boolean" != typeof data18.complete) return validate268.errors = [ { instancePath: instancePath + "/coverage/complete", schemaPath: "#/properties/coverage/properties/complete/type", keyword: "type", @@ -59335,7 +59856,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data18.incomplete_count) { let data20 = data18.incomplete_count, _errs58 = errors; - if ("number" != typeof data20 || !isFinite(data20)) return validate265.errors = [ { + if ("number" != typeof data20 || !isFinite(data20)) return validate268.errors = [ { instancePath: instancePath + "/coverage/incomplete_count", schemaPath: "#/properties/coverage/properties/incomplete_count/type", keyword: "type", @@ -59348,7 +59869,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data18.unavailable_observations) { let data21 = data18.unavailable_observations, _errs60 = errors; - if ("number" != typeof data21 || !isFinite(data21)) return validate265.errors = [ { + if ("number" != typeof data21 || !isFinite(data21)) return validate268.errors = [ { instancePath: instancePath + "/coverage/unavailable_observations", schemaPath: "#/properties/coverage/properties/unavailable_observations/type", keyword: "type", @@ -59361,7 +59882,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data18.observations_dropped) { let data22 = data18.observations_dropped, _errs62 = errors; - if ("number" != typeof data22 || !isFinite(data22)) return validate265.errors = [ { + if ("number" != typeof data22 || !isFinite(data22)) return validate268.errors = [ { instancePath: instancePath + "/coverage/observations_dropped", schemaPath: "#/properties/coverage/properties/observations_dropped/type", keyword: "type", @@ -59374,7 +59895,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data18.collection_errors) { let data23 = data18.collection_errors, _errs64 = errors; - if ("number" != typeof data23 || !isFinite(data23)) return validate265.errors = [ { + if ("number" != typeof data23 || !isFinite(data23)) return validate268.errors = [ { instancePath: instancePath + "/coverage/collection_errors", schemaPath: "#/properties/coverage/properties/collection_errors/type", keyword: "type", @@ -59387,7 +59908,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data18.collection_timeouts) { let data24 = data18.collection_timeouts, _errs66 = errors; - if ("number" != typeof data24 || !isFinite(data24)) return validate265.errors = [ { + if ("number" != typeof data24 || !isFinite(data24)) return validate268.errors = [ { instancePath: instancePath + "/coverage/collection_timeouts", schemaPath: "#/properties/coverage/properties/collection_timeouts/type", keyword: "type", @@ -59400,7 +59921,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data18.abandoned_helpers) { let data25 = data18.abandoned_helpers, _errs68 = errors; - if ("number" != typeof data25 || !isFinite(data25)) return validate265.errors = [ { + if ("number" != typeof data25 || !isFinite(data25)) return validate268.errors = [ { instancePath: instancePath + "/coverage/abandoned_helpers", schemaPath: "#/properties/coverage/properties/abandoned_helpers/type", keyword: "type", @@ -59412,7 +59933,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid28 = !0; if (valid28 && void 0 !== data18.coverage_overflows) { let data26 = data18.coverage_overflows; - if ("number" != typeof data26 || !isFinite(data26)) return validate265.errors = [ { + if ("number" != typeof data26 || !isFinite(data26)) return validate268.errors = [ { instancePath: instancePath + "/coverage/coverage_overflows", schemaPath: "#/properties/coverage/properties/coverage_overflows/type", keyword: "type", @@ -59434,7 +59955,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.observations) { let data27 = data.observations, _errs72 = errors; if (errors === _errs72) { - if (!Array.isArray(data27)) return validate265.errors = [ { + if (!Array.isArray(data27)) return validate268.errors = [ { instancePath: instancePath + "/observations", schemaPath: "#/properties/observations/type", keyword: "type", @@ -59447,7 +59968,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data28 = data27[i0], _errs74 = errors; if (errors === _errs74) { - if (!data28 || "object" != typeof data28 || Array.isArray(data28)) return validate265.errors = [ { + if (!data28 || "object" != typeof data28 || Array.isArray(data28)) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/type", keyword: "type", @@ -59455,7 +59976,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data28.metric) return validate265.errors = [ { + if (void 0 === data28.metric) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59463,7 +59984,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "metric" } } ], !1; - if (void 0 === data28.resource) return validate265.errors = [ { + if (void 0 === data28.resource) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59471,7 +59992,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "resource" } } ], !1; - if (void 0 === data28.scope) return validate265.errors = [ { + if (void 0 === data28.scope) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59479,7 +60000,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "scope" } } ], !1; - if (void 0 === data28.capability) return validate265.errors = [ { + if (void 0 === data28.capability) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59487,7 +60008,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capability" } } ], !1; - if (void 0 === data28.freshness) return validate265.errors = [ { + if (void 0 === data28.freshness) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59495,7 +60016,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "freshness" } } ], !1; - if (void 0 === data28.unit) return validate265.errors = [ { + if (void 0 === data28.unit) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59503,7 +60024,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unit" } } ], !1; - if (void 0 === data28.value) return validate265.errors = [ { + if (void 0 === data28.value) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0, schemaPath: "#/properties/observations/items/required", keyword: "required", @@ -59513,7 +60034,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data28.metric) { let _errs77 = errors; - if ("string" != typeof data28.metric) return validate265.errors = [ { + if ("string" != typeof data28.metric) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/metric", schemaPath: "#/properties/observations/items/properties/metric/type", keyword: "type", @@ -59526,7 +60047,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data28.resource) { let _errs79 = errors; - if ("string" != typeof data28.resource) return validate265.errors = [ { + if ("string" != typeof data28.resource) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/resource", schemaPath: "#/properties/observations/items/properties/resource/type", keyword: "type", @@ -59539,7 +60060,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid33) { if (void 0 !== data28.scope) { let _errs81 = errors; - if ("string" != typeof data28.scope) return validate265.errors = [ { + if ("string" != typeof data28.scope) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/scope", schemaPath: "#/properties/observations/items/properties/scope/type", keyword: "type", @@ -59552,7 +60073,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data28.capability) { let _errs83 = errors; - if ("string" != typeof data28.capability) return validate265.errors = [ { + if ("string" != typeof data28.capability) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/capability", schemaPath: "#/properties/observations/items/properties/capability/type", keyword: "type", @@ -59565,7 +60086,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data28.freshness) { let _errs85 = errors; - if ("string" != typeof data28.freshness) return validate265.errors = [ { + if ("string" != typeof data28.freshness) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/freshness", schemaPath: "#/properties/observations/items/properties/freshness/type", keyword: "type", @@ -59578,7 +60099,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid36) { if (void 0 !== data28.unit) { let _errs87 = errors; - if ("string" != typeof data28.unit) return validate265.errors = [ { + if ("string" != typeof data28.unit) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/unit", schemaPath: "#/properties/observations/items/properties/unit/type", keyword: "type", @@ -59623,7 +60144,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err11), errors++, validate265.errors = vErrors, !1; + return vErrors.push(err11), errors++, validate268.errors = vErrors, !1; } errors = _errs90, vErrors.length = _errs90; var valid38 = _errs89 === errors; @@ -59631,7 +60152,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid38) { if (void 0 !== data28.sampled_monotonic_ms) { let data36 = data28.sampled_monotonic_ms, _errs95 = errors; - if ("number" != typeof data36 || !isFinite(data36)) return validate265.errors = [ { + if ("number" != typeof data36 || !isFinite(data36)) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/sampled_monotonic_ms", schemaPath: "#/properties/observations/items/properties/sampled_monotonic_ms/type", keyword: "type", @@ -59643,7 +60164,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid40 = !0; if (valid40 && void 0 !== data28.observed_at_ms) { let data37 = data28.observed_at_ms; - if ("number" != typeof data37 || !isFinite(data37)) return validate265.errors = [ { + if ("number" != typeof data37 || !isFinite(data37)) return validate268.errors = [ { instancePath: instancePath + "/observations/" + i0 + "/observed_at_ms", schemaPath: "#/properties/observations/items/properties/observed_at_ms/type", keyword: "type", @@ -59670,7 +60191,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.thresholds) { let data38 = data.thresholds, _errs99 = errors; if (errors === _errs99) { - if (!Array.isArray(data38)) return validate265.errors = [ { + if (!Array.isArray(data38)) return validate268.errors = [ { instancePath: instancePath + "/thresholds", schemaPath: "#/properties/thresholds/type", keyword: "type", @@ -59683,7 +60204,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD for (let i1 = 0; i1 < len1; i1++) { let data39 = data38[i1], _errs101 = errors; if (errors === _errs101) { - if (!data39 || "object" != typeof data39 || Array.isArray(data39)) return validate265.errors = [ { + if (!data39 || "object" != typeof data39 || Array.isArray(data39)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/type", keyword: "type", @@ -59691,7 +60212,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data39.condition) return validate265.errors = [ { + if (void 0 === data39.condition) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59699,7 +60220,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "condition" } } ], !1; - if (void 0 === data39.enabled) return validate265.errors = [ { + if (void 0 === data39.enabled) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59707,7 +60228,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data39.overridden) return validate265.errors = [ { + if (void 0 === data39.overridden) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59715,7 +60236,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "overridden" } } ], !1; - if (void 0 === data39.direction) return validate265.errors = [ { + if (void 0 === data39.direction) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59723,7 +60244,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "direction" } } ], !1; - if (void 0 === data39.unit) return validate265.errors = [ { + if (void 0 === data39.unit) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59731,7 +60252,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unit" } } ], !1; - if (void 0 === data39.warning_threshold) return validate265.errors = [ { + if (void 0 === data39.warning_threshold) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59739,7 +60260,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "warning_threshold" } } ], !1; - if (void 0 === data39.critical_threshold) return validate265.errors = [ { + if (void 0 === data39.critical_threshold) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59747,7 +60268,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "critical_threshold" } } ], !1; - if (void 0 === data39.recovery_threshold) return validate265.errors = [ { + if (void 0 === data39.recovery_threshold) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59755,7 +60276,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recovery_threshold" } } ], !1; - if (void 0 === data39.warning_for_seconds) return validate265.errors = [ { + if (void 0 === data39.warning_for_seconds) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59763,7 +60284,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "warning_for_seconds" } } ], !1; - if (void 0 === data39.critical_for_seconds) return validate265.errors = [ { + if (void 0 === data39.critical_for_seconds) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59771,7 +60292,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "critical_for_seconds" } } ], !1; - if (void 0 === data39.recovery_for_seconds) return validate265.errors = [ { + if (void 0 === data39.recovery_for_seconds) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1, schemaPath: "#/properties/thresholds/items/required", keyword: "required", @@ -59781,7 +60302,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data39.condition) { let _errs104 = errors; - if ("string" != typeof data39.condition) return validate265.errors = [ { + if ("string" != typeof data39.condition) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/condition", schemaPath: "#/properties/thresholds/items/properties/condition/type", keyword: "type", @@ -59794,7 +60315,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid44) { if (void 0 !== data39.enabled) { let _errs106 = errors; - if ("boolean" != typeof data39.enabled) return validate265.errors = [ { + if ("boolean" != typeof data39.enabled) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/enabled", schemaPath: "#/properties/thresholds/items/properties/enabled/type", keyword: "type", @@ -59807,7 +60328,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid45) { if (void 0 !== data39.overridden) { let _errs108 = errors; - if ("boolean" != typeof data39.overridden) return validate265.errors = [ { + if ("boolean" != typeof data39.overridden) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/overridden", schemaPath: "#/properties/thresholds/items/properties/overridden/type", keyword: "type", @@ -59820,7 +60341,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid46) { if (void 0 !== data39.direction) { let _errs110 = errors; - if ("string" != typeof data39.direction) return validate265.errors = [ { + if ("string" != typeof data39.direction) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/direction", schemaPath: "#/properties/thresholds/items/properties/direction/type", keyword: "type", @@ -59833,7 +60354,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid47) { if (void 0 !== data39.unit) { let _errs112 = errors; - if ("string" != typeof data39.unit) return validate265.errors = [ { + if ("string" != typeof data39.unit) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/unit", schemaPath: "#/properties/thresholds/items/properties/unit/type", keyword: "type", @@ -59846,7 +60367,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid48) { if (void 0 !== data39.warning_threshold) { let data45 = data39.warning_threshold, _errs114 = errors; - if ("number" != typeof data45 || !isFinite(data45)) return validate265.errors = [ { + if ("number" != typeof data45 || !isFinite(data45)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/warning_threshold", schemaPath: "#/properties/thresholds/items/properties/warning_threshold/type", keyword: "type", @@ -59859,7 +60380,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid49) { if (void 0 !== data39.critical_threshold) { let data46 = data39.critical_threshold, _errs116 = errors; - if ("number" != typeof data46 || !isFinite(data46)) return validate265.errors = [ { + if ("number" != typeof data46 || !isFinite(data46)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/critical_threshold", schemaPath: "#/properties/thresholds/items/properties/critical_threshold/type", keyword: "type", @@ -59872,7 +60393,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid50) { if (void 0 !== data39.recovery_threshold) { let data47 = data39.recovery_threshold, _errs118 = errors; - if ("number" != typeof data47 || !isFinite(data47)) return validate265.errors = [ { + if ("number" != typeof data47 || !isFinite(data47)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/recovery_threshold", schemaPath: "#/properties/thresholds/items/properties/recovery_threshold/type", keyword: "type", @@ -59885,7 +60406,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid51) { if (void 0 !== data39.warning_for_seconds) { let data48 = data39.warning_for_seconds, _errs120 = errors; - if ("number" != typeof data48 || !isFinite(data48)) return validate265.errors = [ { + if ("number" != typeof data48 || !isFinite(data48)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/warning_for_seconds", schemaPath: "#/properties/thresholds/items/properties/warning_for_seconds/type", keyword: "type", @@ -59898,7 +60419,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid52) { if (void 0 !== data39.critical_for_seconds) { let data49 = data39.critical_for_seconds, _errs122 = errors; - if ("number" != typeof data49 || !isFinite(data49)) return validate265.errors = [ { + if ("number" != typeof data49 || !isFinite(data49)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/critical_for_seconds", schemaPath: "#/properties/thresholds/items/properties/critical_for_seconds/type", keyword: "type", @@ -59910,7 +60431,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid53 = !0; if (valid53 && void 0 !== data39.recovery_for_seconds) { let data50 = data39.recovery_for_seconds; - if ("number" != typeof data50 || !isFinite(data50)) return validate265.errors = [ { + if ("number" != typeof data50 || !isFinite(data50)) return validate268.errors = [ { instancePath: instancePath + "/thresholds/" + i1 + "/recovery_for_seconds", schemaPath: "#/properties/thresholds/items/properties/recovery_for_seconds/type", keyword: "type", @@ -59939,7 +60460,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.active_incidents) { let data51 = data.active_incidents, _errs126 = errors; if (errors === _errs126) { - if (!Array.isArray(data51)) return validate265.errors = [ { + if (!Array.isArray(data51)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents", schemaPath: "#/properties/active_incidents/type", keyword: "type", @@ -59952,7 +60473,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD for (let i2 = 0; i2 < len2; i2++) { let data52 = data51[i2], _errs128 = errors; if (errors === _errs128) { - if (!data52 || "object" != typeof data52 || Array.isArray(data52)) return validate265.errors = [ { + if (!data52 || "object" != typeof data52 || Array.isArray(data52)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/type", keyword: "type", @@ -59960,7 +60481,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data52.incident_id) return validate265.errors = [ { + if (void 0 === data52.incident_id) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -59968,7 +60489,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "incident_id" } } ], !1; - if (void 0 === data52.condition) return validate265.errors = [ { + if (void 0 === data52.condition) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -59976,7 +60497,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "condition" } } ], !1; - if (void 0 === data52.subject) return validate265.errors = [ { + if (void 0 === data52.subject) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -59984,7 +60505,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "subject" } } ], !1; - if (void 0 === data52.scope) return validate265.errors = [ { + if (void 0 === data52.scope) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -59992,7 +60513,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "scope" } } ], !1; - if (void 0 === data52.state) return validate265.errors = [ { + if (void 0 === data52.state) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -60000,7 +60521,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "state" } } ], !1; - if (void 0 === data52.severity) return validate265.errors = [ { + if (void 0 === data52.severity) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -60008,7 +60529,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "severity" } } ], !1; - if (void 0 === data52.first_observed_at_ms) return validate265.errors = [ { + if (void 0 === data52.first_observed_at_ms) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -60016,7 +60537,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "first_observed_at_ms" } } ], !1; - if (void 0 === data52.last_observed_at_ms) return validate265.errors = [ { + if (void 0 === data52.last_observed_at_ms) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -60024,7 +60545,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_observed_at_ms" } } ], !1; - if (void 0 === data52.persistence_pending) return validate265.errors = [ { + if (void 0 === data52.persistence_pending) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -60032,7 +60553,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "persistence_pending" } } ], !1; - if (void 0 === data52.observation) return validate265.errors = [ { + if (void 0 === data52.observation) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2, schemaPath: "#/properties/active_incidents/items/required", keyword: "required", @@ -60042,7 +60563,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data52.incident_id) { let _errs131 = errors; - if ("string" != typeof data52.incident_id) return validate265.errors = [ { + if ("string" != typeof data52.incident_id) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/incident_id", schemaPath: "#/properties/active_incidents/items/properties/incident_id/type", keyword: "type", @@ -60055,7 +60576,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid57) { if (void 0 !== data52.condition) { let _errs133 = errors; - if ("string" != typeof data52.condition) return validate265.errors = [ { + if ("string" != typeof data52.condition) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/condition", schemaPath: "#/properties/active_incidents/items/properties/condition/type", keyword: "type", @@ -60068,7 +60589,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid58) { if (void 0 !== data52.subject) { let _errs135 = errors; - if ("string" != typeof data52.subject) return validate265.errors = [ { + if ("string" != typeof data52.subject) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/subject", schemaPath: "#/properties/active_incidents/items/properties/subject/type", keyword: "type", @@ -60081,7 +60602,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid59) { if (void 0 !== data52.scope) { let _errs137 = errors; - if ("string" != typeof data52.scope) return validate265.errors = [ { + if ("string" != typeof data52.scope) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/scope", schemaPath: "#/properties/active_incidents/items/properties/scope/type", keyword: "type", @@ -60094,7 +60615,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid60) { if (void 0 !== data52.state) { let _errs139 = errors; - if ("string" != typeof data52.state) return validate265.errors = [ { + if ("string" != typeof data52.state) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/state", schemaPath: "#/properties/active_incidents/items/properties/state/type", keyword: "type", @@ -60107,7 +60628,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid61) { if (void 0 !== data52.severity) { let _errs141 = errors; - if ("string" != typeof data52.severity) return validate265.errors = [ { + if ("string" != typeof data52.severity) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/severity", schemaPath: "#/properties/active_incidents/items/properties/severity/type", keyword: "type", @@ -60120,7 +60641,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid62) { if (void 0 !== data52.first_observed_at_ms) { let data59 = data52.first_observed_at_ms, _errs143 = errors; - if ("number" != typeof data59 || !isFinite(data59)) return validate265.errors = [ { + if ("number" != typeof data59 || !isFinite(data59)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/first_observed_at_ms", schemaPath: "#/properties/active_incidents/items/properties/first_observed_at_ms/type", keyword: "type", @@ -60133,7 +60654,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid63) { if (void 0 !== data52.last_observed_at_ms) { let data60 = data52.last_observed_at_ms, _errs145 = errors; - if ("number" != typeof data60 || !isFinite(data60)) return validate265.errors = [ { + if ("number" != typeof data60 || !isFinite(data60)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/last_observed_at_ms", schemaPath: "#/properties/active_incidents/items/properties/last_observed_at_ms/type", keyword: "type", @@ -60146,7 +60667,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid64) { if (void 0 !== data52.persistence_pending) { let _errs147 = errors; - if ("boolean" != typeof data52.persistence_pending) return validate265.errors = [ { + if ("boolean" != typeof data52.persistence_pending) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/persistence_pending", schemaPath: "#/properties/active_incidents/items/properties/persistence_pending/type", keyword: "type", @@ -60160,7 +60681,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data52.observation) { let data62 = data52.observation, _errs149 = errors; if (errors === _errs149) { - if (!data62 || "object" != typeof data62 || Array.isArray(data62)) return validate265.errors = [ { + if (!data62 || "object" != typeof data62 || Array.isArray(data62)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/type", keyword: "type", @@ -60168,7 +60689,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data62.metric) return validate265.errors = [ { + if (void 0 === data62.metric) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60176,7 +60697,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "metric" } } ], !1; - if (void 0 === data62.resource) return validate265.errors = [ { + if (void 0 === data62.resource) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60184,7 +60705,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "resource" } } ], !1; - if (void 0 === data62.scope) return validate265.errors = [ { + if (void 0 === data62.scope) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60192,7 +60713,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "scope" } } ], !1; - if (void 0 === data62.capability) return validate265.errors = [ { + if (void 0 === data62.capability) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60200,7 +60721,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capability" } } ], !1; - if (void 0 === data62.freshness) return validate265.errors = [ { + if (void 0 === data62.freshness) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60208,7 +60729,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "freshness" } } ], !1; - if (void 0 === data62.unit) return validate265.errors = [ { + if (void 0 === data62.unit) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60216,7 +60737,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unit" } } ], !1; - if (void 0 === data62.value) return validate265.errors = [ { + if (void 0 === data62.value) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation", schemaPath: "#/properties/active_incidents/items/properties/observation/required", keyword: "required", @@ -60226,7 +60747,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data62.metric) { let _errs152 = errors; - if ("string" != typeof data62.metric) return validate265.errors = [ { + if ("string" != typeof data62.metric) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/metric", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/metric/type", keyword: "type", @@ -60239,7 +60760,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid67) { if (void 0 !== data62.resource) { let _errs154 = errors; - if ("string" != typeof data62.resource) return validate265.errors = [ { + if ("string" != typeof data62.resource) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/resource", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/resource/type", keyword: "type", @@ -60252,7 +60773,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid68) { if (void 0 !== data62.scope) { let _errs156 = errors; - if ("string" != typeof data62.scope) return validate265.errors = [ { + if ("string" != typeof data62.scope) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/scope", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/scope/type", keyword: "type", @@ -60265,7 +60786,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid69) { if (void 0 !== data62.capability) { let _errs158 = errors; - if ("string" != typeof data62.capability) return validate265.errors = [ { + if ("string" != typeof data62.capability) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/capability", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/capability/type", keyword: "type", @@ -60278,7 +60799,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid70) { if (void 0 !== data62.freshness) { let _errs160 = errors; - if ("string" != typeof data62.freshness) return validate265.errors = [ { + if ("string" != typeof data62.freshness) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/freshness", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/freshness/type", keyword: "type", @@ -60291,7 +60812,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid71) { if (void 0 !== data62.unit) { let _errs162 = errors; - if ("string" != typeof data62.unit) return validate265.errors = [ { + if ("string" != typeof data62.unit) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/unit", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/unit/type", keyword: "type", @@ -60336,7 +60857,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err14), errors++, validate265.errors = vErrors, !1; + return vErrors.push(err14), errors++, validate268.errors = vErrors, !1; } errors = _errs165, vErrors.length = _errs165; var valid73 = _errs164 === errors; @@ -60344,7 +60865,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid73) { if (void 0 !== data62.sampled_monotonic_ms) { let data70 = data62.sampled_monotonic_ms, _errs170 = errors; - if ("number" != typeof data70 || !isFinite(data70)) return validate265.errors = [ { + if ("number" != typeof data70 || !isFinite(data70)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/sampled_monotonic_ms", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/sampled_monotonic_ms/type", keyword: "type", @@ -60356,7 +60877,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid75 = !0; if (valid75 && void 0 !== data62.observed_at_ms) { let data71 = data62.observed_at_ms; - if ("number" != typeof data71 || !isFinite(data71)) return validate265.errors = [ { + if ("number" != typeof data71 || !isFinite(data71)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/observation/observed_at_ms", schemaPath: "#/properties/active_incidents/items/properties/observation/properties/observed_at_ms/type", keyword: "type", @@ -60378,7 +60899,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid66 && void 0 !== data52.thresholds) { let data72 = data52.thresholds; if (errors == errors) { - if (!data72 || "object" != typeof data72 || Array.isArray(data72)) return validate265.errors = [ { + if (!data72 || "object" != typeof data72 || Array.isArray(data72)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/type", keyword: "type", @@ -60386,7 +60907,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data72.condition) return validate265.errors = [ { + if (void 0 === data72.condition) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60394,7 +60915,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "condition" } } ], !1; - if (void 0 === data72.enabled) return validate265.errors = [ { + if (void 0 === data72.enabled) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60402,7 +60923,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data72.overridden) return validate265.errors = [ { + if (void 0 === data72.overridden) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60410,7 +60931,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "overridden" } } ], !1; - if (void 0 === data72.direction) return validate265.errors = [ { + if (void 0 === data72.direction) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60418,7 +60939,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "direction" } } ], !1; - if (void 0 === data72.unit) return validate265.errors = [ { + if (void 0 === data72.unit) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60426,7 +60947,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unit" } } ], !1; - if (void 0 === data72.warning_threshold) return validate265.errors = [ { + if (void 0 === data72.warning_threshold) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60434,7 +60955,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "warning_threshold" } } ], !1; - if (void 0 === data72.critical_threshold) return validate265.errors = [ { + if (void 0 === data72.critical_threshold) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60442,7 +60963,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "critical_threshold" } } ], !1; - if (void 0 === data72.recovery_threshold) return validate265.errors = [ { + if (void 0 === data72.recovery_threshold) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60450,7 +60971,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recovery_threshold" } } ], !1; - if (void 0 === data72.warning_for_seconds) return validate265.errors = [ { + if (void 0 === data72.warning_for_seconds) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60458,7 +60979,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "warning_for_seconds" } } ], !1; - if (void 0 === data72.critical_for_seconds) return validate265.errors = [ { + if (void 0 === data72.critical_for_seconds) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60466,7 +60987,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "critical_for_seconds" } } ], !1; - if (void 0 === data72.recovery_for_seconds) return validate265.errors = [ { + if (void 0 === data72.recovery_for_seconds) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/required", keyword: "required", @@ -60476,7 +60997,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data72.condition) { let _errs177 = errors; - if ("string" != typeof data72.condition) return validate265.errors = [ { + if ("string" != typeof data72.condition) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/condition", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/condition/type", keyword: "type", @@ -60489,7 +61010,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid78) { if (void 0 !== data72.enabled) { let _errs179 = errors; - if ("boolean" != typeof data72.enabled) return validate265.errors = [ { + if ("boolean" != typeof data72.enabled) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/enabled", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/enabled/type", keyword: "type", @@ -60502,7 +61023,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid79) { if (void 0 !== data72.overridden) { let _errs181 = errors; - if ("boolean" != typeof data72.overridden) return validate265.errors = [ { + if ("boolean" != typeof data72.overridden) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/overridden", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/overridden/type", keyword: "type", @@ -60515,7 +61036,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid80) { if (void 0 !== data72.direction) { let _errs183 = errors; - if ("string" != typeof data72.direction) return validate265.errors = [ { + if ("string" != typeof data72.direction) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/direction", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/direction/type", keyword: "type", @@ -60528,7 +61049,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid81) { if (void 0 !== data72.unit) { let _errs185 = errors; - if ("string" != typeof data72.unit) return validate265.errors = [ { + if ("string" != typeof data72.unit) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/unit", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/unit/type", keyword: "type", @@ -60541,7 +61062,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid82) { if (void 0 !== data72.warning_threshold) { let data78 = data72.warning_threshold, _errs187 = errors; - if ("number" != typeof data78 || !isFinite(data78)) return validate265.errors = [ { + if ("number" != typeof data78 || !isFinite(data78)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/warning_threshold", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/warning_threshold/type", keyword: "type", @@ -60554,7 +61075,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid83) { if (void 0 !== data72.critical_threshold) { let data79 = data72.critical_threshold, _errs189 = errors; - if ("number" != typeof data79 || !isFinite(data79)) return validate265.errors = [ { + if ("number" != typeof data79 || !isFinite(data79)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/critical_threshold", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/critical_threshold/type", keyword: "type", @@ -60567,7 +61088,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid84) { if (void 0 !== data72.recovery_threshold) { let data80 = data72.recovery_threshold, _errs191 = errors; - if ("number" != typeof data80 || !isFinite(data80)) return validate265.errors = [ { + if ("number" != typeof data80 || !isFinite(data80)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/recovery_threshold", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/recovery_threshold/type", keyword: "type", @@ -60580,7 +61101,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid85) { if (void 0 !== data72.warning_for_seconds) { let data81 = data72.warning_for_seconds, _errs193 = errors; - if ("number" != typeof data81 || !isFinite(data81)) return validate265.errors = [ { + if ("number" != typeof data81 || !isFinite(data81)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/warning_for_seconds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/warning_for_seconds/type", keyword: "type", @@ -60593,7 +61114,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid86) { if (void 0 !== data72.critical_for_seconds) { let data82 = data72.critical_for_seconds, _errs195 = errors; - if ("number" != typeof data82 || !isFinite(data82)) return validate265.errors = [ { + if ("number" != typeof data82 || !isFinite(data82)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/critical_for_seconds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/critical_for_seconds/type", keyword: "type", @@ -60605,7 +61126,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid87 = !0; if (valid87 && void 0 !== data72.recovery_for_seconds) { let data83 = data72.recovery_for_seconds; - if ("number" != typeof data83 || !isFinite(data83)) return validate265.errors = [ { + if ("number" != typeof data83 || !isFinite(data83)) return validate268.errors = [ { instancePath: instancePath + "/active_incidents/" + i2 + "/thresholds/recovery_for_seconds", schemaPath: "#/properties/active_incidents/items/properties/thresholds/properties/recovery_for_seconds/type", keyword: "type", @@ -60645,7 +61166,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.recent_samples) { let data84 = data.recent_samples, _errs199 = errors; if (errors === _errs199) { - if (!Array.isArray(data84)) return validate265.errors = [ { + if (!Array.isArray(data84)) return validate268.errors = [ { instancePath: instancePath + "/recent_samples", schemaPath: "#/properties/recent_samples/type", keyword: "type", @@ -60658,7 +61179,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD for (let i3 = 0; i3 < len3; i3++) { let data85 = data84[i3], _errs201 = errors; if (errors === _errs201) { - if (!data85 || "object" != typeof data85 || Array.isArray(data85)) return validate265.errors = [ { + if (!data85 || "object" != typeof data85 || Array.isArray(data85)) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3, schemaPath: "#/properties/recent_samples/items/type", keyword: "type", @@ -60666,7 +61187,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data85.sequence) return validate265.errors = [ { + if (void 0 === data85.sequence) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3, schemaPath: "#/properties/recent_samples/items/required", keyword: "required", @@ -60674,7 +61195,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sequence" } } ], !1; - if (void 0 === data85.completed_at_ms) return validate265.errors = [ { + if (void 0 === data85.completed_at_ms) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3, schemaPath: "#/properties/recent_samples/items/required", keyword: "required", @@ -60682,7 +61203,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "completed_at_ms" } } ], !1; - if (void 0 === data85.observation_count) return validate265.errors = [ { + if (void 0 === data85.observation_count) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3, schemaPath: "#/properties/recent_samples/items/required", keyword: "required", @@ -60690,7 +61211,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observation_count" } } ], !1; - if (void 0 === data85.observations_dropped) return validate265.errors = [ { + if (void 0 === data85.observations_dropped) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3, schemaPath: "#/properties/recent_samples/items/required", keyword: "required", @@ -60700,7 +61221,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data85.sequence) { let data86 = data85.sequence, _errs204 = errors; - if ("number" != typeof data86 || !isFinite(data86)) return validate265.errors = [ { + if ("number" != typeof data86 || !isFinite(data86)) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3 + "/sequence", schemaPath: "#/properties/recent_samples/items/properties/sequence/type", keyword: "type", @@ -60713,7 +61234,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid91) { if (void 0 !== data85.completed_at_ms) { let data87 = data85.completed_at_ms, _errs206 = errors; - if ("number" != typeof data87 || !isFinite(data87)) return validate265.errors = [ { + if ("number" != typeof data87 || !isFinite(data87)) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3 + "/completed_at_ms", schemaPath: "#/properties/recent_samples/items/properties/completed_at_ms/type", keyword: "type", @@ -60726,7 +61247,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid92) { if (void 0 !== data85.observation_count) { let data88 = data85.observation_count, _errs208 = errors; - if ("number" != typeof data88 || !isFinite(data88)) return validate265.errors = [ { + if ("number" != typeof data88 || !isFinite(data88)) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3 + "/observation_count", schemaPath: "#/properties/recent_samples/items/properties/observation_count/type", keyword: "type", @@ -60738,7 +61259,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid93 = !0; if (valid93 && void 0 !== data85.observations_dropped) { let data89 = data85.observations_dropped; - if ("number" != typeof data89 || !isFinite(data89)) return validate265.errors = [ { + if ("number" != typeof data89 || !isFinite(data89)) return validate268.errors = [ { instancePath: instancePath + "/recent_samples/" + i3 + "/observations_dropped", schemaPath: "#/properties/recent_samples/items/properties/observations_dropped/type", keyword: "type", @@ -60760,7 +61281,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.self_observability) { let data90 = data.self_observability, _errs212 = errors; if (errors === _errs212) { - if (!data90 || "object" != typeof data90 || Array.isArray(data90)) return validate265.errors = [ { + if (!data90 || "object" != typeof data90 || Array.isArray(data90)) return validate268.errors = [ { instancePath: instancePath + "/self_observability", schemaPath: "#/properties/self_observability/type", keyword: "type", @@ -60768,7 +61289,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data90.collectors) return validate265.errors = [ { + if (void 0 === data90.collectors) return validate268.errors = [ { instancePath: instancePath + "/self_observability", schemaPath: "#/properties/self_observability/required", keyword: "required", @@ -60776,7 +61297,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "collectors" } } ], !1; - if (void 0 === data90.coverage_overflows) return validate265.errors = [ { + if (void 0 === data90.coverage_overflows) return validate268.errors = [ { instancePath: instancePath + "/self_observability", schemaPath: "#/properties/self_observability/required", keyword: "required", @@ -60784,7 +61305,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "coverage_overflows" } } ], !1; - if (void 0 === data90.incident_persistence) return validate265.errors = [ { + if (void 0 === data90.incident_persistence) return validate268.errors = [ { instancePath: instancePath + "/self_observability", schemaPath: "#/properties/self_observability/required", keyword: "required", @@ -60792,7 +61313,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "incident_persistence" } } ], !1; - if (void 0 === data90.event_delivery) return validate265.errors = [ { + if (void 0 === data90.event_delivery) return validate268.errors = [ { instancePath: instancePath + "/self_observability", schemaPath: "#/properties/self_observability/required", keyword: "required", @@ -60803,7 +61324,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data90.collectors) { let data91 = data90.collectors, _errs215 = errors; if (errors === _errs215) { - if (!Array.isArray(data91)) return validate265.errors = [ { + if (!Array.isArray(data91)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors", schemaPath: "#/properties/self_observability/properties/collectors/type", keyword: "type", @@ -60816,7 +61337,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD for (let i4 = 0; i4 < len4; i4++) { let data92 = data91[i4], _errs217 = errors; if (errors === _errs217) { - if (!data92 || "object" != typeof data92 || Array.isArray(data92)) return validate265.errors = [ { + if (!data92 || "object" != typeof data92 || Array.isArray(data92)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/type", keyword: "type", @@ -60824,7 +61345,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data92.collector) return validate265.errors = [ { + if (void 0 === data92.collector) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60832,7 +61353,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "collector" } } ], !1; - if (void 0 === data92.scope) return validate265.errors = [ { + if (void 0 === data92.scope) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60840,7 +61361,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "scope" } } ], !1; - if (void 0 === data92.tier) return validate265.errors = [ { + if (void 0 === data92.tier) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60848,7 +61369,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "tier" } } ], !1; - if (void 0 === data92.attempts) return validate265.errors = [ { + if (void 0 === data92.attempts) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60856,7 +61377,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "attempts" } } ], !1; - if (void 0 === data92.completions) return validate265.errors = [ { + if (void 0 === data92.completions) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60864,7 +61385,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "completions" } } ], !1; - if (void 0 === data92.failures) return validate265.errors = [ { + if (void 0 === data92.failures) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60872,7 +61393,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "failures" } } ], !1; - if (void 0 === data92.timeouts) return validate265.errors = [ { + if (void 0 === data92.timeouts) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60880,7 +61401,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timeouts" } } ], !1; - if (void 0 === data92.overlap_skips) return validate265.errors = [ { + if (void 0 === data92.overlap_skips) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60888,7 +61409,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "overlap_skips" } } ], !1; - if (void 0 === data92.last_duration_ms) return validate265.errors = [ { + if (void 0 === data92.last_duration_ms) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60896,7 +61417,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_duration_ms" } } ], !1; - if (void 0 === data92.maximum_duration_ms) return validate265.errors = [ { + if (void 0 === data92.maximum_duration_ms) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60904,7 +61425,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "maximum_duration_ms" } } ], !1; - if (void 0 === data92.busy) return validate265.errors = [ { + if (void 0 === data92.busy) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60912,7 +61433,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "busy" } } ], !1; - if (void 0 === data92.stale) return validate265.errors = [ { + if (void 0 === data92.stale) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4, schemaPath: "#/properties/self_observability/properties/collectors/items/required", keyword: "required", @@ -60922,7 +61443,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data92.collector) { let _errs220 = errors; - if ("string" != typeof data92.collector) return validate265.errors = [ { + if ("string" != typeof data92.collector) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/collector", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/collector/type", keyword: "type", @@ -60935,7 +61456,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid98) { if (void 0 !== data92.scope) { let _errs222 = errors; - if ("string" != typeof data92.scope) return validate265.errors = [ { + if ("string" != typeof data92.scope) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/scope", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/scope/type", keyword: "type", @@ -60948,7 +61469,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid99) { if (void 0 !== data92.tier) { let data95 = data92.tier, _errs224 = errors; - if ("number" != typeof data95 || !isFinite(data95)) return validate265.errors = [ { + if ("number" != typeof data95 || !isFinite(data95)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/tier", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/tier/type", keyword: "type", @@ -60961,7 +61482,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid100) { if (void 0 !== data92.attempts) { let data96 = data92.attempts, _errs226 = errors; - if ("number" != typeof data96 || !isFinite(data96)) return validate265.errors = [ { + if ("number" != typeof data96 || !isFinite(data96)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/attempts", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/attempts/type", keyword: "type", @@ -60974,7 +61495,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid101) { if (void 0 !== data92.completions) { let data97 = data92.completions, _errs228 = errors; - if ("number" != typeof data97 || !isFinite(data97)) return validate265.errors = [ { + if ("number" != typeof data97 || !isFinite(data97)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/completions", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/completions/type", keyword: "type", @@ -60987,7 +61508,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid102) { if (void 0 !== data92.failures) { let data98 = data92.failures, _errs230 = errors; - if ("number" != typeof data98 || !isFinite(data98)) return validate265.errors = [ { + if ("number" != typeof data98 || !isFinite(data98)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/failures", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/failures/type", keyword: "type", @@ -61000,7 +61521,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid103) { if (void 0 !== data92.timeouts) { let data99 = data92.timeouts, _errs232 = errors; - if ("number" != typeof data99 || !isFinite(data99)) return validate265.errors = [ { + if ("number" != typeof data99 || !isFinite(data99)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/timeouts", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/timeouts/type", keyword: "type", @@ -61013,7 +61534,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid104) { if (void 0 !== data92.overlap_skips) { let data100 = data92.overlap_skips, _errs234 = errors; - if ("number" != typeof data100 || !isFinite(data100)) return validate265.errors = [ { + if ("number" != typeof data100 || !isFinite(data100)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/overlap_skips", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/overlap_skips/type", keyword: "type", @@ -61026,7 +61547,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid105) { if (void 0 !== data92.last_duration_ms) { let data101 = data92.last_duration_ms, _errs236 = errors; - if ("number" != typeof data101 || !isFinite(data101)) return validate265.errors = [ { + if ("number" != typeof data101 || !isFinite(data101)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/last_duration_ms", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/last_duration_ms/type", keyword: "type", @@ -61039,7 +61560,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid106) { if (void 0 !== data92.maximum_duration_ms) { let data102 = data92.maximum_duration_ms, _errs238 = errors; - if ("number" != typeof data102 || !isFinite(data102)) return validate265.errors = [ { + if ("number" != typeof data102 || !isFinite(data102)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/maximum_duration_ms", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/maximum_duration_ms/type", keyword: "type", @@ -61052,7 +61573,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid107) { if (void 0 !== data92.busy) { let _errs240 = errors; - if ("boolean" != typeof data92.busy) return validate265.errors = [ { + if ("boolean" != typeof data92.busy) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/busy", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/busy/type", keyword: "type", @@ -61062,7 +61583,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid108 = _errs240 === errors; } else valid108 = !0; - if (valid108 && void 0 !== data92.stale && "boolean" != typeof data92.stale) return validate265.errors = [ { + if (valid108 && void 0 !== data92.stale && "boolean" != typeof data92.stale) return validate268.errors = [ { instancePath: instancePath + "/self_observability/collectors/" + i4 + "/stale", schemaPath: "#/properties/self_observability/properties/collectors/items/properties/stale/type", keyword: "type", @@ -61090,7 +61611,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid96) { if (void 0 !== data90.coverage_overflows) { let data105 = data90.coverage_overflows, _errs244 = errors; - if ("number" != typeof data105 || !isFinite(data105)) return validate265.errors = [ { + if ("number" != typeof data105 || !isFinite(data105)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/coverage_overflows", schemaPath: "#/properties/self_observability/properties/coverage_overflows/type", keyword: "type", @@ -61104,7 +61625,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data90.incident_persistence) { let data106 = data90.incident_persistence, _errs246 = errors; if (errors === _errs246) { - if (!data106 || "object" != typeof data106 || Array.isArray(data106)) return validate265.errors = [ { + if (!data106 || "object" != typeof data106 || Array.isArray(data106)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/incident_persistence", schemaPath: "#/properties/self_observability/properties/incident_persistence/type", keyword: "type", @@ -61114,7 +61635,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data106.pending) { let data107 = data106.pending, _errs249 = errors; - if ("number" != typeof data107 || !isFinite(data107)) return validate265.errors = [ { + if ("number" != typeof data107 || !isFinite(data107)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/incident_persistence/pending", schemaPath: "#/properties/self_observability/properties/incident_persistence/properties/pending/type", keyword: "type", @@ -61127,7 +61648,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid112) { if (void 0 !== data106.failures) { let data108 = data106.failures, _errs251 = errors; - if ("number" != typeof data108 || !isFinite(data108)) return validate265.errors = [ { + if ("number" != typeof data108 || !isFinite(data108)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/incident_persistence/failures", schemaPath: "#/properties/self_observability/properties/incident_persistence/properties/failures/type", keyword: "type", @@ -61139,7 +61660,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid113 = !0; if (valid113 && void 0 !== data106.retries) { let data109 = data106.retries; - if ("number" != typeof data109 || !isFinite(data109)) return validate265.errors = [ { + if ("number" != typeof data109 || !isFinite(data109)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/incident_persistence/retries", schemaPath: "#/properties/self_observability/properties/incident_persistence/properties/retries/type", keyword: "type", @@ -61155,7 +61676,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid111 && void 0 !== data90.event_delivery) { let data110 = data90.event_delivery; if (errors == errors) { - if (!data110 || "object" != typeof data110 || Array.isArray(data110)) return validate265.errors = [ { + if (!data110 || "object" != typeof data110 || Array.isArray(data110)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery", schemaPath: "#/properties/self_observability/properties/event_delivery/type", keyword: "type", @@ -61165,7 +61686,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data110.degraded) { let _errs258 = errors; - if ("boolean" != typeof data110.degraded) return validate265.errors = [ { + if ("boolean" != typeof data110.degraded) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery/degraded", schemaPath: "#/properties/self_observability/properties/event_delivery/properties/degraded/type", keyword: "type", @@ -61178,7 +61699,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid116) { if (void 0 !== data110.circular_report_path) { let _errs260 = errors; - if ("boolean" != typeof data110.circular_report_path) return validate265.errors = [ { + if ("boolean" != typeof data110.circular_report_path) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery/circular_report_path", schemaPath: "#/properties/self_observability/properties/event_delivery/properties/circular_report_path/type", keyword: "type", @@ -61191,7 +61712,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid117) { if (void 0 !== data110.outbox_full) { let _errs262 = errors; - if ("boolean" != typeof data110.outbox_full) return validate265.errors = [ { + if ("boolean" != typeof data110.outbox_full) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery/outbox_full", schemaPath: "#/properties/self_observability/properties/event_delivery/properties/outbox_full/type", keyword: "type", @@ -61204,7 +61725,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid118) { if (void 0 !== data110.all_destinations_unavailable) { let _errs264 = errors; - if ("boolean" != typeof data110.all_destinations_unavailable) return validate265.errors = [ { + if ("boolean" != typeof data110.all_destinations_unavailable) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery/all_destinations_unavailable", schemaPath: "#/properties/self_observability/properties/event_delivery/properties/all_destinations_unavailable/type", keyword: "type", @@ -61217,7 +61738,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid119 && void 0 !== data110.outbox) { let data115 = data110.outbox; if (errors == errors) { - if (!data115 || "object" != typeof data115 || Array.isArray(data115)) return validate265.errors = [ { + if (!data115 || "object" != typeof data115 || Array.isArray(data115)) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery/outbox", schemaPath: "#/properties/self_observability/properties/event_delivery/properties/outbox/type", keyword: "type", @@ -61225,7 +61746,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 !== data115.available && "boolean" != typeof data115.available) return validate265.errors = [ { + if (void 0 !== data115.available && "boolean" != typeof data115.available) return validate268.errors = [ { instancePath: instancePath + "/self_observability/event_delivery/outbox/available", schemaPath: "#/properties/self_observability/properties/event_delivery/properties/outbox/properties/available/type", keyword: "type", @@ -61248,7 +61769,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid95 && void 0 !== data.evaluator) { let data117 = data.evaluator; if (errors == errors) { - if (!data117 || "object" != typeof data117 || Array.isArray(data117)) return validate265.errors = [ { + if (!data117 || "object" != typeof data117 || Array.isArray(data117)) return validate268.errors = [ { instancePath: instancePath + "/evaluator", schemaPath: "#/properties/evaluator/type", keyword: "type", @@ -61256,7 +61777,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data117.tracked_conditions) return validate265.errors = [ { + if (void 0 === data117.tracked_conditions) return validate268.errors = [ { instancePath: instancePath + "/evaluator", schemaPath: "#/properties/evaluator/required", keyword: "required", @@ -61264,7 +61785,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "tracked_conditions" } } ], !1; - if (void 0 === data117.active_incidents) return validate265.errors = [ { + if (void 0 === data117.active_incidents) return validate268.errors = [ { instancePath: instancePath + "/evaluator", schemaPath: "#/properties/evaluator/required", keyword: "required", @@ -61272,7 +61793,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "active_incidents" } } ], !1; - if (void 0 === data117.pending_persistence) return validate265.errors = [ { + if (void 0 === data117.pending_persistence) return validate268.errors = [ { instancePath: instancePath + "/evaluator", schemaPath: "#/properties/evaluator/required", keyword: "required", @@ -61282,7 +61803,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data117.tracked_conditions) { let data118 = data117.tracked_conditions, _errs274 = errors; - if ("number" != typeof data118 || data118 % 1 || isNaN(data118) || !isFinite(data118)) return validate265.errors = [ { + if ("number" != typeof data118 || data118 % 1 || isNaN(data118) || !isFinite(data118)) return validate268.errors = [ { instancePath: instancePath + "/evaluator/tracked_conditions", schemaPath: "#/properties/evaluator/properties/tracked_conditions/type", keyword: "type", @@ -61295,7 +61816,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD if (valid123) { if (void 0 !== data117.active_incidents) { let data119 = data117.active_incidents, _errs276 = errors; - if ("number" != typeof data119 || data119 % 1 || isNaN(data119) || !isFinite(data119)) return validate265.errors = [ { + if ("number" != typeof data119 || data119 % 1 || isNaN(data119) || !isFinite(data119)) return validate268.errors = [ { instancePath: instancePath + "/evaluator/active_incidents", schemaPath: "#/properties/evaluator/properties/active_incidents/type", keyword: "type", @@ -61307,7 +61828,7 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } else valid124 = !0; if (valid124 && void 0 !== data117.pending_persistence) { let data120 = data117.pending_persistence; - if ("number" != typeof data120 || data120 % 1 || isNaN(data120) || !isFinite(data120)) return validate265.errors = [ { + if ("number" != typeof data120 || data120 % 1 || isNaN(data120) || !isFinite(data120)) return validate268.errors = [ { instancePath: instancePath + "/evaluator/pending_persistence", schemaPath: "#/properties/evaluator/properties/pending_persistence/type", keyword: "type", @@ -61330,22 +61851,22 @@ function validate265(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate265.errors = vErrors, 0 === errors; + return validate268.errors = vErrors, 0 === errors; } -validate265.evaluated = { +validate268.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_HostHealthIncidents = validate266; +var __contract_HostHealthIncidents = validate269; -function validate266(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate266.evaluated; +function validate269(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate269.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate266.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate269.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -61353,7 +61874,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.schema_version) return validate266.errors = [ { + if (void 0 === data.schema_version) return validate269.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -61361,7 +61882,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "schema_version" } } ], !1; - if (void 0 === data.count) return validate266.errors = [ { + if (void 0 === data.count) return validate269.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -61369,7 +61890,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "count" } } ], !1; - if (void 0 === data.incidents) return validate266.errors = [ { + if (void 0 === data.incidents) return validate269.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -61377,7 +61898,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "incidents" } } ], !1; - if (void 0 === data.next_cursor) return validate266.errors = [ { + if (void 0 === data.next_cursor) return validate269.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -61387,7 +61908,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.schema_version) { let data0 = data.schema_version, _errs2 = errors; - if ("number" != typeof data0 || !isFinite(data0)) return validate266.errors = [ { + if ("number" != typeof data0 || !isFinite(data0)) return validate269.errors = [ { instancePath: instancePath + "/schema_version", schemaPath: "#/properties/schema_version/type", keyword: "type", @@ -61400,7 +61921,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.count) { let data1 = data.count, _errs4 = errors; - if ("number" != typeof data1 || !isFinite(data1)) return validate266.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate269.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -61414,7 +61935,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.incidents) { let data2 = data.incidents, _errs6 = errors; if (errors === _errs6) { - if (!Array.isArray(data2)) return validate266.errors = [ { + if (!Array.isArray(data2)) return validate269.errors = [ { instancePath: instancePath + "/incidents", schemaPath: "#/properties/incidents/type", keyword: "type", @@ -61427,7 +61948,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data3 = data2[i0], _errs8 = errors; if (errors === _errs8) { - if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate266.errors = [ { + if (!data3 || "object" != typeof data3 || Array.isArray(data3)) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/type", keyword: "type", @@ -61435,7 +61956,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data3.incident_id) return validate266.errors = [ { + if (void 0 === data3.incident_id) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61443,7 +61964,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "incident_id" } } ], !1; - if (void 0 === data3.condition) return validate266.errors = [ { + if (void 0 === data3.condition) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61451,7 +61972,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "condition" } } ], !1; - if (void 0 === data3.subject) return validate266.errors = [ { + if (void 0 === data3.subject) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61459,7 +61980,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "subject" } } ], !1; - if (void 0 === data3.scope) return validate266.errors = [ { + if (void 0 === data3.scope) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61467,7 +61988,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "scope" } } ], !1; - if (void 0 === data3.state) return validate266.errors = [ { + if (void 0 === data3.state) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61475,7 +61996,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "state" } } ], !1; - if (void 0 === data3.severity) return validate266.errors = [ { + if (void 0 === data3.severity) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61483,7 +62004,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "severity" } } ], !1; - if (void 0 === data3.first_observed_at_ms) return validate266.errors = [ { + if (void 0 === data3.first_observed_at_ms) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61491,7 +62012,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "first_observed_at_ms" } } ], !1; - if (void 0 === data3.last_observed_at_ms) return validate266.errors = [ { + if (void 0 === data3.last_observed_at_ms) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61499,7 +62020,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_observed_at_ms" } } ], !1; - if (void 0 === data3.closed_at_ms) return validate266.errors = [ { + if (void 0 === data3.closed_at_ms) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61507,7 +62028,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "closed_at_ms" } } ], !1; - if (void 0 === data3.reconciliation) return validate266.errors = [ { + if (void 0 === data3.reconciliation) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61515,7 +62036,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "reconciliation" } } ], !1; - if (void 0 === data3.revision) return validate266.errors = [ { + if (void 0 === data3.revision) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61523,7 +62044,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data3.observation) return validate266.errors = [ { + if (void 0 === data3.observation) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0, schemaPath: "#/properties/incidents/items/required", keyword: "required", @@ -61533,7 +62054,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data3.incident_id) { let _errs11 = errors; - if ("string" != typeof data3.incident_id) return validate266.errors = [ { + if ("string" != typeof data3.incident_id) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/incident_id", schemaPath: "#/properties/incidents/items/properties/incident_id/type", keyword: "type", @@ -61546,7 +62067,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data3.condition) { let _errs13 = errors; - if ("string" != typeof data3.condition) return validate266.errors = [ { + if ("string" != typeof data3.condition) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/condition", schemaPath: "#/properties/incidents/items/properties/condition/type", keyword: "type", @@ -61559,7 +62080,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data3.subject) { let _errs15 = errors; - if ("string" != typeof data3.subject) return validate266.errors = [ { + if ("string" != typeof data3.subject) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/subject", schemaPath: "#/properties/incidents/items/properties/subject/type", keyword: "type", @@ -61572,7 +62093,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data3.scope) { let _errs17 = errors; - if ("string" != typeof data3.scope) return validate266.errors = [ { + if ("string" != typeof data3.scope) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/scope", schemaPath: "#/properties/incidents/items/properties/scope/type", keyword: "type", @@ -61585,7 +62106,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data3.state) { let _errs19 = errors; - if ("string" != typeof data3.state) return validate266.errors = [ { + if ("string" != typeof data3.state) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/state", schemaPath: "#/properties/incidents/items/properties/state/type", keyword: "type", @@ -61598,7 +62119,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data3.severity) { let _errs21 = errors; - if ("string" != typeof data3.severity) return validate266.errors = [ { + if ("string" != typeof data3.severity) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/severity", schemaPath: "#/properties/incidents/items/properties/severity/type", keyword: "type", @@ -61611,7 +62132,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data3.first_observed_at_ms) { let data10 = data3.first_observed_at_ms, _errs23 = errors; - if ("number" != typeof data10 || !isFinite(data10)) return validate266.errors = [ { + if ("number" != typeof data10 || !isFinite(data10)) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/first_observed_at_ms", schemaPath: "#/properties/incidents/items/properties/first_observed_at_ms/type", keyword: "type", @@ -61624,7 +62145,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data3.last_observed_at_ms) { let data11 = data3.last_observed_at_ms, _errs25 = errors; - if ("number" != typeof data11 || !isFinite(data11)) return validate266.errors = [ { + if ("number" != typeof data11 || !isFinite(data11)) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/last_observed_at_ms", schemaPath: "#/properties/incidents/items/properties/last_observed_at_ms/type", keyword: "type", @@ -61669,7 +62190,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err2), errors++, validate266.errors = vErrors, !1; + return vErrors.push(err2), errors++, validate269.errors = vErrors, !1; } errors = _errs28, vErrors.length = _errs28; var valid12 = _errs27 === errors; @@ -61677,7 +62198,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data3.reconciliation) { let _errs33 = errors; - if ("string" != typeof data3.reconciliation) return validate266.errors = [ { + if ("string" != typeof data3.reconciliation) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/reconciliation", schemaPath: "#/properties/incidents/items/properties/reconciliation/type", keyword: "type", @@ -61690,7 +62211,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data3.revision) { let data14 = data3.revision, _errs35 = errors; - if ("number" != typeof data14 || !isFinite(data14)) return validate266.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/revision", schemaPath: "#/properties/incidents/items/properties/revision/type", keyword: "type", @@ -61703,7 +62224,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid15 && void 0 !== data3.observation) { let data15 = data3.observation; if (errors == errors) { - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate266.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/type", keyword: "type", @@ -61711,7 +62232,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data15.metric) return validate266.errors = [ { + if (void 0 === data15.metric) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/required", keyword: "required", @@ -61719,7 +62240,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "metric" } } ], !1; - if (void 0 === data15.resource) return validate266.errors = [ { + if (void 0 === data15.resource) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/required", keyword: "required", @@ -61727,7 +62248,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "resource" } } ], !1; - if (void 0 === data15.scope) return validate266.errors = [ { + if (void 0 === data15.scope) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/required", keyword: "required", @@ -61735,7 +62256,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "scope" } } ], !1; - if (void 0 === data15.capability) return validate266.errors = [ { + if (void 0 === data15.capability) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/required", keyword: "required", @@ -61743,7 +62264,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capability" } } ], !1; - if (void 0 === data15.unit) return validate266.errors = [ { + if (void 0 === data15.unit) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/required", keyword: "required", @@ -61751,7 +62272,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "unit" } } ], !1; - if (void 0 === data15.value) return validate266.errors = [ { + if (void 0 === data15.value) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation", schemaPath: "#/properties/incidents/items/properties/observation/required", keyword: "required", @@ -61761,7 +62282,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data15.metric) { let _errs40 = errors; - if ("string" != typeof data15.metric) return validate266.errors = [ { + if ("string" != typeof data15.metric) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation/metric", schemaPath: "#/properties/incidents/items/properties/observation/properties/metric/type", keyword: "type", @@ -61774,7 +62295,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data15.resource) { let _errs42 = errors; - if ("string" != typeof data15.resource) return validate266.errors = [ { + if ("string" != typeof data15.resource) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation/resource", schemaPath: "#/properties/incidents/items/properties/observation/properties/resource/type", keyword: "type", @@ -61787,7 +62308,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data15.scope) { let _errs44 = errors; - if ("string" != typeof data15.scope) return validate266.errors = [ { + if ("string" != typeof data15.scope) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation/scope", schemaPath: "#/properties/incidents/items/properties/observation/properties/scope/type", keyword: "type", @@ -61800,7 +62321,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data15.capability) { let _errs46 = errors; - if ("string" != typeof data15.capability) return validate266.errors = [ { + if ("string" != typeof data15.capability) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation/capability", schemaPath: "#/properties/incidents/items/properties/observation/properties/capability/type", keyword: "type", @@ -61813,7 +62334,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data15.unit) { let _errs48 = errors; - if ("string" != typeof data15.unit) return validate266.errors = [ { + if ("string" != typeof data15.unit) return validate269.errors = [ { instancePath: instancePath + "/incidents/" + i0 + "/observation/unit", schemaPath: "#/properties/incidents/items/properties/observation/properties/unit/type", keyword: "type", @@ -61857,7 +62378,7 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err5), errors++, validate266.errors = vErrors, !1; + return vErrors.push(err5), errors++, validate269.errors = vErrors, !1; } errors = _errs51, vErrors.length = _errs51; } @@ -61918,28 +62439,28 @@ function validate266(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err8), errors++, validate266.errors = vErrors, !1; + return vErrors.push(err8), errors++, validate269.errors = vErrors, !1; } errors = _errs57, vErrors.length = _errs57; } } } } - return validate266.errors = vErrors, 0 === errors; + return validate269.errors = vErrors, 0 === errors; } -validate266.evaluated = { +validate269.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_IceServersResponse = validate463; +var __contract_IceServersResponse = validate473; -function validate463(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate463.evaluated; +function validate473(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate473.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate463.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate473.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -61947,7 +62468,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.ice_servers) return validate463.errors = [ { + if (void 0 === data.ice_servers) return validate473.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -61957,7 +62478,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.ice_servers) { let data0 = data.ice_servers; - if (!Array.isArray(data0)) return validate463.errors = [ { + if (!Array.isArray(data0)) return validate473.errors = [ { instancePath: instancePath + "/ice_servers", schemaPath: "#/properties/ice_servers/type", keyword: "type", @@ -61970,7 +62491,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data1 = data0[i0], _errs4 = 0; if (0 === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate463.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0, schemaPath: "#/properties/ice_servers/items/type", keyword: "type", @@ -61978,7 +62499,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.urls) return validate463.errors = [ { + if (void 0 === data1.urls) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0, schemaPath: "#/properties/ice_servers/items/required", keyword: "required", @@ -61989,7 +62510,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.urls) { let data2 = data1.urls, _errs7 = 0; if (0 === _errs7) { - if (!Array.isArray(data2)) return validate463.errors = [ { + if (!Array.isArray(data2)) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0 + "/urls", schemaPath: "#/properties/ice_servers/items/properties/urls/type", keyword: "type", @@ -61997,7 +62518,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data2.length < 1) return validate463.errors = [ { + if (data2.length < 1) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0 + "/urls", schemaPath: "#/properties/ice_servers/items/properties/urls/minItems", keyword: "minItems", @@ -62009,7 +62530,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD let len1 = data2.length; for (let i1 = 0; i1 < len1; i1++) { let _errs9 = 0; - if ("string" != typeof data2[i1]) return validate463.errors = [ { + if ("string" != typeof data2[i1]) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0 + "/urls/" + i1, schemaPath: "#/properties/ice_servers/items/properties/urls/items/type", keyword: "type", @@ -62026,7 +62547,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data1.username) { let _errs11 = 0; - if ("string" != typeof data1.username) return validate463.errors = [ { + if ("string" != typeof data1.username) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0 + "/username", schemaPath: "#/properties/ice_servers/items/properties/username/type", keyword: "type", @@ -62036,7 +62557,7 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid4 = 0 === _errs11; } else valid4 = !0; - if (valid4 && void 0 !== data1.credential && "string" != typeof data1.credential) return validate463.errors = [ { + if (valid4 && void 0 !== data1.credential && "string" != typeof data1.credential) return validate473.errors = [ { instancePath: instancePath + "/ice_servers/" + i0 + "/credential", schemaPath: "#/properties/ice_servers/items/properties/credential/type", keyword: "type", @@ -62050,21 +62571,21 @@ function validate463(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate463.errors = [], !0; + return validate473.errors = [], !0; } -validate463.evaluated = { +validate473.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingModeLevel = validate412; +var __contract_ImagingModeLevel = validate422; -function validate412(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate412.evaluated; +function validate422(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate422.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate412.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate422.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -62074,7 +62595,7 @@ function validate412(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("mode" !== key0 && "level" !== key0) return validate412.errors = [ { + for (let key0 in data) if ("mode" !== key0 && "level" !== key0) return validate422.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -62085,7 +62606,7 @@ function validate412(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.mode) { let _errs2 = 0; - if ("string" != typeof data.mode) return validate412.errors = [ { + if ("string" != typeof data.mode) return validate422.errors = [ { instancePath: instancePath + "/mode", schemaPath: "#/properties/mode/type", keyword: "type", @@ -62097,7 +62618,7 @@ function validate412(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.level) { let data1 = data.level; - if ("number" != typeof data1 || !isFinite(data1)) return validate412.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate422.errors = [ { instancePath: instancePath + "/level", schemaPath: "#/properties/level/type", keyword: "type", @@ -62108,19 +62629,19 @@ function validate412(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate412.errors = [], !0; + return validate422.errors = [], !0; } -validate412.evaluated = { +validate422.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingModeLevelUpdate = validate413; +var __contract_ImagingModeLevelUpdate = validate423; -function validate413(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate413.evaluated; +function validate423(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate423.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if ("number" == typeof data && isFinite(data)) { @@ -62286,23 +62807,23 @@ function validate413(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err12), errors++, validate413.errors = vErrors, !1; + return vErrors.push(err12), errors++, validate423.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate413.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate423.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate413.evaluated = { +validate423.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_ImagingModeOptions = validate417; +var __contract_ImagingModeOptions = validate427; -function validate417(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate417.evaluated; +function validate427(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate427.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate417.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate427.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -62310,7 +62831,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.modes) return validate417.errors = [ { + if (void 0 === data.modes) return validate427.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -62320,7 +62841,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("modes" !== key0 && "level" !== key0) return validate417.errors = [ { + for (let key0 in data) if ("modes" !== key0 && "level" !== key0) return validate427.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -62332,7 +62853,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.modes) { let data0 = data.modes, _errs2 = 0; if (0 === _errs2) { - if (!Array.isArray(data0)) return validate417.errors = [ { + if (!Array.isArray(data0)) return validate427.errors = [ { instancePath: instancePath + "/modes", schemaPath: "#/properties/modes/type", keyword: "type", @@ -62344,7 +62865,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = 0; - if ("string" != typeof data0[i0]) return validate417.errors = [ { + if ("string" != typeof data0[i0]) return validate427.errors = [ { instancePath: instancePath + "/modes/" + i0, schemaPath: "#/properties/modes/items/type", keyword: "type", @@ -62360,7 +62881,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.level) { let data2 = data.level; - if (!data2 || "object" != typeof data2 || Array.isArray(data2)) return validate417.errors = [ { + if (!data2 || "object" != typeof data2 || Array.isArray(data2)) return validate427.errors = [ { instancePath: instancePath + "/level", schemaPath: "#/$defs/DeviceFloatRange/type", keyword: "type", @@ -62368,7 +62889,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data2.min) return validate417.errors = [ { + if (void 0 === data2.min) return validate427.errors = [ { instancePath: instancePath + "/level", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62376,7 +62897,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data2.max) return validate417.errors = [ { + if (void 0 === data2.max) return validate427.errors = [ { instancePath: instancePath + "/level", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62386,7 +62907,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs9 = 0; - for (let key1 in data2) if ("min" !== key1 && "max" !== key1) return validate417.errors = [ { + for (let key1 in data2) if ("min" !== key1 && "max" !== key1) return validate427.errors = [ { instancePath: instancePath + "/level", schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", keyword: "additionalProperties", @@ -62397,7 +62918,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs9) { if (void 0 !== data2.min) { let data3 = data2.min, _errs10 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate417.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate427.errors = [ { instancePath: instancePath + "/level/min", schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", keyword: "type", @@ -62409,7 +62930,7 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD } else valid4 = !0; if (valid4 && void 0 !== data2.max) { let data4 = data2.max; - if ("number" != typeof data4 || !isFinite(data4)) return validate417.errors = [ { + if ("number" != typeof data4 || !isFinite(data4)) return validate427.errors = [ { instancePath: instancePath + "/level/max", schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", keyword: "type", @@ -62423,16 +62944,16 @@ function validate417(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate417.errors = [], !0; + return validate427.errors = [], !0; } -validate417.evaluated = { +validate427.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingOptions = validate418, schema491 = { +var __contract_ImagingOptions = validate428, schema503 = { type: "object", properties: { video_source_token: { @@ -62476,11 +62997,11 @@ var __contract_ImagingOptions = validate418, schema491 = { additionalProperties: !1 }; -function validate418(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate418.evaluated; +function validate428(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate428.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate418.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate428.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -62488,7 +63009,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.video_source_token) return validate418.errors = [ { + if (void 0 === data.video_source_token) return validate428.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -62496,7 +63017,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "video_source_token" } } ], !1; - if (void 0 === data.ir_cut_filter_modes) return validate418.errors = [ { + if (void 0 === data.ir_cut_filter_modes) return validate428.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -62506,7 +63027,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema491.properties, key0)) return validate418.errors = [ { + for (let key0 in data) if (!func7.call(schema503.properties, key0)) return validate428.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -62517,7 +63038,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.video_source_token) { let _errs2 = errors; - if ("string" != typeof data.video_source_token) return validate418.errors = [ { + if ("string" != typeof data.video_source_token) return validate428.errors = [ { instancePath: instancePath + "/video_source_token", schemaPath: "#/properties/video_source_token/type", keyword: "type", @@ -62531,7 +63052,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.brightness) { let data1 = data.brightness, _errs4 = errors; if (errors == errors) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate418.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate428.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/$defs/DeviceFloatRange/type", keyword: "type", @@ -62539,7 +63060,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.min) return validate418.errors = [ { + if (void 0 === data1.min) return validate428.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62547,7 +63068,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data1.max) return validate418.errors = [ { + if (void 0 === data1.max) return validate428.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62557,7 +63078,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs7 = errors; - for (let key1 in data1) if ("min" !== key1 && "max" !== key1) return validate418.errors = [ { + for (let key1 in data1) if ("min" !== key1 && "max" !== key1) return validate428.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", keyword: "additionalProperties", @@ -62568,7 +63089,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (_errs7 === errors) { if (void 0 !== data1.min) { let data2 = data1.min, _errs8 = errors; - if ("number" != typeof data2 || !isFinite(data2)) return validate418.errors = [ { + if ("number" != typeof data2 || !isFinite(data2)) return validate428.errors = [ { instancePath: instancePath + "/brightness/min", schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", keyword: "type", @@ -62580,7 +63101,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } else valid3 = !0; if (valid3 && void 0 !== data1.max) { let data3 = data1.max; - if ("number" != typeof data3 || !isFinite(data3)) return validate418.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate428.errors = [ { instancePath: instancePath + "/brightness/max", schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", keyword: "type", @@ -62598,7 +63119,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.color_saturation) { let data4 = data.color_saturation, _errs12 = errors; if (errors == errors) { - if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate418.errors = [ { + if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate428.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/$defs/DeviceFloatRange/type", keyword: "type", @@ -62606,7 +63127,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data4.min) return validate418.errors = [ { + if (void 0 === data4.min) return validate428.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62614,7 +63135,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data4.max) return validate418.errors = [ { + if (void 0 === data4.max) return validate428.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62624,7 +63145,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs15 = errors; - for (let key2 in data4) if ("min" !== key2 && "max" !== key2) return validate418.errors = [ { + for (let key2 in data4) if ("min" !== key2 && "max" !== key2) return validate428.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", keyword: "additionalProperties", @@ -62635,7 +63156,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (_errs15 === errors) { if (void 0 !== data4.min) { let data5 = data4.min, _errs16 = errors; - if ("number" != typeof data5 || !isFinite(data5)) return validate418.errors = [ { + if ("number" != typeof data5 || !isFinite(data5)) return validate428.errors = [ { instancePath: instancePath + "/color_saturation/min", schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", keyword: "type", @@ -62647,7 +63168,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } else valid7 = !0; if (valid7 && void 0 !== data4.max) { let data6 = data4.max; - if ("number" != typeof data6 || !isFinite(data6)) return validate418.errors = [ { + if ("number" != typeof data6 || !isFinite(data6)) return validate428.errors = [ { instancePath: instancePath + "/color_saturation/max", schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", keyword: "type", @@ -62665,7 +63186,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.contrast) { let data7 = data.contrast, _errs20 = errors; if (errors == errors) { - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate418.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate428.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/$defs/DeviceFloatRange/type", keyword: "type", @@ -62673,7 +63194,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data7.min) return validate418.errors = [ { + if (void 0 === data7.min) return validate428.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62681,7 +63202,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data7.max) return validate418.errors = [ { + if (void 0 === data7.max) return validate428.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62691,7 +63212,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs23 = errors; - for (let key3 in data7) if ("min" !== key3 && "max" !== key3) return validate418.errors = [ { + for (let key3 in data7) if ("min" !== key3 && "max" !== key3) return validate428.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", keyword: "additionalProperties", @@ -62702,7 +63223,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (_errs23 === errors) { if (void 0 !== data7.min) { let data8 = data7.min, _errs24 = errors; - if ("number" != typeof data8 || !isFinite(data8)) return validate418.errors = [ { + if ("number" != typeof data8 || !isFinite(data8)) return validate428.errors = [ { instancePath: instancePath + "/contrast/min", schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", keyword: "type", @@ -62714,7 +63235,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } else valid11 = !0; if (valid11 && void 0 !== data7.max) { let data9 = data7.max; - if ("number" != typeof data9 || !isFinite(data9)) return validate418.errors = [ { + if ("number" != typeof data9 || !isFinite(data9)) return validate428.errors = [ { instancePath: instancePath + "/contrast/max", schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", keyword: "type", @@ -62732,7 +63253,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.sharpness) { let data10 = data.sharpness, _errs28 = errors; if (errors == errors) { - if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate418.errors = [ { + if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate428.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/$defs/DeviceFloatRange/type", keyword: "type", @@ -62740,7 +63261,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data10.min) return validate418.errors = [ { + if (void 0 === data10.min) return validate428.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62748,7 +63269,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data10.max) return validate418.errors = [ { + if (void 0 === data10.max) return validate428.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62758,7 +63279,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs31 = errors; - for (let key4 in data10) if ("min" !== key4 && "max" !== key4) return validate418.errors = [ { + for (let key4 in data10) if ("min" !== key4 && "max" !== key4) return validate428.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", keyword: "additionalProperties", @@ -62769,7 +63290,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (_errs31 === errors) { if (void 0 !== data10.min) { let data11 = data10.min, _errs32 = errors; - if ("number" != typeof data11 || !isFinite(data11)) return validate418.errors = [ { + if ("number" != typeof data11 || !isFinite(data11)) return validate428.errors = [ { instancePath: instancePath + "/sharpness/min", schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", keyword: "type", @@ -62781,7 +63302,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } else valid15 = !0; if (valid15 && void 0 !== data10.max) { let data12 = data10.max; - if ("number" != typeof data12 || !isFinite(data12)) return validate418.errors = [ { + if ("number" != typeof data12 || !isFinite(data12)) return validate428.errors = [ { instancePath: instancePath + "/sharpness/max", schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", keyword: "type", @@ -62799,7 +63320,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.noise_reduction) { let data13 = data.noise_reduction, _errs36 = errors; if (errors == errors) { - if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate418.errors = [ { + if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate428.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/$defs/DeviceFloatRange/type", keyword: "type", @@ -62807,7 +63328,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data13.min) return validate418.errors = [ { + if (void 0 === data13.min) return validate428.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62815,7 +63336,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "min" } } ], !1; - if (void 0 === data13.max) return validate418.errors = [ { + if (void 0 === data13.max) return validate428.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/$defs/DeviceFloatRange/required", keyword: "required", @@ -62825,7 +63346,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs39 = errors; - for (let key5 in data13) if ("min" !== key5 && "max" !== key5) return validate418.errors = [ { + for (let key5 in data13) if ("min" !== key5 && "max" !== key5) return validate428.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", keyword: "additionalProperties", @@ -62836,7 +63357,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (_errs39 === errors) { if (void 0 !== data13.min) { let data14 = data13.min, _errs40 = errors; - if ("number" != typeof data14 || !isFinite(data14)) return validate418.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate428.errors = [ { instancePath: instancePath + "/noise_reduction/min", schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", keyword: "type", @@ -62848,7 +63369,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } else valid19 = !0; if (valid19 && void 0 !== data13.max) { let data15 = data13.max; - if ("number" != typeof data15 || !isFinite(data15)) return validate418.errors = [ { + if ("number" != typeof data15 || !isFinite(data15)) return validate428.errors = [ { instancePath: instancePath + "/noise_reduction/max", schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", keyword: "type", @@ -62865,55 +63386,55 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data.backlight_compensation) { let _errs44 = errors; - validate417(data.backlight_compensation, { + validate427(data.backlight_compensation, { instancePath: instancePath + "/backlight_compensation", parentData: data, parentDataProperty: "backlight_compensation", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate417.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate427.errors), errors = vErrors.length); var valid21 = _errs44 === errors; } else valid21 = !0; if (valid21) { if (void 0 !== data.wide_dynamic_range) { let _errs45 = errors; - validate417(data.wide_dynamic_range, { + validate427(data.wide_dynamic_range, { instancePath: instancePath + "/wide_dynamic_range", parentData: data, parentDataProperty: "wide_dynamic_range", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate417.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate427.errors), errors = vErrors.length); var valid22 = _errs45 === errors; } else valid22 = !0; if (valid22) { if (void 0 !== data.tone_compensation) { let _errs46 = errors; - validate417(data.tone_compensation, { + validate427(data.tone_compensation, { instancePath: instancePath + "/tone_compensation", parentData: data, parentDataProperty: "tone_compensation", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate417.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate427.errors), errors = vErrors.length); var valid23 = _errs46 === errors; } else valid23 = !0; if (valid23) { if (void 0 !== data.defogging) { let _errs47 = errors; - validate417(data.defogging, { + validate427(data.defogging, { instancePath: instancePath + "/defogging", parentData: data, parentDataProperty: "defogging", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate417.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate427.errors), errors = vErrors.length); var valid24 = _errs47 === errors; } else valid24 = !0; if (valid24 && void 0 !== data.ir_cut_filter_modes) { let data20 = data.ir_cut_filter_modes; if (errors == errors) { - if (!Array.isArray(data20)) return validate418.errors = [ { + if (!Array.isArray(data20)) return validate428.errors = [ { instancePath: instancePath + "/ir_cut_filter_modes", schemaPath: "#/properties/ir_cut_filter_modes/type", keyword: "type", @@ -62925,7 +63446,7 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD let len0 = data20.length; for (let i0 = 0; i0 < len0; i0++) { let _errs50 = errors; - if ("string" != typeof data20[i0]) return validate418.errors = [ { + if ("string" != typeof data20[i0]) return validate428.errors = [ { instancePath: instancePath + "/ir_cut_filter_modes/" + i0, schemaPath: "#/properties/ir_cut_filter_modes/items/type", keyword: "type", @@ -62950,22 +63471,22 @@ function validate418(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate418.errors = vErrors, 0 === errors; + return validate428.errors = vErrors, 0 === errors; } -validate418.evaluated = { +validate428.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingOptionsResponse = validate423; +var __contract_ImagingOptionsResponse = validate433; -function validate423(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate423.evaluated; +function validate433(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate433.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate423.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate433.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -62973,7 +63494,7 @@ function validate423(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate423.errors = [ { + if (void 0 === data.success) return validate433.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -62981,7 +63502,7 @@ function validate423(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.options) return validate423.errors = [ { + if (void 0 === data.options) return validate433.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -62991,7 +63512,7 @@ function validate423(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("success" !== key0 && "options" !== key0) return validate423.errors = [ { + for (let key0 in data) if ("success" !== key0 && "options" !== key0) return validate433.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -63002,7 +63523,7 @@ function validate423(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.success) { let _errs2 = errors; - if (!0 !== data.success) return validate423.errors = [ { + if (!0 !== data.success) return validate433.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -63012,26 +63533,26 @@ function validate423(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid0 = _errs2 === errors; } else valid0 = !0; - valid0 && void 0 !== data.options && (validate418(data.options, { + valid0 && void 0 !== data.options && (validate428(data.options, { instancePath: instancePath + "/options", parentData: data, parentDataProperty: "options", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate418.errors), errors = vErrors.length)); + }) || (vErrors = vErrors.concat(validate428.errors), errors = vErrors.length)); } } } - return validate423.errors = vErrors, 0 === errors; + return validate433.errors = vErrors, 0 === errors; } -validate423.evaluated = { +validate433.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingSettings = validate414, schema483 = { +var __contract_ImagingSettings = validate424, schema495 = { type: "object", properties: { video_source_token: { @@ -63072,10 +63593,10 @@ var __contract_ImagingSettings = validate414, schema483 = { additionalProperties: !1 }; -function validate414(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate414.evaluated; +function validate424(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate424.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate414.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate424.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -63083,7 +63604,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.video_source_token) return validate414.errors = [ { + if (void 0 === data.video_source_token) return validate424.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -63093,7 +63614,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema483.properties, key0)) return validate414.errors = [ { + for (let key0 in data) if (!func7.call(schema495.properties, key0)) return validate424.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -63104,7 +63625,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.video_source_token) { let _errs2 = 0; - if ("string" != typeof data.video_source_token) return validate414.errors = [ { + if ("string" != typeof data.video_source_token) return validate424.errors = [ { instancePath: instancePath + "/video_source_token", schemaPath: "#/properties/video_source_token/type", keyword: "type", @@ -63117,7 +63638,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.brightness) { let data1 = data.brightness, _errs4 = 0; - if ("number" != typeof data1 || !isFinite(data1)) return validate414.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate424.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/properties/brightness/type", keyword: "type", @@ -63130,7 +63651,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.color_saturation) { let data2 = data.color_saturation, _errs6 = 0; - if ("number" != typeof data2 || !isFinite(data2)) return validate414.errors = [ { + if ("number" != typeof data2 || !isFinite(data2)) return validate424.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/properties/color_saturation/type", keyword: "type", @@ -63143,7 +63664,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.contrast) { let data3 = data.contrast, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate414.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate424.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/properties/contrast/type", keyword: "type", @@ -63156,7 +63677,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.sharpness) { let data4 = data.sharpness, _errs10 = 0; - if ("number" != typeof data4 || !isFinite(data4)) return validate414.errors = [ { + if ("number" != typeof data4 || !isFinite(data4)) return validate424.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/properties/sharpness/type", keyword: "type", @@ -63169,7 +63690,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.noise_reduction) { let data5 = data.noise_reduction, _errs12 = 0; - if ("number" != typeof data5 || !isFinite(data5)) return validate414.errors = [ { + if ("number" != typeof data5 || !isFinite(data5)) return validate424.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/properties/noise_reduction/type", keyword: "type", @@ -63182,7 +63703,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.backlight_compensation) { let data6 = data.backlight_compensation, _errs14 = 0; - if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate414.errors = [ { + if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate424.errors = [ { instancePath: instancePath + "/backlight_compensation", schemaPath: "#/$defs/ImagingModeLevel/type", keyword: "type", @@ -63192,7 +63713,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs17 = 0; - for (let key1 in data6) if ("mode" !== key1 && "level" !== key1) return validate414.errors = [ { + for (let key1 in data6) if ("mode" !== key1 && "level" !== key1) return validate424.errors = [ { instancePath: instancePath + "/backlight_compensation", schemaPath: "#/$defs/ImagingModeLevel/additionalProperties", keyword: "additionalProperties", @@ -63203,7 +63724,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs17) { if (void 0 !== data6.mode) { let _errs18 = 0; - if ("string" != typeof data6.mode) return validate414.errors = [ { + if ("string" != typeof data6.mode) return validate424.errors = [ { instancePath: instancePath + "/backlight_compensation/mode", schemaPath: "#/$defs/ImagingModeLevel/properties/mode/type", keyword: "type", @@ -63215,7 +63736,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } else valid8 = !0; if (valid8 && void 0 !== data6.level) { let data8 = data6.level; - if ("number" != typeof data8 || !isFinite(data8)) return validate414.errors = [ { + if ("number" != typeof data8 || !isFinite(data8)) return validate424.errors = [ { instancePath: instancePath + "/backlight_compensation/level", schemaPath: "#/$defs/ImagingModeLevel/properties/level/type", keyword: "type", @@ -63231,7 +63752,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.wide_dynamic_range) { let data9 = data.wide_dynamic_range, _errs22 = 0; - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate414.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate424.errors = [ { instancePath: instancePath + "/wide_dynamic_range", schemaPath: "#/$defs/ImagingModeLevel/type", keyword: "type", @@ -63241,7 +63762,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs25 = 0; - for (let key2 in data9) if ("mode" !== key2 && "level" !== key2) return validate414.errors = [ { + for (let key2 in data9) if ("mode" !== key2 && "level" !== key2) return validate424.errors = [ { instancePath: instancePath + "/wide_dynamic_range", schemaPath: "#/$defs/ImagingModeLevel/additionalProperties", keyword: "additionalProperties", @@ -63252,7 +63773,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs25) { if (void 0 !== data9.mode) { let _errs26 = 0; - if ("string" != typeof data9.mode) return validate414.errors = [ { + if ("string" != typeof data9.mode) return validate424.errors = [ { instancePath: instancePath + "/wide_dynamic_range/mode", schemaPath: "#/$defs/ImagingModeLevel/properties/mode/type", keyword: "type", @@ -63264,7 +63785,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } else valid12 = !0; if (valid12 && void 0 !== data9.level) { let data11 = data9.level; - if ("number" != typeof data11 || !isFinite(data11)) return validate414.errors = [ { + if ("number" != typeof data11 || !isFinite(data11)) return validate424.errors = [ { instancePath: instancePath + "/wide_dynamic_range/level", schemaPath: "#/$defs/ImagingModeLevel/properties/level/type", keyword: "type", @@ -63280,7 +63801,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.tone_compensation) { let data12 = data.tone_compensation, _errs30 = 0; - if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate414.errors = [ { + if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate424.errors = [ { instancePath: instancePath + "/tone_compensation", schemaPath: "#/$defs/ImagingModeLevel/type", keyword: "type", @@ -63290,7 +63811,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs33 = 0; - for (let key3 in data12) if ("mode" !== key3 && "level" !== key3) return validate414.errors = [ { + for (let key3 in data12) if ("mode" !== key3 && "level" !== key3) return validate424.errors = [ { instancePath: instancePath + "/tone_compensation", schemaPath: "#/$defs/ImagingModeLevel/additionalProperties", keyword: "additionalProperties", @@ -63301,7 +63822,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs33) { if (void 0 !== data12.mode) { let _errs34 = 0; - if ("string" != typeof data12.mode) return validate414.errors = [ { + if ("string" != typeof data12.mode) return validate424.errors = [ { instancePath: instancePath + "/tone_compensation/mode", schemaPath: "#/$defs/ImagingModeLevel/properties/mode/type", keyword: "type", @@ -63313,7 +63834,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } else valid16 = !0; if (valid16 && void 0 !== data12.level) { let data14 = data12.level; - if ("number" != typeof data14 || !isFinite(data14)) return validate414.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate424.errors = [ { instancePath: instancePath + "/tone_compensation/level", schemaPath: "#/$defs/ImagingModeLevel/properties/level/type", keyword: "type", @@ -63329,7 +63850,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data.defogging) { let data15 = data.defogging, _errs38 = 0; - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate414.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate424.errors = [ { instancePath: instancePath + "/defogging", schemaPath: "#/$defs/ImagingModeLevel/type", keyword: "type", @@ -63339,7 +63860,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs41 = 0; - for (let key4 in data15) if ("mode" !== key4 && "level" !== key4) return validate414.errors = [ { + for (let key4 in data15) if ("mode" !== key4 && "level" !== key4) return validate424.errors = [ { instancePath: instancePath + "/defogging", schemaPath: "#/$defs/ImagingModeLevel/additionalProperties", keyword: "additionalProperties", @@ -63350,7 +63871,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs41) { if (void 0 !== data15.mode) { let _errs42 = 0; - if ("string" != typeof data15.mode) return validate414.errors = [ { + if ("string" != typeof data15.mode) return validate424.errors = [ { instancePath: instancePath + "/defogging/mode", schemaPath: "#/$defs/ImagingModeLevel/properties/mode/type", keyword: "type", @@ -63362,7 +63883,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } else valid20 = !0; if (valid20 && void 0 !== data15.level) { let data17 = data15.level; - if ("number" != typeof data17 || !isFinite(data17)) return validate414.errors = [ { + if ("number" != typeof data17 || !isFinite(data17)) return validate424.errors = [ { instancePath: instancePath + "/defogging/level", schemaPath: "#/$defs/ImagingModeLevel/properties/level/type", keyword: "type", @@ -63375,7 +63896,7 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } var valid18 = 0 === _errs38; } else valid18 = !0; - if (valid18 && void 0 !== data.ir_cut_filter && "string" != typeof data.ir_cut_filter) return validate414.errors = [ { + if (valid18 && void 0 !== data.ir_cut_filter && "string" != typeof data.ir_cut_filter) return validate424.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/type", keyword: "type", @@ -63394,16 +63915,16 @@ function validate414(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate414.errors = [], !0; + return validate424.errors = [], !0; } -validate414.evaluated = { +validate424.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingSettingsRequest = validate425, schema498 = { +var __contract_ImagingSettingsRequest = validate435, schema510 = { type: "object", properties: { brightness: { @@ -63609,8 +64130,8 @@ var __contract_ImagingSettingsRequest = validate425, schema498 = { } ] }; -function validate425(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate425.evaluated; +function validate435(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate435.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs1 = errors, valid0 = !1, _errs2 = errors; if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.brightness) { @@ -65431,10 +65952,10 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err144), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err144), errors++, validate435.errors = vErrors, !1; } if (errors = _errs1, vErrors.length = _errs1, 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate425.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate435.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -65442,7 +65963,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data).length < 1) return validate425.errors = [ { + if (Object.keys(data).length < 1) return validate435.errors = [ { instancePath: instancePath, schemaPath: "#/minProperties", keyword: "minProperties", @@ -65452,7 +65973,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs130 = errors; - for (let key8 in data) if (!func5.call(schema498.properties, key8)) return validate425.errors = [ { + for (let key8 in data) if (!func7.call(schema510.properties, key8)) return validate435.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -65464,7 +65985,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.brightness) { let data32 = data.brightness, _errs131 = errors; if (errors === _errs131) { - if ("number" != typeof data32 || !isFinite(data32)) return validate425.errors = [ { + if ("number" != typeof data32 || !isFinite(data32)) return validate435.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/properties/brightness/type", keyword: "type", @@ -65472,7 +65993,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data32 > 34028234663852886e22 || isNaN(data32)) return validate425.errors = [ { + if (data32 > 34028234663852886e22 || isNaN(data32)) return validate435.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/properties/brightness/maximum", keyword: "maximum", @@ -65481,7 +66002,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 34028234663852886e22 } } ], !1; - if (data32 < -34028234663852886e22 || isNaN(data32)) return validate425.errors = [ { + if (data32 < -34028234663852886e22 || isNaN(data32)) return validate435.errors = [ { instancePath: instancePath + "/brightness", schemaPath: "#/properties/brightness/minimum", keyword: "minimum", @@ -65497,7 +66018,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.color_saturation) { let data33 = data.color_saturation, _errs133 = errors; if (errors === _errs133) { - if ("number" != typeof data33 || !isFinite(data33)) return validate425.errors = [ { + if ("number" != typeof data33 || !isFinite(data33)) return validate435.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/properties/color_saturation/type", keyword: "type", @@ -65505,7 +66026,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data33 > 34028234663852886e22 || isNaN(data33)) return validate425.errors = [ { + if (data33 > 34028234663852886e22 || isNaN(data33)) return validate435.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/properties/color_saturation/maximum", keyword: "maximum", @@ -65514,7 +66035,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 34028234663852886e22 } } ], !1; - if (data33 < -34028234663852886e22 || isNaN(data33)) return validate425.errors = [ { + if (data33 < -34028234663852886e22 || isNaN(data33)) return validate435.errors = [ { instancePath: instancePath + "/color_saturation", schemaPath: "#/properties/color_saturation/minimum", keyword: "minimum", @@ -65530,7 +66051,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.contrast) { let data34 = data.contrast, _errs135 = errors; if (errors === _errs135) { - if ("number" != typeof data34 || !isFinite(data34)) return validate425.errors = [ { + if ("number" != typeof data34 || !isFinite(data34)) return validate435.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/properties/contrast/type", keyword: "type", @@ -65538,7 +66059,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data34 > 34028234663852886e22 || isNaN(data34)) return validate425.errors = [ { + if (data34 > 34028234663852886e22 || isNaN(data34)) return validate435.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/properties/contrast/maximum", keyword: "maximum", @@ -65547,7 +66068,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 34028234663852886e22 } } ], !1; - if (data34 < -34028234663852886e22 || isNaN(data34)) return validate425.errors = [ { + if (data34 < -34028234663852886e22 || isNaN(data34)) return validate435.errors = [ { instancePath: instancePath + "/contrast", schemaPath: "#/properties/contrast/minimum", keyword: "minimum", @@ -65563,7 +66084,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.sharpness) { let data35 = data.sharpness, _errs137 = errors; if (errors === _errs137) { - if ("number" != typeof data35 || !isFinite(data35)) return validate425.errors = [ { + if ("number" != typeof data35 || !isFinite(data35)) return validate435.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/properties/sharpness/type", keyword: "type", @@ -65571,7 +66092,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data35 > 34028234663852886e22 || isNaN(data35)) return validate425.errors = [ { + if (data35 > 34028234663852886e22 || isNaN(data35)) return validate435.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/properties/sharpness/maximum", keyword: "maximum", @@ -65580,7 +66101,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 34028234663852886e22 } } ], !1; - if (data35 < -34028234663852886e22 || isNaN(data35)) return validate425.errors = [ { + if (data35 < -34028234663852886e22 || isNaN(data35)) return validate435.errors = [ { instancePath: instancePath + "/sharpness", schemaPath: "#/properties/sharpness/minimum", keyword: "minimum", @@ -65596,7 +66117,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.noise_reduction) { let data36 = data.noise_reduction, _errs139 = errors; if (errors === _errs139) { - if ("number" != typeof data36 || !isFinite(data36)) return validate425.errors = [ { + if ("number" != typeof data36 || !isFinite(data36)) return validate435.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/properties/noise_reduction/type", keyword: "type", @@ -65604,7 +66125,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data36 > 34028234663852886e22 || isNaN(data36)) return validate425.errors = [ { + if (data36 > 34028234663852886e22 || isNaN(data36)) return validate435.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/properties/noise_reduction/maximum", keyword: "maximum", @@ -65613,7 +66134,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 34028234663852886e22 } } ], !1; - if (data36 < -34028234663852886e22 || isNaN(data36)) return validate425.errors = [ { + if (data36 < -34028234663852886e22 || isNaN(data36)) return validate435.errors = [ { instancePath: instancePath + "/noise_reduction", schemaPath: "#/properties/noise_reduction/minimum", keyword: "minimum", @@ -65629,7 +66150,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.saturation) { let data37 = data.saturation, _errs141 = errors; if (errors === _errs141) { - if ("number" != typeof data37 || !isFinite(data37)) return validate425.errors = [ { + if ("number" != typeof data37 || !isFinite(data37)) return validate435.errors = [ { instancePath: instancePath + "/saturation", schemaPath: "#/properties/saturation/type", keyword: "type", @@ -65637,7 +66158,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data37 > 34028234663852886e22 || isNaN(data37)) return validate425.errors = [ { + if (data37 > 34028234663852886e22 || isNaN(data37)) return validate435.errors = [ { instancePath: instancePath + "/saturation", schemaPath: "#/properties/saturation/maximum", keyword: "maximum", @@ -65646,7 +66167,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 34028234663852886e22 } } ], !1; - if (data37 < -34028234663852886e22 || isNaN(data37)) return validate425.errors = [ { + if (data37 < -34028234663852886e22 || isNaN(data37)) return validate435.errors = [ { instancePath: instancePath + "/saturation", schemaPath: "#/properties/saturation/minimum", keyword: "minimum", @@ -65821,7 +66342,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing8 } }; - return vErrors.push(err157), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err157), errors++, validate435.errors = vErrors, !1; } errors = _errs145, vErrors.length = _errs145; var valid55 = _errs143 === errors; @@ -65989,7 +66510,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing9 } }; - return vErrors.push(err170), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err170), errors++, validate435.errors = vErrors, !1; } errors = _errs157, vErrors.length = _errs157; var valid60 = _errs155 === errors; @@ -66157,7 +66678,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing10 } }; - return vErrors.push(err183), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err183), errors++, validate435.errors = vErrors, !1; } errors = _errs169, vErrors.length = _errs169; var valid65 = _errs167 === errors; @@ -66325,7 +66846,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing11 } }; - return vErrors.push(err196), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err196), errors++, validate435.errors = vErrors, !1; } errors = _errs181, vErrors.length = _errs181; var valid70 = _errs179 === errors; @@ -66493,7 +67014,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing12 } }; - return vErrors.push(err209), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err209), errors++, validate435.errors = vErrors, !1; } errors = _errs193, vErrors.length = _errs193; var valid75 = _errs191 === errors; @@ -66661,7 +67182,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing13 } }; - return vErrors.push(err222), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err222), errors++, validate435.errors = vErrors, !1; } errors = _errs205, vErrors.length = _errs205; var valid80 = _errs203 === errors; @@ -66829,7 +67350,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing14 } }; - return vErrors.push(err235), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err235), errors++, validate435.errors = vErrors, !1; } errors = _errs217, vErrors.length = _errs217; var valid85 = _errs215 === errors; @@ -66997,7 +67518,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing15 } }; - return vErrors.push(err248), errors++, validate425.errors = vErrors, !1; + return vErrors.push(err248), errors++, validate435.errors = vErrors, !1; } errors = _errs229, vErrors.length = _errs229; var valid90 = _errs227 === errors; @@ -67006,7 +67527,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.ir_cut_filter) { let data62 = data.ir_cut_filter, _errs239 = errors; if (errors === _errs239) { - if ("string" != typeof data62) return validate425.errors = [ { + if ("string" != typeof data62) return validate435.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/type", keyword: "type", @@ -67014,7 +67535,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data62) > 15) return validate425.errors = [ { + if (func1(data62) > 15) return validate435.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/maxLength", keyword: "maxLength", @@ -67022,7 +67543,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 15 } } ], !1; - if (func1(data62) < 1) return validate425.errors = [ { + if (func1(data62) < 1) return validate435.errors = [ { instancePath: instancePath + "/ir_cut_filter", schemaPath: "#/properties/ir_cut_filter/minLength", keyword: "minLength", @@ -67037,7 +67558,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.ircut) { let data63 = data.ircut, _errs241 = errors; if (errors === _errs241) { - if ("string" != typeof data63) return validate425.errors = [ { + if ("string" != typeof data63) return validate435.errors = [ { instancePath: instancePath + "/ircut", schemaPath: "#/properties/ircut/type", keyword: "type", @@ -67045,7 +67566,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data63) > 15) return validate425.errors = [ { + if (func1(data63) > 15) return validate435.errors = [ { instancePath: instancePath + "/ircut", schemaPath: "#/properties/ircut/maxLength", keyword: "maxLength", @@ -67053,7 +67574,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD limit: 15 } } ], !1; - if (func1(data63) < 1) return validate425.errors = [ { + if (func1(data63) < 1) return validate435.errors = [ { instancePath: instancePath + "/ircut", schemaPath: "#/properties/ircut/minLength", keyword: "minLength", @@ -67064,7 +67585,7 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD } var valid96 = _errs241 === errors; } else valid96 = !0; - if (valid96 && void 0 !== data.force_persistence && "boolean" != typeof data.force_persistence) return validate425.errors = [ { + if (valid96 && void 0 !== data.force_persistence && "boolean" != typeof data.force_persistence) return validate435.errors = [ { instancePath: instancePath + "/force_persistence", schemaPath: "#/properties/force_persistence/type", keyword: "type", @@ -67090,22 +67611,22 @@ function validate425(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate425.errors = vErrors, 0 === errors; + return validate435.errors = vErrors, 0 === errors; } -validate425.evaluated = { +validate435.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_ImagingSettingsResponse = validate415; +var __contract_ImagingSettingsResponse = validate425; -function validate415(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate415.evaluated; +function validate425(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate425.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate415.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate425.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -67113,7 +67634,7 @@ function validate415(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate415.errors = [ { + if (void 0 === data.success) return validate425.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -67121,7 +67642,7 @@ function validate415(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.settings) return validate415.errors = [ { + if (void 0 === data.settings) return validate425.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -67131,7 +67652,7 @@ function validate415(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("success" !== key0 && "settings" !== key0) return validate415.errors = [ { + for (let key0 in data) if ("success" !== key0 && "settings" !== key0) return validate425.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -67142,7 +67663,7 @@ function validate415(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.success) { let _errs2 = errors; - if (!0 !== data.success) return validate415.errors = [ { + if (!0 !== data.success) return validate425.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -67152,689 +67673,1254 @@ function validate415(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid0 = _errs2 === errors; } else valid0 = !0; - valid0 && void 0 !== data.settings && (validate414(data.settings, { + valid0 && void 0 !== data.settings && (validate424(data.settings, { instancePath: instancePath + "/settings", parentData: data, parentDataProperty: "settings", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate414.errors), errors = vErrors.length)); + }) || (vErrors = vErrors.concat(validate424.errors), errors = vErrors.length)); } } } - return validate415.errors = vErrors, 0 === errors; + return validate425.errors = vErrors, 0 === errors; } -validate415.evaluated = { +validate425.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_LegacyApiKeyCreated = validate179; - -function validate179(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate179.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate179.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate179.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" +var __contract_IntegrationCapabilities = validate501, schema625 = { + type: "object", + properties: { + contract_versions: { + type: "object", + properties: { + observations: { + type: "integer", + minimum: 1 + }, + classifications: { + type: "integer", + minimum: 1 + }, + observation_event: { + type: "string", + minLength: 1 + } + }, + required: [ "observations", "classifications", "observation_event" ], + additionalProperties: !0 + }, + source: { + type: "object", + properties: { + id: { + type: "string", + pattern: "^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + }, + kind: { + const: "lightnvr" + }, + installation_uuid: { + $ref: "#/$defs/ObservationUuid" + }, + product_version: { + type: "string", + minLength: 1 + } + }, + required: [ "id", "kind", "installation_uuid", "product_version" ], + additionalProperties: !0 + }, + principal: { + type: "object", + properties: { + writer_namespace: { + type: [ "string", "null" ], + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + }, + required: [ "writer_namespace" ], + additionalProperties: !0 + }, + operations: { + type: "object", + properties: { + "observations.history": { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + "observations.snapshot": { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + "observations.annotate": { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + track_lifecycle: { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + recorded_frame: { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + event_clip: { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + range_clip: { + $ref: "#/$defs/IntegrationCapabilityOperation" + } + }, + required: [ "observations.history", "observations.snapshot", "observations.annotate", "track_lifecycle", "recorded_frame", "event_clip", "range_clip" ], + additionalProperties: { + $ref: "#/$defs/IntegrationCapabilityOperation" + }, + description: "Unsupported features are declared explicitly; never infer support from an error." + }, + limits: { + type: "object", + properties: { + history_page_default: { + type: "integer", + minimum: 1 + }, + history_page_max: { + type: "integer", + minimum: 1 + }, + history_filter_cameras_max: { + type: "integer", + minimum: 1 + }, + history_filter_labels_max: { + type: "integer", + minimum: 1 + }, + max_objects_per_batch: { + type: "integer", + minimum: 1 + }, + label_max_bytes: { + type: "integer", + minimum: 1 + }, + snapshot_retry_after_max_seconds: { + type: "integer", + minimum: 1 + }, + annotations_per_observation_max: { + type: "integer", + minimum: 1 + } + }, + required: [ "history_page_default", "history_page_max", "history_filter_cameras_max", "history_filter_labels_max", "max_objects_per_batch", "label_max_bytes", "snapshot_retry_after_max_seconds", "annotations_per_observation_max" ], + additionalProperties: !0 + }, + history: { + type: "object", + properties: { + retention_days: { + type: "integer", + minimum: 1 + }, + oldest_cursor: { + type: [ "string", "null" ], + maxLength: 256 + }, + oldest_observed_at: { + anyOf: [ { + $ref: "#/$defs/ObservationTime" + }, { + type: "null" + } ] + } + }, + required: [ "retention_days", "oldest_cursor", "oldest_observed_at" ], + additionalProperties: !0 + }, + delivery: { + type: "object", + properties: { + mqtt_enabled: { + type: "boolean" + }, + topic: { + type: [ "string", "null" ], + description: "Configured topic with a literal {camera_uuid} placeholder." + } + }, + required: [ "mqtt_enabled", "topic" ], + additionalProperties: !0 + }, + cameras: { + type: "array", + maxItems: 1024, + items: { + type: "object", + properties: { + camera_uuid: { + $ref: "#/$defs/ObservationUuid" + }, + evidence_enabled: { + type: "boolean" + }, + actions: { + type: "array", + uniqueItems: !0, + items: { + type: "string", + enum: [ "live.view", "snapshot.create", "observations.annotate" ] + } + } + }, + required: [ "camera_uuid", "evidence_enabled", "actions" ], + additionalProperties: !0 + } } - } ], !1; - if (void 0 === data.api_key) return validate179.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "api_key" + }, + required: [ "contract_versions", "source", "principal", "operations", "limits", "history", "delivery", "cameras" ], + additionalProperties: !0, + description: "Capability document scoped to the calling principal (contract §9)." +}, schema624 = { + type: "object", + properties: { + supported: { + type: "boolean" + }, + authorized: { + type: "boolean" + }, + reason: { + type: "string", + enum: [ "not_implemented", "not_available_in_this_version", "not_authorized", "invalid_writer_namespace", "reserved_writer_namespace" ] } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "api_key" !== key0) return validate179.errors = [ { + }, + required: [ "supported", "authorized" ], + additionalProperties: !0 +}, pattern215 = new RegExp("^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", "u"); + +function validate501(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate501.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate501.errors = [ { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/type", + keyword: "type", params: { - additionalProperty: key0 + type: "object" } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate179.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", + if (void 0 === data.contract_versions) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "contract_versions" + } + } ], !1; + if (void 0 === data.source) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "source" + } + } ], !1; + if (void 0 === data.principal) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "principal" + } + } ], !1; + if (void 0 === data.operations) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "operations" + } + } ], !1; + if (void 0 === data.limits) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "limits" + } + } ], !1; + if (void 0 === data.history) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "history" + } + } ], !1; + if (void 0 === data.delivery) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "delivery" + } + } ], !1; + if (void 0 === data.cameras) return validate501.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "cameras" + } + } ], !1; + if (void 0 !== data.contract_versions) { + let data0 = data.contract_versions, _errs2 = errors; + if (errors === _errs2) { + if (!data0 || "object" != typeof data0 || Array.isArray(data0)) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions", + schemaPath: "#/properties/contract_versions/type", + keyword: "type", params: { - allowedValue: !0 + type: "object" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0 && void 0 !== data.api_key) { - let data1 = data.api_key; - if ("string" != typeof data1) return validate179.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/type", - keyword: "type", + if (void 0 === data0.observations) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions", + schemaPath: "#/properties/contract_versions/required", + keyword: "required", params: { - type: "string" + missingProperty: "observations" } } ], !1; - if (func1(data1) < 1) return validate179.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/minLength", - keyword: "minLength", + if (void 0 === data0.classifications) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions", + schemaPath: "#/properties/contract_versions/required", + keyword: "required", params: { - limit: 1 + missingProperty: "classifications" } } ], !1; - } - } - } - return validate179.errors = [], !0; -} - -validate179.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LiveCameraSlot = validate227; - -function validate227(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate227.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate227.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.camera_uuid) return validate227.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "camera_uuid" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("camera_uuid" !== key0) return validate227.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1 && void 0 !== data.camera_uuid) { - let data0 = data.camera_uuid; - if ("string" != typeof data0) return validate227.errors = [ { - instancePath: instancePath + "/camera_uuid", - schemaPath: "#/$defs/CameraUuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (!pattern4.test(data0)) return validate227.errors = [ { - instancePath: instancePath + "/camera_uuid", - schemaPath: "#/$defs/CameraUuid/pattern", - keyword: "pattern", - params: { - pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" - } - } ], !1; - } - } - return validate227.errors = [], !0; -} - -validate227.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LiveLayout = validate232, schema248 = { - type: "object", - properties: { - name: { - type: "string", - minLength: 1, - maxLength: 127 - }, - is_shared: { - type: "boolean" - }, - location_uuid: { - anyOf: [ { - $ref: "#/$defs/CameraUuid" - }, { - type: "null" - } ] - }, - availability: { - type: "string", - enum: [ "all", "live", "offline", "never_connected", "disabled" ] - }, - columns: { - type: "integer", - minimum: 1, - maximum: 9 - }, - rows: { - type: "integer", - minimum: 1, - maximum: 9 - }, - camera_slots: { - type: "array", - items: { - $ref: "#/$defs/LiveCameraSlot" - }, - maxItems: 36 - }, - uuid: { - $ref: "#/$defs/CameraUuid" - }, - owned: { - type: "boolean" - }, - partially_authorized: { - type: "boolean" - }, - revision: { - type: "integer", - minimum: 1 - }, - created_at: { - type: "integer", - minimum: 0 - }, - updated_at: { - type: "integer", - minimum: 0 - } - }, - required: [ "name", "is_shared", "location_uuid", "availability", "columns", "rows", "camera_slots", "uuid", "owned", "partially_authorized", "revision", "created_at", "updated_at" ], - additionalProperties: !1 -}; - -function validate232(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate232.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.name) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data.is_shared) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "is_shared" - } - } ], !1; - if (void 0 === data.location_uuid) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "location_uuid" - } - } ], !1; - if (void 0 === data.availability) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "availability" - } - } ], !1; - if (void 0 === data.columns) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "columns" - } - } ], !1; - if (void 0 === data.rows) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "rows" - } - } ], !1; - if (void 0 === data.camera_slots) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "camera_slots" - } - } ], !1; - if (void 0 === data.uuid) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "uuid" - } - } ], !1; - if (void 0 === data.owned) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "owned" - } - } ], !1; - if (void 0 === data.partially_authorized) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "partially_authorized" - } - } ], !1; - if (void 0 === data.revision) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "revision" - } - } ], !1; - if (void 0 === data.created_at) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "created_at" - } - } ], !1; - if (void 0 === data.updated_at) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "updated_at" - } - } ], !1; - { - let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema248.properties, key0)) return validate232.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (_errs1 === errors) { - if (void 0 !== data.name) { - let data0 = data.name, _errs2 = errors; - if (errors === _errs2) { - if ("string" != typeof data0) return validate232.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/type", + if (void 0 === data0.observation_event) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions", + schemaPath: "#/properties/contract_versions/required", + keyword: "required", + params: { + missingProperty: "observation_event" + } + } ], !1; + if (void 0 !== data0.observations) { + let data1 = data0.observations, _errs5 = errors; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions/observations", + schemaPath: "#/properties/contract_versions/properties/observations/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions/observations", + schemaPath: "#/properties/contract_versions/properties/observations/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid1 = _errs5 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data0.classifications) { + let data2 = data0.classifications, _errs7 = errors; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions/classifications", + schemaPath: "#/properties/contract_versions/properties/classifications/type", keyword: "type", params: { - type: "string" - } - } ], !1; - if (func1(data0) > 127) return validate232.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/maxLength", - keyword: "maxLength", - params: { - limit: 127 + type: "integer" } } ], !1; - if (func1(data0) < 1) return validate232.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/minLength", - keyword: "minLength", + if (errors === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions/classifications", + schemaPath: "#/properties/contract_versions/properties/classifications/minimum", + keyword: "minimum", params: { + comparison: ">=", limit: 1 } } ], !1; + var valid2 = _errs7 === errors; + } else valid2 = !0; + if (valid2 && void 0 !== data0.observation_event) { + let data3 = data0.observation_event; + if (errors == errors) { + if ("string" != typeof data3) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions/observation_event", + schemaPath: "#/properties/contract_versions/properties/observation_event/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data3) < 1) return validate501.errors = [ { + instancePath: instancePath + "/contract_versions/observation_event", + schemaPath: "#/properties/contract_versions/properties/observation_event/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } } - var valid0 = _errs2 === errors; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.is_shared) { - let _errs4 = errors; - if ("boolean" != typeof data.is_shared) return validate232.errors = [ { - instancePath: instancePath + "/is_shared", - schemaPath: "#/properties/is_shared/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid1 = _errs4 === errors; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.location_uuid) { - let data2 = data.location_uuid, _errs6 = errors, _errs7 = errors, valid3 = !1, _errs8 = errors; - if (errors == errors) if ("string" == typeof data2) { - if (!pattern4.test(data2)) { - let err0 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/$defs/CameraUuid/pattern", + } + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.source) { + let data4 = data.source, _errs11 = errors; + if (errors === _errs11) { + if (!data4 || "object" != typeof data4 || Array.isArray(data4)) return validate501.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data4.id) return validate501.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/required", + keyword: "required", + params: { + missingProperty: "id" + } + } ], !1; + if (void 0 === data4.kind) return validate501.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/required", + keyword: "required", + params: { + missingProperty: "kind" + } + } ], !1; + if (void 0 === data4.installation_uuid) return validate501.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/required", + keyword: "required", + params: { + missingProperty: "installation_uuid" + } + } ], !1; + if (void 0 === data4.product_version) return validate501.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/required", + keyword: "required", + params: { + missingProperty: "product_version" + } + } ], !1; + if (void 0 !== data4.id) { + let data5 = data4.id, _errs14 = errors; + if (errors === _errs14) { + if ("string" != typeof data5) return validate501.errors = [ { + instancePath: instancePath + "/source/id", + schemaPath: "#/properties/source/properties/id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern215.test(data5)) return validate501.errors = [ { + instancePath: instancePath + "/source/id", + schemaPath: "#/properties/source/properties/id/pattern", + keyword: "pattern", + params: { + pattern: "^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + var valid5 = _errs14 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data4.kind) { + let _errs16 = errors; + if ("lightnvr" !== data4.kind) return validate501.errors = [ { + instancePath: instancePath + "/source/kind", + schemaPath: "#/properties/source/properties/kind/const", + keyword: "const", + params: { + allowedValue: "lightnvr" + } + } ], !1; + var valid6 = _errs16 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data4.installation_uuid) { + let data7 = data4.installation_uuid, _errs17 = errors; + if (errors == errors) { + if ("string" != typeof data7) return validate501.errors = [ { + instancePath: instancePath + "/source/installation_uuid", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data7)) return validate501.errors = [ { + instancePath: instancePath + "/source/installation_uuid", + schemaPath: "#/$defs/ObservationUuid/pattern", keyword: "pattern", params: { - pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" } - }; - vErrors.push(err0), errors++; + } ], !1; } - } else { - let err1 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/$defs/CameraUuid/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err1), errors++; - } - var _valid0 = _errs8 === errors; - valid3 = valid3 || _valid0; - let _errs11 = errors; - if (null !== data2) { - let err2 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/properties/location_uuid/anyOf/1/type", - keyword: "type", - params: { - type: "null" - } - }; - vErrors.push(err2), errors++; - } - if (_valid0 = _errs11 === errors, valid3 = valid3 || _valid0, !valid3) { - let err3 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/properties/location_uuid/anyOf", - keyword: "anyOf", - params: {} - }; - return vErrors.push(err3), errors++, validate232.errors = vErrors, !1; - } - errors = _errs7, vErrors.length = _errs7; - var valid2 = _errs6 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.availability) { - let data3 = data.availability, _errs13 = errors; - if ("string" != typeof data3) return validate232.errors = [ { - instancePath: instancePath + "/availability", - schemaPath: "#/properties/availability/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("all" !== data3 && "live" !== data3 && "offline" !== data3 && "never_connected" !== data3 && "disabled" !== data3) return validate232.errors = [ { - instancePath: instancePath + "/availability", - schemaPath: "#/properties/availability/enum", - keyword: "enum", - params: { - allowedValues: schema248.properties.availability.enum - } - } ], !1; - var valid5 = _errs13 === errors; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.columns) { - let data4 = data.columns, _errs15 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate232.errors = [ { - instancePath: instancePath + "/columns", - schemaPath: "#/properties/columns/type", + var valid7 = _errs17 === errors; + } else valid7 = !0; + if (valid7 && void 0 !== data4.product_version) { + let data8 = data4.product_version; + if (errors == errors) { + if ("string" != typeof data8) return validate501.errors = [ { + instancePath: instancePath + "/source/product_version", + schemaPath: "#/properties/source/properties/product_version/type", keyword: "type", params: { - type: "integer" + type: "string" } } ], !1; - if (errors === _errs15 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 9 || isNaN(data4)) return validate232.errors = [ { - instancePath: instancePath + "/columns", - schemaPath: "#/properties/columns/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 9 - } - } ], !1; - if (data4 < 1 || isNaN(data4)) return validate232.errors = [ { - instancePath: instancePath + "/columns", - schemaPath: "#/properties/columns/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 - } - } ], !1; - } - var valid6 = _errs15 === errors; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.rows) { - let data5 = data.rows, _errs17 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate232.errors = [ { - instancePath: instancePath + "/rows", - schemaPath: "#/properties/rows/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (errors === _errs17 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 9 || isNaN(data5)) return validate232.errors = [ { - instancePath: instancePath + "/rows", - schemaPath: "#/properties/rows/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 9 - } - } ], !1; - if (data5 < 1 || isNaN(data5)) return validate232.errors = [ { - instancePath: instancePath + "/rows", - schemaPath: "#/properties/rows/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 - } - } ], !1; + if (func1(data8) < 1) return validate501.errors = [ { + instancePath: instancePath + "/source/product_version", + schemaPath: "#/properties/source/properties/product_version/minLength", + keyword: "minLength", + params: { + limit: 1 } - var valid7 = _errs17 === errors; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.camera_slots) { - let data6 = data.camera_slots, _errs19 = errors; - if (errors === _errs19) { - if (!Array.isArray(data6)) return validate232.errors = [ { - instancePath: instancePath + "/camera_slots", - schemaPath: "#/properties/camera_slots/type", + } ], !1; + } + } + } + } + } + var valid4 = _errs11 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.principal) { + let data9 = data.principal, _errs22 = errors; + if (errors === _errs22) { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate501.errors = [ { + instancePath: instancePath + "/principal", + schemaPath: "#/properties/principal/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data9.writer_namespace) return validate501.errors = [ { + instancePath: instancePath + "/principal", + schemaPath: "#/properties/principal/required", + keyword: "required", + params: { + missingProperty: "writer_namespace" + } + } ], !1; + if (void 0 !== data9.writer_namespace) { + let data10 = data9.writer_namespace, _errs25 = errors; + if ("string" != typeof data10 && null !== data10) return validate501.errors = [ { + instancePath: instancePath + "/principal/writer_namespace", + schemaPath: "#/properties/principal/properties/writer_namespace/type", + keyword: "type", + params: { + type: schema625.properties.principal.properties.writer_namespace.type + } + } ], !1; + if (errors === _errs25 && "string" == typeof data10 && !pattern161.test(data10)) return validate501.errors = [ { + instancePath: instancePath + "/principal/writer_namespace", + schemaPath: "#/properties/principal/properties/writer_namespace/pattern", + keyword: "pattern", + params: { + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + } ], !1; + } + } + var valid10 = _errs22 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.operations) { + let data11 = data.operations, _errs27 = errors; + if (errors === _errs27) { + if (!data11 || "object" != typeof data11 || Array.isArray(data11)) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data11["observations.history"]) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "observations.history" + } + } ], !1; + if (void 0 === data11["observations.snapshot"]) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "observations.snapshot" + } + } ], !1; + if (void 0 === data11["observations.annotate"]) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "observations.annotate" + } + } ], !1; + if (void 0 === data11.track_lifecycle) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "track_lifecycle" + } + } ], !1; + if (void 0 === data11.recorded_frame) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "recorded_frame" + } + } ], !1; + if (void 0 === data11.event_clip) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "event_clip" + } + } ], !1; + if (void 0 === data11.range_clip) return validate501.errors = [ { + instancePath: instancePath + "/operations", + schemaPath: "#/properties/operations/required", + keyword: "required", + params: { + missingProperty: "range_clip" + } + } ], !1; + { + let _errs29 = errors; + for (let key0 in data11) if ("observations.history" !== key0 && "observations.snapshot" !== key0 && "observations.annotate" !== key0 && "track_lifecycle" !== key0 && "recorded_frame" !== key0 && "event_clip" !== key0 && "range_clip" !== key0) { + let data12 = data11[key0], _errs30 = errors; + if (errors == errors) { + if (!data12 || "object" != typeof data12 || Array.isArray(data12)) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data12.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data12.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data12.supported) { + let _errs34 = errors; + if ("boolean" != typeof data12.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1") + "/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid15 = _errs34 === errors; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data12.authorized) { + let _errs36 = errors; + if ("boolean" != typeof data12.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1") + "/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", keyword: "type", params: { - type: "array" + type: "boolean" } } ], !1; - if (data6.length > 36) return validate232.errors = [ { - instancePath: instancePath + "/camera_slots", - schemaPath: "#/properties/camera_slots/maxItems", - keyword: "maxItems", + var valid16 = _errs36 === errors; + } else valid16 = !0; + if (valid16 && void 0 !== data12.reason) { + let data15 = data12.reason; + if ("string" != typeof data15) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1") + "/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", params: { - limit: 36 + type: "string" } } ], !1; - { - let len0 = data6.length; - for (let i0 = 0; i0 < len0; i0++) { - let _errs21 = errors; - if (validate227(data6[i0], { - instancePath: instancePath + "/camera_slots/" + i0, - parentData: data6, - parentDataProperty: i0, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate227.errors), errors = vErrors.length), _errs21 !== errors) break; + if ("not_implemented" !== data15 && "not_available_in_this_version" !== data15 && "not_authorized" !== data15 && "invalid_writer_namespace" !== data15 && "reserved_writer_namespace" !== data15) return validate501.errors = [ { + instancePath: instancePath + "/operations/" + key0.replace(/~/g, "~0").replace(/\//g, "~1") + "/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum } - } + } ], !1; } - var valid8 = _errs19 === errors; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.uuid) { - let data8 = data.uuid, _errs22 = errors; - if (errors == errors) { - if ("string" != typeof data8) return validate232.errors = [ { - instancePath: instancePath + "/uuid", - schemaPath: "#/$defs/CameraUuid/type", + } + } + if (_errs30 !== errors) break; + } + if (_errs29 === errors) { + if (void 0 !== data11["observations.history"]) { + let data16 = data11["observations.history"], _errs40 = errors; + if (errors == errors) { + if (!data16 || "object" != typeof data16 || Array.isArray(data16)) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data16.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data16.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data16.supported) { + let _errs44 = errors; + if ("boolean" != typeof data16.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid20 = _errs44 === errors; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data16.authorized) { + let _errs46 = errors; + if ("boolean" != typeof data16.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid21 = _errs46 === errors; + } else valid21 = !0; + if (valid21 && void 0 !== data16.reason) { + let data19 = data16.reason; + if ("string" != typeof data19) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", keyword: "type", params: { type: "string" } } ], !1; - if (!pattern4.test(data8)) return validate232.errors = [ { - instancePath: instancePath + "/uuid", - schemaPath: "#/$defs/CameraUuid/pattern", - keyword: "pattern", + if ("not_implemented" !== data19 && "not_available_in_this_version" !== data19 && "not_authorized" !== data19 && "invalid_writer_namespace" !== data19 && "reserved_writer_namespace" !== data19) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.history/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", params: { - pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + allowedValues: schema624.properties.reason.enum } } ], !1; } - var valid10 = _errs22 === errors; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.owned) { - let _errs25 = errors; - if ("boolean" != typeof data.owned) return validate232.errors = [ { - instancePath: instancePath + "/owned", - schemaPath: "#/properties/owned/type", + } + } + var valid18 = _errs40 === errors; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data11["observations.snapshot"]) { + let data20 = data11["observations.snapshot"], _errs50 = errors; + if (errors == errors) { + if (!data20 || "object" != typeof data20 || Array.isArray(data20)) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data20.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data20.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data20.supported) { + let _errs54 = errors; + if ("boolean" != typeof data20.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", keyword: "type", params: { type: "boolean" } } ], !1; - var valid12 = _errs25 === errors; - } else valid12 = !0; - if (valid12) { - if (void 0 !== data.partially_authorized) { - let _errs27 = errors; - if ("boolean" != typeof data.partially_authorized) return validate232.errors = [ { - instancePath: instancePath + "/partially_authorized", - schemaPath: "#/properties/partially_authorized/type", + var valid25 = _errs54 === errors; + } else valid25 = !0; + if (valid25) { + if (void 0 !== data20.authorized) { + let _errs56 = errors; + if ("boolean" != typeof data20.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", keyword: "type", params: { type: "boolean" } } ], !1; - var valid13 = _errs27 === errors; - } else valid13 = !0; - if (valid13) { - if (void 0 !== data.revision) { - let data11 = data.revision, _errs29 = errors; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate232.errors = [ { - instancePath: instancePath + "/revision", - schemaPath: "#/properties/revision/type", + var valid26 = _errs56 === errors; + } else valid26 = !0; + if (valid26 && void 0 !== data20.reason) { + let data23 = data20.reason; + if ("string" != typeof data23) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("not_implemented" !== data23 && "not_available_in_this_version" !== data23 && "not_authorized" !== data23 && "invalid_writer_namespace" !== data23 && "reserved_writer_namespace" !== data23) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.snapshot/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum + } + } ], !1; + } + } + } + var valid23 = _errs50 === errors; + } else valid23 = !0; + if (valid23) { + if (void 0 !== data11["observations.annotate"]) { + let data24 = data11["observations.annotate"], _errs60 = errors; + if (errors == errors) { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data24.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data24.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data24.supported) { + let _errs64 = errors; + if ("boolean" != typeof data24.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid30 = _errs64 === errors; + } else valid30 = !0; + if (valid30) { + if (void 0 !== data24.authorized) { + let _errs66 = errors; + if ("boolean" != typeof data24.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", keyword: "type", params: { - type: "integer" + type: "boolean" } } ], !1; - if (errors === _errs29 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate232.errors = [ { - instancePath: instancePath + "/revision", - schemaPath: "#/properties/revision/minimum", - keyword: "minimum", + var valid31 = _errs66 === errors; + } else valid31 = !0; + if (valid31 && void 0 !== data24.reason) { + let data27 = data24.reason; + if ("string" != typeof data27) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", params: { - comparison: ">=", - limit: 1 + type: "string" } } ], !1; - var valid14 = _errs29 === errors; - } else valid14 = !0; - if (valid14) { - if (void 0 !== data.created_at) { - let data12 = data.created_at, _errs31 = errors; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate232.errors = [ { - instancePath: instancePath + "/created_at", - schemaPath: "#/properties/created_at/type", + if ("not_implemented" !== data27 && "not_available_in_this_version" !== data27 && "not_authorized" !== data27 && "invalid_writer_namespace" !== data27 && "reserved_writer_namespace" !== data27) return validate501.errors = [ { + instancePath: instancePath + "/operations/observations.annotate/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum + } + } ], !1; + } + } + } + var valid28 = _errs60 === errors; + } else valid28 = !0; + if (valid28) { + if (void 0 !== data11.track_lifecycle) { + let data28 = data11.track_lifecycle, _errs70 = errors; + if (errors == errors) { + if (!data28 || "object" != typeof data28 || Array.isArray(data28)) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data28.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data28.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data28.supported) { + let _errs74 = errors; + if ("boolean" != typeof data28.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid35 = _errs74 === errors; + } else valid35 = !0; + if (valid35) { + if (void 0 !== data28.authorized) { + let _errs76 = errors; + if ("boolean" != typeof data28.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", keyword: "type", params: { - type: "integer" + type: "boolean" } } ], !1; - if (errors === _errs31 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate232.errors = [ { - instancePath: instancePath + "/created_at", - schemaPath: "#/properties/created_at/minimum", - keyword: "minimum", + var valid36 = _errs76 === errors; + } else valid36 = !0; + if (valid36 && void 0 !== data28.reason) { + let data31 = data28.reason; + if ("string" != typeof data31) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", params: { - comparison: ">=", - limit: 0 + type: "string" } } ], !1; - var valid15 = _errs31 === errors; - } else valid15 = !0; - if (valid15 && void 0 !== data.updated_at) { - let data13 = data.updated_at, _errs33 = errors; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate232.errors = [ { - instancePath: instancePath + "/updated_at", - schemaPath: "#/properties/updated_at/type", + if ("not_implemented" !== data31 && "not_available_in_this_version" !== data31 && "not_authorized" !== data31 && "invalid_writer_namespace" !== data31 && "reserved_writer_namespace" !== data31) return validate501.errors = [ { + instancePath: instancePath + "/operations/track_lifecycle/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum + } + } ], !1; + } + } + } + var valid33 = _errs70 === errors; + } else valid33 = !0; + if (valid33) { + if (void 0 !== data11.recorded_frame) { + let data32 = data11.recorded_frame, _errs80 = errors; + if (errors == errors) { + if (!data32 || "object" != typeof data32 || Array.isArray(data32)) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data32.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data32.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data32.supported) { + let _errs84 = errors; + if ("boolean" != typeof data32.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", keyword: "type", params: { - type: "integer" + type: "boolean" } } ], !1; - if (errors === _errs33 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate232.errors = [ { - instancePath: instancePath + "/updated_at", - schemaPath: "#/properties/updated_at/minimum", - keyword: "minimum", + var valid40 = _errs84 === errors; + } else valid40 = !0; + if (valid40) { + if (void 0 !== data32.authorized) { + let _errs86 = errors; + if ("boolean" != typeof data32.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid41 = _errs86 === errors; + } else valid41 = !0; + if (valid41 && void 0 !== data32.reason) { + let data35 = data32.reason; + if ("string" != typeof data35) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("not_implemented" !== data35 && "not_available_in_this_version" !== data35 && "not_authorized" !== data35 && "invalid_writer_namespace" !== data35 && "reserved_writer_namespace" !== data35) return validate501.errors = [ { + instancePath: instancePath + "/operations/recorded_frame/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum + } + } ], !1; + } + } + } + var valid38 = _errs80 === errors; + } else valid38 = !0; + if (valid38) { + if (void 0 !== data11.event_clip) { + let data36 = data11.event_clip, _errs90 = errors; + if (errors == errors) { + if (!data36 || "object" != typeof data36 || Array.isArray(data36)) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", params: { - comparison: ">=", - limit: 0 + type: "object" + } + } ], !1; + if (void 0 === data36.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data36.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" + } + } ], !1; + if (void 0 !== data36.supported) { + let _errs94 = errors; + if ("boolean" != typeof data36.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid45 = _errs94 === errors; + } else valid45 = !0; + if (valid45) { + if (void 0 !== data36.authorized) { + let _errs96 = errors; + if ("boolean" != typeof data36.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid46 = _errs96 === errors; + } else valid46 = !0; + if (valid46 && void 0 !== data36.reason) { + let data39 = data36.reason; + if ("string" != typeof data39) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("not_implemented" !== data39 && "not_available_in_this_version" !== data39 && "not_authorized" !== data39 && "invalid_writer_namespace" !== data39 && "reserved_writer_namespace" !== data39) return validate501.errors = [ { + instancePath: instancePath + "/operations/event_clip/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum + } + } ], !1; + } + } + } + var valid43 = _errs90 === errors; + } else valid43 = !0; + if (valid43 && void 0 !== data11.range_clip) { + let data40 = data11.range_clip; + if (errors == errors) { + if (!data40 || "object" != typeof data40 || Array.isArray(data40)) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip", + schemaPath: "#/$defs/IntegrationCapabilityOperation/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data40.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "supported" + } + } ], !1; + if (void 0 === data40.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip", + schemaPath: "#/$defs/IntegrationCapabilityOperation/required", + keyword: "required", + params: { + missingProperty: "authorized" } } ], !1; + if (void 0 !== data40.supported) { + let _errs104 = errors; + if ("boolean" != typeof data40.supported) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip/supported", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid50 = _errs104 === errors; + } else valid50 = !0; + if (valid50) { + if (void 0 !== data40.authorized) { + let _errs106 = errors; + if ("boolean" != typeof data40.authorized) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip/authorized", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/authorized/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid51 = _errs106 === errors; + } else valid51 = !0; + if (valid51 && void 0 !== data40.reason) { + let data43 = data40.reason; + if ("string" != typeof data43) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("not_implemented" !== data43 && "not_available_in_this_version" !== data43 && "not_authorized" !== data43 && "invalid_writer_namespace" !== data43 && "reserved_writer_namespace" !== data43) return validate501.errors = [ { + instancePath: instancePath + "/operations/range_clip/reason", + schemaPath: "#/$defs/IntegrationCapabilityOperation/properties/reason/enum", + keyword: "enum", + params: { + allowedValues: schema624.properties.reason.enum + } + } ], !1; + } + } } } } @@ -67845,340 +68931,604 @@ function validate232(data, {instancePath: instancePath = "", parentData: parentD } } } - } - } - } - } - } - return validate232.errors = vErrors, 0 === errors; -} - -validate232.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LiveLayoutCreate = validate228, schema244 = { - type: "object", - properties: { - name: { - type: "string", - minLength: 1, - maxLength: 127 - }, - is_shared: { - type: "boolean" - }, - location_uuid: { - anyOf: [ { - $ref: "#/$defs/CameraUuid" - }, { - type: "null" - } ] - }, - availability: { - type: "string", - enum: [ "all", "live", "offline", "never_connected", "disabled" ] - }, - columns: { - type: "integer", - minimum: 1, - maximum: 9 - }, - rows: { - type: "integer", - minimum: 1, - maximum: 9 - }, - camera_slots: { - type: "array", - items: { - $ref: "#/$defs/LiveCameraSlot" - }, - maxItems: 36 - } - }, - required: [ "name", "availability", "columns", "rows", "camera_slots" ], - additionalProperties: !1 -}; - -function validate228(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate228.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.name) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data.availability) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "availability" - } - } ], !1; - if (void 0 === data.columns) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "columns" - } - } ], !1; - if (void 0 === data.rows) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "rows" - } - } ], !1; - if (void 0 === data.camera_slots) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "camera_slots" - } - } ], !1; - { - let _errs1 = errors; - for (let key0 in data) if ("name" !== key0 && "is_shared" !== key0 && "location_uuid" !== key0 && "availability" !== key0 && "columns" !== key0 && "rows" !== key0 && "camera_slots" !== key0) return validate228.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (_errs1 === errors) { - if (void 0 !== data.name) { - let data0 = data.name, _errs2 = errors; - if (errors === _errs2) { - if ("string" != typeof data0) return validate228.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data0) > 127) return validate228.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/maxLength", - keyword: "maxLength", - params: { - limit: 127 - } - } ], !1; - if (func1(data0) < 1) return validate228.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid0 = _errs2 === errors; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.is_shared) { - let _errs4 = errors; - if ("boolean" != typeof data.is_shared) return validate228.errors = [ { - instancePath: instancePath + "/is_shared", - schemaPath: "#/properties/is_shared/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid1 = _errs4 === errors; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.location_uuid) { - let data2 = data.location_uuid, _errs6 = errors, _errs7 = errors, valid3 = !1, _errs8 = errors; - if (errors == errors) if ("string" == typeof data2) { - if (!pattern4.test(data2)) { - let err0 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/$defs/CameraUuid/pattern", - keyword: "pattern", - params: { - pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" - } - }; - vErrors.push(err0), errors++; - } - } else { - let err1 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/$defs/CameraUuid/type", + var valid12 = _errs27 === errors; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data.limits) { + let data44 = data.limits, _errs110 = errors; + if (errors === _errs110) { + if (!data44 || "object" != typeof data44 || Array.isArray(data44)) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/type", keyword: "type", params: { - type: "string" + type: "object" } - }; - vErrors.push(err1), errors++; - } - var _valid0 = _errs8 === errors; - valid3 = valid3 || _valid0; - let _errs11 = errors; - if (null !== data2) { - let err2 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/properties/location_uuid/anyOf/1/type", - keyword: "type", + } ], !1; + if (void 0 === data44.history_page_default) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", params: { - type: "null" + missingProperty: "history_page_default" } - }; - vErrors.push(err2), errors++; - } - if (_valid0 = _errs11 === errors, valid3 = valid3 || _valid0, !valid3) { - let err3 = { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/properties/location_uuid/anyOf", - keyword: "anyOf", - params: {} - }; - return vErrors.push(err3), errors++, validate228.errors = vErrors, !1; - } - errors = _errs7, vErrors.length = _errs7; - var valid2 = _errs6 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.availability) { - let data3 = data.availability, _errs13 = errors; - if ("string" != typeof data3) return validate228.errors = [ { - instancePath: instancePath + "/availability", - schemaPath: "#/properties/availability/type", - keyword: "type", + } ], !1; + if (void 0 === data44.history_page_max) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", params: { - type: "string" + missingProperty: "history_page_max" } } ], !1; - if ("all" !== data3 && "live" !== data3 && "offline" !== data3 && "never_connected" !== data3 && "disabled" !== data3) return validate228.errors = [ { - instancePath: instancePath + "/availability", - schemaPath: "#/properties/availability/enum", - keyword: "enum", + if (void 0 === data44.history_filter_cameras_max) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", params: { - allowedValues: schema244.properties.availability.enum + missingProperty: "history_filter_cameras_max" } } ], !1; - var valid5 = _errs13 === errors; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.columns) { - let data4 = data.columns, _errs15 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate228.errors = [ { - instancePath: instancePath + "/columns", - schemaPath: "#/properties/columns/type", + if (void 0 === data44.history_filter_labels_max) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", + params: { + missingProperty: "history_filter_labels_max" + } + } ], !1; + if (void 0 === data44.max_objects_per_batch) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", + params: { + missingProperty: "max_objects_per_batch" + } + } ], !1; + if (void 0 === data44.label_max_bytes) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", + params: { + missingProperty: "label_max_bytes" + } + } ], !1; + if (void 0 === data44.snapshot_retry_after_max_seconds) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", + params: { + missingProperty: "snapshot_retry_after_max_seconds" + } + } ], !1; + if (void 0 === data44.annotations_per_observation_max) return validate501.errors = [ { + instancePath: instancePath + "/limits", + schemaPath: "#/properties/limits/required", + keyword: "required", + params: { + missingProperty: "annotations_per_observation_max" + } + } ], !1; + if (void 0 !== data44.history_page_default) { + let data45 = data44.history_page_default, _errs113 = errors; + if ("number" != typeof data45 || data45 % 1 || isNaN(data45) || !isFinite(data45)) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_page_default", + schemaPath: "#/properties/limits/properties/history_page_default/type", keyword: "type", params: { type: "integer" } } ], !1; - if (errors === _errs15 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 9 || isNaN(data4)) return validate228.errors = [ { - instancePath: instancePath + "/columns", - schemaPath: "#/properties/columns/maximum", - keyword: "maximum", + if (errors === _errs113 && "number" == typeof data45 && isFinite(data45) && (data45 < 1 || isNaN(data45))) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_page_default", + schemaPath: "#/properties/limits/properties/history_page_default/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid54 = _errs113 === errors; + } else valid54 = !0; + if (valid54) { + if (void 0 !== data44.history_page_max) { + let data46 = data44.history_page_max, _errs115 = errors; + if ("number" != typeof data46 || data46 % 1 || isNaN(data46) || !isFinite(data46)) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_page_max", + schemaPath: "#/properties/limits/properties/history_page_max/type", + keyword: "type", params: { - comparison: "<=", - limit: 9 + type: "integer" } } ], !1; - if (data4 < 1 || isNaN(data4)) return validate228.errors = [ { - instancePath: instancePath + "/columns", - schemaPath: "#/properties/columns/minimum", + if (errors === _errs115 && "number" == typeof data46 && isFinite(data46) && (data46 < 1 || isNaN(data46))) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_page_max", + schemaPath: "#/properties/limits/properties/history_page_max/minimum", keyword: "minimum", params: { comparison: ">=", limit: 1 } } ], !1; + var valid55 = _errs115 === errors; + } else valid55 = !0; + if (valid55) { + if (void 0 !== data44.history_filter_cameras_max) { + let data47 = data44.history_filter_cameras_max, _errs117 = errors; + if ("number" != typeof data47 || data47 % 1 || isNaN(data47) || !isFinite(data47)) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_filter_cameras_max", + schemaPath: "#/properties/limits/properties/history_filter_cameras_max/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs117 && "number" == typeof data47 && isFinite(data47) && (data47 < 1 || isNaN(data47))) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_filter_cameras_max", + schemaPath: "#/properties/limits/properties/history_filter_cameras_max/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid56 = _errs117 === errors; + } else valid56 = !0; + if (valid56) { + if (void 0 !== data44.history_filter_labels_max) { + let data48 = data44.history_filter_labels_max, _errs119 = errors; + if ("number" != typeof data48 || data48 % 1 || isNaN(data48) || !isFinite(data48)) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_filter_labels_max", + schemaPath: "#/properties/limits/properties/history_filter_labels_max/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs119 && "number" == typeof data48 && isFinite(data48) && (data48 < 1 || isNaN(data48))) return validate501.errors = [ { + instancePath: instancePath + "/limits/history_filter_labels_max", + schemaPath: "#/properties/limits/properties/history_filter_labels_max/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid57 = _errs119 === errors; + } else valid57 = !0; + if (valid57) { + if (void 0 !== data44.max_objects_per_batch) { + let data49 = data44.max_objects_per_batch, _errs121 = errors; + if ("number" != typeof data49 || data49 % 1 || isNaN(data49) || !isFinite(data49)) return validate501.errors = [ { + instancePath: instancePath + "/limits/max_objects_per_batch", + schemaPath: "#/properties/limits/properties/max_objects_per_batch/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs121 && "number" == typeof data49 && isFinite(data49) && (data49 < 1 || isNaN(data49))) return validate501.errors = [ { + instancePath: instancePath + "/limits/max_objects_per_batch", + schemaPath: "#/properties/limits/properties/max_objects_per_batch/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid58 = _errs121 === errors; + } else valid58 = !0; + if (valid58) { + if (void 0 !== data44.label_max_bytes) { + let data50 = data44.label_max_bytes, _errs123 = errors; + if ("number" != typeof data50 || data50 % 1 || isNaN(data50) || !isFinite(data50)) return validate501.errors = [ { + instancePath: instancePath + "/limits/label_max_bytes", + schemaPath: "#/properties/limits/properties/label_max_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs123 && "number" == typeof data50 && isFinite(data50) && (data50 < 1 || isNaN(data50))) return validate501.errors = [ { + instancePath: instancePath + "/limits/label_max_bytes", + schemaPath: "#/properties/limits/properties/label_max_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid59 = _errs123 === errors; + } else valid59 = !0; + if (valid59) { + if (void 0 !== data44.snapshot_retry_after_max_seconds) { + let data51 = data44.snapshot_retry_after_max_seconds, _errs125 = errors; + if ("number" != typeof data51 || data51 % 1 || isNaN(data51) || !isFinite(data51)) return validate501.errors = [ { + instancePath: instancePath + "/limits/snapshot_retry_after_max_seconds", + schemaPath: "#/properties/limits/properties/snapshot_retry_after_max_seconds/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs125 && "number" == typeof data51 && isFinite(data51) && (data51 < 1 || isNaN(data51))) return validate501.errors = [ { + instancePath: instancePath + "/limits/snapshot_retry_after_max_seconds", + schemaPath: "#/properties/limits/properties/snapshot_retry_after_max_seconds/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid60 = _errs125 === errors; + } else valid60 = !0; + if (valid60 && void 0 !== data44.annotations_per_observation_max) { + let data52 = data44.annotations_per_observation_max, _errs127 = errors; + if ("number" != typeof data52 || data52 % 1 || isNaN(data52) || !isFinite(data52)) return validate501.errors = [ { + instancePath: instancePath + "/limits/annotations_per_observation_max", + schemaPath: "#/properties/limits/properties/annotations_per_observation_max/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs127 && "number" == typeof data52 && isFinite(data52) && (data52 < 1 || isNaN(data52))) return validate501.errors = [ { + instancePath: instancePath + "/limits/annotations_per_observation_max", + schemaPath: "#/properties/limits/properties/annotations_per_observation_max/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + } + } + } + } } - var valid6 = _errs15 === errors; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.rows) { - let data5 = data.rows, _errs17 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate228.errors = [ { - instancePath: instancePath + "/rows", - schemaPath: "#/properties/rows/type", + } + } + var valid53 = _errs110 === errors; + } else valid53 = !0; + if (valid53) { + if (void 0 !== data.history) { + let data53 = data.history, _errs129 = errors; + if (errors === _errs129) { + if (!data53 || "object" != typeof data53 || Array.isArray(data53)) return validate501.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data53.retention_days) return validate501.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "retention_days" + } + } ], !1; + if (void 0 === data53.oldest_cursor) return validate501.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "oldest_cursor" + } + } ], !1; + if (void 0 === data53.oldest_observed_at) return validate501.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "oldest_observed_at" + } + } ], !1; + if (void 0 !== data53.retention_days) { + let data54 = data53.retention_days, _errs132 = errors; + if ("number" != typeof data54 || data54 % 1 || isNaN(data54) || !isFinite(data54)) return validate501.errors = [ { + instancePath: instancePath + "/history/retention_days", + schemaPath: "#/properties/history/properties/retention_days/type", keyword: "type", params: { type: "integer" } } ], !1; - if (errors === _errs17 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 9 || isNaN(data5)) return validate228.errors = [ { - instancePath: instancePath + "/rows", - schemaPath: "#/properties/rows/maximum", - keyword: "maximum", + if (errors === _errs132 && "number" == typeof data54 && isFinite(data54) && (data54 < 1 || isNaN(data54))) return validate501.errors = [ { + instancePath: instancePath + "/history/retention_days", + schemaPath: "#/properties/history/properties/retention_days/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid63 = _errs132 === errors; + } else valid63 = !0; + if (valid63) { + if (void 0 !== data53.oldest_cursor) { + let data55 = data53.oldest_cursor, _errs134 = errors; + if ("string" != typeof data55 && null !== data55) return validate501.errors = [ { + instancePath: instancePath + "/history/oldest_cursor", + schemaPath: "#/properties/history/properties/oldest_cursor/type", + keyword: "type", params: { - comparison: "<=", - limit: 9 + type: schema625.properties.history.properties.oldest_cursor.type } } ], !1; - if (data5 < 1 || isNaN(data5)) return validate228.errors = [ { - instancePath: instancePath + "/rows", - schemaPath: "#/properties/rows/minimum", - keyword: "minimum", + if (errors === _errs134 && "string" == typeof data55 && func1(data55) > 256) return validate501.errors = [ { + instancePath: instancePath + "/history/oldest_cursor", + schemaPath: "#/properties/history/properties/oldest_cursor/maxLength", + keyword: "maxLength", params: { - comparison: ">=", - limit: 1 + limit: 256 } } ], !1; + var valid64 = _errs134 === errors; + } else valid64 = !0; + if (valid64 && void 0 !== data53.oldest_observed_at) { + let data56 = data53.oldest_observed_at, _errs137 = errors, valid66 = !1, _errs138 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data56) { + if (!formats2.validate(data56)) { + let err0 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err0), errors++; + } + } else { + let err1 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err1), errors++; + } + var _valid0 = _errs138 === errors; + valid66 = valid66 || _valid0; + let _errs141 = errors; + if (null !== data56) { + let err2 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/properties/history/properties/oldest_observed_at/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err2), errors++; + } + if (_valid0 = _errs141 === errors, valid66 = valid66 || _valid0, !valid66) { + let err3 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/properties/history/properties/oldest_observed_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err3), errors++, validate501.errors = vErrors, !1; + } + errors = _errs137, vErrors.length = _errs137; } - var valid7 = _errs17 === errors; - } else valid7 = !0; - if (valid7 && void 0 !== data.camera_slots) { - let data6 = data.camera_slots; - if (errors == errors) { - if (!Array.isArray(data6)) return validate228.errors = [ { - instancePath: instancePath + "/camera_slots", - schemaPath: "#/properties/camera_slots/type", + } + } + var valid62 = _errs129 === errors; + } else valid62 = !0; + if (valid62) { + if (void 0 !== data.delivery) { + let data57 = data.delivery, _errs143 = errors; + if (errors === _errs143) { + if (!data57 || "object" != typeof data57 || Array.isArray(data57)) return validate501.errors = [ { + instancePath: instancePath + "/delivery", + schemaPath: "#/properties/delivery/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data57.mqtt_enabled) return validate501.errors = [ { + instancePath: instancePath + "/delivery", + schemaPath: "#/properties/delivery/required", + keyword: "required", + params: { + missingProperty: "mqtt_enabled" + } + } ], !1; + if (void 0 === data57.topic) return validate501.errors = [ { + instancePath: instancePath + "/delivery", + schemaPath: "#/properties/delivery/required", + keyword: "required", + params: { + missingProperty: "topic" + } + } ], !1; + if (void 0 !== data57.mqtt_enabled) { + let _errs146 = errors; + if ("boolean" != typeof data57.mqtt_enabled) return validate501.errors = [ { + instancePath: instancePath + "/delivery/mqtt_enabled", + schemaPath: "#/properties/delivery/properties/mqtt_enabled/type", keyword: "type", params: { - type: "array" + type: "boolean" } } ], !1; - if (data6.length > 36) return validate228.errors = [ { - instancePath: instancePath + "/camera_slots", - schemaPath: "#/properties/camera_slots/maxItems", - keyword: "maxItems", + var valid69 = _errs146 === errors; + } else valid69 = !0; + if (valid69 && void 0 !== data57.topic) { + let data59 = data57.topic; + if ("string" != typeof data59 && null !== data59) return validate501.errors = [ { + instancePath: instancePath + "/delivery/topic", + schemaPath: "#/properties/delivery/properties/topic/type", + keyword: "type", params: { - limit: 36 + type: schema625.properties.delivery.properties.topic.type } } ], !1; - { - let len0 = data6.length; - for (let i0 = 0; i0 < len0; i0++) { - let _errs21 = errors; - if (validate227(data6[i0], { - instancePath: instancePath + "/camera_slots/" + i0, - parentData: data6, - parentDataProperty: i0, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate227.errors), errors = vErrors.length), _errs21 !== errors) break; + } + } + var valid68 = _errs143 === errors; + } else valid68 = !0; + if (valid68 && void 0 !== data.cameras) { + let data60 = data.cameras; + if (errors == errors) { + if (!Array.isArray(data60)) return validate501.errors = [ { + instancePath: instancePath + "/cameras", + schemaPath: "#/properties/cameras/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data60.length > 1024) return validate501.errors = [ { + instancePath: instancePath + "/cameras", + schemaPath: "#/properties/cameras/maxItems", + keyword: "maxItems", + params: { + limit: 1024 + } + } ], !1; + { + let len0 = data60.length; + for (let i0 = 0; i0 < len0; i0++) { + let data61 = data60[i0], _errs152 = errors; + if (errors === _errs152) { + if (!data61 || "object" != typeof data61 || Array.isArray(data61)) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data61.camera_uuid) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "camera_uuid" + } + } ], !1; + if (void 0 === data61.evidence_enabled) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "evidence_enabled" + } + } ], !1; + if (void 0 === data61.actions) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "actions" + } + } ], !1; + if (void 0 !== data61.camera_uuid) { + let data62 = data61.camera_uuid, _errs155 = errors; + if (errors == errors) { + if ("string" != typeof data62) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/camera_uuid", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data62)) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/camera_uuid", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + var valid73 = _errs155 === errors; + } else valid73 = !0; + if (valid73) { + if (void 0 !== data61.evidence_enabled) { + let _errs158 = errors; + if ("boolean" != typeof data61.evidence_enabled) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/evidence_enabled", + schemaPath: "#/properties/cameras/items/properties/evidence_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid75 = _errs158 === errors; + } else valid75 = !0; + if (valid75 && void 0 !== data61.actions) { + let data64 = data61.actions; + if (errors == errors) { + if (!Array.isArray(data64)) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/actions", + schemaPath: "#/properties/cameras/items/properties/actions/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + var valid77 = !0; + let len1 = data64.length; + for (let i1 = 0; i1 < len1; i1++) { + let data65 = data64[i1], _errs162 = errors; + if ("string" != typeof data65) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/actions/" + i1, + schemaPath: "#/properties/cameras/items/properties/actions/items/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("live.view" !== data65 && "snapshot.create" !== data65 && "observations.annotate" !== data65) return validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/actions/" + i1, + schemaPath: "#/properties/cameras/items/properties/actions/items/enum", + keyword: "enum", + params: { + allowedValues: schema625.properties.cameras.items.properties.actions.items.enum + } + } ], !1; + if (!(valid77 = _errs162 === errors)) break; + } + if (valid77) { + let j0, i2 = data64.length; + if (i2 > 1) { + let indices0 = {}; + for (;i2--; ) { + let item0 = data64[i2]; + if ("string" == typeof item0) { + if ("number" == typeof indices0[item0]) return j0 = indices0[item0], validate501.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/actions", + schemaPath: "#/properties/cameras/items/properties/actions/uniqueItems", + keyword: "uniqueItems", + params: { + i: i2, + j: j0 + } + } ], !1; + indices0[item0] = i2; + } + } + } + } + } + } + } + } } + if (_errs152 !== errors) break; } } } @@ -68190,68 +69540,227 @@ function validate228(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate228.errors = vErrors, 0 === errors; + return validate501.errors = vErrors, 0 === errors; } -validate228.evaluated = { +validate501.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_LiveLayoutUpdate = validate230, schema246 = { - type: "object", - properties: { - name: { - type: "string", - minLength: 1, - maxLength: 127 - }, - is_shared: { - type: "boolean" - }, - location_uuid: { - anyOf: [ { - $ref: "#/$defs/CameraUuid" - }, { - type: "null" - } ] - }, - availability: { - type: "string", - enum: [ "all", "live", "offline", "never_connected", "disabled" ] - }, - columns: { - type: "integer", - minimum: 1, - maximum: 9 - }, - rows: { - type: "integer", - minimum: 1, - maximum: 9 - }, - camera_slots: { - type: "array", - items: { - $ref: "#/$defs/LiveCameraSlot" - }, - maxItems: 36 - }, - revision: { - type: "integer", - minimum: 1 - } - }, - required: [ "name", "availability", "columns", "rows", "camera_slots", "revision" ], - additionalProperties: !1 -}; +var __contract_LegacyApiKeyCreated = validate179; -function validate230(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate230.evaluated; +function validate179(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate179.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate179.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate179.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.api_key) return validate179.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "api_key" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("success" !== key0 && "api_key" !== key0) return validate179.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate179.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0 && void 0 !== data.api_key) { + let data1 = data.api_key; + if ("string" != typeof data1) return validate179.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data1) < 1) return validate179.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + } + } + return validate179.errors = [], !0; +} + +validate179.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_LiveCameraSlot = validate227; + +function validate227(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate227.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate227.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.camera_uuid) return validate227.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "camera_uuid" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("camera_uuid" !== key0) return validate227.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1 && void 0 !== data.camera_uuid) { + let data0 = data.camera_uuid; + if ("string" != typeof data0) return validate227.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/$defs/CameraUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern4.test(data0)) return validate227.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/$defs/CameraUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + } + } ], !1; + } + } + return validate227.errors = [], !0; +} + +validate227.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_LiveLayout = validate232, schema248 = { + type: "object", + properties: { + name: { + type: "string", + minLength: 1, + maxLength: 127 + }, + is_shared: { + type: "boolean" + }, + location_uuid: { + anyOf: [ { + $ref: "#/$defs/CameraUuid" + }, { + type: "null" + } ] + }, + availability: { + type: "string", + enum: [ "all", "live", "offline", "never_connected", "disabled" ] + }, + columns: { + type: "integer", + minimum: 1, + maximum: 9 + }, + rows: { + type: "integer", + minimum: 1, + maximum: 9 + }, + camera_slots: { + type: "array", + items: { + $ref: "#/$defs/LiveCameraSlot" + }, + maxItems: 36 + }, + uuid: { + $ref: "#/$defs/CameraUuid" + }, + owned: { + type: "boolean" + }, + partially_authorized: { + type: "boolean" + }, + revision: { + type: "integer", + minimum: 1 + }, + created_at: { + type: "integer", + minimum: 0 + }, + updated_at: { + type: "integer", + minimum: 0 + } + }, + required: [ "name", "is_shared", "location_uuid", "availability", "columns", "rows", "camera_slots", "uuid", "owned", "partially_authorized", "revision", "created_at", "updated_at" ], + additionalProperties: !1 +}; + +function validate232(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate232.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate230.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -68259,7 +69768,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate230.errors = [ { + if (void 0 === data.name) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -68267,7 +69776,23 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.availability) return validate230.errors = [ { + if (void 0 === data.is_shared) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "is_shared" + } + } ], !1; + if (void 0 === data.location_uuid) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "location_uuid" + } + } ], !1; + if (void 0 === data.availability) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -68275,7 +69800,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "availability" } } ], !1; - if (void 0 === data.columns) return validate230.errors = [ { + if (void 0 === data.columns) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -68283,7 +69808,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "columns" } } ], !1; - if (void 0 === data.rows) return validate230.errors = [ { + if (void 0 === data.rows) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -68291,7 +69816,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "rows" } } ], !1; - if (void 0 === data.camera_slots) return validate230.errors = [ { + if (void 0 === data.camera_slots) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -68299,7 +69824,31 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_slots" } } ], !1; - if (void 0 === data.revision) return validate230.errors = [ { + if (void 0 === data.uuid) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "uuid" + } + } ], !1; + if (void 0 === data.owned) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "owned" + } + } ], !1; + if (void 0 === data.partially_authorized) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "partially_authorized" + } + } ], !1; + if (void 0 === data.revision) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -68307,9 +69856,25 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; + if (void 0 === data.created_at) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "created_at" + } + } ], !1; + if (void 0 === data.updated_at) return validate232.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "updated_at" + } + } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("name" !== key0 && "is_shared" !== key0 && "location_uuid" !== key0 && "availability" !== key0 && "columns" !== key0 && "rows" !== key0 && "camera_slots" !== key0 && "revision" !== key0) return validate230.errors = [ { + for (let key0 in data) if (!func7.call(schema248.properties, key0)) return validate232.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -68321,7 +69886,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate230.errors = [ { + if ("string" != typeof data0) return validate232.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -68329,7 +69894,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate230.errors = [ { + if (func1(data0) > 127) return validate232.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -68337,7 +69902,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate230.errors = [ { + if (func1(data0) < 1) return validate232.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -68351,7 +69916,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.is_shared) { let _errs4 = errors; - if ("boolean" != typeof data.is_shared) return validate230.errors = [ { + if ("boolean" != typeof data.is_shared) return validate232.errors = [ { instancePath: instancePath + "/is_shared", schemaPath: "#/properties/is_shared/type", keyword: "type", @@ -68408,7 +69973,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate230.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate232.errors = vErrors, !1; } errors = _errs7, vErrors.length = _errs7; var valid2 = _errs6 === errors; @@ -68416,7 +69981,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.availability) { let data3 = data.availability, _errs13 = errors; - if ("string" != typeof data3) return validate230.errors = [ { + if ("string" != typeof data3) return validate232.errors = [ { instancePath: instancePath + "/availability", schemaPath: "#/properties/availability/type", keyword: "type", @@ -68424,12 +69989,12 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("all" !== data3 && "live" !== data3 && "offline" !== data3 && "never_connected" !== data3 && "disabled" !== data3) return validate230.errors = [ { + if ("all" !== data3 && "live" !== data3 && "offline" !== data3 && "never_connected" !== data3 && "disabled" !== data3) return validate232.errors = [ { instancePath: instancePath + "/availability", schemaPath: "#/properties/availability/enum", keyword: "enum", params: { - allowedValues: schema246.properties.availability.enum + allowedValues: schema248.properties.availability.enum } } ], !1; var valid5 = _errs13 === errors; @@ -68437,7 +70002,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.columns) { let data4 = data.columns, _errs15 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate230.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate232.errors = [ { instancePath: instancePath + "/columns", schemaPath: "#/properties/columns/type", keyword: "type", @@ -68446,7 +70011,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs15 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 9 || isNaN(data4)) return validate230.errors = [ { + if (data4 > 9 || isNaN(data4)) return validate232.errors = [ { instancePath: instancePath + "/columns", schemaPath: "#/properties/columns/maximum", keyword: "maximum", @@ -68455,7 +70020,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD limit: 9 } } ], !1; - if (data4 < 1 || isNaN(data4)) return validate230.errors = [ { + if (data4 < 1 || isNaN(data4)) return validate232.errors = [ { instancePath: instancePath + "/columns", schemaPath: "#/properties/columns/minimum", keyword: "minimum", @@ -68470,7 +70035,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.rows) { let data5 = data.rows, _errs17 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate230.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate232.errors = [ { instancePath: instancePath + "/rows", schemaPath: "#/properties/rows/type", keyword: "type", @@ -68479,7 +70044,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs17 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 9 || isNaN(data5)) return validate230.errors = [ { + if (data5 > 9 || isNaN(data5)) return validate232.errors = [ { instancePath: instancePath + "/rows", schemaPath: "#/properties/rows/maximum", keyword: "maximum", @@ -68488,7 +70053,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD limit: 9 } } ], !1; - if (data5 < 1 || isNaN(data5)) return validate230.errors = [ { + if (data5 < 1 || isNaN(data5)) return validate232.errors = [ { instancePath: instancePath + "/rows", schemaPath: "#/properties/rows/minimum", keyword: "minimum", @@ -68504,7 +70069,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_slots) { let data6 = data.camera_slots, _errs19 = errors; if (errors === _errs19) { - if (!Array.isArray(data6)) return validate230.errors = [ { + if (!Array.isArray(data6)) return validate232.errors = [ { instancePath: instancePath + "/camera_slots", schemaPath: "#/properties/camera_slots/type", keyword: "type", @@ -68512,7 +70077,7 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data6.length > 36) return validate230.errors = [ { + if (data6.length > 36) return validate232.errors = [ { instancePath: instancePath + "/camera_slots", schemaPath: "#/properties/camera_slots/maxItems", keyword: "maxItems", @@ -68536,25 +70101,123 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD } var valid8 = _errs19 === errors; } else valid8 = !0; - if (valid8 && void 0 !== data.revision) { - let data8 = data.revision, _errs22 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate230.errors = [ { - instancePath: instancePath + "/revision", - schemaPath: "#/properties/revision/type", - keyword: "type", - params: { - type: "integer" + if (valid8) { + if (void 0 !== data.uuid) { + let data8 = data.uuid, _errs22 = errors; + if (errors == errors) { + if ("string" != typeof data8) return validate232.errors = [ { + instancePath: instancePath + "/uuid", + schemaPath: "#/$defs/CameraUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern4.test(data8)) return validate232.errors = [ { + instancePath: instancePath + "/uuid", + schemaPath: "#/$defs/CameraUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + } + } ], !1; } - } ], !1; - if (errors === _errs22 && "number" == typeof data8 && isFinite(data8) && (data8 < 1 || isNaN(data8))) return validate230.errors = [ { - instancePath: instancePath + "/revision", - schemaPath: "#/properties/revision/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 + var valid10 = _errs22 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.owned) { + let _errs25 = errors; + if ("boolean" != typeof data.owned) return validate232.errors = [ { + instancePath: instancePath + "/owned", + schemaPath: "#/properties/owned/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid12 = _errs25 === errors; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data.partially_authorized) { + let _errs27 = errors; + if ("boolean" != typeof data.partially_authorized) return validate232.errors = [ { + instancePath: instancePath + "/partially_authorized", + schemaPath: "#/properties/partially_authorized/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid13 = _errs27 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.revision) { + let data11 = data.revision, _errs29 = errors; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate232.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs29 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate232.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid14 = _errs29 === errors; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.created_at) { + let data12 = data.created_at, _errs31 = errors; + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate232.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/properties/created_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs31 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate232.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/properties/created_at/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid15 = _errs31 === errors; + } else valid15 = !0; + if (valid15 && void 0 !== data.updated_at) { + let data13 = data.updated_at, _errs33 = errors; + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate232.errors = [ { + instancePath: instancePath + "/updated_at", + schemaPath: "#/properties/updated_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs33 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate232.errors = [ { + instancePath: instancePath + "/updated_at", + schemaPath: "#/properties/updated_at/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } } - } ], !1; + } } } } @@ -68565,22 +70228,64 @@ function validate230(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate230.errors = vErrors, 0 === errors; + return validate232.errors = vErrors, 0 === errors; } -validate230.evaluated = { +validate232.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_LiveLayoutsResponse = validate234; +var __contract_LiveLayoutCreate = validate228, schema244 = { + type: "object", + properties: { + name: { + type: "string", + minLength: 1, + maxLength: 127 + }, + is_shared: { + type: "boolean" + }, + location_uuid: { + anyOf: [ { + $ref: "#/$defs/CameraUuid" + }, { + type: "null" + } ] + }, + availability: { + type: "string", + enum: [ "all", "live", "offline", "never_connected", "disabled" ] + }, + columns: { + type: "integer", + minimum: 1, + maximum: 9 + }, + rows: { + type: "integer", + minimum: 1, + maximum: 9 + }, + camera_slots: { + type: "array", + items: { + $ref: "#/$defs/LiveCameraSlot" + }, + maxItems: 36 + } + }, + required: [ "name", "availability", "columns", "rows", "camera_slots" ], + additionalProperties: !1 +}; -function validate234(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate234.evaluated; +function validate228(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate228.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate234.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate228.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -68588,41 +70293,49 @@ function validate234(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.layouts) return validate234.errors = [ { + if (void 0 === data.name) return validate228.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "layouts" + missingProperty: "name" } } ], !1; - if (void 0 === data.count) return validate234.errors = [ { + if (void 0 === data.availability) return validate228.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "count" + missingProperty: "availability" } } ], !1; - if (void 0 === data.can_share) return validate234.errors = [ { + if (void 0 === data.columns) return validate228.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "can_share" + missingProperty: "columns" } } ], !1; - if (void 0 === data.can_modify) return validate234.errors = [ { + if (void 0 === data.rows) return validate228.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "can_modify" + missingProperty: "rows" + } + } ], !1; + if (void 0 === data.camera_slots) return validate228.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "camera_slots" } } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("layouts" !== key0 && "count" !== key0 && "can_share" !== key0 && "can_modify" !== key0) return validate234.errors = [ { + for (let key0 in data) if ("name" !== key0 && "is_shared" !== key0 && "location_uuid" !== key0 && "availability" !== key0 && "columns" !== key0 && "rows" !== key0 && "camera_slots" !== key0) return validate228.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -68631,977 +70344,595 @@ function validate234(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (_errs1 === errors) { - if (void 0 !== data.layouts) { - let data0 = data.layouts, _errs2 = errors; + if (void 0 !== data.name) { + let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate234.errors = [ { - instancePath: instancePath + "/layouts", - schemaPath: "#/properties/layouts/type", + if ("string" != typeof data0) return validate228.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/type", keyword: "type", params: { - type: "array" + type: "string" } } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let _errs4 = errors; - if (validate232(data0[i0], { - instancePath: instancePath + "/layouts/" + i0, - parentData: data0, - parentDataProperty: i0, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate232.errors), errors = vErrors.length), _errs4 !== errors) break; + if (func1(data0) > 127) return validate228.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/maxLength", + keyword: "maxLength", + params: { + limit: 127 } - } + } ], !1; + if (func1(data0) < 1) return validate228.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; } var valid0 = _errs2 === errors; } else valid0 = !0; if (valid0) { - if (void 0 !== data.count) { - let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate234.errors = [ { - instancePath: instancePath + "/count", - schemaPath: "#/properties/count/type", + if (void 0 !== data.is_shared) { + let _errs4 = errors; + if ("boolean" != typeof data.is_shared) return validate228.errors = [ { + instancePath: instancePath + "/is_shared", + schemaPath: "#/properties/is_shared/type", keyword: "type", params: { - type: "integer" + type: "boolean" } } ], !1; - if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate234.errors = [ { - instancePath: instancePath + "/count", - schemaPath: "#/properties/count/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.location_uuid) { + let data2 = data.location_uuid, _errs6 = errors, _errs7 = errors, valid3 = !1, _errs8 = errors; + if (errors == errors) if ("string" == typeof data2) { + if (!pattern4.test(data2)) { + let err0 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/$defs/CameraUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + } + }; + vErrors.push(err0), errors++; + } + } else { + let err1 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/$defs/CameraUuid/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err1), errors++; } - } ], !1; - var valid2 = _errs5 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.can_share) { - let _errs7 = errors; - if ("boolean" != typeof data.can_share) return validate234.errors = [ { - instancePath: instancePath + "/can_share", - schemaPath: "#/properties/can_share/type", - keyword: "type", - params: { - type: "boolean" + var _valid0 = _errs8 === errors; + valid3 = valid3 || _valid0; + let _errs11 = errors; + if (null !== data2) { + let err2 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/properties/location_uuid/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err2), errors++; + } + if (_valid0 = _errs11 === errors, valid3 = valid3 || _valid0, !valid3) { + let err3 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/properties/location_uuid/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err3), errors++, validate228.errors = vErrors, !1; + } + errors = _errs7, vErrors.length = _errs7; + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.availability) { + let data3 = data.availability, _errs13 = errors; + if ("string" != typeof data3) return validate228.errors = [ { + instancePath: instancePath + "/availability", + schemaPath: "#/properties/availability/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("all" !== data3 && "live" !== data3 && "offline" !== data3 && "never_connected" !== data3 && "disabled" !== data3) return validate228.errors = [ { + instancePath: instancePath + "/availability", + schemaPath: "#/properties/availability/enum", + keyword: "enum", + params: { + allowedValues: schema244.properties.availability.enum + } + } ], !1; + var valid5 = _errs13 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.columns) { + let data4 = data.columns, _errs15 = errors; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate228.errors = [ { + instancePath: instancePath + "/columns", + schemaPath: "#/properties/columns/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs15 && "number" == typeof data4 && isFinite(data4)) { + if (data4 > 9 || isNaN(data4)) return validate228.errors = [ { + instancePath: instancePath + "/columns", + schemaPath: "#/properties/columns/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 9 + } + } ], !1; + if (data4 < 1 || isNaN(data4)) return validate228.errors = [ { + instancePath: instancePath + "/columns", + schemaPath: "#/properties/columns/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid6 = _errs15 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.rows) { + let data5 = data.rows, _errs17 = errors; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate228.errors = [ { + instancePath: instancePath + "/rows", + schemaPath: "#/properties/rows/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs17 && "number" == typeof data5 && isFinite(data5)) { + if (data5 > 9 || isNaN(data5)) return validate228.errors = [ { + instancePath: instancePath + "/rows", + schemaPath: "#/properties/rows/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 9 + } + } ], !1; + if (data5 < 1 || isNaN(data5)) return validate228.errors = [ { + instancePath: instancePath + "/rows", + schemaPath: "#/properties/rows/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid7 = _errs17 === errors; + } else valid7 = !0; + if (valid7 && void 0 !== data.camera_slots) { + let data6 = data.camera_slots; + if (errors == errors) { + if (!Array.isArray(data6)) return validate228.errors = [ { + instancePath: instancePath + "/camera_slots", + schemaPath: "#/properties/camera_slots/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data6.length > 36) return validate228.errors = [ { + instancePath: instancePath + "/camera_slots", + schemaPath: "#/properties/camera_slots/maxItems", + keyword: "maxItems", + params: { + limit: 36 + } + } ], !1; + { + let len0 = data6.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs21 = errors; + if (validate227(data6[i0], { + instancePath: instancePath + "/camera_slots/" + i0, + parentData: data6, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate227.errors), errors = vErrors.length), _errs21 !== errors) break; + } + } + } + } } - } ], !1; - var valid3 = _errs7 === errors; - } else valid3 = !0; - if (valid3 && void 0 !== data.can_modify && "boolean" != typeof data.can_modify) return validate234.errors = [ { - instancePath: instancePath + "/can_modify", - schemaPath: "#/properties/can_modify/type", - keyword: "type", - params: { - type: "boolean" } - } ], !1; + } } } } } } - return validate234.errors = vErrors, 0 === errors; + return validate228.errors = vErrors, 0 === errors; } -validate234.evaluated = { +validate228.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_LiveStreamSummaryPage = validate52, schema33 = { - type: "string", - enum: [ "auto", "webrtc_only", "mse_only", "hls_only", "webrtc_then_mse", "mse_then_hls" ] -}, schema46 = { +var __contract_LiveLayoutUpdate = validate230, schema246 = { type: "object", properties: { - camera_uuid: { - type: "string" - }, name: { - type: "string" - }, - location_uuid: { - type: "string" - }, - location_name: { - type: "string" - }, - location_path: { - type: "string" - }, - codec: { - type: "string" - }, - tags: { - type: "string" - }, - enabled: { - type: "boolean" - }, - record: { - type: "boolean" - }, - record_on_schedule: { - type: "boolean" - }, - detection_based_recording: { - type: "boolean" - }, - isOnvif: { - type: "boolean" + type: "string", + minLength: 1, + maxLength: 127 }, - recording_active: { + is_shared: { type: "boolean" }, - width: { - type: "integer" - }, - height: { - type: "integer" - }, - fps: { - type: "integer" - }, - protocol: { - type: "integer" + location_uuid: { + anyOf: [ { + $ref: "#/$defs/CameraUuid" + }, { + type: "null" + } ] }, - first_video_at: { - type: "integer", - description: "Unix seconds; zero means not observed." + availability: { + type: "string", + enum: [ "all", "live", "offline", "never_connected", "disabled" ] }, - last_frame_ts: { + columns: { type: "integer", - description: "Unix seconds; zero means not observed." + minimum: 1, + maximum: 9 }, - last_recording_at: { + rows: { type: "integer", - description: "Unix seconds; zero means not observed." - }, - current_fps: { - type: "number" + minimum: 1, + maximum: 9 }, - status: { - $ref: "#/$defs/StreamStatus" + camera_slots: { + type: "array", + items: { + $ref: "#/$defs/LiveCameraSlot" + }, + maxItems: 36 }, - availability: { - type: "string", - enum: [ "live", "offline", "never_connected", "disabled" ] + revision: { + type: "integer", + minimum: 1 } }, - required: [ "camera_uuid", "name", "location_uuid", "location_name", "location_path", "codec", "tags", "enabled", "record", "record_on_schedule", "detection_based_recording", "isOnvif", "recording_active", "width", "height", "fps", "protocol", "first_video_at", "last_frame_ts", "last_recording_at", "current_fps", "status", "availability" ], - additionalProperties: !0, - description: "Administrative and live summary fields. Unlike detail, summary rows contain no input URL, credentials or error_message." + required: [ "name", "availability", "columns", "rows", "camera_slots", "revision" ], + additionalProperties: !1 }; -function validate39(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate39.evaluated; +function validate230(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate230.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.camera_uuid) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "camera_uuid" - } - } ], !1; - if (void 0 === data.name) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data.location_uuid) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "location_uuid" - } - } ], !1; - if (void 0 === data.location_name) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "location_name" - } - } ], !1; - if (void 0 === data.location_path) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "location_path" - } - } ], !1; - if (void 0 === data.codec) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "codec" - } - } ], !1; - if (void 0 === data.tags) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "tags" - } - } ], !1; - if (void 0 === data.enabled) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "enabled" - } - } ], !1; - if (void 0 === data.record) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "record" - } - } ], !1; - if (void 0 === data.record_on_schedule) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "record_on_schedule" - } - } ], !1; - if (void 0 === data.detection_based_recording) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "detection_based_recording" - } - } ], !1; - if (void 0 === data.isOnvif) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "isOnvif" - } - } ], !1; - if (void 0 === data.recording_active) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "recording_active" - } - } ], !1; - if (void 0 === data.width) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "width" - } - } ], !1; - if (void 0 === data.height) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "height" - } - } ], !1; - if (void 0 === data.fps) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "fps" - } - } ], !1; - if (void 0 === data.protocol) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "protocol" - } - } ], !1; - if (void 0 === data.first_video_at) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "first_video_at" - } - } ], !1; - if (void 0 === data.last_frame_ts) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "last_frame_ts" - } - } ], !1; - if (void 0 === data.last_recording_at) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "last_recording_at" - } - } ], !1; - if (void 0 === data.current_fps) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "current_fps" - } - } ], !1; - if (void 0 === data.status) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "status" - } - } ], !1; - if (void 0 === data.availability) return validate39.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "availability" - } - } ], !1; - if (void 0 !== data.camera_uuid) { - let _errs2 = 0; - if ("string" != typeof data.camera_uuid) return validate39.errors = [ { - instancePath: instancePath + "/camera_uuid", - schemaPath: "#/properties/camera_uuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.name) { - let _errs4 = 0; - if ("string" != typeof data.name) return validate39.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.location_uuid) { - let _errs6 = 0; - if ("string" != typeof data.location_uuid) return validate39.errors = [ { - instancePath: instancePath + "/location_uuid", - schemaPath: "#/properties/location_uuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.location_name) { - let _errs8 = 0; - if ("string" != typeof data.location_name) return validate39.errors = [ { - instancePath: instancePath + "/location_name", - schemaPath: "#/properties/location_name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.location_path) { - let _errs10 = 0; - if ("string" != typeof data.location_path) return validate39.errors = [ { - instancePath: instancePath + "/location_path", - schemaPath: "#/properties/location_path/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.codec) { - let _errs12 = 0; - if ("string" != typeof data.codec) return validate39.errors = [ { - instancePath: instancePath + "/codec", - schemaPath: "#/properties/codec/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.tags) { - let _errs14 = 0; - if ("string" != typeof data.tags) return validate39.errors = [ { - instancePath: instancePath + "/tags", - schemaPath: "#/properties/tags/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.enabled) { - let _errs16 = 0; - if ("boolean" != typeof data.enabled) return validate39.errors = [ { - instancePath: instancePath + "/enabled", - schemaPath: "#/properties/enabled/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.record) { - let _errs18 = 0; - if ("boolean" != typeof data.record) return validate39.errors = [ { - instancePath: instancePath + "/record", - schemaPath: "#/properties/record/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.record_on_schedule) { - let _errs20 = 0; - if ("boolean" != typeof data.record_on_schedule) return validate39.errors = [ { - instancePath: instancePath + "/record_on_schedule", - schemaPath: "#/properties/record_on_schedule/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.detection_based_recording) { - let _errs22 = 0; - if ("boolean" != typeof data.detection_based_recording) return validate39.errors = [ { - instancePath: instancePath + "/detection_based_recording", - schemaPath: "#/properties/detection_based_recording/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid10 = 0 === _errs22; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.isOnvif) { - let _errs24 = 0; - if ("boolean" != typeof data.isOnvif) return validate39.errors = [ { - instancePath: instancePath + "/isOnvif", - schemaPath: "#/properties/isOnvif/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid11 = 0 === _errs24; - } else valid11 = !0; - if (valid11) { - if (void 0 !== data.recording_active) { - let _errs26 = 0; - if ("boolean" != typeof data.recording_active) return validate39.errors = [ { - instancePath: instancePath + "/recording_active", - schemaPath: "#/properties/recording_active/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid12 = 0 === _errs26; - } else valid12 = !0; - if (valid12) { - if (void 0 !== data.width) { - let data13 = data.width, _errs28 = 0; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate39.errors = [ { - instancePath: instancePath + "/width", - schemaPath: "#/properties/width/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid13 = 0 === _errs28; - } else valid13 = !0; - if (valid13) { - if (void 0 !== data.height) { - let data14 = data.height, _errs30 = 0; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate39.errors = [ { - instancePath: instancePath + "/height", - schemaPath: "#/properties/height/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid14 = 0 === _errs30; - } else valid14 = !0; - if (valid14) { - if (void 0 !== data.fps) { - let data15 = data.fps, _errs32 = 0; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate39.errors = [ { - instancePath: instancePath + "/fps", - schemaPath: "#/properties/fps/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid15 = 0 === _errs32; - } else valid15 = !0; - if (valid15) { - if (void 0 !== data.protocol) { - let data16 = data.protocol, _errs34 = 0; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate39.errors = [ { - instancePath: instancePath + "/protocol", - schemaPath: "#/properties/protocol/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid16 = 0 === _errs34; - } else valid16 = !0; - if (valid16) { - if (void 0 !== data.first_video_at) { - let data17 = data.first_video_at, _errs36 = 0; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate39.errors = [ { - instancePath: instancePath + "/first_video_at", - schemaPath: "#/properties/first_video_at/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid17 = 0 === _errs36; - } else valid17 = !0; - if (valid17) { - if (void 0 !== data.last_frame_ts) { - let data18 = data.last_frame_ts, _errs38 = 0; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate39.errors = [ { - instancePath: instancePath + "/last_frame_ts", - schemaPath: "#/properties/last_frame_ts/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid18 = 0 === _errs38; - } else valid18 = !0; - if (valid18) { - if (void 0 !== data.last_recording_at) { - let data19 = data.last_recording_at, _errs40 = 0; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate39.errors = [ { - instancePath: instancePath + "/last_recording_at", - schemaPath: "#/properties/last_recording_at/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid19 = 0 === _errs40; - } else valid19 = !0; - if (valid19) { - if (void 0 !== data.current_fps) { - let data20 = data.current_fps, _errs42 = 0; - if ("number" != typeof data20 || !isFinite(data20)) return validate39.errors = [ { - instancePath: instancePath + "/current_fps", - schemaPath: "#/properties/current_fps/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid20 = 0 === _errs42; - } else valid20 = !0; - if (valid20) { - if (void 0 !== data.status) { - let data21 = data.status, _errs44 = 0; - if ("string" != typeof data21) return validate39.errors = [ { - instancePath: instancePath + "/status", - schemaPath: "#/$defs/StreamStatus/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("Stopped" !== data21 && "Starting" !== data21 && "Running" !== data21 && "Stopping" !== data21 && "Error" !== data21 && "Reconnecting" !== data21 && "Degraded" !== data21 && "Unknown" !== data21) return validate39.errors = [ { - instancePath: instancePath + "/status", - schemaPath: "#/$defs/StreamStatus/enum", - keyword: "enum", - params: { - allowedValues: schema32.enum - } - } ], !1; - var valid21 = 0 === _errs44; - } else valid21 = !0; - if (valid21 && void 0 !== data.availability) { - let data22 = data.availability; - if ("string" != typeof data22) return validate39.errors = [ { - instancePath: instancePath + "/availability", - schemaPath: "#/properties/availability/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("live" !== data22 && "offline" !== data22 && "never_connected" !== data22 && "disabled" !== data22) return validate39.errors = [ { - instancePath: instancePath + "/availability", - schemaPath: "#/properties/availability/enum", - keyword: "enum", - params: { - allowedValues: schema46.properties.availability.enum - } - } ], !1; - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - return validate39.errors = [], !0; -} - -function validate42(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate42.evaluated; - evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); - let _errs0 = errors; - if (validate39(data, { - instancePath: instancePath, - parentData: parentData, - parentDataProperty: parentDataProperty, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate39.errors), errors = vErrors.length), _errs0 === errors && errors == errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate42.errors = [ { + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/type", + schemaPath: "#/type", keyword: "type", params: { type: "object" } } ], !1; - if (void 0 === data.streaming_enabled) return validate42.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "streaming_enabled" - } - } ], !1; - if (void 0 === data.privacy_mode) return validate42.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "privacy_mode" - } - } ], !1; - if (void 0 === data.has_sub_stream) return validate42.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "has_sub_stream" - } - } ], !1; - if (void 0 === data.ptz_enabled) return validate42.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "ptz_enabled" - } - } ], !1; - if (void 0 === data.backchannel_enabled) return validate42.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "backchannel_enabled" - } - } ], !1; - if (void 0 === data.go2rtc_hls_available) return validate42.errors = [ { + if (void 0 === data.name) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "go2rtc_hls_available" + missingProperty: "name" } } ], !1; - if (void 0 === data.playback_transport) return validate42.errors = [ { + if (void 0 === data.availability) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "playback_transport" + missingProperty: "availability" } } ], !1; - if (void 0 === data.eptz_config) return validate42.errors = [ { + if (void 0 === data.columns) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "eptz_config" + missingProperty: "columns" } } ], !1; - if (void 0 === data.detection_model) return validate42.errors = [ { + if (void 0 === data.rows) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "detection_model" + missingProperty: "rows" } } ], !1; - if (void 0 === data.can_control_privacy) return validate42.errors = [ { + if (void 0 === data.camera_slots) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "can_control_privacy" + missingProperty: "camera_slots" } } ], !1; - if (void 0 === data.can_configure) return validate42.errors = [ { + if (void 0 === data.revision) return validate230.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "can_configure" + missingProperty: "revision" } } ], !1; - if (void 0 !== data.streaming_enabled) { - let _errs4 = errors; - if ("boolean" != typeof data.streaming_enabled) return validate42.errors = [ { - instancePath: instancePath + "/streaming_enabled", - schemaPath: "#/allOf/1/properties/streaming_enabled/type", - keyword: "type", + { + let _errs1 = errors; + for (let key0 in data) if ("name" !== key0 && "is_shared" !== key0 && "location_uuid" !== key0 && "availability" !== key0 && "columns" !== key0 && "rows" !== key0 && "camera_slots" !== key0 && "revision" !== key0) return validate230.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", params: { - type: "boolean" + additionalProperty: key0 } } ], !1; - var valid1 = _errs4 === errors; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.privacy_mode) { - let _errs6 = errors; - if ("boolean" != typeof data.privacy_mode) return validate42.errors = [ { - instancePath: instancePath + "/privacy_mode", - schemaPath: "#/allOf/1/properties/privacy_mode/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid2 = _errs6 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.has_sub_stream) { - let _errs8 = errors; - if ("boolean" != typeof data.has_sub_stream) return validate42.errors = [ { - instancePath: instancePath + "/has_sub_stream", - schemaPath: "#/allOf/1/properties/has_sub_stream/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid3 = _errs8 === errors; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.ptz_enabled) { - let _errs10 = errors; - if ("boolean" != typeof data.ptz_enabled) return validate42.errors = [ { - instancePath: instancePath + "/ptz_enabled", - schemaPath: "#/allOf/1/properties/ptz_enabled/type", + if (_errs1 === errors) { + if (void 0 !== data.name) { + let data0 = data.name, _errs2 = errors; + if (errors === _errs2) { + if ("string" != typeof data0) return validate230.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 127) return validate230.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/maxLength", + keyword: "maxLength", + params: { + limit: 127 + } + } ], !1; + if (func1(data0) < 1) return validate230.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.is_shared) { + let _errs4 = errors; + if ("boolean" != typeof data.is_shared) return validate230.errors = [ { + instancePath: instancePath + "/is_shared", + schemaPath: "#/properties/is_shared/type", keyword: "type", params: { type: "boolean" } } ], !1; - var valid4 = _errs10 === errors; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.backchannel_enabled) { - let _errs12 = errors; - if ("boolean" != typeof data.backchannel_enabled) return validate42.errors = [ { - instancePath: instancePath + "/backchannel_enabled", - schemaPath: "#/allOf/1/properties/backchannel_enabled/type", - keyword: "type", - params: { - type: "boolean" + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.location_uuid) { + let data2 = data.location_uuid, _errs6 = errors, _errs7 = errors, valid3 = !1, _errs8 = errors; + if (errors == errors) if ("string" == typeof data2) { + if (!pattern4.test(data2)) { + let err0 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/$defs/CameraUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + } + }; + vErrors.push(err0), errors++; } - } ], !1; - var valid5 = _errs12 === errors; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.go2rtc_hls_available) { - let _errs14 = errors; - if ("boolean" != typeof data.go2rtc_hls_available) return validate42.errors = [ { - instancePath: instancePath + "/go2rtc_hls_available", - schemaPath: "#/allOf/1/properties/go2rtc_hls_available/type", + } else { + let err1 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/$defs/CameraUuid/type", keyword: "type", params: { - type: "boolean" + type: "string" + } + }; + vErrors.push(err1), errors++; + } + var _valid0 = _errs8 === errors; + valid3 = valid3 || _valid0; + let _errs11 = errors; + if (null !== data2) { + let err2 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/properties/location_uuid/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err2), errors++; + } + if (_valid0 = _errs11 === errors, valid3 = valid3 || _valid0, !valid3) { + let err3 = { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/properties/location_uuid/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err3), errors++, validate230.errors = vErrors, !1; + } + errors = _errs7, vErrors.length = _errs7; + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.availability) { + let data3 = data.availability, _errs13 = errors; + if ("string" != typeof data3) return validate230.errors = [ { + instancePath: instancePath + "/availability", + schemaPath: "#/properties/availability/type", + keyword: "type", + params: { + type: "string" } } ], !1; - var valid6 = _errs14 === errors; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.playback_transport) { - let data6 = data.playback_transport, _errs16 = errors; - if ("string" != typeof data6) return validate42.errors = [ { - instancePath: instancePath + "/playback_transport", - schemaPath: "#/$defs/PlaybackTransport/type", + if ("all" !== data3 && "live" !== data3 && "offline" !== data3 && "never_connected" !== data3 && "disabled" !== data3) return validate230.errors = [ { + instancePath: instancePath + "/availability", + schemaPath: "#/properties/availability/enum", + keyword: "enum", + params: { + allowedValues: schema246.properties.availability.enum + } + } ], !1; + var valid5 = _errs13 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.columns) { + let data4 = data.columns, _errs15 = errors; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate230.errors = [ { + instancePath: instancePath + "/columns", + schemaPath: "#/properties/columns/type", keyword: "type", params: { - type: "string" - } - } ], !1; - if ("auto" !== data6 && "webrtc_only" !== data6 && "mse_only" !== data6 && "hls_only" !== data6 && "webrtc_then_mse" !== data6 && "mse_then_hls" !== data6) return validate42.errors = [ { - instancePath: instancePath + "/playback_transport", - schemaPath: "#/$defs/PlaybackTransport/enum", - keyword: "enum", - params: { - allowedValues: schema33.enum + type: "integer" } } ], !1; - var valid7 = _errs16 === errors; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.eptz_config) { - let _errs19 = errors; - if ("string" != typeof data.eptz_config) return validate42.errors = [ { - instancePath: instancePath + "/eptz_config", - schemaPath: "#/allOf/1/properties/eptz_config/type", + if (errors === _errs15 && "number" == typeof data4 && isFinite(data4)) { + if (data4 > 9 || isNaN(data4)) return validate230.errors = [ { + instancePath: instancePath + "/columns", + schemaPath: "#/properties/columns/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 9 + } + } ], !1; + if (data4 < 1 || isNaN(data4)) return validate230.errors = [ { + instancePath: instancePath + "/columns", + schemaPath: "#/properties/columns/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid6 = _errs15 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.rows) { + let data5 = data.rows, _errs17 = errors; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate230.errors = [ { + instancePath: instancePath + "/rows", + schemaPath: "#/properties/rows/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - var valid9 = _errs19 === errors; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.detection_model) { - let _errs21 = errors; - if ("string" != typeof data.detection_model) return validate42.errors = [ { - instancePath: instancePath + "/detection_model", - schemaPath: "#/allOf/1/properties/detection_model/type", - keyword: "type", + if (errors === _errs17 && "number" == typeof data5 && isFinite(data5)) { + if (data5 > 9 || isNaN(data5)) return validate230.errors = [ { + instancePath: instancePath + "/rows", + schemaPath: "#/properties/rows/maximum", + keyword: "maximum", params: { - type: "string" + comparison: "<=", + limit: 9 } } ], !1; - var valid10 = _errs21 === errors; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.can_control_privacy) { - let data9 = data.can_control_privacy, _errs23 = errors; - if ("boolean" != typeof data9) return validate42.errors = [ { - instancePath: instancePath + "/can_control_privacy", - schemaPath: "#/allOf/1/properties/can_control_privacy/type", + if (data5 < 1 || isNaN(data5)) return validate230.errors = [ { + instancePath: instancePath + "/rows", + schemaPath: "#/properties/rows/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid7 = _errs17 === errors; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.camera_slots) { + let data6 = data.camera_slots, _errs19 = errors; + if (errors === _errs19) { + if (!Array.isArray(data6)) return validate230.errors = [ { + instancePath: instancePath + "/camera_slots", + schemaPath: "#/properties/camera_slots/type", keyword: "type", params: { - type: "boolean" - } - } ], !1; - if (!1 !== data9) return validate42.errors = [ { - instancePath: instancePath + "/can_control_privacy", - schemaPath: "#/allOf/1/properties/can_control_privacy/const", - keyword: "const", - params: { - allowedValue: !1 + type: "array" } } ], !1; - var valid11 = _errs23 === errors; - } else valid11 = !0; - if (valid11 && void 0 !== data.can_configure) { - let data10 = data.can_configure; - if ("boolean" != typeof data10) return validate42.errors = [ { - instancePath: instancePath + "/can_configure", - schemaPath: "#/allOf/1/properties/can_configure/type", - keyword: "type", + if (data6.length > 36) return validate230.errors = [ { + instancePath: instancePath + "/camera_slots", + schemaPath: "#/properties/camera_slots/maxItems", + keyword: "maxItems", params: { - type: "boolean" + limit: 36 } } ], !1; - if (!1 !== data10) return validate42.errors = [ { - instancePath: instancePath + "/can_configure", - schemaPath: "#/allOf/1/properties/can_configure/const", - keyword: "const", - params: { - allowedValue: !1 + { + let len0 = data6.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs21 = errors; + if (validate227(data6[i0], { + instancePath: instancePath + "/camera_slots/" + i0, + parentData: data6, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate227.errors), errors = vErrors.length), _errs21 !== errors) break; } - } ], !1; + } } + var valid8 = _errs19 === errors; + } else valid8 = !0; + if (valid8 && void 0 !== data.revision) { + let data8 = data.revision, _errs22 = errors; + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate230.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs22 && "number" == typeof data8 && isFinite(data8) && (data8 < 1 || isNaN(data8))) return validate230.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; } } } @@ -69612,497 +70943,140 @@ function validate42(data, {instancePath: instancePath = "", parentData: parentDa } } } - return validate42.errors = vErrors, 0 === errors; + return validate230.errors = vErrors, 0 === errors; } -function validate46(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate46.evaluated; - evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); - let _errs0 = errors; - if (validate39(data, { - instancePath: instancePath, - parentData: parentData, - parentDataProperty: parentDataProperty, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate39.errors), errors = vErrors.length), _errs0 === errors && errors == errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate46.errors = [ { +validate230.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_LiveLayoutsResponse = validate234; + +function validate234(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate234.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate234.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/type", + schemaPath: "#/type", keyword: "type", params: { type: "object" } } ], !1; - if (void 0 === data.streaming_enabled) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "streaming_enabled" - } - } ], !1; - if (void 0 === data.privacy_mode) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "privacy_mode" - } - } ], !1; - if (void 0 === data.has_sub_stream) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "has_sub_stream" - } - } ], !1; - if (void 0 === data.ptz_enabled) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "ptz_enabled" - } - } ], !1; - if (void 0 === data.backchannel_enabled) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "backchannel_enabled" - } - } ], !1; - if (void 0 === data.go2rtc_hls_available) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "go2rtc_hls_available" - } - } ], !1; - if (void 0 === data.playback_transport) return validate46.errors = [ { - instancePath: instancePath, - schemaPath: "#/allOf/1/required", - keyword: "required", - params: { - missingProperty: "playback_transport" - } - } ], !1; - if (void 0 === data.eptz_config) return validate46.errors = [ { + if (void 0 === data.layouts) return validate234.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "eptz_config" + missingProperty: "layouts" } } ], !1; - if (void 0 === data.detection_model) return validate46.errors = [ { + if (void 0 === data.count) return validate234.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "detection_model" + missingProperty: "count" } } ], !1; - if (void 0 === data.can_control_privacy) return validate46.errors = [ { + if (void 0 === data.can_share) return validate234.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "can_control_privacy" + missingProperty: "can_share" } } ], !1; - if (void 0 === data.can_configure) return validate46.errors = [ { + if (void 0 === data.can_modify) return validate234.errors = [ { instancePath: instancePath, - schemaPath: "#/allOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "can_configure" + missingProperty: "can_modify" } } ], !1; - if (void 0 !== data.streaming_enabled) { - let _errs4 = errors; - if ("boolean" != typeof data.streaming_enabled) return validate46.errors = [ { - instancePath: instancePath + "/streaming_enabled", - schemaPath: "#/allOf/1/properties/streaming_enabled/type", - keyword: "type", + { + let _errs1 = errors; + for (let key0 in data) if ("layouts" !== key0 && "count" !== key0 && "can_share" !== key0 && "can_modify" !== key0) return validate234.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", params: { - type: "boolean" + additionalProperty: key0 } } ], !1; - var valid1 = _errs4 === errors; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.privacy_mode) { - let _errs6 = errors; - if ("boolean" != typeof data.privacy_mode) return validate46.errors = [ { - instancePath: instancePath + "/privacy_mode", - schemaPath: "#/allOf/1/properties/privacy_mode/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid2 = _errs6 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.has_sub_stream) { - let _errs8 = errors; - if ("boolean" != typeof data.has_sub_stream) return validate46.errors = [ { - instancePath: instancePath + "/has_sub_stream", - schemaPath: "#/allOf/1/properties/has_sub_stream/type", - keyword: "type", - params: { - type: "boolean" + if (_errs1 === errors) { + if (void 0 !== data.layouts) { + let data0 = data.layouts, _errs2 = errors; + if (errors === _errs2) { + if (!Array.isArray(data0)) return validate234.errors = [ { + instancePath: instancePath + "/layouts", + schemaPath: "#/properties/layouts/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs4 = errors; + if (validate232(data0[i0], { + instancePath: instancePath + "/layouts/" + i0, + parentData: data0, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate232.errors), errors = vErrors.length), _errs4 !== errors) break; + } } - } ], !1; - var valid3 = _errs8 === errors; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.ptz_enabled) { - let _errs10 = errors; - if ("boolean" != typeof data.ptz_enabled) return validate46.errors = [ { - instancePath: instancePath + "/ptz_enabled", - schemaPath: "#/allOf/1/properties/ptz_enabled/type", + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.count) { + let data2 = data.count, _errs5 = errors; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate234.errors = [ { + instancePath: instancePath + "/count", + schemaPath: "#/properties/count/type", keyword: "type", params: { - type: "boolean" + type: "integer" } } ], !1; - var valid4 = _errs10 === errors; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.backchannel_enabled) { - let _errs12 = errors; - if ("boolean" != typeof data.backchannel_enabled) return validate46.errors = [ { - instancePath: instancePath + "/backchannel_enabled", - schemaPath: "#/allOf/1/properties/backchannel_enabled/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid5 = _errs12 === errors; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.go2rtc_hls_available) { - let _errs14 = errors; - if ("boolean" != typeof data.go2rtc_hls_available) return validate46.errors = [ { - instancePath: instancePath + "/go2rtc_hls_available", - schemaPath: "#/allOf/1/properties/go2rtc_hls_available/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid6 = _errs14 === errors; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.playback_transport) { - let data6 = data.playback_transport, _errs16 = errors; - if ("string" != typeof data6) return validate46.errors = [ { - instancePath: instancePath + "/playback_transport", - schemaPath: "#/$defs/PlaybackTransport/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("auto" !== data6 && "webrtc_only" !== data6 && "mse_only" !== data6 && "hls_only" !== data6 && "webrtc_then_mse" !== data6 && "mse_then_hls" !== data6) return validate46.errors = [ { - instancePath: instancePath + "/playback_transport", - schemaPath: "#/$defs/PlaybackTransport/enum", - keyword: "enum", - params: { - allowedValues: schema33.enum - } - } ], !1; - var valid7 = _errs16 === errors; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.eptz_config) { - let _errs19 = errors; - if ("string" != typeof data.eptz_config) return validate46.errors = [ { - instancePath: instancePath + "/eptz_config", - schemaPath: "#/allOf/1/properties/eptz_config/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid9 = _errs19 === errors; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.detection_model) { - let _errs21 = errors; - if ("string" != typeof data.detection_model) return validate46.errors = [ { - instancePath: instancePath + "/detection_model", - schemaPath: "#/allOf/1/properties/detection_model/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid10 = _errs21 === errors; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.can_control_privacy) { - let data9 = data.can_control_privacy, _errs23 = errors; - if ("boolean" != typeof data9) return validate46.errors = [ { - instancePath: instancePath + "/can_control_privacy", - schemaPath: "#/allOf/1/properties/can_control_privacy/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data9) return validate46.errors = [ { - instancePath: instancePath + "/can_control_privacy", - schemaPath: "#/allOf/1/properties/can_control_privacy/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid11 = _errs23 === errors; - } else valid11 = !0; - if (valid11 && void 0 !== data.can_configure) { - let data10 = data.can_configure; - if ("boolean" != typeof data10) return validate46.errors = [ { - instancePath: instancePath + "/can_configure", - schemaPath: "#/allOf/1/properties/can_configure/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data10) return validate46.errors = [ { - instancePath: instancePath + "/can_configure", - schemaPath: "#/allOf/1/properties/can_configure/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - } - } - } - } - } - } - } - } - } - } - } - return validate46.errors = vErrors, 0 === errors; -} - -function validate52(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate52.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate52.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.streams) return validate52.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "streams" - } - } ], !1; - if (void 0 === data.total) return validate52.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "total" - } - } ], !1; - if (void 0 === data.page) return validate52.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "page" - } - } ], !1; - if (void 0 === data.page_size) return validate52.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "page_size" - } - } ], !1; - if (void 0 === data.total_pages) return validate52.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "total_pages" - } - } ], !1; - if (void 0 !== data.streams) { - let data0 = data.streams, _errs2 = errors; - if (errors === _errs2) { - if (!Array.isArray(data0)) return validate52.errors = [ { - instancePath: instancePath + "/streams", - schemaPath: "#/properties/streams/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = errors, _errs5 = errors, valid2 = !1, _errs6 = errors; - validate42(data1, { - instancePath: instancePath + "/streams/" + i0, - parentData: data0, - parentDataProperty: i0, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate42.errors), errors = vErrors.length); - var _valid0 = _errs6 === errors; - if (valid2 = valid2 || _valid0, _valid0) var props0 = !0; - let _errs7 = errors; - if (validate46(data1, { - instancePath: instancePath + "/streams/" + i0, - parentData: data0, - parentDataProperty: i0, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate46.errors), errors = vErrors.length), _valid0 = _errs7 === errors, - valid2 = valid2 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), !valid2) { - let err0 = { - instancePath: instancePath + "/streams/" + i0, - schemaPath: "#/properties/streams/items/anyOf", - keyword: "anyOf", - params: {} - }; - return vErrors.push(err0), errors++, validate52.errors = vErrors, !1; - } - if (errors = _errs5, vErrors.length = _errs5, _errs4 !== errors) break; - } - } - } - var valid0 = _errs2 === errors; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.total) { - let data2 = data.total, _errs8 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate52.errors = [ { - instancePath: instancePath + "/total", - schemaPath: "#/properties/total/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (errors === _errs8 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate52.errors = [ { - instancePath: instancePath + "/total", - schemaPath: "#/properties/total/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid3 = _errs8 === errors; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.page) { - let data3 = data.page, _errs10 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate52.errors = [ { - instancePath: instancePath + "/page", - schemaPath: "#/properties/page/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (errors === _errs10 && "number" == typeof data3 && isFinite(data3)) { - if (data3 > 1e6 || isNaN(data3)) return validate52.errors = [ { - instancePath: instancePath + "/page", - schemaPath: "#/properties/page/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1e6 - } - } ], !1; - if (data3 < 1 || isNaN(data3)) return validate52.errors = [ { - instancePath: instancePath + "/page", - schemaPath: "#/properties/page/minimum", + if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate234.errors = [ { + instancePath: instancePath + "/count", + schemaPath: "#/properties/count/minimum", keyword: "minimum", params: { comparison: ">=", - limit: 1 - } - } ], !1; - } - var valid4 = _errs10 === errors; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.page_size) { - let data4 = data.page_size, _errs12 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate52.errors = [ { - instancePath: instancePath + "/page_size", - schemaPath: "#/properties/page_size/type", - keyword: "type", - params: { - type: "integer" + limit: 0 } } ], !1; - if (errors === _errs12 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 100 || isNaN(data4)) return validate52.errors = [ { - instancePath: instancePath + "/page_size", - schemaPath: "#/properties/page_size/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 100 - } - } ], !1; - if (data4 < 1 || isNaN(data4)) return validate52.errors = [ { - instancePath: instancePath + "/page_size", - schemaPath: "#/properties/page_size/minimum", - keyword: "minimum", + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.can_share) { + let _errs7 = errors; + if ("boolean" != typeof data.can_share) return validate234.errors = [ { + instancePath: instancePath + "/can_share", + schemaPath: "#/properties/can_share/type", + keyword: "type", params: { - comparison: ">=", - limit: 1 + type: "boolean" } } ], !1; - } - var valid5 = _errs12 === errors; - } else valid5 = !0; - if (valid5 && void 0 !== data.total_pages) { - let data5 = data.total_pages, _errs14 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate52.errors = [ { - instancePath: instancePath + "/total_pages", - schemaPath: "#/properties/total_pages/type", + var valid3 = _errs7 === errors; + } else valid3 = !0; + if (valid3 && void 0 !== data.can_modify && "boolean" != typeof data.can_modify) return validate234.errors = [ { + instancePath: instancePath + "/can_modify", + schemaPath: "#/properties/can_modify/type", keyword: "type", params: { - type: "integer" - } - } ], !1; - if (errors === _errs14 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate52.errors = [ { - instancePath: instancePath + "/total_pages", - schemaPath: "#/properties/total_pages/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 + type: "boolean" } } ], !1; } @@ -70110,61 +71084,104 @@ function validate52(data, {instancePath: instancePath = "", parentData: parentDa } } } - return validate52.errors = vErrors, 0 === errors; + return validate234.errors = vErrors, 0 === errors; } -validate39.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}, validate42.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}, validate46.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}, validate52.evaluated = { +validate234.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_LoginConfig = validate75, schema80 = { +var __contract_LiveStreamSummaryPage = validate52, schema33 = { + type: "string", + enum: [ "auto", "webrtc_only", "mse_only", "hls_only", "webrtc_then_mse", "mse_then_hls" ] +}, schema46 = { type: "object", properties: { - force_mfa_on_login: { + camera_uuid: { + type: "string" + }, + name: { + type: "string" + }, + location_uuid: { + type: "string" + }, + location_name: { + type: "string" + }, + location_path: { + type: "string" + }, + codec: { + type: "string" + }, + tags: { + type: "string" + }, + enabled: { type: "boolean" }, - remember_device_enabled: { + record: { type: "boolean" }, - auth_timeout_hours: { - type: "number" + record_on_schedule: { + type: "boolean" }, - auth_absolute_timeout_hours: { - type: "number" + detection_based_recording: { + type: "boolean" }, - trusted_device_days: { + isOnvif: { + type: "boolean" + }, + recording_active: { + type: "boolean" + }, + width: { + type: "integer" + }, + height: { + type: "integer" + }, + fps: { + type: "integer" + }, + protocol: { + type: "integer" + }, + first_video_at: { + type: "integer", + description: "Unix seconds; zero means not observed." + }, + last_frame_ts: { + type: "integer", + description: "Unix seconds; zero means not observed." + }, + last_recording_at: { + type: "integer", + description: "Unix seconds; zero means not observed." + }, + current_fps: { type: "number" }, - secret: !1, - api_key: !1, - password: !1, - password_hash: !1, - token_hash: !1, - totp_secret: !1, - totp_token: !1 + status: { + $ref: "#/$defs/StreamStatus" + }, + availability: { + type: "string", + enum: [ "live", "offline", "never_connected", "disabled" ] + } }, - required: [ "force_mfa_on_login", "remember_device_enabled", "auth_timeout_hours", "auth_absolute_timeout_hours", "trusted_device_days" ], - additionalProperties: !1 + required: [ "camera_uuid", "name", "location_uuid", "location_name", "location_path", "codec", "tags", "enabled", "record", "record_on_schedule", "detection_based_recording", "isOnvif", "recording_active", "width", "height", "fps", "protocol", "first_video_at", "last_frame_ts", "last_recording_at", "current_fps", "status", "availability" ], + additionalProperties: !0, + description: "Administrative and live summary fields. Unlike detail, summary rows contain no input URL, credentials or error_message." }; -function validate75(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate75.evaluated; +function validate39(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate39.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate75.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -70172,437 +71189,195 @@ function validate75(data, {instancePath: instancePath = "", parentData: parentDa type: "object" } } ], !1; - if (void 0 === data.force_mfa_on_login) return validate75.errors = [ { + if (void 0 === data.camera_uuid) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "force_mfa_on_login" + missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.remember_device_enabled) return validate75.errors = [ { + if (void 0 === data.name) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "remember_device_enabled" + missingProperty: "name" } } ], !1; - if (void 0 === data.auth_timeout_hours) return validate75.errors = [ { + if (void 0 === data.location_uuid) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "auth_timeout_hours" + missingProperty: "location_uuid" } } ], !1; - if (void 0 === data.auth_absolute_timeout_hours) return validate75.errors = [ { + if (void 0 === data.location_name) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "auth_absolute_timeout_hours" + missingProperty: "location_name" } } ], !1; - if (void 0 === data.trusted_device_days) return validate75.errors = [ { + if (void 0 === data.location_path) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "trusted_device_days" + missingProperty: "location_path" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema80.properties, key0)) return validate75.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.force_mfa_on_login) { - let _errs2 = 0; - if ("boolean" != typeof data.force_mfa_on_login) return validate75.errors = [ { - instancePath: instancePath + "/force_mfa_on_login", - schemaPath: "#/properties/force_mfa_on_login/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.remember_device_enabled) { - let _errs4 = 0; - if ("boolean" != typeof data.remember_device_enabled) return validate75.errors = [ { - instancePath: instancePath + "/remember_device_enabled", - schemaPath: "#/properties/remember_device_enabled/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.auth_timeout_hours) { - let data2 = data.auth_timeout_hours, _errs6 = 0; - if ("number" != typeof data2 || !isFinite(data2)) return validate75.errors = [ { - instancePath: instancePath + "/auth_timeout_hours", - schemaPath: "#/properties/auth_timeout_hours/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.auth_absolute_timeout_hours) { - let data3 = data.auth_absolute_timeout_hours, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate75.errors = [ { - instancePath: instancePath + "/auth_absolute_timeout_hours", - schemaPath: "#/properties/auth_absolute_timeout_hours/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.trusted_device_days) { - let data4 = data.trusted_device_days, _errs10 = 0; - if ("number" != typeof data4 || !isFinite(data4)) return validate75.errors = [ { - instancePath: instancePath + "/trusted_device_days", - schemaPath: "#/properties/trusted_device_days/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.secret) return validate75.errors = [ { - instancePath: instancePath + "/secret", - schemaPath: "#/properties/secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.api_key) return validate75.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password) return validate75.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password_hash) return validate75.errors = [ { - instancePath: instancePath + "/password_hash", - schemaPath: "#/properties/password_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.token_hash) return validate75.errors = [ { - instancePath: instancePath + "/token_hash", - schemaPath: "#/properties/token_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_secret) return validate75.errors = [ { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_token) return validate75.errors = [ { - instancePath: instancePath + "/totp_token", - schemaPath: "#/properties/totp_token/false schema", - keyword: "false schema", - params: {} - } ], !1; - } - } - } - } - } + if (void 0 === data.codec) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "codec" } - } - return validate75.errors = [], !0; -} - -validate75.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LoginLockoutCleared = validate181; - -function validate181(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate181.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate181.errors = [ { + } ], !1; + if (void 0 === data.tags) return validate39.errors = [ { instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "tags" } } ], !1; - if (void 0 === data.success) return validate181.errors = [ { + if (void 0 === data.enabled) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "success" + missingProperty: "enabled" } } ], !1; - if (void 0 === data.cleared) return validate181.errors = [ { + if (void 0 === data.record) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "cleared" + missingProperty: "record" } } ], !1; - if (void 0 === data.message) return validate181.errors = [ { + if (void 0 === data.record_on_schedule) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "message" + missingProperty: "record_on_schedule" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "cleared" !== key0 && "message" !== key0) return validate181.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate181.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.cleared) { - let _errs3 = 0; - if ("boolean" != typeof data.cleared) return validate181.errors = [ { - instancePath: instancePath + "/cleared", - schemaPath: "#/properties/cleared/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid1 = 0 === _errs3; - } else valid1 = !0; - if (valid1 && void 0 !== data.message && "string" != typeof data.message) return validate181.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - } + if (void 0 === data.detection_based_recording) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "detection_based_recording" } - } - return validate181.errors = [], !0; -} - -validate181.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LoginMfaChallenge = validate72; - -function validate72(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate72.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate72.errors = [ { + } ], !1; + if (void 0 === data.isOnvif) return validate39.errors = [ { instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "isOnvif" } } ], !1; - if (void 0 === data.totp_required) return validate72.errors = [ { + if (void 0 === data.recording_active) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "totp_required" + missingProperty: "recording_active" } } ], !1; - if (void 0 === data.totp_token) return validate72.errors = [ { + if (void 0 === data.width) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "totp_token" + missingProperty: "width" } } ], !1; - if (void 0 !== data.totp_required) { - let _errs2 = 0; - if (!0 !== data.totp_required) return validate72.errors = [ { - instancePath: instancePath + "/totp_required", - schemaPath: "#/properties/totp_required/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.totp_token) { - let data1 = data.totp_token, _errs3 = 0; - if (0 === _errs3) { - if ("string" != typeof data1) return validate72.errors = [ { - instancePath: instancePath + "/totp_token", - schemaPath: "#/properties/totp_token/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data1) < 1) return validate72.errors = [ { - instancePath: instancePath + "/totp_token", - schemaPath: "#/properties/totp_token/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid1 = 0 === _errs3; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.secret) return validate72.errors = [ { - instancePath: instancePath + "/secret", - schemaPath: "#/properties/secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.api_key) return validate72.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password) return validate72.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password_hash) return validate72.errors = [ { - instancePath: instancePath + "/password_hash", - schemaPath: "#/properties/password_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.token_hash) return validate72.errors = [ { - instancePath: instancePath + "/token_hash", - schemaPath: "#/properties/token_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_secret) return validate72.errors = [ { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.success) return validate72.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.redirect) return validate72.errors = [ { - instancePath: instancePath + "/redirect", - schemaPath: "#/properties/redirect/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.must_change_password) return validate72.errors = [ { - instancePath: instancePath + "/must_change_password", - schemaPath: "#/properties/must_change_password/false schema", - keyword: "false schema", - params: {} - } ], !1; + if (void 0 === data.height) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "height" } - } - return validate72.errors = [], !0; -} - -validate72.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LoginRequest = validate70; - -function validate70(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate70.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate70.errors = [ { + } ], !1; + if (void 0 === data.fps) return validate39.errors = [ { instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "fps" } } ], !1; - if (void 0 === data.username) return validate70.errors = [ { + if (void 0 === data.protocol) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "username" + missingProperty: "protocol" } } ], !1; - if (void 0 === data.password) return validate70.errors = [ { + if (void 0 === data.first_video_at) return validate39.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "password" + missingProperty: "first_video_at" } } ], !1; - if (void 0 !== data.username) { + if (void 0 === data.last_frame_ts) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "last_frame_ts" + } + } ], !1; + if (void 0 === data.last_recording_at) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "last_recording_at" + } + } ], !1; + if (void 0 === data.current_fps) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "current_fps" + } + } ], !1; + if (void 0 === data.status) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "status" + } + } ], !1; + if (void 0 === data.availability) return validate39.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "availability" + } + } ], !1; + if (void 0 !== data.camera_uuid) { let _errs2 = 0; - if ("string" != typeof data.username) return validate70.errors = [ { - instancePath: instancePath + "/username", - schemaPath: "#/properties/username/type", + if ("string" != typeof data.camera_uuid) return validate39.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/properties/camera_uuid/type", keyword: "type", params: { type: "string" @@ -70611,11 +71386,11 @@ function validate70(data, {instancePath: instancePath = "", parentData: parentDa var valid0 = 0 === _errs2; } else valid0 = !0; if (valid0) { - if (void 0 !== data.password) { + if (void 0 !== data.name) { let _errs4 = 0; - if ("string" != typeof data.password) return validate70.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/type", + if ("string" != typeof data.name) return validate39.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/type", keyword: "type", params: { type: "string" @@ -70624,11 +71399,11 @@ function validate70(data, {instancePath: instancePath = "", parentData: parentDa var valid1 = 0 === _errs4; } else valid1 = !0; if (valid1) { - if (void 0 !== data.totp_code) { + if (void 0 !== data.location_uuid) { let _errs6 = 0; - if ("string" != typeof data.totp_code) return validate70.errors = [ { - instancePath: instancePath + "/totp_code", - schemaPath: "#/properties/totp_code/type", + if ("string" != typeof data.location_uuid) return validate39.errors = [ { + instancePath: instancePath + "/location_uuid", + schemaPath: "#/properties/location_uuid/type", keyword: "type", params: { type: "string" @@ -70636,194 +71411,290 @@ function validate70(data, {instancePath: instancePath = "", parentData: parentDa } ], !1; var valid2 = 0 === _errs6; } else valid2 = !0; - if (valid2 && void 0 !== data.remember_device && "boolean" != typeof data.remember_device) return validate70.errors = [ { - instancePath: instancePath + "/remember_device", - schemaPath: "#/properties/remember_device/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - } - } - return validate70.errors = [], !0; -} - -validate70.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LoginResponse = validate73; - -function validate73(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate73.evaluated; - evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); - let _errs0 = errors, valid0 = !1, _errs1 = errors; - if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.success) { - let err0 = { - instancePath: instancePath, - schemaPath: "#/$defs/LoginSuccess/required", - keyword: "required", - params: { - missingProperty: "success" - } - }; - vErrors.push(err0), errors++; - } else if (void 0 === data.redirect) { - let err1 = { - instancePath: instancePath, - schemaPath: "#/$defs/LoginSuccess/required", - keyword: "required", - params: { - missingProperty: "redirect" - } - }; - vErrors.push(err1), errors++; - } else if (void 0 === data.must_change_password) { - let err2 = { - instancePath: instancePath, - schemaPath: "#/$defs/LoginSuccess/required", - keyword: "required", - params: { - missingProperty: "must_change_password" - } - }; - vErrors.push(err2), errors++; - } else { - if (void 0 !== data.success) { - let _errs5 = errors; - if (!0 !== data.success) { - let err3 = { - instancePath: instancePath + "/success", - schemaPath: "#/$defs/LoginSuccess/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - }; - vErrors.push(err3), errors++; - } - var valid2 = _errs5 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.redirect) { - let _errs6 = errors; - if ("/index.html" !== data.redirect) { - let err4 = { - instancePath: instancePath + "/redirect", - schemaPath: "#/$defs/LoginSuccess/properties/redirect/const", - keyword: "const", + if (valid2) { + if (void 0 !== data.location_name) { + let _errs8 = 0; + if ("string" != typeof data.location_name) return validate39.errors = [ { + instancePath: instancePath + "/location_name", + schemaPath: "#/properties/location_name/type", + keyword: "type", params: { - allowedValue: "/index.html" + type: "string" } - }; - vErrors.push(err4), errors++; - } - var valid3 = _errs6 === errors; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.must_change_password) { - let _errs7 = errors; - if ("boolean" != typeof data.must_change_password) { - let err5 = { - instancePath: instancePath + "/must_change_password", - schemaPath: "#/$defs/LoginSuccess/properties/must_change_password/type", + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.location_path) { + let _errs10 = 0; + if ("string" != typeof data.location_path) return validate39.errors = [ { + instancePath: instancePath + "/location_path", + schemaPath: "#/properties/location_path/type", keyword: "type", params: { - type: "boolean" + type: "string" } - }; - vErrors.push(err5), errors++; - } - var valid4 = _errs7 === errors; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.secret) { - var valid5 = !1; - let err6 = { - instancePath: instancePath + "/secret", - schemaPath: "#/$defs/LoginSuccess/properties/secret/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err6), errors++; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.api_key) { - var valid6 = !1; - let err7 = { - instancePath: instancePath + "/api_key", - schemaPath: "#/$defs/LoginSuccess/properties/api_key/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err7), errors++; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.password) { - var valid7 = !1; - let err8 = { - instancePath: instancePath + "/password", - schemaPath: "#/$defs/LoginSuccess/properties/password/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err8), errors++; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.password_hash) { - var valid8 = !1; - let err9 = { - instancePath: instancePath + "/password_hash", - schemaPath: "#/$defs/LoginSuccess/properties/password_hash/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err9), errors++; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.token_hash) { - var valid9 = !1; - let err10 = { - instancePath: instancePath + "/token_hash", - schemaPath: "#/$defs/LoginSuccess/properties/token_hash/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err10), errors++; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.totp_secret) { - var valid10 = !1; - let err11 = { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/$defs/LoginSuccess/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err11), errors++; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.totp_token) { - var valid11 = !1; - let err12 = { - instancePath: instancePath + "/totp_token", - schemaPath: "#/$defs/LoginSuccess/properties/totp_token/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err12), errors++; - } else valid11 = !0; - if (valid11 && void 0 !== data.totp_required) { - let err13 = { - instancePath: instancePath + "/totp_required", - schemaPath: "#/$defs/LoginSuccess/properties/totp_required/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err13), errors++; + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.codec) { + let _errs12 = 0; + if ("string" != typeof data.codec) return validate39.errors = [ { + instancePath: instancePath + "/codec", + schemaPath: "#/properties/codec/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.tags) { + let _errs14 = 0; + if ("string" != typeof data.tags) return validate39.errors = [ { + instancePath: instancePath + "/tags", + schemaPath: "#/properties/tags/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.enabled) { + let _errs16 = 0; + if ("boolean" != typeof data.enabled) return validate39.errors = [ { + instancePath: instancePath + "/enabled", + schemaPath: "#/properties/enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.record) { + let _errs18 = 0; + if ("boolean" != typeof data.record) return validate39.errors = [ { + instancePath: instancePath + "/record", + schemaPath: "#/properties/record/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.record_on_schedule) { + let _errs20 = 0; + if ("boolean" != typeof data.record_on_schedule) return validate39.errors = [ { + instancePath: instancePath + "/record_on_schedule", + schemaPath: "#/properties/record_on_schedule/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.detection_based_recording) { + let _errs22 = 0; + if ("boolean" != typeof data.detection_based_recording) return validate39.errors = [ { + instancePath: instancePath + "/detection_based_recording", + schemaPath: "#/properties/detection_based_recording/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid10 = 0 === _errs22; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.isOnvif) { + let _errs24 = 0; + if ("boolean" != typeof data.isOnvif) return validate39.errors = [ { + instancePath: instancePath + "/isOnvif", + schemaPath: "#/properties/isOnvif/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid11 = 0 === _errs24; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.recording_active) { + let _errs26 = 0; + if ("boolean" != typeof data.recording_active) return validate39.errors = [ { + instancePath: instancePath + "/recording_active", + schemaPath: "#/properties/recording_active/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid12 = 0 === _errs26; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data.width) { + let data13 = data.width, _errs28 = 0; + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate39.errors = [ { + instancePath: instancePath + "/width", + schemaPath: "#/properties/width/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid13 = 0 === _errs28; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.height) { + let data14 = data.height, _errs30 = 0; + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate39.errors = [ { + instancePath: instancePath + "/height", + schemaPath: "#/properties/height/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid14 = 0 === _errs30; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.fps) { + let data15 = data.fps, _errs32 = 0; + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate39.errors = [ { + instancePath: instancePath + "/fps", + schemaPath: "#/properties/fps/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid15 = 0 === _errs32; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data.protocol) { + let data16 = data.protocol, _errs34 = 0; + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate39.errors = [ { + instancePath: instancePath + "/protocol", + schemaPath: "#/properties/protocol/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid16 = 0 === _errs34; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data.first_video_at) { + let data17 = data.first_video_at, _errs36 = 0; + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate39.errors = [ { + instancePath: instancePath + "/first_video_at", + schemaPath: "#/properties/first_video_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid17 = 0 === _errs36; + } else valid17 = !0; + if (valid17) { + if (void 0 !== data.last_frame_ts) { + let data18 = data.last_frame_ts, _errs38 = 0; + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate39.errors = [ { + instancePath: instancePath + "/last_frame_ts", + schemaPath: "#/properties/last_frame_ts/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid18 = 0 === _errs38; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data.last_recording_at) { + let data19 = data.last_recording_at, _errs40 = 0; + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate39.errors = [ { + instancePath: instancePath + "/last_recording_at", + schemaPath: "#/properties/last_recording_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid19 = 0 === _errs40; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data.current_fps) { + let data20 = data.current_fps, _errs42 = 0; + if ("number" != typeof data20 || !isFinite(data20)) return validate39.errors = [ { + instancePath: instancePath + "/current_fps", + schemaPath: "#/properties/current_fps/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid20 = 0 === _errs42; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data.status) { + let data21 = data.status, _errs44 = 0; + if ("string" != typeof data21) return validate39.errors = [ { + instancePath: instancePath + "/status", + schemaPath: "#/$defs/StreamStatus/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("Stopped" !== data21 && "Starting" !== data21 && "Running" !== data21 && "Stopping" !== data21 && "Error" !== data21 && "Reconnecting" !== data21 && "Degraded" !== data21 && "Unknown" !== data21) return validate39.errors = [ { + instancePath: instancePath + "/status", + schemaPath: "#/$defs/StreamStatus/enum", + keyword: "enum", + params: { + allowedValues: schema32.enum + } + } ], !1; + var valid21 = 0 === _errs44; + } else valid21 = !0; + if (valid21 && void 0 !== data.availability) { + let data22 = data.availability; + if ("string" != typeof data22) return validate39.errors = [ { + instancePath: instancePath + "/availability", + schemaPath: "#/properties/availability/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("live" !== data22 && "offline" !== data22 && "never_connected" !== data22 && "disabled" !== data22) return validate39.errors = [ { + instancePath: instancePath + "/availability", + schemaPath: "#/properties/availability/enum", + keyword: "enum", + params: { + allowedValues: schema46.properties.availability.enum + } + } ], !1; + } + } + } + } + } + } + } + } + } + } + } } } } @@ -70834,180 +71705,280 @@ function validate73(data, {instancePath: instancePath = "", parentData: parentDa } } } - } else { - let err14 = { + } + return validate39.errors = [], !0; +} + +function validate42(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate42.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs0 = errors; + if (validate39(data, { + instancePath: instancePath, + parentData: parentData, + parentDataProperty: parentDataProperty, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate39.errors), errors = vErrors.length), _errs0 === errors && errors == errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate42.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/LoginSuccess/type", + schemaPath: "#/allOf/1/type", keyword: "type", params: { type: "object" } - }; - vErrors.push(err14), errors++; - } - var _valid0 = _errs1 === errors; - if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; - let _errs9 = errors; - if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.totp_required) { - let err15 = { + } ], !1; + if (void 0 === data.streaming_enabled) return validate42.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/LoginMfaChallenge/required", + schemaPath: "#/allOf/1/required", keyword: "required", params: { - missingProperty: "totp_required" + missingProperty: "streaming_enabled" } - }; - vErrors.push(err15), errors++; - } else if (void 0 === data.totp_token) { - let err16 = { + } ], !1; + if (void 0 === data.privacy_mode) return validate42.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/LoginMfaChallenge/required", + schemaPath: "#/allOf/1/required", keyword: "required", params: { - missingProperty: "totp_token" + missingProperty: "privacy_mode" } - }; - vErrors.push(err16), errors++; - } else { - if (void 0 !== data.totp_required) { - let _errs13 = errors; - if (!0 !== data.totp_required) { - let err17 = { - instancePath: instancePath + "/totp_required", - schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_required/const", - keyword: "const", - params: { - allowedValue: !0 - } - }; - vErrors.push(err17), errors++; + } ], !1; + if (void 0 === data.has_sub_stream) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "has_sub_stream" } - var valid14 = _errs13 === errors; - } else valid14 = !0; - if (valid14) { - if (void 0 !== data.totp_token) { - let data12 = data.totp_token, _errs14 = errors; - if (errors === _errs14) if ("string" == typeof data12) { - if (func1(data12) < 1) { - let err18 = { - instancePath: instancePath + "/totp_token", - schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_token/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err18), errors++; - } - } else { - let err19 = { - instancePath: instancePath + "/totp_token", - schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_token/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err19), errors++; + } ], !1; + if (void 0 === data.ptz_enabled) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "ptz_enabled" + } + } ], !1; + if (void 0 === data.backchannel_enabled) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "backchannel_enabled" + } + } ], !1; + if (void 0 === data.go2rtc_hls_available) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "go2rtc_hls_available" + } + } ], !1; + if (void 0 === data.playback_transport) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "playback_transport" + } + } ], !1; + if (void 0 === data.eptz_config) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "eptz_config" + } + } ], !1; + if (void 0 === data.detection_model) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "detection_model" + } + } ], !1; + if (void 0 === data.can_control_privacy) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "can_control_privacy" + } + } ], !1; + if (void 0 === data.can_configure) return validate42.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "can_configure" + } + } ], !1; + if (void 0 !== data.streaming_enabled) { + let _errs4 = errors; + if ("boolean" != typeof data.streaming_enabled) return validate42.errors = [ { + instancePath: instancePath + "/streaming_enabled", + schemaPath: "#/allOf/1/properties/streaming_enabled/type", + keyword: "type", + params: { + type: "boolean" } - var valid15 = _errs14 === errors; - } else valid15 = !0; - if (valid15) { - if (void 0 !== data.secret) { - var valid16 = !1; - let err20 = { - instancePath: instancePath + "/secret", - schemaPath: "#/$defs/LoginMfaChallenge/properties/secret/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err20), errors++; - } else valid16 = !0; - if (valid16) { - if (void 0 !== data.api_key) { - var valid17 = !1; - let err21 = { - instancePath: instancePath + "/api_key", - schemaPath: "#/$defs/LoginMfaChallenge/properties/api_key/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err21), errors++; - } else valid17 = !0; - if (valid17) { - if (void 0 !== data.password) { - var valid18 = !1; - let err22 = { - instancePath: instancePath + "/password", - schemaPath: "#/$defs/LoginMfaChallenge/properties/password/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err22), errors++; - } else valid18 = !0; - if (valid18) { - if (void 0 !== data.password_hash) { - var valid19 = !1; - let err23 = { - instancePath: instancePath + "/password_hash", - schemaPath: "#/$defs/LoginMfaChallenge/properties/password_hash/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err23), errors++; - } else valid19 = !0; - if (valid19) { - if (void 0 !== data.token_hash) { - var valid20 = !1; - let err24 = { - instancePath: instancePath + "/token_hash", - schemaPath: "#/$defs/LoginMfaChallenge/properties/token_hash/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err24), errors++; - } else valid20 = !0; - if (valid20) { - if (void 0 !== data.totp_secret) { - var valid21 = !1; - let err25 = { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err25), errors++; - } else valid21 = !0; - if (valid21) { - if (void 0 !== data.success) { - var valid22 = !1; - let err26 = { - instancePath: instancePath + "/success", - schemaPath: "#/$defs/LoginMfaChallenge/properties/success/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err26), errors++; - } else valid22 = !0; - if (valid22) { - if (void 0 !== data.redirect) { - var valid23 = !1; - let err27 = { - instancePath: instancePath + "/redirect", - schemaPath: "#/$defs/LoginMfaChallenge/properties/redirect/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err27), errors++; - } else valid23 = !0; - if (valid23 && void 0 !== data.must_change_password) { - let err28 = { - instancePath: instancePath + "/must_change_password", - schemaPath: "#/$defs/LoginMfaChallenge/properties/must_change_password/false schema", - keyword: "false schema", - params: {} - }; - vErrors.push(err28), errors++; + } ], !1; + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.privacy_mode) { + let _errs6 = errors; + if ("boolean" != typeof data.privacy_mode) return validate42.errors = [ { + instancePath: instancePath + "/privacy_mode", + schemaPath: "#/allOf/1/properties/privacy_mode/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.has_sub_stream) { + let _errs8 = errors; + if ("boolean" != typeof data.has_sub_stream) return validate42.errors = [ { + instancePath: instancePath + "/has_sub_stream", + schemaPath: "#/allOf/1/properties/has_sub_stream/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid3 = _errs8 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.ptz_enabled) { + let _errs10 = errors; + if ("boolean" != typeof data.ptz_enabled) return validate42.errors = [ { + instancePath: instancePath + "/ptz_enabled", + schemaPath: "#/allOf/1/properties/ptz_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid4 = _errs10 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.backchannel_enabled) { + let _errs12 = errors; + if ("boolean" != typeof data.backchannel_enabled) return validate42.errors = [ { + instancePath: instancePath + "/backchannel_enabled", + schemaPath: "#/allOf/1/properties/backchannel_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid5 = _errs12 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.go2rtc_hls_available) { + let _errs14 = errors; + if ("boolean" != typeof data.go2rtc_hls_available) return validate42.errors = [ { + instancePath: instancePath + "/go2rtc_hls_available", + schemaPath: "#/allOf/1/properties/go2rtc_hls_available/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid6 = _errs14 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.playback_transport) { + let data6 = data.playback_transport, _errs16 = errors; + if ("string" != typeof data6) return validate42.errors = [ { + instancePath: instancePath + "/playback_transport", + schemaPath: "#/$defs/PlaybackTransport/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("auto" !== data6 && "webrtc_only" !== data6 && "mse_only" !== data6 && "hls_only" !== data6 && "webrtc_then_mse" !== data6 && "mse_then_hls" !== data6) return validate42.errors = [ { + instancePath: instancePath + "/playback_transport", + schemaPath: "#/$defs/PlaybackTransport/enum", + keyword: "enum", + params: { + allowedValues: schema33.enum + } + } ], !1; + var valid7 = _errs16 === errors; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.eptz_config) { + let _errs19 = errors; + if ("string" != typeof data.eptz_config) return validate42.errors = [ { + instancePath: instancePath + "/eptz_config", + schemaPath: "#/allOf/1/properties/eptz_config/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid9 = _errs19 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.detection_model) { + let _errs21 = errors; + if ("string" != typeof data.detection_model) return validate42.errors = [ { + instancePath: instancePath + "/detection_model", + schemaPath: "#/allOf/1/properties/detection_model/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid10 = _errs21 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.can_control_privacy) { + let data9 = data.can_control_privacy, _errs23 = errors; + if ("boolean" != typeof data9) return validate42.errors = [ { + instancePath: instancePath + "/can_control_privacy", + schemaPath: "#/allOf/1/properties/can_control_privacy/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!1 !== data9) return validate42.errors = [ { + instancePath: instancePath + "/can_control_privacy", + schemaPath: "#/allOf/1/properties/can_control_privacy/const", + keyword: "const", + params: { + allowedValue: !1 + } + } ], !1; + var valid11 = _errs23 === errors; + } else valid11 = !0; + if (valid11 && void 0 !== data.can_configure) { + let data10 = data.can_configure; + if ("boolean" != typeof data10) return validate42.errors = [ { + instancePath: instancePath + "/can_configure", + schemaPath: "#/allOf/1/properties/can_configure/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!1 !== data10) return validate42.errors = [ { + instancePath: instancePath + "/can_configure", + schemaPath: "#/allOf/1/properties/can_configure/const", + keyword: "const", + params: { + allowedValue: !1 + } + } ], !1; } } } @@ -71018,286 +71989,560 @@ function validate73(data, {instancePath: instancePath = "", parentData: parentDa } } } - } else { - let err29 = { + } + return validate42.errors = vErrors, 0 === errors; +} + +function validate46(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate46.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs0 = errors; + if (validate39(data, { + instancePath: instancePath, + parentData: parentData, + parentDataProperty: parentDataProperty, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate39.errors), errors = vErrors.length), _errs0 === errors && errors == errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate46.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/LoginMfaChallenge/type", + schemaPath: "#/allOf/1/type", keyword: "type", params: { type: "object" } - }; - vErrors.push(err29), errors++; - } - if (_valid0 = _errs9 === errors, valid0 = valid0 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), - !valid0) { - let err30 = { + } ], !1; + if (void 0 === data.streaming_enabled) return validate46.errors = [ { instancePath: instancePath, - schemaPath: "#/anyOf", - keyword: "anyOf", - params: {} - }; - return vErrors.push(err30), errors++, validate73.errors = vErrors, !1; - } - return errors = _errs0, vErrors.length = _errs0, validate73.errors = vErrors, evaluated0.props = props0, - 0 === errors; -} - -validate73.evaluated = { - dynamicProps: !0, - dynamicItems: !1 -}; - -var __contract_LoginSuccess = validate71; - -function validate71(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate71.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate71.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate71.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.redirect) return validate71.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "redirect" - } - } ], !1; - if (void 0 === data.must_change_password) return validate71.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "must_change_password" - } - } ], !1; - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate71.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", + schemaPath: "#/allOf/1/required", + keyword: "required", params: { - allowedValue: !0 + missingProperty: "streaming_enabled" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.redirect) { - let _errs3 = 0; - if ("/index.html" !== data.redirect) return validate71.errors = [ { - instancePath: instancePath + "/redirect", - schemaPath: "#/properties/redirect/const", - keyword: "const", + if (void 0 === data.privacy_mode) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "privacy_mode" + } + } ], !1; + if (void 0 === data.has_sub_stream) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "has_sub_stream" + } + } ], !1; + if (void 0 === data.ptz_enabled) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "ptz_enabled" + } + } ], !1; + if (void 0 === data.backchannel_enabled) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "backchannel_enabled" + } + } ], !1; + if (void 0 === data.go2rtc_hls_available) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "go2rtc_hls_available" + } + } ], !1; + if (void 0 === data.playback_transport) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "playback_transport" + } + } ], !1; + if (void 0 === data.eptz_config) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "eptz_config" + } + } ], !1; + if (void 0 === data.detection_model) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "detection_model" + } + } ], !1; + if (void 0 === data.can_control_privacy) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "can_control_privacy" + } + } ], !1; + if (void 0 === data.can_configure) return validate46.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/required", + keyword: "required", + params: { + missingProperty: "can_configure" + } + } ], !1; + if (void 0 !== data.streaming_enabled) { + let _errs4 = errors; + if ("boolean" != typeof data.streaming_enabled) return validate46.errors = [ { + instancePath: instancePath + "/streaming_enabled", + schemaPath: "#/allOf/1/properties/streaming_enabled/type", + keyword: "type", params: { - allowedValue: "/index.html" + type: "boolean" } } ], !1; - var valid1 = 0 === _errs3; + var valid1 = _errs4 === errors; } else valid1 = !0; if (valid1) { - if (void 0 !== data.must_change_password) { - let _errs4 = 0; - if ("boolean" != typeof data.must_change_password) return validate71.errors = [ { - instancePath: instancePath + "/must_change_password", - schemaPath: "#/properties/must_change_password/type", + if (void 0 !== data.privacy_mode) { + let _errs6 = errors; + if ("boolean" != typeof data.privacy_mode) return validate46.errors = [ { + instancePath: instancePath + "/privacy_mode", + schemaPath: "#/allOf/1/properties/privacy_mode/type", keyword: "type", params: { type: "boolean" } } ], !1; - var valid2 = 0 === _errs4; + var valid2 = _errs6 === errors; } else valid2 = !0; if (valid2) { - if (void 0 !== data.secret) return validate71.errors = [ { - instancePath: instancePath + "/secret", - schemaPath: "#/properties/secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.api_key) return validate71.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password) return validate71.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password_hash) return validate71.errors = [ { - instancePath: instancePath + "/password_hash", - schemaPath: "#/properties/password_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.token_hash) return validate71.errors = [ { - instancePath: instancePath + "/token_hash", - schemaPath: "#/properties/token_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_secret) return validate71.errors = [ { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_token) return validate71.errors = [ { - instancePath: instancePath + "/totp_token", - schemaPath: "#/properties/totp_token/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_required) return validate71.errors = [ { - instancePath: instancePath + "/totp_required", - schemaPath: "#/properties/totp_required/false schema", - keyword: "false schema", - params: {} - } ], !1; - } - } - } - return validate71.errors = [], !0; -} - -validate71.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_LogoutResponse = validate74; + if (void 0 !== data.has_sub_stream) { + let _errs8 = errors; + if ("boolean" != typeof data.has_sub_stream) return validate46.errors = [ { + instancePath: instancePath + "/has_sub_stream", + schemaPath: "#/allOf/1/properties/has_sub_stream/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid3 = _errs8 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.ptz_enabled) { + let _errs10 = errors; + if ("boolean" != typeof data.ptz_enabled) return validate46.errors = [ { + instancePath: instancePath + "/ptz_enabled", + schemaPath: "#/allOf/1/properties/ptz_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid4 = _errs10 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.backchannel_enabled) { + let _errs12 = errors; + if ("boolean" != typeof data.backchannel_enabled) return validate46.errors = [ { + instancePath: instancePath + "/backchannel_enabled", + schemaPath: "#/allOf/1/properties/backchannel_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid5 = _errs12 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.go2rtc_hls_available) { + let _errs14 = errors; + if ("boolean" != typeof data.go2rtc_hls_available) return validate46.errors = [ { + instancePath: instancePath + "/go2rtc_hls_available", + schemaPath: "#/allOf/1/properties/go2rtc_hls_available/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid6 = _errs14 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.playback_transport) { + let data6 = data.playback_transport, _errs16 = errors; + if ("string" != typeof data6) return validate46.errors = [ { + instancePath: instancePath + "/playback_transport", + schemaPath: "#/$defs/PlaybackTransport/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("auto" !== data6 && "webrtc_only" !== data6 && "mse_only" !== data6 && "hls_only" !== data6 && "webrtc_then_mse" !== data6 && "mse_then_hls" !== data6) return validate46.errors = [ { + instancePath: instancePath + "/playback_transport", + schemaPath: "#/$defs/PlaybackTransport/enum", + keyword: "enum", + params: { + allowedValues: schema33.enum + } + } ], !1; + var valid7 = _errs16 === errors; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.eptz_config) { + let _errs19 = errors; + if ("string" != typeof data.eptz_config) return validate46.errors = [ { + instancePath: instancePath + "/eptz_config", + schemaPath: "#/allOf/1/properties/eptz_config/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid9 = _errs19 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.detection_model) { + let _errs21 = errors; + if ("string" != typeof data.detection_model) return validate46.errors = [ { + instancePath: instancePath + "/detection_model", + schemaPath: "#/allOf/1/properties/detection_model/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid10 = _errs21 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.can_control_privacy) { + let data9 = data.can_control_privacy, _errs23 = errors; + if ("boolean" != typeof data9) return validate46.errors = [ { + instancePath: instancePath + "/can_control_privacy", + schemaPath: "#/allOf/1/properties/can_control_privacy/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data9) return validate46.errors = [ { + instancePath: instancePath + "/can_control_privacy", + schemaPath: "#/allOf/1/properties/can_control_privacy/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid11 = _errs23 === errors; + } else valid11 = !0; + if (valid11 && void 0 !== data.can_configure) { + let data10 = data.can_configure; + if ("boolean" != typeof data10) return validate46.errors = [ { + instancePath: instancePath + "/can_configure", + schemaPath: "#/allOf/1/properties/can_configure/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data10) return validate46.errors = [ { + instancePath: instancePath + "/can_configure", + schemaPath: "#/allOf/1/properties/can_configure/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + } + } + } + } + } + } + } + } + } + } + } + return validate46.errors = vErrors, 0 === errors; +} -function validate74(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate74.evaluated; +function validate52(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate52.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate74.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate74.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.redirect) return validate74.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "redirect" - } - } ], !1; - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate74.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate52.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", params: { - allowedValue: !0 + type: "object" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.redirect) { - let _errs3 = 0; - if ("/login.html?logout=true" !== data.redirect) return validate74.errors = [ { - instancePath: instancePath + "/redirect", - schemaPath: "#/properties/redirect/const", - keyword: "const", - params: { - allowedValue: "/login.html?logout=true" + if (void 0 === data.streams) return validate52.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "streams" + } + } ], !1; + if (void 0 === data.total) return validate52.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "total" + } + } ], !1; + if (void 0 === data.page) return validate52.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "page" + } + } ], !1; + if (void 0 === data.page_size) return validate52.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "page_size" + } + } ], !1; + if (void 0 === data.total_pages) return validate52.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "total_pages" + } + } ], !1; + if (void 0 !== data.streams) { + let data0 = data.streams, _errs2 = errors; + if (errors === _errs2) { + if (!Array.isArray(data0)) return validate52.errors = [ { + instancePath: instancePath + "/streams", + schemaPath: "#/properties/streams/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = errors, _errs5 = errors, valid2 = !1, _errs6 = errors; + validate42(data1, { + instancePath: instancePath + "/streams/" + i0, + parentData: data0, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate42.errors), errors = vErrors.length); + var _valid0 = _errs6 === errors; + if (valid2 = valid2 || _valid0, _valid0) var props0 = !0; + let _errs7 = errors; + if (validate46(data1, { + instancePath: instancePath + "/streams/" + i0, + parentData: data0, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate46.errors), errors = vErrors.length), _valid0 = _errs7 === errors, + valid2 = valid2 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), !valid2) { + let err0 = { + instancePath: instancePath + "/streams/" + i0, + schemaPath: "#/properties/streams/items/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err0), errors++, validate52.errors = vErrors, !1; + } + if (errors = _errs5, vErrors.length = _errs5, _errs4 !== errors) break; + } } - } ], !1; - var valid1 = 0 === _errs3; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.secret) return validate74.errors = [ { - instancePath: instancePath + "/secret", - schemaPath: "#/properties/secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.api_key) return validate74.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password) return validate74.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password_hash) return validate74.errors = [ { - instancePath: instancePath + "/password_hash", - schemaPath: "#/properties/password_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.token_hash) return validate74.errors = [ { - instancePath: instancePath + "/token_hash", - schemaPath: "#/properties/token_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_secret) return validate74.errors = [ { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_token) return validate74.errors = [ { - instancePath: instancePath + "/totp_token", - schemaPath: "#/properties/totp_token/false schema", - keyword: "false schema", - params: {} - } ], !1; + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.total) { + let data2 = data.total, _errs8 = errors; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate52.errors = [ { + instancePath: instancePath + "/total", + schemaPath: "#/properties/total/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs8 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate52.errors = [ { + instancePath: instancePath + "/total", + schemaPath: "#/properties/total/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid3 = _errs8 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.page) { + let data3 = data.page, _errs10 = errors; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate52.errors = [ { + instancePath: instancePath + "/page", + schemaPath: "#/properties/page/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs10 && "number" == typeof data3 && isFinite(data3)) { + if (data3 > 1e6 || isNaN(data3)) return validate52.errors = [ { + instancePath: instancePath + "/page", + schemaPath: "#/properties/page/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1e6 + } + } ], !1; + if (data3 < 1 || isNaN(data3)) return validate52.errors = [ { + instancePath: instancePath + "/page", + schemaPath: "#/properties/page/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid4 = _errs10 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.page_size) { + let data4 = data.page_size, _errs12 = errors; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate52.errors = [ { + instancePath: instancePath + "/page_size", + schemaPath: "#/properties/page_size/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs12 && "number" == typeof data4 && isFinite(data4)) { + if (data4 > 100 || isNaN(data4)) return validate52.errors = [ { + instancePath: instancePath + "/page_size", + schemaPath: "#/properties/page_size/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 100 + } + } ], !1; + if (data4 < 1 || isNaN(data4)) return validate52.errors = [ { + instancePath: instancePath + "/page_size", + schemaPath: "#/properties/page_size/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid5 = _errs12 === errors; + } else valid5 = !0; + if (valid5 && void 0 !== data.total_pages) { + let data5 = data.total_pages, _errs14 = errors; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate52.errors = [ { + instancePath: instancePath + "/total_pages", + schemaPath: "#/properties/total_pages/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs14 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate52.errors = [ { + instancePath: instancePath + "/total_pages", + schemaPath: "#/properties/total_pages/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } } } - return validate74.errors = [], !0; + return validate52.errors = vErrors, 0 === errors; } -validate74.evaluated = { +validate39.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate42.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate46.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate52.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_LprStreamSummaryPage = validate55; +var __contract_LoginConfig = validate75, schema80 = { + type: "object", + properties: { + force_mfa_on_login: { + type: "boolean" + }, + remember_device_enabled: { + type: "boolean" + }, + auth_timeout_hours: { + type: "number" + }, + auth_absolute_timeout_hours: { + type: "number" + }, + trusted_device_days: { + type: "number" + }, + secret: !1, + api_key: !1, + password: !1, + password_hash: !1, + token_hash: !1, + totp_secret: !1, + totp_token: !1 + }, + required: [ "force_mfa_on_login", "remember_device_enabled", "auth_timeout_hours", "auth_absolute_timeout_hours", "trusted_device_days" ], + additionalProperties: !1 +}; -function validate55(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate55.evaluated; +function validate75(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate75.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate55.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate75.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -71305,722 +72550,409 @@ function validate55(data, {instancePath: instancePath = "", parentData: parentDa type: "object" } } ], !1; - if (void 0 === data.streams) return validate55.errors = [ { + if (void 0 === data.force_mfa_on_login) return validate75.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "streams" + missingProperty: "force_mfa_on_login" } } ], !1; - if (void 0 === data.total) return validate55.errors = [ { + if (void 0 === data.remember_device_enabled) return validate75.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "total" + missingProperty: "remember_device_enabled" } } ], !1; - if (void 0 === data.page) return validate55.errors = [ { + if (void 0 === data.auth_timeout_hours) return validate75.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "page" + missingProperty: "auth_timeout_hours" } } ], !1; - if (void 0 === data.page_size) return validate55.errors = [ { + if (void 0 === data.auth_absolute_timeout_hours) return validate75.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "page_size" + missingProperty: "auth_absolute_timeout_hours" } } ], !1; - if (void 0 === data.total_pages) return validate55.errors = [ { + if (void 0 === data.trusted_device_days) return validate75.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "total_pages" + missingProperty: "trusted_device_days" } } ], !1; - if (void 0 !== data.streams) { - let data0 = data.streams, _errs2 = 0; - if (0 === _errs2) { - if (!Array.isArray(data0)) return validate55.errors = [ { - instancePath: instancePath + "/streams", - schemaPath: "#/properties/streams/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate55.errors = [ { - instancePath: instancePath + "/streams/" + i0, - schemaPath: "#/$defs/LprStreamSummary/type", + { + let _errs1 = 0; + for (let key0 in data) if (!func7.call(schema80.properties, key0)) return validate75.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.force_mfa_on_login) { + let _errs2 = 0; + if ("boolean" != typeof data.force_mfa_on_login) return validate75.errors = [ { + instancePath: instancePath + "/force_mfa_on_login", + schemaPath: "#/properties/force_mfa_on_login/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.remember_device_enabled) { + let _errs4 = 0; + if ("boolean" != typeof data.remember_device_enabled) return validate75.errors = [ { + instancePath: instancePath + "/remember_device_enabled", + schemaPath: "#/properties/remember_device_enabled/type", keyword: "type", params: { - type: "object" - } - } ], !1; - if (void 0 === data1.camera_uuid) return validate55.errors = [ { - instancePath: instancePath + "/streams/" + i0, - schemaPath: "#/$defs/LprStreamSummary/required", - keyword: "required", - params: { - missingProperty: "camera_uuid" - } - } ], !1; - if (void 0 === data1.name) return validate55.errors = [ { - instancePath: instancePath + "/streams/" + i0, - schemaPath: "#/$defs/LprStreamSummary/required", - keyword: "required", - params: { - missingProperty: "name" + type: "boolean" } } ], !1; - if (void 0 !== data1.camera_uuid) { - let _errs8 = 0; - if ("string" != typeof data1.camera_uuid) return validate55.errors = [ { - instancePath: instancePath + "/streams/" + i0 + "/camera_uuid", - schemaPath: "#/$defs/LprStreamSummary/properties/camera_uuid/type", + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.auth_timeout_hours) { + let data2 = data.auth_timeout_hours, _errs6 = 0; + if ("number" != typeof data2 || !isFinite(data2)) return validate75.errors = [ { + instancePath: instancePath + "/auth_timeout_hours", + schemaPath: "#/properties/auth_timeout_hours/type", keyword: "type", params: { - type: "string" + type: "number" } } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3 && void 0 !== data1.name && "string" != typeof data1.name) return validate55.errors = [ { - instancePath: instancePath + "/streams/" + i0 + "/name", - schemaPath: "#/$defs/LprStreamSummary/properties/name/type", - keyword: "type", - params: { - type: "string" + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.auth_absolute_timeout_hours) { + let data3 = data.auth_absolute_timeout_hours, _errs8 = 0; + if ("number" != typeof data3 || !isFinite(data3)) return validate75.errors = [ { + instancePath: instancePath + "/auth_absolute_timeout_hours", + schemaPath: "#/properties/auth_absolute_timeout_hours/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.trusted_device_days) { + let data4 = data.trusted_device_days, _errs10 = 0; + if ("number" != typeof data4 || !isFinite(data4)) return validate75.errors = [ { + instancePath: instancePath + "/trusted_device_days", + schemaPath: "#/properties/trusted_device_days/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.secret) return validate75.errors = [ { + instancePath: instancePath + "/secret", + schemaPath: "#/properties/secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.api_key) return validate75.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password) return validate75.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password_hash) return validate75.errors = [ { + instancePath: instancePath + "/password_hash", + schemaPath: "#/properties/password_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.token_hash) return validate75.errors = [ { + instancePath: instancePath + "/token_hash", + schemaPath: "#/properties/token_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_secret) return validate75.errors = [ { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_token) return validate75.errors = [ { + instancePath: instancePath + "/totp_token", + schemaPath: "#/properties/totp_token/false schema", + keyword: "false schema", + params: {} + } ], !1; + } } - } ], !1; - if (0 !== _errs4) break; + } } } } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.total) { - let data4 = data.total, _errs12 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate55.errors = [ { - instancePath: instancePath + "/total", - schemaPath: "#/properties/total/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs12 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate55.errors = [ { - instancePath: instancePath + "/total", - schemaPath: "#/properties/total/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.page) { - let data5 = data.page, _errs14 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate55.errors = [ { - instancePath: instancePath + "/page", - schemaPath: "#/properties/page/type", - keyword: "type", + } + return validate75.errors = [], !0; +} + +validate75.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_LoginLockoutCleared = validate181; + +function validate181(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate181.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate181.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate181.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.cleared) return validate181.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "cleared" + } + } ], !1; + if (void 0 === data.message) return validate181.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("success" !== key0 && "cleared" !== key0 && "message" !== key0) return validate181.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate181.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", params: { - type: "integer" + allowedValue: !0 } } ], !1; - if (0 === _errs14 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 1e6 || isNaN(data5)) return validate55.errors = [ { - instancePath: instancePath + "/page", - schemaPath: "#/properties/page/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1e6 - } - } ], !1; - if (data5 < 1 || isNaN(data5)) return validate55.errors = [ { - instancePath: instancePath + "/page", - schemaPath: "#/properties/page/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 - } - } ], !1; - } - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.page_size) { - let data6 = data.page_size, _errs16 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate55.errors = [ { - instancePath: instancePath + "/page_size", - schemaPath: "#/properties/page_size/type", + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.cleared) { + let _errs3 = 0; + if ("boolean" != typeof data.cleared) return validate181.errors = [ { + instancePath: instancePath + "/cleared", + schemaPath: "#/properties/cleared/type", keyword: "type", params: { - type: "integer" + type: "boolean" } } ], !1; - if (0 === _errs16 && "number" == typeof data6 && isFinite(data6)) { - if (data6 > 100 || isNaN(data6)) return validate55.errors = [ { - instancePath: instancePath + "/page_size", - schemaPath: "#/properties/page_size/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 100 - } - } ], !1; - if (data6 < 1 || isNaN(data6)) return validate55.errors = [ { - instancePath: instancePath + "/page_size", - schemaPath: "#/properties/page_size/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 - } - } ], !1; + var valid1 = 0 === _errs3; + } else valid1 = !0; + if (valid1 && void 0 !== data.message && "string" != typeof data.message) return validate181.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" } - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7 && void 0 !== data.total_pages) { - let data7 = data.total_pages, _errs18 = 0; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate55.errors = [ { - instancePath: instancePath + "/total_pages", - schemaPath: "#/properties/total_pages/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs18 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate55.errors = [ { - instancePath: instancePath + "/total_pages", - schemaPath: "#/properties/total_pages/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - } + } ], !1; } } } - return validate55.errors = [], !0; + return validate181.errors = [], !0; } -validate55.evaluated = { +validate181.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_MotionTrigger = validate306, schema334 = { - type: "object", - properties: { - stream: { - type: "string", - minLength: 1 - }, - action: { - type: "string", - enum: [ "start", "stop", "pulse" ] - }, - duration_ms: { - type: "integer", - minimum: 1, - maximum: 6e5 - }, - label: { - type: "string", - minLength: 1 - }, - confidence: { - type: "number", - minimum: 0, - maximum: 1 - }, - objects: { - type: "array", - items: { - oneOf: [ { - type: "string", - minLength: 1 - }, { - type: "object", - properties: { - label: { - type: "string", - minLength: 1 - }, - confidence: { - type: "number", - minimum: 0, - maximum: 1 - } - }, - required: [ "label" ], - additionalProperties: !1 - } ] - } - }, - tags: { - type: "array", - items: { - type: "string", - minLength: 1 - } - } - }, - required: [ "stream", "action" ], - additionalProperties: !1 -}; +var __contract_LoginMfaChallenge = validate72; -function validate306(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate306.evaluated; +function validate72(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate72.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate306.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.stream) return validate306.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", + !data || "object" != typeof data || Array.isArray(data)) return validate72.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.totp_required) return validate72.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "totp_required" + } + } ], !1; + if (void 0 === data.totp_token) return validate72.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "totp_token" + } + } ], !1; + if (void 0 !== data.totp_required) { + let _errs2 = 0; + if (!0 !== data.totp_required) return validate72.errors = [ { + instancePath: instancePath + "/totp_required", + schemaPath: "#/properties/totp_required/const", + keyword: "const", params: { - missingProperty: "stream" + allowedValue: !0 } } ], !1; - if (void 0 === data.action) return validate306.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "action" + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.totp_token) { + let data1 = data.totp_token, _errs3 = 0; + if (0 === _errs3) { + if ("string" != typeof data1) return validate72.errors = [ { + instancePath: instancePath + "/totp_token", + schemaPath: "#/properties/totp_token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data1) < 1) return validate72.errors = [ { + instancePath: instancePath + "/totp_token", + schemaPath: "#/properties/totp_token/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; } - } ], !1; - { - let _errs1 = errors; - for (let key0 in data) if ("stream" !== key0 && "action" !== key0 && "duration_ms" !== key0 && "label" !== key0 && "confidence" !== key0 && "objects" !== key0 && "tags" !== key0) return validate306.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } + var valid1 = 0 === _errs3; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.secret) return validate72.errors = [ { + instancePath: instancePath + "/secret", + schemaPath: "#/properties/secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.api_key) return validate72.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password) return validate72.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password_hash) return validate72.errors = [ { + instancePath: instancePath + "/password_hash", + schemaPath: "#/properties/password_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.token_hash) return validate72.errors = [ { + instancePath: instancePath + "/token_hash", + schemaPath: "#/properties/token_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_secret) return validate72.errors = [ { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.success) return validate72.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.redirect) return validate72.errors = [ { + instancePath: instancePath + "/redirect", + schemaPath: "#/properties/redirect/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.must_change_password) return validate72.errors = [ { + instancePath: instancePath + "/must_change_password", + schemaPath: "#/properties/must_change_password/false schema", + keyword: "false schema", + params: {} } ], !1; - if (_errs1 === errors) { - if (void 0 !== data.stream) { - let data0 = data.stream, _errs2 = errors; - if (errors === _errs2) { - if ("string" != typeof data0) return validate306.errors = [ { - instancePath: instancePath + "/stream", - schemaPath: "#/properties/stream/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data0) < 1) return validate306.errors = [ { - instancePath: instancePath + "/stream", - schemaPath: "#/properties/stream/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid0 = _errs2 === errors; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.action) { - let data1 = data.action, _errs4 = errors; - if ("string" != typeof data1) return validate306.errors = [ { - instancePath: instancePath + "/action", - schemaPath: "#/properties/action/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("start" !== data1 && "stop" !== data1 && "pulse" !== data1) return validate306.errors = [ { - instancePath: instancePath + "/action", - schemaPath: "#/properties/action/enum", - keyword: "enum", - params: { - allowedValues: schema334.properties.action.enum - } - } ], !1; - var valid1 = _errs4 === errors; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.duration_ms) { - let data2 = data.duration_ms, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate306.errors = [ { - instancePath: instancePath + "/duration_ms", - schemaPath: "#/properties/duration_ms/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 6e5 || isNaN(data2)) return validate306.errors = [ { - instancePath: instancePath + "/duration_ms", - schemaPath: "#/properties/duration_ms/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 6e5 - } - } ], !1; - if (data2 < 1 || isNaN(data2)) return validate306.errors = [ { - instancePath: instancePath + "/duration_ms", - schemaPath: "#/properties/duration_ms/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 - } - } ], !1; - } - var valid2 = _errs6 === errors; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.label) { - let data3 = data.label, _errs8 = errors; - if (errors === _errs8) { - if ("string" != typeof data3) return validate306.errors = [ { - instancePath: instancePath + "/label", - schemaPath: "#/properties/label/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data3) < 1) return validate306.errors = [ { - instancePath: instancePath + "/label", - schemaPath: "#/properties/label/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid3 = _errs8 === errors; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.confidence) { - let data4 = data.confidence, _errs10 = errors; - if (errors === _errs10) { - if ("number" != typeof data4 || !isFinite(data4)) return validate306.errors = [ { - instancePath: instancePath + "/confidence", - schemaPath: "#/properties/confidence/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - if (data4 > 1 || isNaN(data4)) return validate306.errors = [ { - instancePath: instancePath + "/confidence", - schemaPath: "#/properties/confidence/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 - } - } ], !1; - if (data4 < 0 || isNaN(data4)) return validate306.errors = [ { - instancePath: instancePath + "/confidence", - schemaPath: "#/properties/confidence/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - } - var valid4 = _errs10 === errors; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.objects) { - let data5 = data.objects, _errs12 = errors; - if (errors === _errs12) { - if (!Array.isArray(data5)) return validate306.errors = [ { - instancePath: instancePath + "/objects", - schemaPath: "#/properties/objects/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len0 = data5.length; - for (let i0 = 0; i0 < len0; i0++) { - let data6 = data5[i0], _errs14 = errors, _errs15 = errors, valid7 = !1, passing0 = null, _errs16 = errors; - if (errors === _errs16) if ("string" == typeof data6) { - if (func1(data6) < 1) { - let err0 = { - instancePath: instancePath + "/objects/" + i0, - schemaPath: "#/properties/objects/items/oneOf/0/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err0), errors++; - } - } else { - let err1 = { - instancePath: instancePath + "/objects/" + i0, - schemaPath: "#/properties/objects/items/oneOf/0/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err1), errors++; - } - _errs16 === errors && (valid7 = !0, passing0 = 0); - let _errs18 = errors; - if (errors === _errs18) if (data6 && "object" == typeof data6 && !Array.isArray(data6)) if (void 0 === data6.label) { - let err2 = { - instancePath: instancePath + "/objects/" + i0, - schemaPath: "#/properties/objects/items/oneOf/1/required", - keyword: "required", - params: { - missingProperty: "label" - } - }; - vErrors.push(err2), errors++; - } else { - let _errs20 = errors; - for (let key1 in data6) if ("label" !== key1 && "confidence" !== key1) { - let err3 = { - instancePath: instancePath + "/objects/" + i0, - schemaPath: "#/properties/objects/items/oneOf/1/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key1 - } - }; - vErrors.push(err3), errors++; - break; - } - if (_errs20 === errors) { - if (void 0 !== data6.label) { - let data7 = data6.label, _errs21 = errors; - if (errors === _errs21) if ("string" == typeof data7) { - if (func1(data7) < 1) { - let err4 = { - instancePath: instancePath + "/objects/" + i0 + "/label", - schemaPath: "#/properties/objects/items/oneOf/1/properties/label/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err4), errors++; - } - } else { - let err5 = { - instancePath: instancePath + "/objects/" + i0 + "/label", - schemaPath: "#/properties/objects/items/oneOf/1/properties/label/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err5), errors++; - } - var valid8 = _errs21 === errors; - } else valid8 = !0; - if (valid8 && void 0 !== data6.confidence) { - let data8 = data6.confidence; - if (errors == errors) if ("number" == typeof data8 && isFinite(data8)) { - if (data8 > 1 || isNaN(data8)) { - let err6 = { - instancePath: instancePath + "/objects/" + i0 + "/confidence", - schemaPath: "#/properties/objects/items/oneOf/1/properties/confidence/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 - } - }; - vErrors.push(err6), errors++; - } else if (data8 < 0 || isNaN(data8)) { - let err7 = { - instancePath: instancePath + "/objects/" + i0 + "/confidence", - schemaPath: "#/properties/objects/items/oneOf/1/properties/confidence/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - }; - vErrors.push(err7), errors++; - } - } else { - let err8 = { - instancePath: instancePath + "/objects/" + i0 + "/confidence", - schemaPath: "#/properties/objects/items/oneOf/1/properties/confidence/type", - keyword: "type", - params: { - type: "number" - } - }; - vErrors.push(err8), errors++; - } - } - } - } else { - let err9 = { - instancePath: instancePath + "/objects/" + i0, - schemaPath: "#/properties/objects/items/oneOf/1/type", - keyword: "type", - params: { - type: "object" - } - }; - vErrors.push(err9), errors++; - } - var _valid1 = _errs18 === errors; - if (_valid1 && valid7 ? (valid7 = !1, passing0 = [ passing0, 1 ]) : _valid1 && (valid7 = !0, - passing0 = 1), !valid7) { - let err10 = { - instancePath: instancePath + "/objects/" + i0, - schemaPath: "#/properties/objects/items/oneOf", - keyword: "oneOf", - params: { - passingSchemas: passing0 - } - }; - return vErrors.push(err10), errors++, validate306.errors = vErrors, !1; - } - if (errors = _errs15, vErrors.length = _errs15, _errs14 !== errors) break; - } - } - } - var valid5 = _errs12 === errors; - } else valid5 = !0; - if (valid5 && void 0 !== data.tags) { - let data9 = data.tags; - if (errors == errors) { - if (!Array.isArray(data9)) return validate306.errors = [ { - instancePath: instancePath + "/tags", - schemaPath: "#/properties/tags/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len1 = data9.length; - for (let i1 = 0; i1 < len1; i1++) { - let data10 = data9[i1], _errs27 = errors; - if (errors === _errs27) { - if ("string" != typeof data10) return validate306.errors = [ { - instancePath: instancePath + "/tags/" + i1, - schemaPath: "#/properties/tags/items/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data10) < 1) return validate306.errors = [ { - instancePath: instancePath + "/tags/" + i1, - schemaPath: "#/properties/tags/items/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - if (_errs27 !== errors) break; - } - } - } - } - } - } - } - } - } - } } } - return validate306.errors = vErrors, 0 === errors; + return validate72.errors = [], !0; } -validate306.evaluated = { +validate72.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_MotionTriggered = validate307, schema335 = { - type: "object", - properties: { - success: { - type: "boolean", - const: !0 - }, - stream: { - type: "string" - }, - action: { - type: "string", - enum: [ "start", "stop", "pulse" ] - }, - duration_ms: { - type: "integer", - minimum: 1, - maximum: 6e5 - }, - recording_triggered: { - type: "boolean" - }, - detections_stored: { - type: "integer", - minimum: 0 - }, - tags_applied: { - type: "integer", - minimum: 0 - }, - warning: { - type: "string" - } - }, - required: [ "success", "stream", "action", "recording_triggered", "detections_stored", "tags_applied" ], - additionalProperties: !1 -}; +var __contract_LoginRequest = validate70; -function validate307(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate307.evaluated; +function validate70(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate70.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate307.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate70.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -72028,217 +72960,435 @@ function validate307(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate307.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.stream) return validate307.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "stream" - } - } ], !1; - if (void 0 === data.action) return validate307.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "action" - } - } ], !1; - if (void 0 === data.recording_triggered) return validate307.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "recording_triggered" - } - } ], !1; - if (void 0 === data.detections_stored) return validate307.errors = [ { + if (void 0 === data.username) return validate70.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "detections_stored" + missingProperty: "username" } } ], !1; - if (void 0 === data.tags_applied) return validate307.errors = [ { + if (void 0 === data.password) return validate70.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "tags_applied" + missingProperty: "password" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "stream" !== key0 && "action" !== key0 && "duration_ms" !== key0 && "recording_triggered" !== key0 && "detections_stored" !== key0 && "tags_applied" !== key0 && "warning" !== key0) return validate307.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + if (void 0 !== data.username) { + let _errs2 = 0; + if ("string" != typeof data.username) return validate70.errors = [ { + instancePath: instancePath + "/username", + schemaPath: "#/properties/username/type", + keyword: "type", params: { - additionalProperty: key0 + type: "string" } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.success) { - let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate307.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/type", + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.password) { + let _errs4 = 0; + if ("string" != typeof data.password) return validate70.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.totp_code) { + let _errs6 = 0; + if ("string" != typeof data.totp_code) return validate70.errors = [ { + instancePath: instancePath + "/totp_code", + schemaPath: "#/properties/totp_code/type", keyword: "type", params: { - type: "boolean" - } - } ], !1; - if (!0 !== data0) return validate307.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 + type: "string" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.stream) { - let _errs4 = 0; - if ("string" != typeof data.stream) return validate307.errors = [ { - instancePath: instancePath + "/stream", - schemaPath: "#/properties/stream/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.action) { - let data2 = data.action, _errs6 = 0; - if ("string" != typeof data2) return validate307.errors = [ { - instancePath: instancePath + "/action", - schemaPath: "#/properties/action/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("start" !== data2 && "stop" !== data2 && "pulse" !== data2) return validate307.errors = [ { - instancePath: instancePath + "/action", - schemaPath: "#/properties/action/enum", - keyword: "enum", + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2 && void 0 !== data.remember_device && "boolean" != typeof data.remember_device) return validate70.errors = [ { + instancePath: instancePath + "/remember_device", + schemaPath: "#/properties/remember_device/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + } + } + return validate70.errors = [], !0; +} + +validate70.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_LoginResponse = validate73; + +function validate73(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate73.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs0 = errors, valid0 = !1, _errs1 = errors; + if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.success) { + let err0 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginSuccess/required", + keyword: "required", + params: { + missingProperty: "success" + } + }; + vErrors.push(err0), errors++; + } else if (void 0 === data.redirect) { + let err1 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginSuccess/required", + keyword: "required", + params: { + missingProperty: "redirect" + } + }; + vErrors.push(err1), errors++; + } else if (void 0 === data.must_change_password) { + let err2 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginSuccess/required", + keyword: "required", + params: { + missingProperty: "must_change_password" + } + }; + vErrors.push(err2), errors++; + } else { + if (void 0 !== data.success) { + let _errs5 = errors; + if (!0 !== data.success) { + let err3 = { + instancePath: instancePath + "/success", + schemaPath: "#/$defs/LoginSuccess/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + }; + vErrors.push(err3), errors++; + } + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.redirect) { + let _errs6 = errors; + if ("/index.html" !== data.redirect) { + let err4 = { + instancePath: instancePath + "/redirect", + schemaPath: "#/$defs/LoginSuccess/properties/redirect/const", + keyword: "const", + params: { + allowedValue: "/index.html" + } + }; + vErrors.push(err4), errors++; + } + var valid3 = _errs6 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.must_change_password) { + let _errs7 = errors; + if ("boolean" != typeof data.must_change_password) { + let err5 = { + instancePath: instancePath + "/must_change_password", + schemaPath: "#/$defs/LoginSuccess/properties/must_change_password/type", + keyword: "type", params: { - allowedValues: schema335.properties.action.enum + type: "boolean" } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.duration_ms) { - let data3 = data.duration_ms, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate307.errors = [ { - instancePath: instancePath + "/duration_ms", - schemaPath: "#/properties/duration_ms/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs8 && "number" == typeof data3 && isFinite(data3)) { - if (data3 > 6e5 || isNaN(data3)) return validate307.errors = [ { - instancePath: instancePath + "/duration_ms", - schemaPath: "#/properties/duration_ms/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 6e5 - } - } ], !1; - if (data3 < 1 || isNaN(data3)) return validate307.errors = [ { - instancePath: instancePath + "/duration_ms", - schemaPath: "#/properties/duration_ms/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 1 + }; + vErrors.push(err5), errors++; + } + var valid4 = _errs7 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.secret) { + var valid5 = !1; + let err6 = { + instancePath: instancePath + "/secret", + schemaPath: "#/$defs/LoginSuccess/properties/secret/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err6), errors++; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.api_key) { + var valid6 = !1; + let err7 = { + instancePath: instancePath + "/api_key", + schemaPath: "#/$defs/LoginSuccess/properties/api_key/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err7), errors++; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.password) { + var valid7 = !1; + let err8 = { + instancePath: instancePath + "/password", + schemaPath: "#/$defs/LoginSuccess/properties/password/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err8), errors++; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.password_hash) { + var valid8 = !1; + let err9 = { + instancePath: instancePath + "/password_hash", + schemaPath: "#/$defs/LoginSuccess/properties/password_hash/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err9), errors++; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.token_hash) { + var valid9 = !1; + let err10 = { + instancePath: instancePath + "/token_hash", + schemaPath: "#/$defs/LoginSuccess/properties/token_hash/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err10), errors++; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.totp_secret) { + var valid10 = !1; + let err11 = { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/$defs/LoginSuccess/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err11), errors++; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.totp_token) { + var valid11 = !1; + let err12 = { + instancePath: instancePath + "/totp_token", + schemaPath: "#/$defs/LoginSuccess/properties/totp_token/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err12), errors++; + } else valid11 = !0; + if (valid11 && void 0 !== data.totp_required) { + let err13 = { + instancePath: instancePath + "/totp_required", + schemaPath: "#/$defs/LoginSuccess/properties/totp_required/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err13), errors++; + } + } } - } ], !1; + } } - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.recording_triggered) { - let _errs10 = 0; - if ("boolean" != typeof data.recording_triggered) return validate307.errors = [ { - instancePath: instancePath + "/recording_triggered", - schemaPath: "#/properties/recording_triggered/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.detections_stored) { - let data5 = data.detections_stored, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate307.errors = [ { - instancePath: instancePath + "/detections_stored", - schemaPath: "#/properties/detections_stored/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate307.errors = [ { - instancePath: instancePath + "/detections_stored", - schemaPath: "#/properties/detections_stored/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.tags_applied) { - let data6 = data.tags_applied, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate307.errors = [ { - instancePath: instancePath + "/tags_applied", - schemaPath: "#/properties/tags_applied/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate307.errors = [ { - instancePath: instancePath + "/tags_applied", - schemaPath: "#/properties/tags_applied/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 + } + } + } + } + } + } else { + let err14 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginSuccess/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err14), errors++; + } + var _valid0 = _errs1 === errors; + if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; + let _errs9 = errors; + if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.totp_required) { + let err15 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginMfaChallenge/required", + keyword: "required", + params: { + missingProperty: "totp_required" + } + }; + vErrors.push(err15), errors++; + } else if (void 0 === data.totp_token) { + let err16 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginMfaChallenge/required", + keyword: "required", + params: { + missingProperty: "totp_token" + } + }; + vErrors.push(err16), errors++; + } else { + if (void 0 !== data.totp_required) { + let _errs13 = errors; + if (!0 !== data.totp_required) { + let err17 = { + instancePath: instancePath + "/totp_required", + schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_required/const", + keyword: "const", + params: { + allowedValue: !0 + } + }; + vErrors.push(err17), errors++; + } + var valid14 = _errs13 === errors; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.totp_token) { + let data12 = data.totp_token, _errs14 = errors; + if (errors === _errs14) if ("string" == typeof data12) { + if (func1(data12) < 1) { + let err18 = { + instancePath: instancePath + "/totp_token", + schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_token/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err18), errors++; + } + } else { + let err19 = { + instancePath: instancePath + "/totp_token", + schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_token/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err19), errors++; + } + var valid15 = _errs14 === errors; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data.secret) { + var valid16 = !1; + let err20 = { + instancePath: instancePath + "/secret", + schemaPath: "#/$defs/LoginMfaChallenge/properties/secret/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err20), errors++; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data.api_key) { + var valid17 = !1; + let err21 = { + instancePath: instancePath + "/api_key", + schemaPath: "#/$defs/LoginMfaChallenge/properties/api_key/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err21), errors++; + } else valid17 = !0; + if (valid17) { + if (void 0 !== data.password) { + var valid18 = !1; + let err22 = { + instancePath: instancePath + "/password", + schemaPath: "#/$defs/LoginMfaChallenge/properties/password/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err22), errors++; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data.password_hash) { + var valid19 = !1; + let err23 = { + instancePath: instancePath + "/password_hash", + schemaPath: "#/$defs/LoginMfaChallenge/properties/password_hash/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err23), errors++; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data.token_hash) { + var valid20 = !1; + let err24 = { + instancePath: instancePath + "/token_hash", + schemaPath: "#/$defs/LoginMfaChallenge/properties/token_hash/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err24), errors++; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data.totp_secret) { + var valid21 = !1; + let err25 = { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/$defs/LoginMfaChallenge/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err25), errors++; + } else valid21 = !0; + if (valid21) { + if (void 0 !== data.success) { + var valid22 = !1; + let err26 = { + instancePath: instancePath + "/success", + schemaPath: "#/$defs/LoginMfaChallenge/properties/success/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err26), errors++; + } else valid22 = !0; + if (valid22) { + if (void 0 !== data.redirect) { + var valid23 = !1; + let err27 = { + instancePath: instancePath + "/redirect", + schemaPath: "#/$defs/LoginMfaChallenge/properties/redirect/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err27), errors++; + } else valid23 = !0; + if (valid23 && void 0 !== data.must_change_password) { + let err28 = { + instancePath: instancePath + "/must_change_password", + schemaPath: "#/$defs/LoginMfaChallenge/properties/must_change_password/false schema", + keyword: "false schema", + params: {} + }; + vErrors.push(err28), errors++; } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6 && void 0 !== data.warning && "string" != typeof data.warning) return validate307.errors = [ { - instancePath: instancePath + "/warning", - schemaPath: "#/properties/warning/type", - keyword: "type", - params: { - type: "string" } - } ], !1; + } } } } @@ -72246,22 +73396,42 @@ function validate307(data, {instancePath: instancePath = "", parentData: parentD } } } + } else { + let err29 = { + instancePath: instancePath, + schemaPath: "#/$defs/LoginMfaChallenge/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err29), errors++; } - return validate307.errors = [], !0; + if (_valid0 = _errs9 === errors, valid0 = valid0 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), + !valid0) { + let err30 = { + instancePath: instancePath, + schemaPath: "#/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err30), errors++, validate73.errors = vErrors, !1; + } + return errors = _errs0, vErrors.length = _errs0, validate73.errors = vErrors, evaluated0.props = props0, + 0 === errors; } -validate307.evaluated = { - props: !0, - dynamicProps: !1, +validate73.evaluated = { + dynamicProps: !0, dynamicItems: !1 }; -var __contract_OnvifAddRequest = validate436; +var __contract_LoginSuccess = validate71; -function validate436(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate436.evaluated; +function validate71(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate71.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate436.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate71.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -72269,288 +73439,135 @@ function validate436(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.device_url) return validate436.errors = [ { + if (void 0 === data.success) return validate71.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "device_url" + missingProperty: "success" } } ], !1; - if (void 0 === data.profile_token) return validate436.errors = [ { + if (void 0 === data.redirect) return validate71.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "profile_token" + missingProperty: "redirect" } } ], !1; - if (void 0 === data.stream_name) return validate436.errors = [ { + if (void 0 === data.must_change_password) return validate71.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "stream_name" + missingProperty: "must_change_password" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("device_url" !== key0 && "profile_token" !== key0 && "stream_name" !== key0 && "username" !== key0 && "password" !== key0) return validate436.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate71.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", params: { - additionalProperty: key0 + allowedValue: !0 } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.device_url) { - let data0 = data.device_url, _errs2 = 0; - if (0 === _errs2) { - if ("string" != typeof data0) return validate436.errors = [ { - instancePath: instancePath + "/device_url", - schemaPath: "#/properties/device_url/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data0) > 511) return validate436.errors = [ { - instancePath: instancePath + "/device_url", - schemaPath: "#/properties/device_url/maxLength", - keyword: "maxLength", - params: { - limit: 511 - } - } ], !1; - if (func1(data0) < 1) return validate436.errors = [ { - instancePath: instancePath + "/device_url", - schemaPath: "#/properties/device_url/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.profile_token) { - let data1 = data.profile_token, _errs4 = 0; - if (0 === _errs4) { - if ("string" != typeof data1) return validate436.errors = [ { - instancePath: instancePath + "/profile_token", - schemaPath: "#/properties/profile_token/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data1) > 63) return validate436.errors = [ { - instancePath: instancePath + "/profile_token", - schemaPath: "#/properties/profile_token/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - if (func1(data1) < 1) return validate436.errors = [ { - instancePath: instancePath + "/profile_token", - schemaPath: "#/properties/profile_token/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.stream_name) { - let data2 = data.stream_name, _errs6 = 0; - if (0 === _errs6) { - if ("string" != typeof data2) return validate436.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data2) > 255) return validate436.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/maxLength", - keyword: "maxLength", - params: { - limit: 255 - } - } ], !1; - if (func1(data2) < 1) return validate436.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.username) { - let data3 = data.username, _errs8 = 0; - if (0 === _errs8) { - if ("string" != typeof data3) return validate436.errors = [ { - instancePath: instancePath + "/username", - schemaPath: "#/properties/username/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data3) > 63) return validate436.errors = [ { - instancePath: instancePath + "/username", - schemaPath: "#/properties/username/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - } - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3 && void 0 !== data.password) { - let data4 = data.password; - if ("string" != typeof data4) return validate436.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data4) > 63) return validate436.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - } - } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.redirect) { + let _errs3 = 0; + if ("/index.html" !== data.redirect) return validate71.errors = [ { + instancePath: instancePath + "/redirect", + schemaPath: "#/properties/redirect/const", + keyword: "const", + params: { + allowedValue: "/index.html" } - } - } - } - return validate436.errors = [], !0; -} - -validate436.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_OnvifAddResult = validate437; - -function validate437(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate437.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate437.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate437.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.message) return validate437.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "message" - } - } ], !1; - if (void 0 === data.stream_name) return validate437.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "stream_name" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "message" !== key0 && "stream_name" !== key0) return validate437.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate437.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.message) { - let _errs3 = 0; - if ("string" != typeof data.message) return validate437.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs3; - } else valid1 = !0; - if (valid1 && void 0 !== data.stream_name && "string" != typeof data.stream_name) return validate437.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/type", + } ], !1; + var valid1 = 0 === _errs3; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.must_change_password) { + let _errs4 = 0; + if ("boolean" != typeof data.must_change_password) return validate71.errors = [ { + instancePath: instancePath + "/must_change_password", + schemaPath: "#/properties/must_change_password/type", keyword: "type", params: { - type: "string" + type: "boolean" } } ], !1; + var valid2 = 0 === _errs4; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.secret) return validate71.errors = [ { + instancePath: instancePath + "/secret", + schemaPath: "#/properties/secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.api_key) return validate71.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password) return validate71.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password_hash) return validate71.errors = [ { + instancePath: instancePath + "/password_hash", + schemaPath: "#/properties/password_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.token_hash) return validate71.errors = [ { + instancePath: instancePath + "/token_hash", + schemaPath: "#/properties/token_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_secret) return validate71.errors = [ { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_token) return validate71.errors = [ { + instancePath: instancePath + "/totp_token", + schemaPath: "#/properties/totp_token/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_required) return validate71.errors = [ { + instancePath: instancePath + "/totp_required", + schemaPath: "#/properties/totp_required/false schema", + keyword: "false schema", + params: {} + } ], !1; } } } - return validate437.errors = [], !0; + return validate71.errors = [], !0; } -validate437.evaluated = { +validate71.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_OnvifClaimRequest = validate438; +var __contract_LogoutResponse = validate74; -function validate438(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate438.evaluated; +function validate74(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate74.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate438.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate74.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -72558,174 +73575,107 @@ function validate438(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.inventory_uuid) return validate438.errors = [ { + if (void 0 === data.success) return validate74.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "inventory_uuid" + missingProperty: "success" } } ], !1; - if (void 0 === data.stream_name) return validate438.errors = [ { + if (void 0 === data.redirect) return validate74.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "stream_name" + missingProperty: "redirect" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("inventory_uuid" !== key0 && "stream_name" !== key0) return validate438.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate74.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", params: { - additionalProperty: key0 + allowedValue: !0 } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.inventory_uuid) { - let data0 = data.inventory_uuid, _errs2 = 0; - if (0 === _errs2) { - if ("string" != typeof data0) return validate438.errors = [ { - instancePath: instancePath + "/inventory_uuid", - schemaPath: "#/properties/inventory_uuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (!formats2.test(data0)) return validate438.errors = [ { - instancePath: instancePath + "/inventory_uuid", - schemaPath: "#/properties/inventory_uuid/format", - keyword: "format", - params: { - format: "uuid" - } - } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.redirect) { + let _errs3 = 0; + if ("/login.html?logout=true" !== data.redirect) return validate74.errors = [ { + instancePath: instancePath + "/redirect", + schemaPath: "#/properties/redirect/const", + keyword: "const", + params: { + allowedValue: "/login.html?logout=true" } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0 && void 0 !== data.stream_name) { - let data1 = data.stream_name; - if ("string" != typeof data1) return validate438.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data1) > 255) return validate438.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/maxLength", - keyword: "maxLength", - params: { - limit: 255 - } - } ], !1; - if (func1(data1) < 1) return validate438.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } + } ], !1; + var valid1 = 0 === _errs3; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.secret) return validate74.errors = [ { + instancePath: instancePath + "/secret", + schemaPath: "#/properties/secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.api_key) return validate74.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password) return validate74.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password_hash) return validate74.errors = [ { + instancePath: instancePath + "/password_hash", + schemaPath: "#/properties/password_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.token_hash) return validate74.errors = [ { + instancePath: instancePath + "/token_hash", + schemaPath: "#/properties/token_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_secret) return validate74.errors = [ { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_token) return validate74.errors = [ { + instancePath: instancePath + "/totp_token", + schemaPath: "#/properties/totp_token/false schema", + keyword: "false schema", + params: {} + } ], !1; } } - return validate438.errors = [], !0; + return validate74.errors = [], !0; } -validate438.evaluated = { +validate74.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_OnvifDevice = validate430, schema519 = { - type: "object", - properties: { - endpoint: { - type: "string" - }, - device_service: { - type: "string" - }, - media_service: { - type: "string" - }, - ptz_service: { - type: "string" - }, - imaging_service: { - type: "string" - }, - manufacturer: { - type: "string" - }, - model: { - type: "string" - }, - firmware_version: { - type: "string" - }, - serial_number: { - type: "string" - }, - hardware_id: { - type: "string" - }, - ip_address: { - type: "string" - }, - mac_address: { - type: "string" - }, - inventory_uuid: { - type: "string" - }, - last_scan_network: { - type: "string" - }, - claim_state: { - type: "string" - }, - claimed_camera_uuid: { - type: "string" - }, - discovery_time: { - type: "integer" - }, - first_seen_at: { - type: "integer" - }, - last_seen_at: { - type: "integer" - }, - online: { - type: "boolean" - }, - duplicate_suspected: { - type: "boolean" - }, - addresses: { - type: "array", - items: { - type: "string" - } - } - }, - required: [ "endpoint", "device_service", "media_service", "ptz_service", "imaging_service", "manufacturer", "model", "firmware_version", "serial_number", "hardware_id", "ip_address", "mac_address", "inventory_uuid", "last_scan_network", "claim_state", "claimed_camera_uuid", "discovery_time", "first_seen_at", "last_seen_at", "online", "duplicate_suspected", "addresses" ], - additionalProperties: !1 -}; +var __contract_LprStreamSummaryPage = validate55; -function validate430(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate430.evaluated; +function validate55(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate55.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate430.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate55.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -72733,501 +73683,659 @@ function validate430(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.endpoint) return validate430.errors = [ { + if (void 0 === data.streams) return validate55.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "endpoint" + missingProperty: "streams" } } ], !1; - if (void 0 === data.device_service) return validate430.errors = [ { + if (void 0 === data.total) return validate55.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "device_service" + missingProperty: "total" } } ], !1; - if (void 0 === data.media_service) return validate430.errors = [ { + if (void 0 === data.page) return validate55.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "media_service" + missingProperty: "page" } } ], !1; - if (void 0 === data.ptz_service) return validate430.errors = [ { + if (void 0 === data.page_size) return validate55.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "ptz_service" + missingProperty: "page_size" } } ], !1; - if (void 0 === data.imaging_service) return validate430.errors = [ { + if (void 0 === data.total_pages) return validate55.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "imaging_service" - } - } ], !1; - if (void 0 === data.manufacturer) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "manufacturer" - } - } ], !1; - if (void 0 === data.model) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "model" - } - } ], !1; - if (void 0 === data.firmware_version) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "firmware_version" - } - } ], !1; - if (void 0 === data.serial_number) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "serial_number" - } - } ], !1; - if (void 0 === data.hardware_id) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "hardware_id" - } - } ], !1; - if (void 0 === data.ip_address) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "ip_address" - } - } ], !1; - if (void 0 === data.mac_address) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "mac_address" - } - } ], !1; - if (void 0 === data.inventory_uuid) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "inventory_uuid" - } - } ], !1; - if (void 0 === data.last_scan_network) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "last_scan_network" - } - } ], !1; - if (void 0 === data.claim_state) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "claim_state" - } - } ], !1; - if (void 0 === data.claimed_camera_uuid) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "claimed_camera_uuid" - } - } ], !1; - if (void 0 === data.discovery_time) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "discovery_time" - } - } ], !1; - if (void 0 === data.first_seen_at) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "first_seen_at" - } - } ], !1; - if (void 0 === data.last_seen_at) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "last_seen_at" - } - } ], !1; - if (void 0 === data.online) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "online" - } - } ], !1; - if (void 0 === data.duplicate_suspected) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "duplicate_suspected" - } - } ], !1; - if (void 0 === data.addresses) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "addresses" + missingProperty: "total_pages" } } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema519.properties, key0)) return validate430.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 + if (void 0 !== data.streams) { + let data0 = data.streams, _errs2 = 0; + if (0 === _errs2) { + if (!Array.isArray(data0)) return validate55.errors = [ { + instancePath: instancePath + "/streams", + schemaPath: "#/properties/streams/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate55.errors = [ { + instancePath: instancePath + "/streams/" + i0, + schemaPath: "#/$defs/LprStreamSummary/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.camera_uuid) return validate55.errors = [ { + instancePath: instancePath + "/streams/" + i0, + schemaPath: "#/$defs/LprStreamSummary/required", + keyword: "required", + params: { + missingProperty: "camera_uuid" + } + } ], !1; + if (void 0 === data1.name) return validate55.errors = [ { + instancePath: instancePath + "/streams/" + i0, + schemaPath: "#/$defs/LprStreamSummary/required", + keyword: "required", + params: { + missingProperty: "name" + } + } ], !1; + if (void 0 !== data1.camera_uuid) { + let _errs8 = 0; + if ("string" != typeof data1.camera_uuid) return validate55.errors = [ { + instancePath: instancePath + "/streams/" + i0 + "/camera_uuid", + schemaPath: "#/$defs/LprStreamSummary/properties/camera_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3 && void 0 !== data1.name && "string" != typeof data1.name) return validate55.errors = [ { + instancePath: instancePath + "/streams/" + i0 + "/name", + schemaPath: "#/$defs/LprStreamSummary/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (0 !== _errs4) break; + } } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.endpoint) { - let _errs2 = 0; - if ("string" != typeof data.endpoint) return validate430.errors = [ { - instancePath: instancePath + "/endpoint", - schemaPath: "#/properties/endpoint/type", + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.total) { + let data4 = data.total, _errs12 = 0; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate55.errors = [ { + instancePath: instancePath + "/total", + schemaPath: "#/properties/total/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs12 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate55.errors = [ { + instancePath: instancePath + "/total", + schemaPath: "#/properties/total/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.page) { + let data5 = data.page, _errs14 = 0; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate55.errors = [ { + instancePath: instancePath + "/page", + schemaPath: "#/properties/page/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.device_service) { - let _errs4 = 0; - if ("string" != typeof data.device_service) return validate430.errors = [ { - instancePath: instancePath + "/device_service", - schemaPath: "#/properties/device_service/type", + if (0 === _errs14 && "number" == typeof data5 && isFinite(data5)) { + if (data5 > 1e6 || isNaN(data5)) return validate55.errors = [ { + instancePath: instancePath + "/page", + schemaPath: "#/properties/page/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1e6 + } + } ], !1; + if (data5 < 1 || isNaN(data5)) return validate55.errors = [ { + instancePath: instancePath + "/page", + schemaPath: "#/properties/page/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.page_size) { + let data6 = data.page_size, _errs16 = 0; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate55.errors = [ { + instancePath: instancePath + "/page_size", + schemaPath: "#/properties/page_size/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.media_service) { - let _errs6 = 0; - if ("string" != typeof data.media_service) return validate430.errors = [ { - instancePath: instancePath + "/media_service", - schemaPath: "#/properties/media_service/type", + if (0 === _errs16 && "number" == typeof data6 && isFinite(data6)) { + if (data6 > 100 || isNaN(data6)) return validate55.errors = [ { + instancePath: instancePath + "/page_size", + schemaPath: "#/properties/page_size/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 100 + } + } ], !1; + if (data6 < 1 || isNaN(data6)) return validate55.errors = [ { + instancePath: instancePath + "/page_size", + schemaPath: "#/properties/page_size/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7 && void 0 !== data.total_pages) { + let data7 = data.total_pages, _errs18 = 0; + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate55.errors = [ { + instancePath: instancePath + "/total_pages", + schemaPath: "#/properties/total_pages/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs18 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate55.errors = [ { + instancePath: instancePath + "/total_pages", + schemaPath: "#/properties/total_pages/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + return validate55.errors = [], !0; +} + +validate55.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_MotionTrigger = validate309, schema338 = { + type: "object", + properties: { + stream: { + type: "string", + minLength: 1 + }, + action: { + type: "string", + enum: [ "start", "stop", "pulse" ] + }, + duration_ms: { + type: "integer", + minimum: 1, + maximum: 6e5 + }, + label: { + type: "string", + minLength: 1 + }, + confidence: { + type: "number", + minimum: 0, + maximum: 1 + }, + objects: { + type: "array", + items: { + oneOf: [ { + type: "string", + minLength: 1 + }, { + type: "object", + properties: { + label: { + type: "string", + minLength: 1 + }, + confidence: { + type: "number", + minimum: 0, + maximum: 1 + } + }, + required: [ "label" ], + additionalProperties: !1 + } ] + } + }, + tags: { + type: "array", + items: { + type: "string", + minLength: 1 + } + } + }, + required: [ "stream", "action" ], + additionalProperties: !1 +}; + +function validate309(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate309.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate309.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.stream) return validate309.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream" + } + } ], !1; + if (void 0 === data.action) return validate309.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "action" + } + } ], !1; + { + let _errs1 = errors; + for (let key0 in data) if ("stream" !== key0 && "action" !== key0 && "duration_ms" !== key0 && "label" !== key0 && "confidence" !== key0 && "objects" !== key0 && "tags" !== key0) return validate309.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (_errs1 === errors) { + if (void 0 !== data.stream) { + let data0 = data.stream, _errs2 = errors; + if (errors === _errs2) { + if ("string" != typeof data0) return validate309.errors = [ { + instancePath: instancePath + "/stream", + schemaPath: "#/properties/stream/type", keyword: "type", params: { type: "string" } } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.ptz_service) { - let _errs8 = 0; - if ("string" != typeof data.ptz_service) return validate430.errors = [ { - instancePath: instancePath + "/ptz_service", - schemaPath: "#/properties/ptz_service/type", + if (func1(data0) < 1) return validate309.errors = [ { + instancePath: instancePath + "/stream", + schemaPath: "#/properties/stream/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.action) { + let data1 = data.action, _errs4 = errors; + if ("string" != typeof data1) return validate309.errors = [ { + instancePath: instancePath + "/action", + schemaPath: "#/properties/action/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("start" !== data1 && "stop" !== data1 && "pulse" !== data1) return validate309.errors = [ { + instancePath: instancePath + "/action", + schemaPath: "#/properties/action/enum", + keyword: "enum", + params: { + allowedValues: schema338.properties.action.enum + } + } ], !1; + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.duration_ms) { + let data2 = data.duration_ms, _errs6 = errors; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate309.errors = [ { + instancePath: instancePath + "/duration_ms", + schemaPath: "#/properties/duration_ms/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.imaging_service) { - let _errs10 = 0; - if ("string" != typeof data.imaging_service) return validate430.errors = [ { - instancePath: instancePath + "/imaging_service", - schemaPath: "#/properties/imaging_service/type", - keyword: "type", + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2)) { + if (data2 > 6e5 || isNaN(data2)) return validate309.errors = [ { + instancePath: instancePath + "/duration_ms", + schemaPath: "#/properties/duration_ms/maximum", + keyword: "maximum", params: { - type: "string" + comparison: "<=", + limit: 6e5 } } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.manufacturer) { - let _errs12 = 0; - if ("string" != typeof data.manufacturer) return validate430.errors = [ { - instancePath: instancePath + "/manufacturer", - schemaPath: "#/properties/manufacturer/type", + if (data2 < 1 || isNaN(data2)) return validate309.errors = [ { + instancePath: instancePath + "/duration_ms", + schemaPath: "#/properties/duration_ms/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.label) { + let data3 = data.label, _errs8 = errors; + if (errors === _errs8) { + if ("string" != typeof data3) return validate309.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", keyword: "type", params: { type: "string" } } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.model) { - let _errs14 = 0; - if ("string" != typeof data.model) return validate430.errors = [ { - instancePath: instancePath + "/model", - schemaPath: "#/properties/model/type", + if (func1(data3) < 1) return validate309.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid3 = _errs8 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.confidence) { + let data4 = data.confidence, _errs10 = errors; + if (errors === _errs10) { + if ("number" != typeof data4 || !isFinite(data4)) return validate309.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/type", keyword: "type", params: { - type: "string" + type: "number" } } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.firmware_version) { - let _errs16 = 0; - if ("string" != typeof data.firmware_version) return validate430.errors = [ { - instancePath: instancePath + "/firmware_version", - schemaPath: "#/properties/firmware_version/type", + if (data4 > 1 || isNaN(data4)) return validate309.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data4 < 0 || isNaN(data4)) return validate309.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid4 = _errs10 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.objects) { + let data5 = data.objects, _errs12 = errors; + if (errors === _errs12) { + if (!Array.isArray(data5)) return validate309.errors = [ { + instancePath: instancePath + "/objects", + schemaPath: "#/properties/objects/type", keyword: "type", params: { - type: "string" + type: "array" } } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.serial_number) { - let _errs18 = 0; - if ("string" != typeof data.serial_number) return validate430.errors = [ { - instancePath: instancePath + "/serial_number", - schemaPath: "#/properties/serial_number/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.hardware_id) { - let _errs20 = 0; - if ("string" != typeof data.hardware_id) return validate430.errors = [ { - instancePath: instancePath + "/hardware_id", - schemaPath: "#/properties/hardware_id/type", - keyword: "type", - params: { - type: "string" + { + let len0 = data5.length; + for (let i0 = 0; i0 < len0; i0++) { + let data6 = data5[i0], _errs14 = errors, _errs15 = errors, valid7 = !1, passing0 = null, _errs16 = errors; + if (errors === _errs16) if ("string" == typeof data6) { + if (func1(data6) < 1) { + let err0 = { + instancePath: instancePath + "/objects/" + i0, + schemaPath: "#/properties/objects/items/oneOf/0/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err0), errors++; } - } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.ip_address) { - let _errs22 = 0; - if ("string" != typeof data.ip_address) return validate430.errors = [ { - instancePath: instancePath + "/ip_address", - schemaPath: "#/properties/ip_address/type", + } else { + let err1 = { + instancePath: instancePath + "/objects/" + i0, + schemaPath: "#/properties/objects/items/oneOf/0/type", keyword: "type", params: { type: "string" } - } ], !1; - var valid10 = 0 === _errs22; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.mac_address) { - let _errs24 = 0; - if ("string" != typeof data.mac_address) return validate430.errors = [ { - instancePath: instancePath + "/mac_address", - schemaPath: "#/properties/mac_address/type", - keyword: "type", + }; + vErrors.push(err1), errors++; + } + _errs16 === errors && (valid7 = !0, passing0 = 0); + let _errs18 = errors; + if (errors === _errs18) if (data6 && "object" == typeof data6 && !Array.isArray(data6)) if (void 0 === data6.label) { + let err2 = { + instancePath: instancePath + "/objects/" + i0, + schemaPath: "#/properties/objects/items/oneOf/1/required", + keyword: "required", + params: { + missingProperty: "label" + } + }; + vErrors.push(err2), errors++; + } else { + let _errs20 = errors; + for (let key1 in data6) if ("label" !== key1 && "confidence" !== key1) { + let err3 = { + instancePath: instancePath + "/objects/" + i0, + schemaPath: "#/properties/objects/items/oneOf/1/additionalProperties", + keyword: "additionalProperties", params: { - type: "string" + additionalProperty: key1 } - } ], !1; - var valid11 = 0 === _errs24; - } else valid11 = !0; - if (valid11) { - if (void 0 !== data.inventory_uuid) { - let _errs26 = 0; - if ("string" != typeof data.inventory_uuid) return validate430.errors = [ { - instancePath: instancePath + "/inventory_uuid", - schemaPath: "#/properties/inventory_uuid/type", - keyword: "type", - params: { - type: "string" + }; + vErrors.push(err3), errors++; + break; + } + if (_errs20 === errors) { + if (void 0 !== data6.label) { + let data7 = data6.label, _errs21 = errors; + if (errors === _errs21) if ("string" == typeof data7) { + if (func1(data7) < 1) { + let err4 = { + instancePath: instancePath + "/objects/" + i0 + "/label", + schemaPath: "#/properties/objects/items/oneOf/1/properties/label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err4), errors++; } - } ], !1; - var valid12 = 0 === _errs26; - } else valid12 = !0; - if (valid12) { - if (void 0 !== data.last_scan_network) { - let _errs28 = 0; - if ("string" != typeof data.last_scan_network) return validate430.errors = [ { - instancePath: instancePath + "/last_scan_network", - schemaPath: "#/properties/last_scan_network/type", + } else { + let err5 = { + instancePath: instancePath + "/objects/" + i0 + "/label", + schemaPath: "#/properties/objects/items/oneOf/1/properties/label/type", keyword: "type", params: { type: "string" } - } ], !1; - var valid13 = 0 === _errs28; - } else valid13 = !0; - if (valid13) { - if (void 0 !== data.claim_state) { - let _errs30 = 0; - if ("string" != typeof data.claim_state) return validate430.errors = [ { - instancePath: instancePath + "/claim_state", - schemaPath: "#/properties/claim_state/type", - keyword: "type", + }; + vErrors.push(err5), errors++; + } + var valid8 = _errs21 === errors; + } else valid8 = !0; + if (valid8 && void 0 !== data6.confidence) { + let data8 = data6.confidence; + if (errors == errors) if ("number" == typeof data8 && isFinite(data8)) { + if (data8 > 1 || isNaN(data8)) { + let err6 = { + instancePath: instancePath + "/objects/" + i0 + "/confidence", + schemaPath: "#/properties/objects/items/oneOf/1/properties/confidence/maximum", + keyword: "maximum", params: { - type: "string" + comparison: "<=", + limit: 1 } - } ], !1; - var valid14 = 0 === _errs30; - } else valid14 = !0; - if (valid14) { - if (void 0 !== data.claimed_camera_uuid) { - let _errs32 = 0; - if ("string" != typeof data.claimed_camera_uuid) return validate430.errors = [ { - instancePath: instancePath + "/claimed_camera_uuid", - schemaPath: "#/properties/claimed_camera_uuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid15 = 0 === _errs32; - } else valid15 = !0; - if (valid15) { - if (void 0 !== data.discovery_time) { - let data16 = data.discovery_time, _errs34 = 0; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate430.errors = [ { - instancePath: instancePath + "/discovery_time", - schemaPath: "#/properties/discovery_time/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid16 = 0 === _errs34; - } else valid16 = !0; - if (valid16) { - if (void 0 !== data.first_seen_at) { - let data17 = data.first_seen_at, _errs36 = 0; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate430.errors = [ { - instancePath: instancePath + "/first_seen_at", - schemaPath: "#/properties/first_seen_at/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid17 = 0 === _errs36; - } else valid17 = !0; - if (valid17) { - if (void 0 !== data.last_seen_at) { - let data18 = data.last_seen_at, _errs38 = 0; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate430.errors = [ { - instancePath: instancePath + "/last_seen_at", - schemaPath: "#/properties/last_seen_at/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid18 = 0 === _errs38; - } else valid18 = !0; - if (valid18) { - if (void 0 !== data.online) { - let _errs40 = 0; - if ("boolean" != typeof data.online) return validate430.errors = [ { - instancePath: instancePath + "/online", - schemaPath: "#/properties/online/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid19 = 0 === _errs40; - } else valid19 = !0; - if (valid19) { - if (void 0 !== data.duplicate_suspected) { - let _errs42 = 0; - if ("boolean" != typeof data.duplicate_suspected) return validate430.errors = [ { - instancePath: instancePath + "/duplicate_suspected", - schemaPath: "#/properties/duplicate_suspected/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid20 = 0 === _errs42; - } else valid20 = !0; - if (valid20 && void 0 !== data.addresses) { - let data21 = data.addresses; - if (!Array.isArray(data21)) return validate430.errors = [ { - instancePath: instancePath + "/addresses", - schemaPath: "#/properties/addresses/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len0 = data21.length; - for (let i0 = 0; i0 < len0; i0++) { - let _errs46 = 0; - if ("string" != typeof data21[i0]) return validate430.errors = [ { - instancePath: instancePath + "/addresses/" + i0, - schemaPath: "#/properties/addresses/items/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (0 !== _errs46) break; - } - } - } - } - } - } + }; + vErrors.push(err6), errors++; + } else if (data8 < 0 || isNaN(data8)) { + let err7 = { + instancePath: instancePath + "/objects/" + i0 + "/confidence", + schemaPath: "#/properties/objects/items/oneOf/1/properties/confidence/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 } - } + }; + vErrors.push(err7), errors++; } + } else { + let err8 = { + instancePath: instancePath + "/objects/" + i0 + "/confidence", + schemaPath: "#/properties/objects/items/oneOf/1/properties/confidence/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err8), errors++; } } } + } else { + let err9 = { + instancePath: instancePath + "/objects/" + i0, + schemaPath: "#/properties/objects/items/oneOf/1/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err9), errors++; + } + var _valid1 = _errs18 === errors; + if (_valid1 && valid7 ? (valid7 = !1, passing0 = [ passing0, 1 ]) : _valid1 && (valid7 = !0, + passing0 = 1), !valid7) { + let err10 = { + instancePath: instancePath + "/objects/" + i0, + schemaPath: "#/properties/objects/items/oneOf", + keyword: "oneOf", + params: { + passingSchemas: passing0 + } + }; + return vErrors.push(err10), errors++, validate309.errors = vErrors, !1; + } + if (errors = _errs15, vErrors.length = _errs15, _errs14 !== errors) break; + } + } + } + var valid5 = _errs12 === errors; + } else valid5 = !0; + if (valid5 && void 0 !== data.tags) { + let data9 = data.tags; + if (errors == errors) { + if (!Array.isArray(data9)) return validate309.errors = [ { + instancePath: instancePath + "/tags", + schemaPath: "#/properties/tags/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len1 = data9.length; + for (let i1 = 0; i1 < len1; i1++) { + let data10 = data9[i1], _errs27 = errors; + if (errors === _errs27) { + if ("string" != typeof data10) return validate309.errors = [ { + instancePath: instancePath + "/tags/" + i1, + schemaPath: "#/properties/tags/items/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data10) < 1) return validate309.errors = [ { + instancePath: instancePath + "/tags/" + i1, + schemaPath: "#/properties/tags/items/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; } + if (_errs27 !== errors) break; } } } @@ -73240,21 +74348,57 @@ function validate430(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate430.errors = [], !0; + return validate309.errors = vErrors, 0 === errors; } -validate430.evaluated = { +validate309.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_OnvifDevicesResponse = validate431; +var __contract_MotionTriggered = validate310, schema339 = { + type: "object", + properties: { + success: { + type: "boolean", + const: !0 + }, + stream: { + type: "string" + }, + action: { + type: "string", + enum: [ "start", "stop", "pulse" ] + }, + duration_ms: { + type: "integer", + minimum: 1, + maximum: 6e5 + }, + recording_triggered: { + type: "boolean" + }, + detections_stored: { + type: "integer", + minimum: 0 + }, + tags_applied: { + type: "integer", + minimum: 0 + }, + warning: { + type: "string" + } + }, + required: [ "success", "stream", "action", "recording_triggered", "detections_stored", "tags_applied" ], + additionalProperties: !1 +}; -function validate431(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate431.evaluated; +function validate310(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate310.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate431.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate310.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -73262,17 +74406,57 @@ function validate431(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.devices) return validate431.errors = [ { + if (void 0 === data.success) return validate310.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "devices" + missingProperty: "success" + } + } ], !1; + if (void 0 === data.stream) return validate310.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream" + } + } ], !1; + if (void 0 === data.action) return validate310.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "action" + } + } ], !1; + if (void 0 === data.recording_triggered) return validate310.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "recording_triggered" + } + } ], !1; + if (void 0 === data.detections_stored) return validate310.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "detections_stored" + } + } ], !1; + if (void 0 === data.tags_applied) return validate310.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "tags_applied" } } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("devices" !== key0) return validate431.errors = [ { + for (let key0 in data) if ("success" !== key0 && "stream" !== key0 && "action" !== key0 && "duration_ms" !== key0 && "recording_triggered" !== key0 && "detections_stored" !== key0 && "tags_applied" !== key0 && "warning" !== key0) return validate310.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -73280,1351 +74464,1366 @@ function validate431(data, {instancePath: instancePath = "", parentData: parentD additionalProperty: key0 } } ], !1; - if (0 === _errs1 && void 0 !== data.devices) { - let data0 = data.devices; - if (!Array.isArray(data0)) return validate431.errors = [ { - instancePath: instancePath + "/devices", - schemaPath: "#/properties/devices/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/type", + if (0 === _errs1) { + if (void 0 !== data.success) { + let data0 = data.success, _errs2 = 0; + if ("boolean" != typeof data0) return validate310.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data0) return validate310.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.stream) { + let _errs4 = 0; + if ("string" != typeof data.stream) return validate310.errors = [ { + instancePath: instancePath + "/stream", + schemaPath: "#/properties/stream/type", keyword: "type", params: { - type: "object" - } - } ], !1; - if (void 0 === data1.endpoint) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "endpoint" + type: "string" } } ], !1; - if (void 0 === data1.device_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "device_service" - } - } ], !1; - if (void 0 === data1.media_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "media_service" - } - } ], !1; - if (void 0 === data1.ptz_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "ptz_service" - } - } ], !1; - if (void 0 === data1.imaging_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "imaging_service" - } - } ], !1; - if (void 0 === data1.manufacturer) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "manufacturer" - } - } ], !1; - if (void 0 === data1.model) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "model" - } - } ], !1; - if (void 0 === data1.firmware_version) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "firmware_version" - } - } ], !1; - if (void 0 === data1.serial_number) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "serial_number" - } - } ], !1; - if (void 0 === data1.hardware_id) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "hardware_id" - } - } ], !1; - if (void 0 === data1.ip_address) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "ip_address" - } - } ], !1; - if (void 0 === data1.mac_address) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "mac_address" - } - } ], !1; - if (void 0 === data1.inventory_uuid) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "inventory_uuid" - } - } ], !1; - if (void 0 === data1.last_scan_network) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "last_scan_network" - } - } ], !1; - if (void 0 === data1.claim_state) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "claim_state" - } - } ], !1; - if (void 0 === data1.claimed_camera_uuid) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "claimed_camera_uuid" - } - } ], !1; - if (void 0 === data1.discovery_time) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "discovery_time" - } - } ], !1; - if (void 0 === data1.first_seen_at) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "first_seen_at" - } - } ], !1; - if (void 0 === data1.last_seen_at) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "last_seen_at" - } - } ], !1; - if (void 0 === data1.online) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "online" - } - } ], !1; - if (void 0 === data1.duplicate_suspected) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "duplicate_suspected" - } - } ], !1; - if (void 0 === data1.addresses) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/required", - keyword: "required", - params: { - missingProperty: "addresses" - } - } ], !1; - { - let _errs7 = 0; - for (let key1 in data1) if (!func5.call(schema519.properties, key1)) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0, - schemaPath: "#/$defs/OnvifDevice/additionalProperties", - keyword: "additionalProperties", + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.action) { + let data2 = data.action, _errs6 = 0; + if ("string" != typeof data2) return validate310.errors = [ { + instancePath: instancePath + "/action", + schemaPath: "#/properties/action/type", + keyword: "type", params: { - additionalProperty: key1 + type: "string" } } ], !1; - if (0 === _errs7) { - if (void 0 !== data1.endpoint) { - let _errs8 = 0; - if ("string" != typeof data1.endpoint) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/endpoint", - schemaPath: "#/$defs/OnvifDevice/properties/endpoint/type", + if ("start" !== data2 && "stop" !== data2 && "pulse" !== data2) return validate310.errors = [ { + instancePath: instancePath + "/action", + schemaPath: "#/properties/action/enum", + keyword: "enum", + params: { + allowedValues: schema339.properties.action.enum + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.duration_ms) { + let data3 = data.duration_ms, _errs8 = 0; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate310.errors = [ { + instancePath: instancePath + "/duration_ms", + schemaPath: "#/properties/duration_ms/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs8 && "number" == typeof data3 && isFinite(data3)) { + if (data3 > 6e5 || isNaN(data3)) return validate310.errors = [ { + instancePath: instancePath + "/duration_ms", + schemaPath: "#/properties/duration_ms/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 6e5 + } + } ], !1; + if (data3 < 1 || isNaN(data3)) return validate310.errors = [ { + instancePath: instancePath + "/duration_ms", + schemaPath: "#/properties/duration_ms/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.recording_triggered) { + let _errs10 = 0; + if ("boolean" != typeof data.recording_triggered) return validate310.errors = [ { + instancePath: instancePath + "/recording_triggered", + schemaPath: "#/properties/recording_triggered/type", keyword: "type", params: { - type: "string" + type: "boolean" } } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data1.device_service) { - let _errs10 = 0; - if ("string" != typeof data1.device_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/device_service", - schemaPath: "#/$defs/OnvifDevice/properties/device_service/type", + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.detections_stored) { + let data5 = data.detections_stored, _errs12 = 0; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate310.errors = [ { + instancePath: instancePath + "/detections_stored", + schemaPath: "#/properties/detections_stored/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data1.media_service) { - let _errs12 = 0; - if ("string" != typeof data1.media_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/media_service", - schemaPath: "#/$defs/OnvifDevice/properties/media_service/type", + if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate310.errors = [ { + instancePath: instancePath + "/detections_stored", + schemaPath: "#/properties/detections_stored/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.tags_applied) { + let data6 = data.tags_applied, _errs14 = 0; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate310.errors = [ { + instancePath: instancePath + "/tags_applied", + schemaPath: "#/properties/tags_applied/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data1.ptz_service) { - let _errs14 = 0; - if ("string" != typeof data1.ptz_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/ptz_service", - schemaPath: "#/$defs/OnvifDevice/properties/ptz_service/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data1.imaging_service) { - let _errs16 = 0; - if ("string" != typeof data1.imaging_service) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/imaging_service", - schemaPath: "#/$defs/OnvifDevice/properties/imaging_service/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data1.manufacturer) { - let _errs18 = 0; - if ("string" != typeof data1.manufacturer) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/manufacturer", - schemaPath: "#/$defs/OnvifDevice/properties/manufacturer/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data1.model) { - let _errs20 = 0; - if ("string" != typeof data1.model) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/model", - schemaPath: "#/$defs/OnvifDevice/properties/model/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data1.firmware_version) { - let _errs22 = 0; - if ("string" != typeof data1.firmware_version) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/firmware_version", - schemaPath: "#/$defs/OnvifDevice/properties/firmware_version/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid10 = 0 === _errs22; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data1.serial_number) { - let _errs24 = 0; - if ("string" != typeof data1.serial_number) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/serial_number", - schemaPath: "#/$defs/OnvifDevice/properties/serial_number/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid11 = 0 === _errs24; - } else valid11 = !0; - if (valid11) { - if (void 0 !== data1.hardware_id) { - let _errs26 = 0; - if ("string" != typeof data1.hardware_id) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/hardware_id", - schemaPath: "#/$defs/OnvifDevice/properties/hardware_id/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid12 = 0 === _errs26; - } else valid12 = !0; - if (valid12) { - if (void 0 !== data1.ip_address) { - let _errs28 = 0; - if ("string" != typeof data1.ip_address) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/ip_address", - schemaPath: "#/$defs/OnvifDevice/properties/ip_address/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid13 = 0 === _errs28; - } else valid13 = !0; - if (valid13) { - if (void 0 !== data1.mac_address) { - let _errs30 = 0; - if ("string" != typeof data1.mac_address) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/mac_address", - schemaPath: "#/$defs/OnvifDevice/properties/mac_address/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid14 = 0 === _errs30; - } else valid14 = !0; - if (valid14) { - if (void 0 !== data1.inventory_uuid) { - let _errs32 = 0; - if ("string" != typeof data1.inventory_uuid) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/inventory_uuid", - schemaPath: "#/$defs/OnvifDevice/properties/inventory_uuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid15 = 0 === _errs32; - } else valid15 = !0; - if (valid15) { - if (void 0 !== data1.last_scan_network) { - let _errs34 = 0; - if ("string" != typeof data1.last_scan_network) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/last_scan_network", - schemaPath: "#/$defs/OnvifDevice/properties/last_scan_network/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid16 = 0 === _errs34; - } else valid16 = !0; - if (valid16) { - if (void 0 !== data1.claim_state) { - let _errs36 = 0; - if ("string" != typeof data1.claim_state) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/claim_state", - schemaPath: "#/$defs/OnvifDevice/properties/claim_state/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid17 = 0 === _errs36; - } else valid17 = !0; - if (valid17) { - if (void 0 !== data1.claimed_camera_uuid) { - let _errs38 = 0; - if ("string" != typeof data1.claimed_camera_uuid) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/claimed_camera_uuid", - schemaPath: "#/$defs/OnvifDevice/properties/claimed_camera_uuid/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid18 = 0 === _errs38; - } else valid18 = !0; - if (valid18) { - if (void 0 !== data1.discovery_time) { - let data18 = data1.discovery_time, _errs40 = 0; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/discovery_time", - schemaPath: "#/$defs/OnvifDevice/properties/discovery_time/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid19 = 0 === _errs40; - } else valid19 = !0; - if (valid19) { - if (void 0 !== data1.first_seen_at) { - let data19 = data1.first_seen_at, _errs42 = 0; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/first_seen_at", - schemaPath: "#/$defs/OnvifDevice/properties/first_seen_at/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid20 = 0 === _errs42; - } else valid20 = !0; - if (valid20) { - if (void 0 !== data1.last_seen_at) { - let data20 = data1.last_seen_at, _errs44 = 0; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/last_seen_at", - schemaPath: "#/$defs/OnvifDevice/properties/last_seen_at/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid21 = 0 === _errs44; - } else valid21 = !0; - if (valid21) { - if (void 0 !== data1.online) { - let _errs46 = 0; - if ("boolean" != typeof data1.online) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/online", - schemaPath: "#/$defs/OnvifDevice/properties/online/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid22 = 0 === _errs46; - } else valid22 = !0; - if (valid22) { - if (void 0 !== data1.duplicate_suspected) { - let _errs48 = 0; - if ("boolean" != typeof data1.duplicate_suspected) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/duplicate_suspected", - schemaPath: "#/$defs/OnvifDevice/properties/duplicate_suspected/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid23 = 0 === _errs48; - } else valid23 = !0; - if (valid23 && void 0 !== data1.addresses) { - let data23 = data1.addresses; - if (!Array.isArray(data23)) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/addresses", - schemaPath: "#/$defs/OnvifDevice/properties/addresses/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len1 = data23.length; - for (let i1 = 0; i1 < len1; i1++) { - let _errs52 = 0; - if ("string" != typeof data23[i1]) return validate431.errors = [ { - instancePath: instancePath + "/devices/" + i0 + "/addresses/" + i1, - schemaPath: "#/$defs/OnvifDevice/properties/addresses/items/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (0 !== _errs52) break; - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } - } + if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate310.errors = [ { + instancePath: instancePath + "/tags_applied", + schemaPath: "#/properties/tags_applied/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6 && void 0 !== data.warning && "string" != typeof data.warning) return validate310.errors = [ { + instancePath: instancePath + "/warning", + schemaPath: "#/properties/warning/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; } } } } - if (0 !== _errs4) break; } } } } - return validate431.errors = [], !0; + return validate310.errors = [], !0; } -validate431.evaluated = { +validate310.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_OnvifDiscoveryRequest = validate432; - -function validate432(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate432.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate432.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" +var __contract_Observation = validate491, schema578 = { + type: "object", + properties: { + engine_key: { + type: "string", + minLength: 1, + maxLength: 64 + }, + engine_type: { + type: "string", + enum: [ "object", "api", "onvif", "external" ] + }, + model: { + type: [ "string", "null" ], + maxLength: 128, + description: "Model basename only, never a path." + }, + model_version: { + type: [ "string", "null" ], + maxLength: 64 } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("network" !== key0) return validate432.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 + }, + required: [ "engine_key", "engine_type", "model", "model_version" ], + additionalProperties: !0 +}, schema582 = { + type: "object", + properties: { + asset_id: { + $ref: "#/$defs/ObservationUuid" + }, + state: { + $ref: "#/$defs/ObservationEvidenceState" + }, + reason: { + anyOf: [ { + $ref: "#/$defs/ObservationEvidenceReason" + }, { + type: "null" + } ] + }, + origin: { + type: [ "string", "null" ], + enum: [ "inference_input", "submitted_image", null ] + }, + media_type: { + type: [ "string", "null" ], + enum: [ "image/jpeg", null ] + }, + width: { + type: [ "integer", "null" ], + minimum: 1 + }, + height: { + type: [ "integer", "null" ], + minimum: 1 + }, + bytes: { + type: [ "integer", "null" ], + minimum: 0 + }, + captured_at: { + anyOf: [ { + $ref: "#/$defs/ObservationTime" + }, { + type: "null" + } ] + }, + expires_at: { + anyOf: [ { + $ref: "#/$defs/ObservationTime" + }, { + type: "null" + } ] + }, + transform: { + anyOf: [ { + $ref: "#/$defs/ObservationTransform" + }, { + type: "null" + } ] + }, + snapshot_url: { + type: "string", + pattern: "^/api/observations/[0-9a-f-]{36}/snapshot$", + description: "Present only when state is ready. Relative and authenticated." + } + }, + required: [ "asset_id", "state", "reason", "origin", "media_type", "width", "height", "bytes", "captured_at", "expires_at", "transform" ], + additionalProperties: !0, + description: "Evidence asset (contract §4). Every observation has one; siblings in a batch share asset_id.", + allOf: [ { + if: { + type: "object", + properties: { + state: { + enum: [ "pending", "ready" ] + } } - } ], !1; - if (0 === _errs1 && void 0 !== data.network) { - let data0 = data.network; - if ("string" != typeof data0) return validate432.errors = [ { - instancePath: instancePath + "/network", - schemaPath: "#/properties/network/type", - keyword: "type", - params: { - type: "string" + }, + then: { + type: "object", + properties: { + reason: { + type: "null" } - } ], !1; - if (func1(data0) > 255) return validate432.errors = [ { - instancePath: instancePath + "/network", - schemaPath: "#/properties/network/maxLength", - keyword: "maxLength", - params: { - limit: 255 + } + }, + else: { + type: "object", + properties: { + reason: { + $ref: "#/$defs/ObservationEvidenceReason" } - } ], !1; - } - } - return validate432.errors = [], !0; -} - -validate432.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_OnvifDiscoveryStatus = validate429; - -function validate429(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate429.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate429.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.enabled) return validate429.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "enabled" - } - } ], !1; - if (void 0 === data.network) return validate429.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "network" - } - } ], !1; - if (void 0 === data.interval) return validate429.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "interval" + } } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("enabled" !== key0 && "network" !== key0 && "interval" !== key0) return validate429.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 + }, { + if: { + type: "object", + properties: { + state: { + const: "ready" + } } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.enabled) { - let _errs2 = 0; - if ("boolean" != typeof data.enabled) return validate429.errors = [ { - instancePath: instancePath + "/enabled", - schemaPath: "#/properties/enabled/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.network) { - let _errs4 = 0; - if ("string" != typeof data.network) return validate429.errors = [ { - instancePath: instancePath + "/network", - schemaPath: "#/properties/network/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1 && void 0 !== data.interval) { - let data2 = data.interval; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate429.errors = [ { - instancePath: instancePath + "/interval", - schemaPath: "#/properties/interval/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; + }, + then: { + type: "object", + properties: { + snapshot_url: { + type: "string" } + }, + required: [ "snapshot_url" ] + }, + else: { + type: "object", + not: { + type: "object", + properties: { + snapshot_url: { + type: "string" + } + }, + required: [ "snapshot_url" ] } } - } - return validate429.errors = [], !0; -} - -validate429.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_OnvifProfile = validate433, schema523 = { + } ] +}, schema575 = { + type: "string", + enum: [ "queue_full", "budget_exhausted", "encode_failed", "write_failed", "interrupted", "no_matching_media", "disabled", "retention_expired", "deleted" ], + description: "Bounded reason (contract §4.3). Consumers treat an unknown reason by its state." +}, schema574 = { + type: "string", + enum: [ "pending", "ready", "unavailable", "expired" ] +}, schema577 = { type: "object", properties: { - token: { - type: "string" - }, - name: { - type: "string" - }, - video_source_token: { - type: "string" - }, - ptz_configuration_token: { - type: "string" + mode: { + type: "string", + enum: [ "none", "resize", "letterbox" ] }, - snapshot_uri: { - type: "string" + source_width: { + type: "integer", + minimum: 1 }, - stream_uri: { - type: "string" + source_height: { + type: "integer", + minimum: 1 }, - encoding: { - type: "string" + inference_width: { + type: "integer", + minimum: 1 }, - width: { - type: "integer" + inference_height: { + type: "integer", + minimum: 1 }, - height: { - type: "integer" + scale_x: { + type: "number", + exclusiveMinimum: 0 }, - fps: { - type: "integer" + scale_y: { + type: "number", + exclusiveMinimum: 0 }, - bitrate: { - type: "integer" + pad_x: { + type: "number", + minimum: 0 }, - ptz_supported: { - type: "boolean" + pad_y: { + type: "number", + minimum: 0 } }, - required: [ "token", "name", "video_source_token", "ptz_configuration_token", "snapshot_uri", "stream_uri", "encoding", "width", "height", "fps", "bitrate", "ptz_supported" ], - additionalProperties: !1 -}; + required: [ "mode", "source_width", "source_height", "inference_width", "inference_height", "scale_x", "scale_y", "pad_x", "pad_y" ], + additionalProperties: !0, + description: "Audit record of the inference-to-stored-image mapping: x = (u - pad_x) / scale_x, y = (v - pad_y) / scale_y (contract §4.4)." +}, pattern209 = new RegExp("^/api/observations/[0-9a-f-]{36}/snapshot$", "u"); -function validate433(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate433.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.token) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "token" - } - } ], !1; - if (void 0 === data.name) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data.video_source_token) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "video_source_token" - } - } ], !1; - if (void 0 === data.ptz_configuration_token) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "ptz_configuration_token" - } - } ], !1; - if (void 0 === data.snapshot_uri) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "snapshot_uri" - } - } ], !1; - if (void 0 === data.stream_uri) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "stream_uri" - } - } ], !1; - if (void 0 === data.encoding) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "encoding" - } - } ], !1; - if (void 0 === data.width) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "width" - } - } ], !1; - if (void 0 === data.height) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "height" - } - } ], !1; - if (void 0 === data.fps) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "fps" +function validate483(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate483.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs1 = errors, _errs2 = errors, valid1 = !0, _errs3 = errors; + if (errors === _errs3) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 !== data.state) { + let data0 = data.state; + if ("pending" !== data0 && "ready" !== data0) { + let err0 = {}; + vErrors.push(err0), errors++; + } } - } ], !1; - if (void 0 === data.bitrate) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "bitrate" + } else { + let err1 = {}; + vErrors.push(err1), errors++; + } + var _valid0 = _errs3 === errors; + let ifClause0; + if (errors = _errs2, vErrors.length = _errs2, _valid0) { + let _errs6 = errors; + if (errors === _errs6) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/0/then/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 !== data.reason && null !== data.reason) return validate483.errors = [ { + instancePath: instancePath + "/reason", + schemaPath: "#/allOf/0/then/properties/reason/type", + keyword: "type", + params: { + type: "null" + } + } ], !1; } - } ], !1; - if (void 0 === data.ptz_supported) return validate433.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "ptz_supported" + if (valid1 = _valid0 = _errs6 === errors, valid1) var props0 = { + reason: !0, + state: !0 + }; + ifClause0 = "then"; + } else { + let _errs10 = errors; + if (errors === _errs10) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/0/else/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 !== data.reason) { + let data2 = data.reason; + if ("string" != typeof data2) return validate483.errors = [ { + instancePath: instancePath + "/reason", + schemaPath: "#/$defs/ObservationEvidenceReason/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("queue_full" !== data2 && "budget_exhausted" !== data2 && "encode_failed" !== data2 && "write_failed" !== data2 && "interrupted" !== data2 && "no_matching_media" !== data2 && "disabled" !== data2 && "retention_expired" !== data2 && "deleted" !== data2) return validate483.errors = [ { + instancePath: instancePath + "/reason", + schemaPath: "#/$defs/ObservationEvidenceReason/enum", + keyword: "enum", + params: { + allowedValues: schema575.enum + } + } ], !1; + } } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema523.properties, key0)) return validate433.errors = [ { + valid1 = _valid0 = _errs10 === errors, valid1 && !0 !== props0 && ((props0 = props0 || {}).reason = !0), + ifClause0 = "else"; + } + if (!valid1) { + let err2 = { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/allOf/0/if", + keyword: "if", params: { - additionalProperty: key0 + failingKeyword: ifClause0 } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.token) { - let _errs2 = 0; - if ("string" != typeof data.token) return validate433.errors = [ { - instancePath: instancePath + "/token", - schemaPath: "#/properties/token/type", + }; + return vErrors.push(err2), errors++, validate483.errors = vErrors, !1; + } + if (_errs1 === errors) { + let _errs15 = errors, _errs16 = errors, valid6 = !0, _errs17 = errors; + if (errors === _errs17) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 !== data.state && "ready" !== data.state) { + let err3 = {}; + vErrors.push(err3), errors++; + } + } else { + let err4 = {}; + vErrors.push(err4), errors++; + } + var _valid1 = _errs17 === errors; + let ifClause1; + if (errors = _errs16, vErrors.length = _errs16, _valid1) { + let _errs20 = errors; + if (errors === _errs20) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/then/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.snapshot_url) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/then/required", + keyword: "required", + params: { + missingProperty: "snapshot_url" + } + } ], !1; + if (void 0 !== data.snapshot_url && "string" != typeof data.snapshot_url) return validate483.errors = [ { + instancePath: instancePath + "/snapshot_url", + schemaPath: "#/allOf/1/then/properties/snapshot_url/type", keyword: "type", params: { type: "string" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.name) { - let _errs4 = 0; - if ("string" != typeof data.name) return validate433.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.video_source_token) { - let _errs6 = 0; - if ("string" != typeof data.video_source_token) return validate433.errors = [ { - instancePath: instancePath + "/video_source_token", - schemaPath: "#/properties/video_source_token/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.ptz_configuration_token) { - let _errs8 = 0; - if ("string" != typeof data.ptz_configuration_token) return validate433.errors = [ { - instancePath: instancePath + "/ptz_configuration_token", - schemaPath: "#/properties/ptz_configuration_token/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.snapshot_uri) { - let _errs10 = 0; - if ("string" != typeof data.snapshot_uri) return validate433.errors = [ { - instancePath: instancePath + "/snapshot_uri", - schemaPath: "#/properties/snapshot_uri/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.stream_uri) { - let _errs12 = 0; - if ("string" != typeof data.stream_uri) return validate433.errors = [ { - instancePath: instancePath + "/stream_uri", - schemaPath: "#/properties/stream_uri/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.encoding) { - let _errs14 = 0; - if ("string" != typeof data.encoding) return validate433.errors = [ { - instancePath: instancePath + "/encoding", - schemaPath: "#/properties/encoding/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.width) { - let data7 = data.width, _errs16 = 0; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate433.errors = [ { - instancePath: instancePath + "/width", - schemaPath: "#/properties/width/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.height) { - let data8 = data.height, _errs18 = 0; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate433.errors = [ { - instancePath: instancePath + "/height", - schemaPath: "#/properties/height/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.fps) { - let data9 = data.fps, _errs20 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate433.errors = [ { - instancePath: instancePath + "/fps", - schemaPath: "#/properties/fps/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.bitrate) { - let data10 = data.bitrate, _errs22 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate433.errors = [ { - instancePath: instancePath + "/bitrate", - schemaPath: "#/properties/bitrate/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid10 = 0 === _errs22; - } else valid10 = !0; - if (valid10 && void 0 !== data.ptz_supported && "boolean" != typeof data.ptz_supported) return validate433.errors = [ { - instancePath: instancePath + "/ptz_supported", - schemaPath: "#/properties/ptz_supported/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - } - } - } - } - } - } - } - } + } + if (valid6 = _valid1 = _errs20 === errors, valid6) var props1 = { + snapshot_url: !0, + state: !0 + }; + ifClause1 = "then"; + } else { + let _errs24 = errors; + if (!data || "object" != typeof data || Array.isArray(data)) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/else/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + let _errs26 = errors, _errs27 = errors; + if (errors === _errs27) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 === data.snapshot_url) { + let err5 = {}; + vErrors.push(err5), errors++; + } else if (void 0 !== data.snapshot_url && "string" != typeof data.snapshot_url) { + let err6 = {}; + vErrors.push(err6), errors++; } + } else { + let err7 = {}; + vErrors.push(err7), errors++; } + if (_errs27 === errors) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/else/not", + keyword: "not", + params: {} + } ], !1; + errors = _errs26, vErrors.length = _errs26, valid6 = _valid1 = _errs24 === errors, + ifClause1 = "else"; } - } - return validate433.errors = [], !0; -} - -validate433.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_OnvifProfilesResponse = validate434; - -function validate434(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate434.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate434.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.profiles) return validate434.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "profiles" + if (!valid6) { + let err8 = { + instancePath: instancePath, + schemaPath: "#/allOf/1/if", + keyword: "if", + params: { + failingKeyword: ifClause1 + } + }; + return vErrors.push(err8), errors++, validate483.errors = vErrors, !1; } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("profiles" !== key0) return validate434.errors = [ { + _errs15 === errors && !0 !== props0 && void 0 !== props1 && (!0 === props1 ? props0 = !0 : (props0 = props0 || {}, + Object.assign(props0, props1))); + } + if (0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate483.errors = [ { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/type", + keyword: "type", params: { - additionalProperty: key0 + type: "object" } } ], !1; - if (0 === _errs1 && void 0 !== data.profiles) { - let data0 = data.profiles; - if (!Array.isArray(data0)) return validate434.errors = [ { - instancePath: instancePath + "/profiles", - schemaPath: "#/properties/profiles/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - if (data0.length > 16) return validate434.errors = [ { - instancePath: instancePath + "/profiles", - schemaPath: "#/properties/profiles/maxItems", - keyword: "maxItems", - params: { - limit: 16 - } - } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data1.token) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "token" - } - } ], !1; - if (void 0 === data1.name) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data1.video_source_token) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "video_source_token" - } - } ], !1; - if (void 0 === data1.ptz_configuration_token) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "ptz_configuration_token" - } - } ], !1; - if (void 0 === data1.snapshot_uri) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "snapshot_uri" - } - } ], !1; - if (void 0 === data1.stream_uri) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "stream_uri" - } - } ], !1; - if (void 0 === data1.encoding) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "encoding" - } - } ], !1; - if (void 0 === data1.width) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "width" - } - } ], !1; - if (void 0 === data1.height) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "height" - } - } ], !1; - if (void 0 === data1.fps) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "fps" - } - } ], !1; - if (void 0 === data1.bitrate) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "bitrate" - } - } ], !1; - if (void 0 === data1.ptz_supported) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/required", - keyword: "required", - params: { - missingProperty: "ptz_supported" - } - } ], !1; - { - let _errs7 = 0; - for (let key1 in data1) if (!func5.call(schema523.properties, key1)) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0, - schemaPath: "#/$defs/OnvifProfile/additionalProperties", - keyword: "additionalProperties", + if (void 0 === data.asset_id) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "asset_id" + } + } ], !1; + if (void 0 === data.state) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "state" + } + } ], !1; + if (void 0 === data.reason) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "reason" + } + } ], !1; + if (void 0 === data.origin) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "origin" + } + } ], !1; + if (void 0 === data.media_type) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "media_type" + } + } ], !1; + if (void 0 === data.width) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "width" + } + } ], !1; + if (void 0 === data.height) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "height" + } + } ], !1; + if (void 0 === data.bytes) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "bytes" + } + } ], !1; + if (void 0 === data.captured_at) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "captured_at" + } + } ], !1; + if (void 0 === data.expires_at) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "expires_at" + } + } ], !1; + if (void 0 === data.transform) return validate483.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "transform" + } + } ], !1; + if (void 0 !== data.asset_id) { + let data6 = data.asset_id, _errs32 = errors; + if (errors == errors) { + if ("string" != typeof data6) return validate483.errors = [ { + instancePath: instancePath + "/asset_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data6)) return validate483.errors = [ { + instancePath: instancePath + "/asset_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + var valid11 = _errs32 === errors; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.state) { + let data7 = data.state, _errs35 = errors; + if ("string" != typeof data7) return validate483.errors = [ { + instancePath: instancePath + "/state", + schemaPath: "#/$defs/ObservationEvidenceState/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("pending" !== data7 && "ready" !== data7 && "unavailable" !== data7 && "expired" !== data7) return validate483.errors = [ { + instancePath: instancePath + "/state", + schemaPath: "#/$defs/ObservationEvidenceState/enum", + keyword: "enum", + params: { + allowedValues: schema574.enum + } + } ], !1; + var valid13 = _errs35 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.reason) { + let data8 = data.reason, _errs38 = errors, _errs39 = errors, valid16 = !1, _errs40 = errors; + if ("string" != typeof data8) { + let err9 = { + instancePath: instancePath + "/reason", + schemaPath: "#/$defs/ObservationEvidenceReason/type", + keyword: "type", params: { - additionalProperty: key1 + type: "string" + } + }; + vErrors.push(err9), errors++; + } + if ("queue_full" !== data8 && "budget_exhausted" !== data8 && "encode_failed" !== data8 && "write_failed" !== data8 && "interrupted" !== data8 && "no_matching_media" !== data8 && "disabled" !== data8 && "retention_expired" !== data8 && "deleted" !== data8) { + let err10 = { + instancePath: instancePath + "/reason", + schemaPath: "#/$defs/ObservationEvidenceReason/enum", + keyword: "enum", + params: { + allowedValues: schema575.enum + } + }; + vErrors.push(err10), errors++; + } + var _valid2 = _errs40 === errors; + valid16 = valid16 || _valid2; + let _errs43 = errors; + if (null !== data8) { + let err11 = { + instancePath: instancePath + "/reason", + schemaPath: "#/properties/reason/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err11), errors++; + } + if (_valid2 = _errs43 === errors, valid16 = valid16 || _valid2, !valid16) { + let err12 = { + instancePath: instancePath + "/reason", + schemaPath: "#/properties/reason/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err12), errors++, validate483.errors = vErrors, !1; + } + errors = _errs39, vErrors.length = _errs39; + var valid15 = _errs38 === errors; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data.origin) { + let data9 = data.origin, _errs45 = errors; + if ("string" != typeof data9 && null !== data9) return validate483.errors = [ { + instancePath: instancePath + "/origin", + schemaPath: "#/properties/origin/type", + keyword: "type", + params: { + type: schema582.properties.origin.type } } ], !1; - if (0 === _errs7) { - if (void 0 !== data1.token) { - let _errs8 = 0; - if ("string" != typeof data1.token) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/token", - schemaPath: "#/$defs/OnvifProfile/properties/token/type", + if ("inference_input" !== data9 && "submitted_image" !== data9 && null !== data9) return validate483.errors = [ { + instancePath: instancePath + "/origin", + schemaPath: "#/properties/origin/enum", + keyword: "enum", + params: { + allowedValues: schema582.properties.origin.enum + } + } ], !1; + var valid18 = _errs45 === errors; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data.media_type) { + let data10 = data.media_type, _errs47 = errors; + if ("string" != typeof data10 && null !== data10) return validate483.errors = [ { + instancePath: instancePath + "/media_type", + schemaPath: "#/properties/media_type/type", + keyword: "type", + params: { + type: schema582.properties.media_type.type + } + } ], !1; + if ("image/jpeg" !== data10 && null !== data10) return validate483.errors = [ { + instancePath: instancePath + "/media_type", + schemaPath: "#/properties/media_type/enum", + keyword: "enum", + params: { + allowedValues: schema582.properties.media_type.enum + } + } ], !1; + var valid19 = _errs47 === errors; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data.width) { + let data11 = data.width, _errs49 = errors; + if (("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) && null !== data11) return validate483.errors = [ { + instancePath: instancePath + "/width", + schemaPath: "#/properties/width/type", keyword: "type", params: { - type: "string" + type: schema582.properties.width.type } } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data1.name) { - let _errs10 = 0; - if ("string" != typeof data1.name) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/name", - schemaPath: "#/$defs/OnvifProfile/properties/name/type", + if (errors === _errs49 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate483.errors = [ { + instancePath: instancePath + "/width", + schemaPath: "#/properties/width/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid20 = _errs49 === errors; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data.height) { + let data12 = data.height, _errs51 = errors; + if (("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) && null !== data12) return validate483.errors = [ { + instancePath: instancePath + "/height", + schemaPath: "#/properties/height/type", keyword: "type", params: { - type: "string" + type: schema582.properties.height.type } } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data1.video_source_token) { - let _errs12 = 0; - if ("string" != typeof data1.video_source_token) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/video_source_token", - schemaPath: "#/$defs/OnvifProfile/properties/video_source_token/type", + if (errors === _errs51 && "number" == typeof data12 && isFinite(data12) && (data12 < 1 || isNaN(data12))) return validate483.errors = [ { + instancePath: instancePath + "/height", + schemaPath: "#/properties/height/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid21 = _errs51 === errors; + } else valid21 = !0; + if (valid21) { + if (void 0 !== data.bytes) { + let data13 = data.bytes, _errs53 = errors; + if (("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) && null !== data13) return validate483.errors = [ { + instancePath: instancePath + "/bytes", + schemaPath: "#/properties/bytes/type", keyword: "type", params: { - type: "string" + type: schema582.properties.bytes.type } } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data1.ptz_configuration_token) { - let _errs14 = 0; - if ("string" != typeof data1.ptz_configuration_token) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/ptz_configuration_token", - schemaPath: "#/$defs/OnvifProfile/properties/ptz_configuration_token/type", - keyword: "type", - params: { - type: "string" + if (errors === _errs53 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate483.errors = [ { + instancePath: instancePath + "/bytes", + schemaPath: "#/properties/bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid22 = _errs53 === errors; + } else valid22 = !0; + if (valid22) { + if (void 0 !== data.captured_at) { + let data14 = data.captured_at, _errs55 = errors, _errs56 = errors, valid24 = !1, _errs57 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data14) { + if (!formats2.validate(data14)) { + let err13 = { + instancePath: instancePath + "/captured_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err13), errors++; } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data1.snapshot_uri) { - let _errs16 = 0; - if ("string" != typeof data1.snapshot_uri) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/snapshot_uri", - schemaPath: "#/$defs/OnvifProfile/properties/snapshot_uri/type", + } else { + let err14 = { + instancePath: instancePath + "/captured_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { type: "string" } - } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data1.stream_uri) { - let _errs18 = 0; - if ("string" != typeof data1.stream_uri) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/stream_uri", - schemaPath: "#/$defs/OnvifProfile/properties/stream_uri/type", + }; + vErrors.push(err14), errors++; + } + var _valid3 = _errs57 === errors; + valid24 = valid24 || _valid3; + let _errs60 = errors; + if (null !== data14) { + let err15 = { + instancePath: instancePath + "/captured_at", + schemaPath: "#/properties/captured_at/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err15), errors++; + } + if (_valid3 = _errs60 === errors, valid24 = valid24 || _valid3, !valid24) { + let err16 = { + instancePath: instancePath + "/captured_at", + schemaPath: "#/properties/captured_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err16), errors++, validate483.errors = vErrors, !1; + } + errors = _errs56, vErrors.length = _errs56; + var valid23 = _errs55 === errors; + } else valid23 = !0; + if (valid23) { + if (void 0 !== data.expires_at) { + let data15 = data.expires_at, _errs62 = errors, _errs63 = errors, valid27 = !1, _errs64 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data15) { + if (!formats2.validate(data15)) { + let err17 = { + instancePath: instancePath + "/expires_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err17), errors++; + } + } else { + let err18 = { + instancePath: instancePath + "/expires_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { type: "string" } - } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data1.encoding) { - let _errs20 = 0; - if ("string" != typeof data1.encoding) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/encoding", - schemaPath: "#/$defs/OnvifProfile/properties/encoding/type", - keyword: "type", + }; + vErrors.push(err18), errors++; + } + var _valid4 = _errs64 === errors; + valid27 = valid27 || _valid4; + let _errs67 = errors; + if (null !== data15) { + let err19 = { + instancePath: instancePath + "/expires_at", + schemaPath: "#/properties/expires_at/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err19), errors++; + } + if (_valid4 = _errs67 === errors, valid27 = valid27 || _valid4, !valid27) { + let err20 = { + instancePath: instancePath + "/expires_at", + schemaPath: "#/properties/expires_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err20), errors++, validate483.errors = vErrors, !1; + } + errors = _errs63, vErrors.length = _errs63; + var valid26 = _errs62 === errors; + } else valid26 = !0; + if (valid26) { + if (void 0 !== data.transform) { + let data16 = data.transform, _errs69 = errors, _errs70 = errors, valid30 = !1, _errs71 = errors; + if (errors == errors) if (data16 && "object" == typeof data16 && !Array.isArray(data16)) if (void 0 === data16.mode) { + let err21 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", params: { - type: "string" + missingProperty: "mode" } - } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data1.width) { - let data9 = data1.width, _errs22 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/width", - schemaPath: "#/$defs/OnvifProfile/properties/width/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid10 = 0 === _errs22; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data1.height) { - let data10 = data1.height, _errs24 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/height", - schemaPath: "#/$defs/OnvifProfile/properties/height/type", + }; + vErrors.push(err21), errors++; + } else if (void 0 === data16.source_width) { + let err22 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "source_width" + } + }; + vErrors.push(err22), errors++; + } else if (void 0 === data16.source_height) { + let err23 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "source_height" + } + }; + vErrors.push(err23), errors++; + } else if (void 0 === data16.inference_width) { + let err24 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "inference_width" + } + }; + vErrors.push(err24), errors++; + } else if (void 0 === data16.inference_height) { + let err25 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "inference_height" + } + }; + vErrors.push(err25), errors++; + } else if (void 0 === data16.scale_x) { + let err26 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "scale_x" + } + }; + vErrors.push(err26), errors++; + } else if (void 0 === data16.scale_y) { + let err27 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "scale_y" + } + }; + vErrors.push(err27), errors++; + } else if (void 0 === data16.pad_x) { + let err28 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "pad_x" + } + }; + vErrors.push(err28), errors++; + } else if (void 0 === data16.pad_y) { + let err29 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/required", + keyword: "required", + params: { + missingProperty: "pad_y" + } + }; + vErrors.push(err29), errors++; + } else { + if (void 0 !== data16.mode) { + let data17 = data16.mode, _errs75 = errors; + if ("string" != typeof data17) { + let err30 = { + instancePath: instancePath + "/transform/mode", + schemaPath: "#/$defs/ObservationTransform/properties/mode/type", keyword: "type", params: { - type: "integer" + type: "string" } - } ], !1; - var valid11 = 0 === _errs24; - } else valid11 = !0; - if (valid11) { - if (void 0 !== data1.fps) { - let data11 = data1.fps, _errs26 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/fps", - schemaPath: "#/$defs/OnvifProfile/properties/fps/type", + }; + vErrors.push(err30), errors++; + } + if ("none" !== data17 && "resize" !== data17 && "letterbox" !== data17) { + let err31 = { + instancePath: instancePath + "/transform/mode", + schemaPath: "#/$defs/ObservationTransform/properties/mode/enum", + keyword: "enum", + params: { + allowedValues: schema577.properties.mode.enum + } + }; + vErrors.push(err31), errors++; + } + var valid32 = _errs75 === errors; + } else valid32 = !0; + if (valid32) { + if (void 0 !== data16.source_width) { + let data18 = data16.source_width, _errs77 = errors; + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) { + let err32 = { + instancePath: instancePath + "/transform/source_width", + schemaPath: "#/$defs/ObservationTransform/properties/source_width/type", keyword: "type", params: { type: "integer" } - } ], !1; - var valid12 = 0 === _errs26; - } else valid12 = !0; - if (valid12) { - if (void 0 !== data1.bitrate) { - let data12 = data1.bitrate, _errs28 = 0; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/bitrate", - schemaPath: "#/$defs/OnvifProfile/properties/bitrate/type", + }; + vErrors.push(err32), errors++; + } + if (errors === _errs77 && "number" == typeof data18 && isFinite(data18) && (data18 < 1 || isNaN(data18))) { + let err33 = { + instancePath: instancePath + "/transform/source_width", + schemaPath: "#/$defs/ObservationTransform/properties/source_width/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + }; + vErrors.push(err33), errors++; + } + var valid33 = _errs77 === errors; + } else valid33 = !0; + if (valid33) { + if (void 0 !== data16.source_height) { + let data19 = data16.source_height, _errs79 = errors; + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) { + let err34 = { + instancePath: instancePath + "/transform/source_height", + schemaPath: "#/$defs/ObservationTransform/properties/source_height/type", keyword: "type", params: { type: "integer" } - } ], !1; - var valid13 = 0 === _errs28; - } else valid13 = !0; - if (valid13 && void 0 !== data1.ptz_supported && "boolean" != typeof data1.ptz_supported) return validate434.errors = [ { - instancePath: instancePath + "/profiles/" + i0 + "/ptz_supported", - schemaPath: "#/$defs/OnvifProfile/properties/ptz_supported/type", - keyword: "type", - params: { - type: "boolean" + }; + vErrors.push(err34), errors++; + } + if (errors === _errs79 && "number" == typeof data19 && isFinite(data19) && (data19 < 1 || isNaN(data19))) { + let err35 = { + instancePath: instancePath + "/transform/source_height", + schemaPath: "#/$defs/ObservationTransform/properties/source_height/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + }; + vErrors.push(err35), errors++; + } + var valid34 = _errs79 === errors; + } else valid34 = !0; + if (valid34) { + if (void 0 !== data16.inference_width) { + let data20 = data16.inference_width, _errs81 = errors; + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) { + let err36 = { + instancePath: instancePath + "/transform/inference_width", + schemaPath: "#/$defs/ObservationTransform/properties/inference_width/type", + keyword: "type", + params: { + type: "integer" + } + }; + vErrors.push(err36), errors++; } - } ], !1; + if (errors === _errs81 && "number" == typeof data20 && isFinite(data20) && (data20 < 1 || isNaN(data20))) { + let err37 = { + instancePath: instancePath + "/transform/inference_width", + schemaPath: "#/$defs/ObservationTransform/properties/inference_width/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + }; + vErrors.push(err37), errors++; + } + var valid35 = _errs81 === errors; + } else valid35 = !0; + if (valid35) { + if (void 0 !== data16.inference_height) { + let data21 = data16.inference_height, _errs83 = errors; + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) { + let err38 = { + instancePath: instancePath + "/transform/inference_height", + schemaPath: "#/$defs/ObservationTransform/properties/inference_height/type", + keyword: "type", + params: { + type: "integer" + } + }; + vErrors.push(err38), errors++; + } + if (errors === _errs83 && "number" == typeof data21 && isFinite(data21) && (data21 < 1 || isNaN(data21))) { + let err39 = { + instancePath: instancePath + "/transform/inference_height", + schemaPath: "#/$defs/ObservationTransform/properties/inference_height/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + }; + vErrors.push(err39), errors++; + } + var valid36 = _errs83 === errors; + } else valid36 = !0; + if (valid36) { + if (void 0 !== data16.scale_x) { + let data22 = data16.scale_x, _errs85 = errors; + if (errors === _errs85) if ("number" == typeof data22 && isFinite(data22)) { + if (data22 <= 0 || isNaN(data22)) { + let err40 = { + instancePath: instancePath + "/transform/scale_x", + schemaPath: "#/$defs/ObservationTransform/properties/scale_x/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + }; + vErrors.push(err40), errors++; + } + } else { + let err41 = { + instancePath: instancePath + "/transform/scale_x", + schemaPath: "#/$defs/ObservationTransform/properties/scale_x/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err41), errors++; + } + var valid37 = _errs85 === errors; + } else valid37 = !0; + if (valid37) { + if (void 0 !== data16.scale_y) { + let data23 = data16.scale_y, _errs87 = errors; + if (errors === _errs87) if ("number" == typeof data23 && isFinite(data23)) { + if (data23 <= 0 || isNaN(data23)) { + let err42 = { + instancePath: instancePath + "/transform/scale_y", + schemaPath: "#/$defs/ObservationTransform/properties/scale_y/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + }; + vErrors.push(err42), errors++; + } + } else { + let err43 = { + instancePath: instancePath + "/transform/scale_y", + schemaPath: "#/$defs/ObservationTransform/properties/scale_y/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err43), errors++; + } + var valid38 = _errs87 === errors; + } else valid38 = !0; + if (valid38) { + if (void 0 !== data16.pad_x) { + let data24 = data16.pad_x, _errs89 = errors; + if (errors === _errs89) if ("number" == typeof data24 && isFinite(data24)) { + if (data24 < 0 || isNaN(data24)) { + let err44 = { + instancePath: instancePath + "/transform/pad_x", + schemaPath: "#/$defs/ObservationTransform/properties/pad_x/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + }; + vErrors.push(err44), errors++; + } + } else { + let err45 = { + instancePath: instancePath + "/transform/pad_x", + schemaPath: "#/$defs/ObservationTransform/properties/pad_x/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err45), errors++; + } + var valid39 = _errs89 === errors; + } else valid39 = !0; + if (valid39 && void 0 !== data16.pad_y) { + let data25 = data16.pad_y; + if (errors == errors) if ("number" == typeof data25 && isFinite(data25)) { + if (data25 < 0 || isNaN(data25)) { + let err46 = { + instancePath: instancePath + "/transform/pad_y", + schemaPath: "#/$defs/ObservationTransform/properties/pad_y/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + }; + vErrors.push(err46), errors++; + } + } else { + let err47 = { + instancePath: instancePath + "/transform/pad_y", + schemaPath: "#/$defs/ObservationTransform/properties/pad_y/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err47), errors++; + } + } + } + } + } + } } } } + } else { + let err48 = { + instancePath: instancePath + "/transform", + schemaPath: "#/$defs/ObservationTransform/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err48), errors++; + } + var _valid5 = _errs71 === errors; + valid30 = valid30 || _valid5; + let _errs93 = errors; + if (null !== data16) { + let err49 = { + instancePath: instancePath + "/transform", + schemaPath: "#/properties/transform/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err49), errors++; + } + if (_valid5 = _errs93 === errors, valid30 = valid30 || _valid5, !valid30) { + let err50 = { + instancePath: instancePath + "/transform", + schemaPath: "#/properties/transform/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err50), errors++, validate483.errors = vErrors, !1; + } + errors = _errs70, vErrors.length = _errs70; + var valid29 = _errs69 === errors; + } else valid29 = !0; + if (valid29 && void 0 !== data.snapshot_url) { + let data26 = data.snapshot_url; + if (errors == errors) { + if ("string" != typeof data26) return validate483.errors = [ { + instancePath: instancePath + "/snapshot_url", + schemaPath: "#/properties/snapshot_url/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern209.test(data26)) return validate483.errors = [ { + instancePath: instancePath + "/snapshot_url", + schemaPath: "#/properties/snapshot_url/pattern", + keyword: "pattern", + params: { + pattern: "^/api/observations/[0-9a-f-]{36}/snapshot$" + } + } ], !1; } } } @@ -74634,155 +75833,25 @@ function validate434(data, {instancePath: instancePath = "", parentData: parentD } } } - if (0 !== _errs4) break; - } - } - } - } - return validate434.errors = [], !0; -} - -validate434.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_OnvifTestRequest = validate435; - -function validate435(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate435.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate435.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.url) return validate435.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "url" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("url" !== key0 && "username" !== key0 && "password" !== key0 && "insecure" !== key0) return validate435.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.url) { - let data0 = data.url, _errs2 = 0; - if (0 === _errs2) { - if ("string" != typeof data0) return validate435.errors = [ { - instancePath: instancePath + "/url", - schemaPath: "#/properties/url/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data0) > 511) return validate435.errors = [ { - instancePath: instancePath + "/url", - schemaPath: "#/properties/url/maxLength", - keyword: "maxLength", - params: { - limit: 511 - } - } ], !1; - if (func1(data0) < 1) return validate435.errors = [ { - instancePath: instancePath + "/url", - schemaPath: "#/properties/url/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.username) { - let data1 = data.username, _errs4 = 0; - if (0 === _errs4) { - if ("string" != typeof data1) return validate435.errors = [ { - instancePath: instancePath + "/username", - schemaPath: "#/properties/username/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data1) > 63) return validate435.errors = [ { - instancePath: instancePath + "/username", - schemaPath: "#/properties/username/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - } - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.password) { - let data2 = data.password, _errs6 = 0; - if (0 === _errs6) { - if ("string" != typeof data2) return validate435.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data2) > 63) return validate435.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - } - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2 && void 0 !== data.insecure && "boolean" != typeof data.insecure) return validate435.errors = [ { - instancePath: instancePath + "/insecure", - schemaPath: "#/properties/insecure/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; } } } } - return validate435.errors = [], !0; + return validate483.errors = vErrors, 0 === errors; } -validate435.evaluated = { +validate483.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_OpenApiDocument = validate69; +var pattern206 = new RegExp("^[0-9]{1,64}$", "u"); -function validate69(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate69.evaluated; +function validate481(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate481.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate69.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate481.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -74790,1705 +75859,1173 @@ function validate69(data, {instancePath: instancePath = "", parentData: parentDa type: "object" } } ], !1; - if (void 0 === data.openapi) return validate69.errors = [ { + if (void 0 === data.id) return validate481.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "openapi" + missingProperty: "id" } } ], !1; - if (void 0 === data.info) return validate69.errors = [ { + if (void 0 === data.namespace) return validate481.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "info" + missingProperty: "namespace" } } ], !1; - if (void 0 === data.paths) return validate69.errors = [ { + if (void 0 === data.session) return validate481.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "paths" + missingProperty: "session" } } ], !1; - if (void 0 === data["x-lightnvr-coverage"]) return validate69.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "x-lightnvr-coverage" + if (void 0 !== data.id) { + let data0 = data.id, _errs2 = 0; + if (0 === _errs2) { + if ("string" != typeof data0) return validate481.errors = [ { + instancePath: instancePath + "/id", + schemaPath: "#/properties/id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern206.test(data0)) return validate481.errors = [ { + instancePath: instancePath + "/id", + schemaPath: "#/properties/id/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9]{1,64}$" + } + } ], !1; } - } ], !1; - if (void 0 !== data.openapi) { - let data0 = data.openapi, _errs2 = 0; - if ("string" != typeof data0) return validate69.errors = [ { - instancePath: instancePath + "/openapi", - schemaPath: "#/properties/openapi/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if ("3.1.1" !== data0) return validate69.errors = [ { - instancePath: instancePath + "/openapi", - schemaPath: "#/properties/openapi/const", - keyword: "const", - params: { - allowedValue: "3.1.1" - } - } ], !1; var valid0 = 0 === _errs2; } else valid0 = !0; if (valid0) { - if (void 0 !== data.info) { - let data1 = data.info, _errs4 = 0; + if (void 0 !== data.namespace) { + let data1 = data.namespace, _errs4 = 0; if (0 === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate69.errors = [ { - instancePath: instancePath + "/info", - schemaPath: "#/properties/info/type", + if ("string" != typeof data1) return validate481.errors = [ { + instancePath: instancePath + "/namespace", + schemaPath: "#/properties/namespace/type", keyword: "type", params: { - type: "object" + type: "string" } } ], !1; - if (void 0 === data1.title) return validate69.errors = [ { - instancePath: instancePath + "/info", - schemaPath: "#/properties/info/required", - keyword: "required", + if (func1(data1) > 64) return validate481.errors = [ { + instancePath: instancePath + "/namespace", + schemaPath: "#/properties/namespace/maxLength", + keyword: "maxLength", params: { - missingProperty: "title" + limit: 64 } } ], !1; - if (void 0 === data1.version) return validate69.errors = [ { - instancePath: instancePath + "/info", - schemaPath: "#/properties/info/required", - keyword: "required", + if (func1(data1) < 1) return validate481.errors = [ { + instancePath: instancePath + "/namespace", + schemaPath: "#/properties/namespace/minLength", + keyword: "minLength", params: { - missingProperty: "version" + limit: 1 } } ], !1; - if (void 0 !== data1.title) { - let _errs7 = 0; - if ("string" != typeof data1.title) return validate69.errors = [ { - instancePath: instancePath + "/info/title", - schemaPath: "#/properties/info/properties/title/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid2 = 0 === _errs7; - } else valid2 = !0; - if (valid2 && void 0 !== data1.version && "string" != typeof data1.version) return validate69.errors = [ { - instancePath: instancePath + "/info/version", - schemaPath: "#/properties/info/properties/version/type", + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1 && void 0 !== data.session) { + let data2 = data.session; + if ("string" != typeof data2) return validate481.errors = [ { + instancePath: instancePath + "/session", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data2)) return validate481.errors = [ { + instancePath: instancePath + "/session", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + } + return validate481.errors = [], !0; +} + +function validate485(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate485.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate485.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.automated) return validate485.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "automated" + } + } ], !1; + if (void 0 === data.owner_confirmed) return validate485.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "owner_confirmed" + } + } ], !1; + if (void 0 !== data.automated) { + let data0 = data.automated, _errs2 = errors; + if (errors === _errs2) { + if (!Array.isArray(data0)) return validate485.errors = [ { + instancePath: instancePath + "/automated", + schemaPath: "#/properties/automated/type", keyword: "type", params: { - type: "string" + type: "array" + } + } ], !1; + if (data0.length > 8) return validate485.errors = [ { + instancePath: instancePath + "/automated", + schemaPath: "#/properties/automated/maxItems", + keyword: "maxItems", + params: { + limit: 8 } } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs4 = errors; + if (validate369(data0[i0], { + instancePath: instancePath + "/automated/" + i0, + parentData: data0, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate369.errors), errors = vErrors.length), _errs4 !== errors) break; + } + } } - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.paths) { - let data4 = data.paths, _errs11 = 0; - if (0 === _errs11 && (!data4 || "object" != typeof data4 || Array.isArray(data4))) return validate69.errors = [ { - instancePath: instancePath + "/paths", - schemaPath: "#/properties/paths/type", + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0 && void 0 !== data.owner_confirmed) { + let data2 = data.owner_confirmed; + if (errors == errors) { + if (!Array.isArray(data2)) return validate485.errors = [ { + instancePath: instancePath + "/owner_confirmed", + schemaPath: "#/properties/owner_confirmed/type", keyword: "type", params: { - type: "object" + type: "array" } } ], !1; - var valid4 = 0 === _errs11; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.components) { - let data5 = data.components, _errs14 = 0; - if (0 === _errs14 && (!data5 || "object" != typeof data5 || Array.isArray(data5))) return validate69.errors = [ { - instancePath: instancePath + "/components", - schemaPath: "#/properties/components/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - var valid5 = 0 === _errs14; - } else valid5 = !0; - if (valid5 && void 0 !== data["x-lightnvr-coverage"]) { - let data6 = data["x-lightnvr-coverage"]; - if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage", - schemaPath: "#/$defs/OpenApiCoverage/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data6.partial) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage", - schemaPath: "#/$defs/OpenApiCoverage/required", - keyword: "required", - params: { - missingProperty: "partial" - } - } ], !1; - if (void 0 === data6.canonical_revision) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage", - schemaPath: "#/$defs/OpenApiCoverage/required", - keyword: "required", - params: { - missingProperty: "canonical_revision" - } - } ], !1; - if (void 0 === data6.covered_operations) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage", - schemaPath: "#/$defs/OpenApiCoverage/required", - keyword: "required", - params: { - missingProperty: "covered_operations" - } - } ], !1; - if (void 0 !== data6.partial) { - let data7 = data6.partial, _errs21 = 0; - if ("boolean" != typeof data7) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage/partial", - schemaPath: "#/$defs/OpenApiCoverage/properties/partial/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data7) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage/partial", - schemaPath: "#/$defs/OpenApiCoverage/properties/partial/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid8 = 0 === _errs21; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data6.canonical_revision) { - let _errs23 = 0; - if ("string" != typeof data6.canonical_revision) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage/canonical_revision", - schemaPath: "#/$defs/OpenApiCoverage/properties/canonical_revision/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid9 = 0 === _errs23; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data6.covered_operations) { - let data9 = data6.covered_operations, _errs25 = 0; - if (0 === _errs25) { - if (!Array.isArray(data9)) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage/covered_operations", - schemaPath: "#/$defs/OpenApiCoverage/properties/covered_operations/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - { - let len0 = data9.length; - for (let i0 = 0; i0 < len0; i0++) { - let _errs27 = 0; - if ("string" != typeof data9[i0]) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage/covered_operations/" + i0, - schemaPath: "#/$defs/OpenApiCoverage/properties/covered_operations/items/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (0 !== _errs27) break; - } - } - } - var valid10 = 0 === _errs25; - } else valid10 = !0; - if (valid10 && void 0 !== data6.description && "string" != typeof data6.description) return validate69.errors = [ { - instancePath: instancePath + "/x-lightnvr-coverage/description", - schemaPath: "#/$defs/OpenApiCoverage/properties/description/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - } + if (data2.length > 8) return validate485.errors = [ { + instancePath: instancePath + "/owner_confirmed", + schemaPath: "#/properties/owner_confirmed/maxItems", + keyword: "maxItems", + params: { + limit: 8 + } + } ], !1; + { + let len1 = data2.length; + for (let i1 = 0; i1 < len1; i1++) { + let _errs7 = errors; + if (validate369(data2[i1], { + instancePath: instancePath + "/owner_confirmed/" + i1, + parentData: data2, + parentDataProperty: i1, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate369.errors), errors = vErrors.length), _errs7 !== errors) break; } } } } } - return validate69.errors = [], !0; + return validate485.errors = vErrors, 0 === errors; } -validate69.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PasswordAdminReset = validate139; - -function validate139(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate139.evaluated; +function validate491(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate491.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate139.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.new_password) return validate139.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "new_password" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("old_password" !== key0 && "new_password" !== key0) return validate139.errors = [ { + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/type", + keyword: "type", params: { - additionalProperty: key0 + type: "object" } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.old_password) { - let _errs2 = 0; - if ("string" != typeof data.old_password) return validate139.errors = [ { - instancePath: instancePath + "/old_password", - schemaPath: "#/properties/old_password/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0 && void 0 !== data.new_password && "string" != typeof data.new_password) return validate139.errors = [ { - instancePath: instancePath + "/new_password", - schemaPath: "#/properties/new_password/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - } - } - return validate139.errors = [], !0; -} - -validate139.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PasswordChangeRequest = validate140; - -function validate140(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate140.evaluated; - evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); - let _errs0 = errors, valid0 = !1, _errs1 = errors; - if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.old_password) { - let err0 = { + if (void 0 === data.id) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/PasswordSelfChange/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "old_password" + missingProperty: "id" } - }; - vErrors.push(err0), errors++; - } else if (void 0 === data.new_password) { - let err1 = { + } ], !1; + if (void 0 === data.source) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/PasswordSelfChange/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "new_password" + missingProperty: "source" } - }; - vErrors.push(err1), errors++; - } else { - let _errs4 = errors; - for (let key0 in data) if ("old_password" !== key0 && "new_password" !== key0) { - let err2 = { - instancePath: instancePath, - schemaPath: "#/$defs/PasswordSelfChange/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - }; - vErrors.push(err2), errors++; - break; - } - if (_errs4 === errors) { - if (void 0 !== data.old_password) { - let _errs5 = errors; - if ("string" != typeof data.old_password) { - let err3 = { - instancePath: instancePath + "/old_password", - schemaPath: "#/$defs/PasswordSelfChange/properties/old_password/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err3), errors++; - } - var valid2 = _errs5 === errors; - } else valid2 = !0; - if (valid2 && void 0 !== data.new_password && "string" != typeof data.new_password) { - let err4 = { - instancePath: instancePath + "/new_password", - schemaPath: "#/$defs/PasswordSelfChange/properties/new_password/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err4), errors++; + } ], !1; + if (void 0 === data.camera_uuid) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "camera_uuid" } - } - } else { - let err5 = { + } ], !1; + if (void 0 === data.stream_name_at_capture) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/PasswordSelfChange/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "stream_name_at_capture" } - }; - vErrors.push(err5), errors++; - } - var _valid0 = _errs1 === errors; - if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; - let _errs9 = errors; - if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.new_password) { - let err6 = { + } ], !1; + if (void 0 === data.batch_id) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/PasswordAdminReset/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "new_password" + missingProperty: "batch_id" } - }; - vErrors.push(err6), errors++; - } else { - let _errs12 = errors; - for (let key1 in data) if ("old_password" !== key1 && "new_password" !== key1) { - let err7 = { - instancePath: instancePath, - schemaPath: "#/$defs/PasswordAdminReset/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key1 - } - }; - vErrors.push(err7), errors++; - break; - } - if (_errs12 === errors) { - if (void 0 !== data.old_password) { - let _errs13 = errors; - if ("string" != typeof data.old_password) { - let err8 = { - instancePath: instancePath + "/old_password", - schemaPath: "#/$defs/PasswordAdminReset/properties/old_password/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err8), errors++; - } - var valid5 = _errs13 === errors; - } else valid5 = !0; - if (valid5 && void 0 !== data.new_password && "string" != typeof data.new_password) { - let err9 = { - instancePath: instancePath + "/new_password", - schemaPath: "#/$defs/PasswordAdminReset/properties/new_password/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err9), errors++; + } ], !1; + if (void 0 === data.object_index) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "object_index" } - } - } else { - let err10 = { + } ], !1; + if (void 0 === data.observed_at) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/$defs/PasswordAdminReset/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "observed_at" } - }; - vErrors.push(err10), errors++; - } - if (_valid0 = _errs9 === errors, valid0 = valid0 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), - !valid0) { - let err11 = { + } ], !1; + if (void 0 === data.created_at) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/anyOf", - keyword: "anyOf", - params: {} - }; - return vErrors.push(err11), errors++, validate140.errors = vErrors, !1; - } - return errors = _errs0, vErrors.length = _errs0, validate140.errors = vErrors, evaluated0.props = props0, - 0 === errors; -} - -validate140.evaluated = { - dynamicProps: !0, - dynamicItems: !1 -}; - -var __contract_PasswordChangedResponse = validate141; - -function validate141(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate141.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate141.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate141.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.must_change_password) return validate141.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "must_change_password" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "must_change_password" !== key0) return validate141.errors = [ { + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "created_at" + } + } ], !1; + if (void 0 === data.detector) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/required", + keyword: "required", params: { - additionalProperty: key0 + missingProperty: "detector" } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate141.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0 && void 0 !== data.must_change_password && !1 !== data.must_change_password) return validate141.errors = [ { - instancePath: instancePath + "/must_change_password", - schemaPath: "#/properties/must_change_password/const", - keyword: "const", - params: { - allowedValue: !1 - } - } ], !1; - } - } - return validate141.errors = [], !0; -} - -validate141.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PasswordLockRequest = validate180; - -function validate180(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate180.evaluated; - return evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data) ? (validate180.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1) : void 0 === data.locked ? (validate180.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "locked" - } - } ], !1) : void 0 !== data.locked && "boolean" != typeof data.locked ? (validate180.errors = [ { - instancePath: instancePath + "/locked", - schemaPath: "#/properties/locked/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1) : (validate180.errors = [], !0); -} - -validate180.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PasswordSelfChange = validate138; - -function validate138(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate138.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate138.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.old_password) return validate138.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "old_password" - } - } ], !1; - if (void 0 === data.new_password) return validate138.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "new_password" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("old_password" !== key0 && "new_password" !== key0) return validate138.errors = [ { + if (void 0 === data.label) return validate491.errors = [ { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/required", + keyword: "required", params: { - additionalProperty: key0 + missingProperty: "label" } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.old_password) { - let _errs2 = 0; - if ("string" != typeof data.old_password) return validate138.errors = [ { - instancePath: instancePath + "/old_password", - schemaPath: "#/properties/old_password/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0 && void 0 !== data.new_password && "string" != typeof data.new_password) return validate138.errors = [ { - instancePath: instancePath + "/new_password", - schemaPath: "#/properties/new_password/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - } - } - return validate138.errors = [], !0; -} - -validate138.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PlayerTelemetry = validate284; - -function validate284(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate284.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate284.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.stream_name) return validate284.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "stream_name" - } - } ], !1; - if (void 0 !== data.stream_name) { - let _errs2 = 0; - if ("string" != typeof data.stream_name) return validate284.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/type", - keyword: "type", + if (void 0 === data.confidence) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", params: { - type: "string" + missingProperty: "confidence" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.session_id) { - let _errs4 = 0; - if ("string" != typeof data.session_id) return validate284.errors = [ { - instancePath: instancePath + "/session_id", - schemaPath: "#/properties/session_id/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.transport) { - let _errs6 = 0; - if ("string" != typeof data.transport) return validate284.errors = [ { - instancePath: instancePath + "/transport", - schemaPath: "#/properties/transport/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.ttff_ms) { - let data3 = data.ttff_ms, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate284.errors = [ { - instancePath: instancePath + "/ttff_ms", - schemaPath: "#/properties/ttff_ms/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.rebuffer_count) { - let data4 = data.rebuffer_count, _errs10 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate284.errors = [ { - instancePath: instancePath + "/rebuffer_count", - schemaPath: "#/properties/rebuffer_count/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.rebuffer_duration_ms) { - let data5 = data.rebuffer_duration_ms, _errs12 = 0; - if ("number" != typeof data5 || !isFinite(data5)) return validate284.errors = [ { - instancePath: instancePath + "/rebuffer_duration_ms", - schemaPath: "#/properties/rebuffer_duration_ms/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.resolution_switches) { - let data6 = data.resolution_switches, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate284.errors = [ { - instancePath: instancePath + "/resolution_switches", - schemaPath: "#/properties/resolution_switches/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6 && void 0 !== data.webrtc_rtt_ms) { - let data7 = data.webrtc_rtt_ms; - if ("number" != typeof data7 || !isFinite(data7)) return validate284.errors = [ { - instancePath: instancePath + "/webrtc_rtt_ms", - schemaPath: "#/properties/webrtc_rtt_ms/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - } - } - } - } + if (void 0 === data.box) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "box" } - } - } - return validate284.errors = [], !0; -} - -validate284.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_ProcessLiveness = validate280; - -function validate280(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate280.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.healthy) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "healthy" - } - } ], !1; - if (void 0 === data.uptime) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "uptime" - } - } ], !1; - if (void 0 === data.totalRequests) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "totalRequests" - } - } ], !1; - if (void 0 === data.failedRequests) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "failedRequests" - } - } ], !1; - if (void 0 === data.timestamp) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "timestamp" - } - } ], !1; - if (void 0 === data.liveness) return validate280.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "liveness" - } - } ], !1; - if (void 0 !== data.healthy) { - let _errs2 = 0; - if ("boolean" != typeof data.healthy) return validate280.errors = [ { - instancePath: instancePath + "/healthy", - schemaPath: "#/properties/healthy/type", - keyword: "type", + } ], !1; + if (void 0 === data.evidence) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", params: { - type: "boolean" + missingProperty: "evidence" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.uptime) { - let data1 = data.uptime, _errs4 = 0; - if ("number" != typeof data1 || !isFinite(data1)) return validate280.errors = [ { - instancePath: instancePath + "/uptime", - schemaPath: "#/properties/uptime/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.totalRequests) { - let data2 = data.totalRequests, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate280.errors = [ { - instancePath: instancePath + "/totalRequests", - schemaPath: "#/properties/totalRequests/type", + if (void 0 === data.track) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "track" + } + } ], !1; + if (void 0 === data.recording_reference) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "recording_reference" + } + } ], !1; + if (void 0 === data.revision) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "revision" + } + } ], !1; + if (void 0 === data.classifications) return validate491.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "classifications" + } + } ], !1; + if (void 0 !== data.id) { + let data0 = data.id, _errs2 = errors; + if (errors == errors) { + if ("string" != typeof data0) return validate491.errors = [ { + instancePath: instancePath + "/id", + schemaPath: "#/$defs/ObservationUuid/type", keyword: "type", params: { - type: "integer" + type: "string" } } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.failedRequests) { - let data3 = data.failedRequests, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate280.errors = [ { - instancePath: instancePath + "/failedRequests", - schemaPath: "#/properties/failedRequests/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.timestamp) { - let _errs10 = 0; - if ("string" != typeof data.timestamp) return validate280.errors = [ { - instancePath: instancePath + "/timestamp", - schemaPath: "#/properties/timestamp/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4 && void 0 !== data.liveness) { - let data5 = data.liveness; - if ("boolean" != typeof data5) return validate280.errors = [ { - instancePath: instancePath + "/liveness", - schemaPath: "#/properties/liveness/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data5) return validate280.errors = [ { - instancePath: instancePath + "/liveness", - schemaPath: "#/properties/liveness/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; + if (!pattern159.test(data0)) return validate491.errors = [ { + instancePath: instancePath + "/id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" } - } - } - } - } - return validate280.errors = [], !0; -} - -validate280.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PtzAxesRequest = validate406; - -function validate406(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate406.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate406.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (Object.keys(data).length < 1) return validate406.errors = [ { - instancePath: instancePath, - schemaPath: "#/minProperties", - keyword: "minProperties", - params: { - limit: 1 - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("pan" !== key0 && "tilt" !== key0 && "zoom" !== key0) return validate406.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 + } ], !1; } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.pan) { - let data0 = data.pan, _errs2 = 0; - if (0 === _errs2) { - if ("number" != typeof data0 || !isFinite(data0)) return validate406.errors = [ { - instancePath: instancePath + "/pan", - schemaPath: "#/properties/pan/type", + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.source) { + let data1 = data.source, _errs5 = errors; + if (errors === _errs5) { + if ("string" != typeof data1) return validate491.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/type", keyword: "type", params: { - type: "number" - } - } ], !1; - if (data0 > 34028234663852886e22 || isNaN(data0)) return validate406.errors = [ { - instancePath: instancePath + "/pan", - schemaPath: "#/properties/pan/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 34028234663852886e22 + type: "string" } } ], !1; - if (data0 < -34028234663852886e22 || isNaN(data0)) return validate406.errors = [ { - instancePath: instancePath + "/pan", - schemaPath: "#/properties/pan/minimum", - keyword: "minimum", + if (!pattern215.test(data1)) return validate491.errors = [ { + instancePath: instancePath + "/source", + schemaPath: "#/properties/source/pattern", + keyword: "pattern", params: { - comparison: ">=", - limit: -34028234663852886e22 + pattern: "^urn:lightnvr:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" } } ], !1; } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.tilt) { - let data1 = data.tilt, _errs4 = 0; - if (0 === _errs4) { - if ("number" != typeof data1 || !isFinite(data1)) return validate406.errors = [ { - instancePath: instancePath + "/tilt", - schemaPath: "#/properties/tilt/type", + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.camera_uuid) { + let data2 = data.camera_uuid, _errs7 = errors; + if (errors == errors) { + if ("string" != typeof data2) return validate491.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/$defs/ObservationUuid/type", keyword: "type", params: { - type: "number" - } - } ], !1; - if (data1 > 34028234663852886e22 || isNaN(data1)) return validate406.errors = [ { - instancePath: instancePath + "/tilt", - schemaPath: "#/properties/tilt/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 34028234663852886e22 + type: "string" } } ], !1; - if (data1 < -34028234663852886e22 || isNaN(data1)) return validate406.errors = [ { - instancePath: instancePath + "/tilt", - schemaPath: "#/properties/tilt/minimum", - keyword: "minimum", + if (!pattern159.test(data2)) return validate491.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", params: { - comparison: ">=", - limit: -34028234663852886e22 + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" } } ], !1; } - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1 && void 0 !== data.zoom) { - let data2 = data.zoom; - if ("number" != typeof data2 || !isFinite(data2)) return validate406.errors = [ { - instancePath: instancePath + "/zoom", - schemaPath: "#/properties/zoom/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - if (data2 > 34028234663852886e22 || isNaN(data2)) return validate406.errors = [ { - instancePath: instancePath + "/zoom", - schemaPath: "#/properties/zoom/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 34028234663852886e22 - } - } ], !1; - if (data2 < -34028234663852886e22 || isNaN(data2)) return validate406.errors = [ { - instancePath: instancePath + "/zoom", - schemaPath: "#/properties/zoom/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: -34028234663852886e22 + var valid3 = _errs7 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.stream_name_at_capture) { + let data3 = data.stream_name_at_capture, _errs10 = errors; + if (errors === _errs10) { + if ("string" != typeof data3) return validate491.errors = [ { + instancePath: instancePath + "/stream_name_at_capture", + schemaPath: "#/properties/stream_name_at_capture/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data3) > 63) return validate491.errors = [ { + instancePath: instancePath + "/stream_name_at_capture", + schemaPath: "#/properties/stream_name_at_capture/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; } - } ], !1; - } - } - } - } - return validate406.errors = [], !0; -} - -validate406.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PtzCapabilities = validate411, schema477 = { - type: "object", - properties: { - ptz_enabled: { - type: "boolean" - }, - onvif_queried: { - type: "boolean" - }, - has_pan_tilt: { - type: "boolean" - }, - has_zoom: { - type: "boolean" - }, - has_continuous_move: { - type: "boolean" - }, - has_absolute_move: { - type: "boolean" - }, - has_relative_move: { - type: "boolean" - }, - has_home_position: { - type: "boolean" - }, - has_presets: { - type: "boolean" - }, - has_home: { - type: "boolean" - }, - pan_range: { - $ref: "#/$defs/DeviceFloatRange" - }, - tilt_range: { - $ref: "#/$defs/DeviceFloatRange" - }, - zoom_range: { - $ref: "#/$defs/DeviceFloatRange" - }, - max_x: { - type: "number" - }, - max_y: { - type: "number" - }, - max_z: { - type: "number" - }, - max_presets: { - type: "integer" - }, - ptz_service_url: { - type: "string" - } - }, - required: [ "ptz_enabled", "onvif_queried", "has_pan_tilt", "has_zoom", "has_continuous_move", "has_absolute_move", "has_relative_move", "has_home_position", "has_presets", "has_home", "pan_range", "tilt_range", "zoom_range", "max_x", "max_y", "max_z", "max_presets", "ptz_service_url" ], - additionalProperties: !1 -}; - -function validate411(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate411.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.ptz_enabled) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "ptz_enabled" - } - } ], !1; - if (void 0 === data.onvif_queried) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "onvif_queried" - } - } ], !1; - if (void 0 === data.has_pan_tilt) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_pan_tilt" - } - } ], !1; - if (void 0 === data.has_zoom) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_zoom" - } - } ], !1; - if (void 0 === data.has_continuous_move) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_continuous_move" - } - } ], !1; - if (void 0 === data.has_absolute_move) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_absolute_move" - } - } ], !1; - if (void 0 === data.has_relative_move) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_relative_move" - } - } ], !1; - if (void 0 === data.has_home_position) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_home_position" - } - } ], !1; - if (void 0 === data.has_presets) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_presets" - } - } ], !1; - if (void 0 === data.has_home) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "has_home" - } - } ], !1; - if (void 0 === data.pan_range) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "pan_range" - } - } ], !1; - if (void 0 === data.tilt_range) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "tilt_range" - } - } ], !1; - if (void 0 === data.zoom_range) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "zoom_range" - } - } ], !1; - if (void 0 === data.max_x) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "max_x" - } - } ], !1; - if (void 0 === data.max_y) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "max_y" - } - } ], !1; - if (void 0 === data.max_z) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "max_z" - } - } ], !1; - if (void 0 === data.max_presets) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "max_presets" - } - } ], !1; - if (void 0 === data.ptz_service_url) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "ptz_service_url" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema477.properties, key0)) return validate411.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.ptz_enabled) { - let _errs2 = 0; - if ("boolean" != typeof data.ptz_enabled) return validate411.errors = [ { - instancePath: instancePath + "/ptz_enabled", - schemaPath: "#/properties/ptz_enabled/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.onvif_queried) { - let _errs4 = 0; - if ("boolean" != typeof data.onvif_queried) return validate411.errors = [ { - instancePath: instancePath + "/onvif_queried", - schemaPath: "#/properties/onvif_queried/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.has_pan_tilt) { - let _errs6 = 0; - if ("boolean" != typeof data.has_pan_tilt) return validate411.errors = [ { - instancePath: instancePath + "/has_pan_tilt", - schemaPath: "#/properties/has_pan_tilt/type", - keyword: "type", - params: { - type: "boolean" + var valid5 = _errs10 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.batch_id) { + let data4 = data.batch_id, _errs12 = errors; + if (errors == errors) { + if ("string" != typeof data4) return validate491.errors = [ { + instancePath: instancePath + "/batch_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data4)) return validate491.errors = [ { + instancePath: instancePath + "/batch_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.has_zoom) { - let _errs8 = 0; - if ("boolean" != typeof data.has_zoom) return validate411.errors = [ { - instancePath: instancePath + "/has_zoom", - schemaPath: "#/properties/has_zoom/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.has_continuous_move) { - let _errs10 = 0; - if ("boolean" != typeof data.has_continuous_move) return validate411.errors = [ { - instancePath: instancePath + "/has_continuous_move", - schemaPath: "#/properties/has_continuous_move/type", + var valid6 = _errs12 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.object_index) { + let data5 = data.object_index, _errs15 = errors; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate491.errors = [ { + instancePath: instancePath + "/object_index", + schemaPath: "#/properties/object_index/type", keyword: "type", params: { - type: "boolean" + type: "integer" } } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.has_absolute_move) { - let _errs12 = 0; - if ("boolean" != typeof data.has_absolute_move) return validate411.errors = [ { - instancePath: instancePath + "/has_absolute_move", - schemaPath: "#/properties/has_absolute_move/type", - keyword: "type", + if (errors === _errs15 && "number" == typeof data5 && isFinite(data5)) { + if (data5 > 19 || isNaN(data5)) return validate491.errors = [ { + instancePath: instancePath + "/object_index", + schemaPath: "#/properties/object_index/maximum", + keyword: "maximum", params: { - type: "boolean" + comparison: "<=", + limit: 19 } } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.has_relative_move) { - let _errs14 = 0; - if ("boolean" != typeof data.has_relative_move) return validate411.errors = [ { - instancePath: instancePath + "/has_relative_move", - schemaPath: "#/properties/has_relative_move/type", + if (data5 < 0 || isNaN(data5)) return validate491.errors = [ { + instancePath: instancePath + "/object_index", + schemaPath: "#/properties/object_index/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid8 = _errs15 === errors; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.observed_at) { + let data6 = data.observed_at, _errs17 = errors; + if (errors == errors && errors == errors) { + if ("string" != typeof data6) return validate491.errors = [ { + instancePath: instancePath + "/observed_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { - type: "boolean" + type: "string" } } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.has_home_position) { - let _errs16 = 0; - if ("boolean" != typeof data.has_home_position) return validate411.errors = [ { - instancePath: instancePath + "/has_home_position", - schemaPath: "#/properties/has_home_position/type", + if (!formats2.validate(data6)) return validate491.errors = [ { + instancePath: instancePath + "/observed_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; + } + var valid9 = _errs17 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.created_at) { + let data7 = data.created_at, _errs20 = errors; + if (errors == errors && errors == errors) { + if ("string" != typeof data7) return validate491.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { - type: "boolean" + type: "string" } } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.has_presets) { - let _errs18 = 0; - if ("boolean" != typeof data.has_presets) return validate411.errors = [ { - instancePath: instancePath + "/has_presets", - schemaPath: "#/properties/has_presets/type", + if (!formats2.validate(data7)) return validate491.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; + } + var valid11 = _errs20 === errors; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.detector) { + let data8 = data.detector, _errs23 = errors; + if (errors == errors) { + if (!data8 || "object" != typeof data8 || Array.isArray(data8)) return validate491.errors = [ { + instancePath: instancePath + "/detector", + schemaPath: "#/$defs/ObservationDetector/type", keyword: "type", params: { - type: "boolean" + type: "object" } } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.has_home) { - let _errs20 = 0; - if ("boolean" != typeof data.has_home) return validate411.errors = [ { - instancePath: instancePath + "/has_home", - schemaPath: "#/properties/has_home/type", + if (void 0 === data8.engine_key) return validate491.errors = [ { + instancePath: instancePath + "/detector", + schemaPath: "#/$defs/ObservationDetector/required", + keyword: "required", + params: { + missingProperty: "engine_key" + } + } ], !1; + if (void 0 === data8.engine_type) return validate491.errors = [ { + instancePath: instancePath + "/detector", + schemaPath: "#/$defs/ObservationDetector/required", + keyword: "required", + params: { + missingProperty: "engine_type" + } + } ], !1; + if (void 0 === data8.model) return validate491.errors = [ { + instancePath: instancePath + "/detector", + schemaPath: "#/$defs/ObservationDetector/required", + keyword: "required", + params: { + missingProperty: "model" + } + } ], !1; + if (void 0 === data8.model_version) return validate491.errors = [ { + instancePath: instancePath + "/detector", + schemaPath: "#/$defs/ObservationDetector/required", + keyword: "required", + params: { + missingProperty: "model_version" + } + } ], !1; + if (void 0 !== data8.engine_key) { + let data9 = data8.engine_key, _errs27 = errors; + if (errors === _errs27) { + if ("string" != typeof data9) return validate491.errors = [ { + instancePath: instancePath + "/detector/engine_key", + schemaPath: "#/$defs/ObservationDetector/properties/engine_key/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data9) > 64) return validate491.errors = [ { + instancePath: instancePath + "/detector/engine_key", + schemaPath: "#/$defs/ObservationDetector/properties/engine_key/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + if (func1(data9) < 1) return validate491.errors = [ { + instancePath: instancePath + "/detector/engine_key", + schemaPath: "#/$defs/ObservationDetector/properties/engine_key/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid15 = _errs27 === errors; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data8.engine_type) { + let data10 = data8.engine_type, _errs29 = errors; + if ("string" != typeof data10) return validate491.errors = [ { + instancePath: instancePath + "/detector/engine_type", + schemaPath: "#/$defs/ObservationDetector/properties/engine_type/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("object" !== data10 && "api" !== data10 && "onvif" !== data10 && "external" !== data10) return validate491.errors = [ { + instancePath: instancePath + "/detector/engine_type", + schemaPath: "#/$defs/ObservationDetector/properties/engine_type/enum", + keyword: "enum", + params: { + allowedValues: schema578.properties.engine_type.enum + } + } ], !1; + var valid16 = _errs29 === errors; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data8.model) { + let data11 = data8.model, _errs31 = errors; + if ("string" != typeof data11 && null !== data11) return validate491.errors = [ { + instancePath: instancePath + "/detector/model", + schemaPath: "#/$defs/ObservationDetector/properties/model/type", + keyword: "type", + params: { + type: schema578.properties.model.type + } + } ], !1; + if (errors === _errs31 && "string" == typeof data11 && func1(data11) > 128) return validate491.errors = [ { + instancePath: instancePath + "/detector/model", + schemaPath: "#/$defs/ObservationDetector/properties/model/maxLength", + keyword: "maxLength", + params: { + limit: 128 + } + } ], !1; + var valid17 = _errs31 === errors; + } else valid17 = !0; + if (valid17 && void 0 !== data8.model_version) { + let data12 = data8.model_version, _errs33 = errors; + if ("string" != typeof data12 && null !== data12) return validate491.errors = [ { + instancePath: instancePath + "/detector/model_version", + schemaPath: "#/$defs/ObservationDetector/properties/model_version/type", + keyword: "type", + params: { + type: schema578.properties.model_version.type + } + } ], !1; + if (errors === _errs33 && "string" == typeof data12 && func1(data12) > 64) return validate491.errors = [ { + instancePath: instancePath + "/detector/model_version", + schemaPath: "#/$defs/ObservationDetector/properties/model_version/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + } + } + } + } + var valid13 = _errs23 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.label) { + let data13 = data.label, _errs35 = errors; + if (errors === _errs35) { + if ("string" != typeof data13) return validate491.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", keyword: "type", params: { - type: "boolean" + type: "string" } } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data.pan_range) { - let data10 = data.pan_range, _errs22 = 0; - if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate411.errors = [ { - instancePath: instancePath + "/pan_range", - schemaPath: "#/$defs/DeviceFloatRange/type", + if (func1(data13) > 31) return validate491.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/maxLength", + keyword: "maxLength", + params: { + limit: 31 + } + } ], !1; + if (func1(data13) < 1) return validate491.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid19 = _errs35 === errors; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data.confidence) { + let data14 = data.confidence, _errs37 = errors; + if (errors === _errs37) { + if ("number" != typeof data14 || !isFinite(data14)) return validate491.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/type", keyword: "type", params: { - type: "object" + type: "number" } } ], !1; - if (void 0 === data10.min) return validate411.errors = [ { - instancePath: instancePath + "/pan_range", - schemaPath: "#/$defs/DeviceFloatRange/required", - keyword: "required", + if (data14 > 1 || isNaN(data14)) return validate491.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/maximum", + keyword: "maximum", params: { - missingProperty: "min" + comparison: "<=", + limit: 1 } } ], !1; - if (void 0 === data10.max) return validate411.errors = [ { - instancePath: instancePath + "/pan_range", - schemaPath: "#/$defs/DeviceFloatRange/required", - keyword: "required", + if (data14 < 0 || isNaN(data14)) return validate491.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/minimum", + keyword: "minimum", params: { - missingProperty: "max" + comparison: ">=", + limit: 0 } } ], !1; - { - let _errs25 = 0; - for (let key1 in data10) if ("min" !== key1 && "max" !== key1) return validate411.errors = [ { - instancePath: instancePath + "/pan_range", - schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", - keyword: "additionalProperties", + } + var valid20 = _errs37 === errors; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data.box) { + let data15 = data.box, _errs39 = errors, _errs40 = errors, valid22 = !1, _errs41 = errors; + if (errors == errors) if (data15 && "object" == typeof data15 && !Array.isArray(data15)) if (void 0 === data15.format) { + let err0 = { + instancePath: instancePath + "/box", + schemaPath: "#/$defs/ObservationBox/required", + keyword: "required", params: { - additionalProperty: key1 + missingProperty: "format" } - } ], !1; - if (0 === _errs25) { - if (void 0 !== data10.min) { - let data11 = data10.min, _errs26 = 0; - if ("number" != typeof data11 || !isFinite(data11)) return validate411.errors = [ { - instancePath: instancePath + "/pan_range/min", - schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid12 = 0 === _errs26; - } else valid12 = !0; - if (valid12 && void 0 !== data10.max) { - let data12 = data10.max; - if ("number" != typeof data12 || !isFinite(data12)) return validate411.errors = [ { - instancePath: instancePath + "/pan_range/max", - schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; + }; + vErrors.push(err0), errors++; + } else if (void 0 === data15.x) { + let err1 = { + instancePath: instancePath + "/box", + schemaPath: "#/$defs/ObservationBox/required", + keyword: "required", + params: { + missingProperty: "x" } - } - } - var valid10 = 0 === _errs22; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data.tilt_range) { - let data13 = data.tilt_range, _errs30 = 0; - if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate411.errors = [ { - instancePath: instancePath + "/tilt_range", - schemaPath: "#/$defs/DeviceFloatRange/type", - keyword: "type", + }; + vErrors.push(err1), errors++; + } else if (void 0 === data15.y) { + let err2 = { + instancePath: instancePath + "/box", + schemaPath: "#/$defs/ObservationBox/required", + keyword: "required", params: { - type: "object" + missingProperty: "y" } - } ], !1; - if (void 0 === data13.min) return validate411.errors = [ { - instancePath: instancePath + "/tilt_range", - schemaPath: "#/$defs/DeviceFloatRange/required", + }; + vErrors.push(err2), errors++; + } else if (void 0 === data15.width) { + let err3 = { + instancePath: instancePath + "/box", + schemaPath: "#/$defs/ObservationBox/required", keyword: "required", params: { - missingProperty: "min" + missingProperty: "width" } - } ], !1; - if (void 0 === data13.max) return validate411.errors = [ { - instancePath: instancePath + "/tilt_range", - schemaPath: "#/$defs/DeviceFloatRange/required", + }; + vErrors.push(err3), errors++; + } else if (void 0 === data15.height) { + let err4 = { + instancePath: instancePath + "/box", + schemaPath: "#/$defs/ObservationBox/required", keyword: "required", params: { - missingProperty: "max" + missingProperty: "height" } - } ], !1; - { - let _errs33 = 0; - for (let key2 in data13) if ("min" !== key2 && "max" !== key2) return validate411.errors = [ { - instancePath: instancePath + "/tilt_range", - schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key2 - } - } ], !1; - if (0 === _errs33) { - if (void 0 !== data13.min) { - let data14 = data13.min, _errs34 = 0; - if ("number" != typeof data14 || !isFinite(data14)) return validate411.errors = [ { - instancePath: instancePath + "/tilt_range/min", - schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", + }; + vErrors.push(err4), errors++; + } else { + if (void 0 !== data15.format) { + let _errs45 = errors; + if ("xywh_normalized" !== data15.format) { + let err5 = { + instancePath: instancePath + "/box/format", + schemaPath: "#/$defs/ObservationBox/properties/format/const", + keyword: "const", + params: { + allowedValue: "xywh_normalized" + } + }; + vErrors.push(err5), errors++; + } + var valid24 = _errs45 === errors; + } else valid24 = !0; + if (valid24) { + if (void 0 !== data15.x) { + let data17 = data15.x, _errs46 = errors; + if (errors === _errs46) if ("number" == typeof data17 && isFinite(data17)) { + if (data17 > 1 || isNaN(data17)) { + let err6 = { + instancePath: instancePath + "/box/x", + schemaPath: "#/$defs/ObservationBox/properties/x/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + }; + vErrors.push(err6), errors++; + } else if (data17 < 0 || isNaN(data17)) { + let err7 = { + instancePath: instancePath + "/box/x", + schemaPath: "#/$defs/ObservationBox/properties/x/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + }; + vErrors.push(err7), errors++; + } + } else { + let err8 = { + instancePath: instancePath + "/box/x", + schemaPath: "#/$defs/ObservationBox/properties/x/type", keyword: "type", params: { type: "number" } - } ], !1; - var valid16 = 0 === _errs34; - } else valid16 = !0; - if (valid16 && void 0 !== data13.max) { - let data15 = data13.max; - if ("number" != typeof data15 || !isFinite(data15)) return validate411.errors = [ { - instancePath: instancePath + "/tilt_range/max", - schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - } - } - } - var valid14 = 0 === _errs30; - } else valid14 = !0; - if (valid14) { - if (void 0 !== data.zoom_range) { - let data16 = data.zoom_range, _errs38 = 0; - if (!data16 || "object" != typeof data16 || Array.isArray(data16)) return validate411.errors = [ { - instancePath: instancePath + "/zoom_range", - schemaPath: "#/$defs/DeviceFloatRange/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data16.min) return validate411.errors = [ { - instancePath: instancePath + "/zoom_range", - schemaPath: "#/$defs/DeviceFloatRange/required", - keyword: "required", - params: { - missingProperty: "min" - } - } ], !1; - if (void 0 === data16.max) return validate411.errors = [ { - instancePath: instancePath + "/zoom_range", - schemaPath: "#/$defs/DeviceFloatRange/required", - keyword: "required", - params: { - missingProperty: "max" + }; + vErrors.push(err8), errors++; } - } ], !1; - { - let _errs41 = 0; - for (let key3 in data16) if ("min" !== key3 && "max" !== key3) return validate411.errors = [ { - instancePath: instancePath + "/zoom_range", - schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key3 - } - } ], !1; - if (0 === _errs41) { - if (void 0 !== data16.min) { - let data17 = data16.min, _errs42 = 0; - if ("number" != typeof data17 || !isFinite(data17)) return validate411.errors = [ { - instancePath: instancePath + "/zoom_range/min", - schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", + var valid25 = _errs46 === errors; + } else valid25 = !0; + if (valid25) { + if (void 0 !== data15.y) { + let data18 = data15.y, _errs48 = errors; + if (errors === _errs48) if ("number" == typeof data18 && isFinite(data18)) { + if (data18 > 1 || isNaN(data18)) { + let err9 = { + instancePath: instancePath + "/box/y", + schemaPath: "#/$defs/ObservationBox/properties/y/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + }; + vErrors.push(err9), errors++; + } else if (data18 < 0 || isNaN(data18)) { + let err10 = { + instancePath: instancePath + "/box/y", + schemaPath: "#/$defs/ObservationBox/properties/y/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + }; + vErrors.push(err10), errors++; + } + } else { + let err11 = { + instancePath: instancePath + "/box/y", + schemaPath: "#/$defs/ObservationBox/properties/y/type", keyword: "type", params: { type: "number" } - } ], !1; - var valid20 = 0 === _errs42; - } else valid20 = !0; - if (valid20 && void 0 !== data16.max) { - let data18 = data16.max; - if ("number" != typeof data18 || !isFinite(data18)) return validate411.errors = [ { - instancePath: instancePath + "/zoom_range/max", - schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", - keyword: "type", - params: { - type: "number" + }; + vErrors.push(err11), errors++; + } + var valid26 = _errs48 === errors; + } else valid26 = !0; + if (valid26) { + if (void 0 !== data15.width) { + let data19 = data15.width, _errs50 = errors; + if (errors === _errs50) if ("number" == typeof data19 && isFinite(data19)) { + if (data19 > 1 || isNaN(data19)) { + let err12 = { + instancePath: instancePath + "/box/width", + schemaPath: "#/$defs/ObservationBox/properties/width/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + }; + vErrors.push(err12), errors++; + } else if (data19 <= 0 || isNaN(data19)) { + let err13 = { + instancePath: instancePath + "/box/width", + schemaPath: "#/$defs/ObservationBox/properties/width/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + }; + vErrors.push(err13), errors++; } - } ], !1; + } else { + let err14 = { + instancePath: instancePath + "/box/width", + schemaPath: "#/$defs/ObservationBox/properties/width/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err14), errors++; + } + var valid27 = _errs50 === errors; + } else valid27 = !0; + if (valid27 && void 0 !== data15.height) { + let data20 = data15.height; + if (errors == errors) if ("number" == typeof data20 && isFinite(data20)) { + if (data20 > 1 || isNaN(data20)) { + let err15 = { + instancePath: instancePath + "/box/height", + schemaPath: "#/$defs/ObservationBox/properties/height/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + }; + vErrors.push(err15), errors++; + } else if (data20 <= 0 || isNaN(data20)) { + let err16 = { + instancePath: instancePath + "/box/height", + schemaPath: "#/$defs/ObservationBox/properties/height/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + }; + vErrors.push(err16), errors++; + } + } else { + let err17 = { + instancePath: instancePath + "/box/height", + schemaPath: "#/$defs/ObservationBox/properties/height/type", + keyword: "type", + params: { + type: "number" + } + }; + vErrors.push(err17), errors++; + } } } } - var valid18 = 0 === _errs38; - } else valid18 = !0; - if (valid18) { - if (void 0 !== data.max_x) { - let data19 = data.max_x, _errs46 = 0; - if ("number" != typeof data19 || !isFinite(data19)) return validate411.errors = [ { - instancePath: instancePath + "/max_x", - schemaPath: "#/properties/max_x/type", + } + } else { + let err18 = { + instancePath: instancePath + "/box", + schemaPath: "#/$defs/ObservationBox/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err18), errors++; + } + var _valid0 = _errs41 === errors; + valid22 = valid22 || _valid0; + let _errs54 = errors; + if (null !== data15) { + let err19 = { + instancePath: instancePath + "/box", + schemaPath: "#/properties/box/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err19), errors++; + } + if (_valid0 = _errs54 === errors, valid22 = valid22 || _valid0, !valid22) { + let err20 = { + instancePath: instancePath + "/box", + schemaPath: "#/properties/box/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err20), errors++, validate491.errors = vErrors, !1; + } + errors = _errs40, vErrors.length = _errs40; + var valid21 = _errs39 === errors; + } else valid21 = !0; + if (valid21) { + if (void 0 !== data.evidence) { + let _errs56 = errors; + validate483(data.evidence, { + instancePath: instancePath + "/evidence", + parentData: data, + parentDataProperty: "evidence", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate483.errors), errors = vErrors.length); + var valid29 = _errs56 === errors; + } else valid29 = !0; + if (valid29) { + if (void 0 !== data.track) { + let data22 = data.track, _errs57 = errors, _errs58 = errors, valid31 = !1, _errs59 = errors; + validate481(data22, { + instancePath: instancePath + "/track", + parentData: data, + parentDataProperty: "track", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate481.errors), errors = vErrors.length); + var _valid1 = _errs59 === errors; + valid31 = valid31 || _valid1; + let _errs60 = errors; + if (null !== data22) { + let err21 = { + instancePath: instancePath + "/track", + schemaPath: "#/properties/track/anyOf/1/type", keyword: "type", params: { - type: "number" + type: "null" } - } ], !1; - var valid22 = 0 === _errs46; - } else valid22 = !0; - if (valid22) { - if (void 0 !== data.max_y) { - let data20 = data.max_y, _errs48 = 0; - if ("number" != typeof data20 || !isFinite(data20)) return validate411.errors = [ { - instancePath: instancePath + "/max_y", - schemaPath: "#/properties/max_y/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - var valid23 = 0 === _errs48; - } else valid23 = !0; - if (valid23) { - if (void 0 !== data.max_z) { - let data21 = data.max_z, _errs50 = 0; - if ("number" != typeof data21 || !isFinite(data21)) return validate411.errors = [ { - instancePath: instancePath + "/max_z", - schemaPath: "#/properties/max_z/type", - keyword: "type", + }; + vErrors.push(err21), errors++; + } + if (_valid1 = _errs60 === errors, valid31 = valid31 || _valid1, !valid31) { + let err22 = { + instancePath: instancePath + "/track", + schemaPath: "#/properties/track/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err22), errors++, validate491.errors = vErrors, !1; + } + errors = _errs58, vErrors.length = _errs58; + var valid30 = _errs57 === errors; + } else valid30 = !0; + if (valid30) { + if (void 0 !== data.recording_reference) { + let data23 = data.recording_reference, _errs62 = errors, _errs63 = errors, valid33 = !1, _errs64 = errors; + if (errors == errors) if (data23 && "object" == typeof data23 && !Array.isArray(data23)) { + if (void 0 === data23.recording_id) { + let err23 = { + instancePath: instancePath + "/recording_reference", + schemaPath: "#/$defs/ObservationRecordingReference/required", + keyword: "required", params: { - type: "number" + missingProperty: "recording_id" } - } ], !1; - var valid24 = 0 === _errs50; - } else valid24 = !0; - if (valid24) { - if (void 0 !== data.max_presets) { - let data22 = data.max_presets, _errs52 = 0; - if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate411.errors = [ { - instancePath: instancePath + "/max_presets", - schemaPath: "#/properties/max_presets/type", + }; + vErrors.push(err23), errors++; + } else if (void 0 !== data23.recording_id) { + let data24 = data23.recording_id, _errs68 = errors; + if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) { + let err24 = { + instancePath: instancePath + "/recording_reference/recording_id", + schemaPath: "#/$defs/ObservationRecordingReference/properties/recording_id/type", keyword: "type", params: { type: "integer" } - } ], !1; - var valid25 = 0 === _errs52; - } else valid25 = !0; - if (valid25 && void 0 !== data.ptz_service_url && "string" != typeof data.ptz_service_url) return validate411.errors = [ { - instancePath: instancePath + "/ptz_service_url", - schemaPath: "#/properties/ptz_service_url/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; + }; + vErrors.push(err24), errors++; + } + if (errors === _errs68 && "number" == typeof data24 && isFinite(data24) && (data24 < 1 || isNaN(data24))) { + let err25 = { + instancePath: instancePath + "/recording_reference/recording_id", + schemaPath: "#/$defs/ObservationRecordingReference/properties/recording_id/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + }; + vErrors.push(err25), errors++; + } } + } else { + let err26 = { + instancePath: instancePath + "/recording_reference", + schemaPath: "#/$defs/ObservationRecordingReference/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err26), errors++; + } + var _valid2 = _errs64 === errors; + valid33 = valid33 || _valid2; + let _errs70 = errors; + if (null !== data23) { + let err27 = { + instancePath: instancePath + "/recording_reference", + schemaPath: "#/properties/recording_reference/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err27), errors++; + } + if (_valid2 = _errs70 === errors, valid33 = valid33 || _valid2, !valid33) { + let err28 = { + instancePath: instancePath + "/recording_reference", + schemaPath: "#/properties/recording_reference/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err28), errors++, validate491.errors = vErrors, !1; } + errors = _errs63, vErrors.length = _errs63; + var valid32 = _errs62 === errors; + } else valid32 = !0; + if (valid32) { + if (void 0 !== data.revision) { + let data25 = data.revision, _errs72 = errors; + if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate491.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs72 && "number" == typeof data25 && isFinite(data25) && (data25 < 1 || isNaN(data25))) return validate491.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid36 = _errs72 === errors; + } else valid36 = !0; + valid36 && void 0 !== data.classifications && (validate485(data.classifications, { + instancePath: instancePath + "/classifications", + parentData: data, + parentDataProperty: "classifications", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate485.errors), errors = vErrors.length)); } } } @@ -76505,384 +77042,670 @@ function validate411(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate411.errors = [], !0; + return validate491.errors = vErrors, 0 === errors; } -validate411.evaluated = { +validate481.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate485.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate491.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_PtzGotoPresetRequest = validate408; - -function validate408(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate408.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate408.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" +var __contract_ObservationChangeEvent = validate497, schema610 = { + type: "object", + properties: { + observation_id: { + $ref: "#/$defs/ObservationUuid" + }, + revision: { + type: "integer", + minimum: 1 + }, + camera_uuid: { + $ref: "#/$defs/ObservationUuid" + }, + label: { + type: "string", + minLength: 1, + maxLength: 31 + }, + observed_at: { + $ref: "#/$defs/ObservationTime" + }, + change_cause: { + type: "string", + enum: [ "created", "evidence", "classification" ] + }, + evidence_state: { + $ref: "#/$defs/ObservationEvidenceState" + }, + evidence_reason: { + anyOf: [ { + $ref: "#/$defs/ObservationEvidenceReason" + }, { + type: "null" + } ] + }, + asset_id: { + $ref: "#/$defs/ObservationUuid" + }, + snapshot_url: { + type: "string", + pattern: "^/api/observations/[0-9a-f-]{36}/snapshot$" } - } ], !1; - if (void 0 === data.token) return validate408.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "token" + }, + required: [ "observation_id", "revision", "camera_uuid", "label", "observed_at", "change_cause", "evidence_state", "evidence_reason", "asset_id" ], + additionalProperties: !0, + description: "Data payload of io.lightnvr.detection.observation_changed.v1 (contract §6.3). Consumers skip classification-only changes for re-classification.", + allOf: [ { + if: { + type: "object", + properties: { + evidence_state: { + enum: [ "pending", "ready" ] + } + } + }, + then: { + type: "object", + properties: { + evidence_reason: { + type: "null" + } + } + }, + else: { + type: "object", + properties: { + evidence_reason: { + $ref: "#/$defs/ObservationEvidenceReason" + } + } } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("token" !== key0) return validate408.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 + }, { + if: { + type: "object", + properties: { + evidence_state: { + const: "ready" + } } - } ], !1; - if (0 === _errs1 && void 0 !== data.token) { - let data0 = data.token; - if ("string" != typeof data0) return validate408.errors = [ { - instancePath: instancePath + "/token", - schemaPath: "#/properties/token/type", + }, + then: { + type: "object", + properties: { + snapshot_url: { + type: "string" + } + }, + required: [ "snapshot_url" ] + }, + else: { + type: "object", + not: { + type: "object", + properties: { + snapshot_url: { + type: "string" + } + }, + required: [ "snapshot_url" ] + } + } + } ] +}; + +function validate497(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate497.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs1 = errors, _errs2 = errors, valid1 = !0, _errs3 = errors; + if (errors === _errs3) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 !== data.evidence_state) { + let data0 = data.evidence_state; + if ("pending" !== data0 && "ready" !== data0) { + let err0 = {}; + vErrors.push(err0), errors++; + } + } + } else { + let err1 = {}; + vErrors.push(err1), errors++; + } + var _valid0 = _errs3 === errors; + let ifClause0; + if (errors = _errs2, vErrors.length = _errs2, _valid0) { + let _errs6 = errors; + if (errors === _errs6) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/0/then/type", keyword: "type", params: { - type: "string" + type: "object" } } ], !1; - if (func1(data0) > 63) return validate408.errors = [ { - instancePath: instancePath + "/token", - schemaPath: "#/properties/token/maxLength", - keyword: "maxLength", + if (void 0 !== data.evidence_reason && null !== data.evidence_reason) return validate497.errors = [ { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/allOf/0/then/properties/evidence_reason/type", + keyword: "type", params: { - limit: 63 + type: "null" } } ], !1; - if (func1(data0) < 1) return validate408.errors = [ { - instancePath: instancePath + "/token", - schemaPath: "#/properties/token/minLength", - keyword: "minLength", + } + if (valid1 = _valid0 = _errs6 === errors, valid1) var props0 = { + evidence_reason: !0, + evidence_state: !0 + }; + ifClause0 = "then"; + } else { + let _errs10 = errors; + if (errors === _errs10) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/0/else/type", + keyword: "type", params: { - limit: 1 + type: "object" } } ], !1; + if (void 0 !== data.evidence_reason) { + let data2 = data.evidence_reason; + if ("string" != typeof data2) return validate497.errors = [ { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/$defs/ObservationEvidenceReason/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("queue_full" !== data2 && "budget_exhausted" !== data2 && "encode_failed" !== data2 && "write_failed" !== data2 && "interrupted" !== data2 && "no_matching_media" !== data2 && "disabled" !== data2 && "retention_expired" !== data2 && "deleted" !== data2) return validate497.errors = [ { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/$defs/ObservationEvidenceReason/enum", + keyword: "enum", + params: { + allowedValues: schema575.enum + } + } ], !1; + } } + valid1 = _valid0 = _errs10 === errors, valid1 && !0 !== props0 && ((props0 = props0 || {}).evidence_reason = !0), + ifClause0 = "else"; } - return validate408.errors = [], !0; -} - -validate408.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PtzPresetRequest = validate407; - -function validate407(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate407.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate407.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("name" !== key0) return validate407.errors = [ { + if (!valid1) { + let err2 = { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/allOf/0/if", + keyword: "if", params: { - additionalProperty: key0 + failingKeyword: ifClause0 } - } ], !1; - if (0 === _errs1 && void 0 !== data.name) { - let data0 = data.name; - if ("string" != typeof data0) return validate407.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/type", + }; + return vErrors.push(err2), errors++, validate497.errors = vErrors, !1; + } + if (_errs1 === errors) { + let _errs15 = errors, _errs16 = errors, valid6 = !0, _errs17 = errors; + if (errors === _errs17) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 !== data.evidence_state && "ready" !== data.evidence_state) { + let err3 = {}; + vErrors.push(err3), errors++; + } + } else { + let err4 = {}; + vErrors.push(err4), errors++; + } + var _valid1 = _errs17 === errors; + let ifClause1; + if (errors = _errs16, vErrors.length = _errs16, _valid1) { + let _errs20 = errors; + if (errors === _errs20) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/then/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.snapshot_url) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/then/required", + keyword: "required", + params: { + missingProperty: "snapshot_url" + } + } ], !1; + if (void 0 !== data.snapshot_url && "string" != typeof data.snapshot_url) return validate497.errors = [ { + instancePath: instancePath + "/snapshot_url", + schemaPath: "#/allOf/1/then/properties/snapshot_url/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + if (valid6 = _valid1 = _errs20 === errors, valid6) var props1 = { + snapshot_url: !0, + evidence_state: !0 + }; + ifClause1 = "then"; + } else { + let _errs24 = errors; + if (!data || "object" != typeof data || Array.isArray(data)) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/else/type", keyword: "type", params: { - type: "string" + type: "object" } } ], !1; - if (func1(data0) > 63) return validate407.errors = [ { - instancePath: instancePath + "/name", - schemaPath: "#/properties/name/maxLength", - keyword: "maxLength", - params: { - limit: 63 + let _errs26 = errors, _errs27 = errors; + if (errors === _errs27) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 === data.snapshot_url) { + let err5 = {}; + vErrors.push(err5), errors++; + } else if (void 0 !== data.snapshot_url && "string" != typeof data.snapshot_url) { + let err6 = {}; + vErrors.push(err6), errors++; } + } else { + let err7 = {}; + vErrors.push(err7), errors++; + } + if (_errs27 === errors) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/allOf/1/else/not", + keyword: "not", + params: {} } ], !1; + errors = _errs26, vErrors.length = _errs26, valid6 = _valid1 = _errs24 === errors, + ifClause1 = "else"; } - } - return validate407.errors = [], !0; -} - -validate407.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PtzPresetResult = validate409; - -function validate409(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate409.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate409.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate409.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.message) return validate409.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "message" - } - } ], !1; - if (void 0 === data.token) return validate409.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "token" + if (!valid6) { + let err8 = { + instancePath: instancePath, + schemaPath: "#/allOf/1/if", + keyword: "if", + params: { + failingKeyword: ifClause1 + } + }; + return vErrors.push(err8), errors++, validate497.errors = vErrors, !1; } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "message" !== key0 && "token" !== key0) return validate409.errors = [ { + _errs15 === errors && !0 !== props0 && void 0 !== props1 && (!0 === props1 ? props0 = !0 : (props0 = props0 || {}, + Object.assign(props0, props1))); + } + if (0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate497.errors = [ { instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", + schemaPath: "#/type", + keyword: "type", params: { - additionalProperty: key0 + type: "object" } } ], !1; - if (0 === _errs1) { - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate409.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", + if (void 0 === data.observation_id) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "observation_id" + } + } ], !1; + if (void 0 === data.revision) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "revision" + } + } ], !1; + if (void 0 === data.camera_uuid) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "camera_uuid" + } + } ], !1; + if (void 0 === data.label) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "label" + } + } ], !1; + if (void 0 === data.observed_at) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "observed_at" + } + } ], !1; + if (void 0 === data.change_cause) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "change_cause" + } + } ], !1; + if (void 0 === data.evidence_state) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "evidence_state" + } + } ], !1; + if (void 0 === data.evidence_reason) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "evidence_reason" + } + } ], !1; + if (void 0 === data.asset_id) return validate497.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "asset_id" + } + } ], !1; + if (void 0 !== data.observation_id) { + let data6 = data.observation_id, _errs32 = errors; + if (errors == errors) { + if ("string" != typeof data6) return validate497.errors = [ { + instancePath: instancePath + "/observation_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", params: { - allowedValue: !0 + type: "string" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.message) { - let _errs3 = 0; - if ("string" != typeof data.message) return validate409.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs3; - } else valid1 = !0; - if (valid1 && void 0 !== data.token && "string" != typeof data.token) return validate409.errors = [ { - instancePath: instancePath + "/token", - schemaPath: "#/properties/token/type", - keyword: "type", + if (!pattern159.test(data6)) return validate497.errors = [ { + instancePath: instancePath + "/observation_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", params: { - type: "string" + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" } } ], !1; } - } - } - return validate409.errors = [], !0; -} - -validate409.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_PtzPresets = validate410; - -function validate410(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate410.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate410.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.presets) return validate410.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "presets" - } - } ], !1; - if (void 0 === data.count) return validate410.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "count" - } - } ], !1; - { - let _errs1 = 0; - for (let key0 in data) if ("presets" !== key0 && "count" !== key0) return validate410.errors = [ { - instancePath: instancePath, - schemaPath: "#/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - } ], !1; - if (0 === _errs1) { - if (void 0 !== data.presets) { - let data0 = data.presets, _errs2 = 0; - if (0 === _errs2) { - if (!Array.isArray(data0)) return validate410.errors = [ { - instancePath: instancePath + "/presets", - schemaPath: "#/properties/presets/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - if (data0.length > 32) return validate410.errors = [ { - instancePath: instancePath + "/presets", - schemaPath: "#/properties/presets/maxItems", - keyword: "maxItems", - params: { - limit: 32 + var valid11 = _errs32 === errors; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.revision) { + let data7 = data.revision, _errs35 = errors; + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate497.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs35 && "number" == typeof data7 && isFinite(data7) && (data7 < 1 || isNaN(data7))) return validate497.errors = [ { + instancePath: instancePath + "/revision", + schemaPath: "#/properties/revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid13 = _errs35 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.camera_uuid) { + let data8 = data.camera_uuid, _errs37 = errors; + if (errors == errors) { + if ("string" != typeof data8) return validate497.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data8)) return validate497.errors = [ { + instancePath: instancePath + "/camera_uuid", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + var valid14 = _errs37 === errors; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.label) { + let data9 = data.label, _errs40 = errors; + if (errors === _errs40) { + if ("string" != typeof data9) return validate497.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data9) > 31) return validate497.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/maxLength", + keyword: "maxLength", + params: { + limit: 31 + } + } ], !1; + if (func1(data9) < 1) return validate497.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; } - } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (0 === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate410.errors = [ { - instancePath: instancePath + "/presets/" + i0, - schemaPath: "#/properties/presets/items/type", + var valid16 = _errs40 === errors; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data.observed_at) { + let data10 = data.observed_at, _errs42 = errors; + if (errors == errors && errors == errors) { + if ("string" != typeof data10) return validate497.errors = [ { + instancePath: instancePath + "/observed_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { - type: "object" + type: "string" } } ], !1; - if (void 0 === data1.token) return validate410.errors = [ { - instancePath: instancePath + "/presets/" + i0, - schemaPath: "#/properties/presets/items/required", - keyword: "required", + if (!formats2.validate(data10)) return validate497.errors = [ { + instancePath: instancePath + "/observed_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", params: { - missingProperty: "token" + format: "date-time" } } ], !1; - if (void 0 === data1.name) return validate410.errors = [ { - instancePath: instancePath + "/presets/" + i0, - schemaPath: "#/properties/presets/items/required", - keyword: "required", + } + var valid17 = _errs42 === errors; + } else valid17 = !0; + if (valid17) { + if (void 0 !== data.change_cause) { + let data11 = data.change_cause, _errs45 = errors; + if ("string" != typeof data11) return validate497.errors = [ { + instancePath: instancePath + "/change_cause", + schemaPath: "#/properties/change_cause/type", + keyword: "type", params: { - missingProperty: "name" + type: "string" } } ], !1; - { - let _errs6 = 0; - for (let key1 in data1) if ("token" !== key1 && "name" !== key1) return validate410.errors = [ { - instancePath: instancePath + "/presets/" + i0, - schemaPath: "#/properties/presets/items/additionalProperties", - keyword: "additionalProperties", + if ("created" !== data11 && "evidence" !== data11 && "classification" !== data11) return validate497.errors = [ { + instancePath: instancePath + "/change_cause", + schemaPath: "#/properties/change_cause/enum", + keyword: "enum", + params: { + allowedValues: schema610.properties.change_cause.enum + } + } ], !1; + var valid19 = _errs45 === errors; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data.evidence_state) { + let data12 = data.evidence_state, _errs47 = errors; + if ("string" != typeof data12) return validate497.errors = [ { + instancePath: instancePath + "/evidence_state", + schemaPath: "#/$defs/ObservationEvidenceState/type", + keyword: "type", params: { - additionalProperty: key1 + type: "string" } } ], !1; - if (0 === _errs6) { - if (void 0 !== data1.token) { - let _errs7 = 0; - if ("string" != typeof data1.token) return validate410.errors = [ { - instancePath: instancePath + "/presets/" + i0 + "/token", - schemaPath: "#/properties/presets/items/properties/token/type", + if ("pending" !== data12 && "ready" !== data12 && "unavailable" !== data12 && "expired" !== data12) return validate497.errors = [ { + instancePath: instancePath + "/evidence_state", + schemaPath: "#/$defs/ObservationEvidenceState/enum", + keyword: "enum", + params: { + allowedValues: schema574.enum + } + } ], !1; + var valid20 = _errs47 === errors; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data.evidence_reason) { + let data13 = data.evidence_reason, _errs50 = errors, _errs51 = errors, valid23 = !1, _errs52 = errors; + if ("string" != typeof data13) { + let err9 = { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/$defs/ObservationEvidenceReason/type", keyword: "type", params: { type: "string" } - } ], !1; - var valid2 = 0 === _errs7; - } else valid2 = !0; - if (valid2 && void 0 !== data1.name && "string" != typeof data1.name) return validate410.errors = [ { - instancePath: instancePath + "/presets/" + i0 + "/name", - schemaPath: "#/properties/presets/items/properties/name/type", - keyword: "type", - params: { - type: "string" + }; + vErrors.push(err9), errors++; + } + if ("queue_full" !== data13 && "budget_exhausted" !== data13 && "encode_failed" !== data13 && "write_failed" !== data13 && "interrupted" !== data13 && "no_matching_media" !== data13 && "disabled" !== data13 && "retention_expired" !== data13 && "deleted" !== data13) { + let err10 = { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/$defs/ObservationEvidenceReason/enum", + keyword: "enum", + params: { + allowedValues: schema575.enum + } + }; + vErrors.push(err10), errors++; + } + var _valid2 = _errs52 === errors; + valid23 = valid23 || _valid2; + let _errs55 = errors; + if (null !== data13) { + let err11 = { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/properties/evidence_reason/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err11), errors++; + } + if (_valid2 = _errs55 === errors, valid23 = valid23 || _valid2, !valid23) { + let err12 = { + instancePath: instancePath + "/evidence_reason", + schemaPath: "#/properties/evidence_reason/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err12), errors++, validate497.errors = vErrors, !1; + } + errors = _errs51, vErrors.length = _errs51; + var valid22 = _errs50 === errors; + } else valid22 = !0; + if (valid22) { + if (void 0 !== data.asset_id) { + let data14 = data.asset_id, _errs57 = errors; + if (errors == errors) { + if ("string" != typeof data14) return validate497.errors = [ { + instancePath: instancePath + "/asset_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data14)) return validate497.errors = [ { + instancePath: instancePath + "/asset_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; } - } ], !1; + var valid25 = _errs57 === errors; + } else valid25 = !0; + if (valid25 && void 0 !== data.snapshot_url) { + let data15 = data.snapshot_url; + if (errors == errors) { + if ("string" != typeof data15) return validate497.errors = [ { + instancePath: instancePath + "/snapshot_url", + schemaPath: "#/properties/snapshot_url/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern209.test(data15)) return validate497.errors = [ { + instancePath: instancePath + "/snapshot_url", + schemaPath: "#/properties/snapshot_url/pattern", + keyword: "pattern", + params: { + pattern: "^/api/observations/[0-9a-f-]{36}/snapshot$" + } + } ], !1; + } + } } } } - if (0 !== _errs4) break; } } } - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0 && void 0 !== data.count) { - let data4 = data.count; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate410.errors = [ { - instancePath: instancePath + "/count", - schemaPath: "#/properties/count/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; } } } - return validate410.errors = [], !0; + return validate497.errors = vErrors, 0 === errors; } -validate410.evaluated = { +validate497.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_RecordingBatchProtectionRequest = validate114; +var __contract_ObservationClassification = validate484; -function validate114(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate114.evaluated; +function validate484(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate484.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate114.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -76890,456 +77713,621 @@ function validate114(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.ids) return validate114.errors = [ { + if (void 0 === data.key) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "ids" + missingProperty: "key" } } ], !1; - if (void 0 === data.protected) return validate114.errors = [ { + if (void 0 === data.writer_namespace) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "protected" + missingProperty: "writer_namespace" } } ], !1; - if (void 0 !== data.ids) { - let _errs2 = 0; - if (0 === _errs2 && !Array.isArray(data.ids)) return validate114.errors = [ { - instancePath: instancePath + "/ids", - schemaPath: "#/properties/ids/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - return valid0 && void 0 !== data.protected && "boolean" != typeof data.protected ? (validate114.errors = [ { - instancePath: instancePath + "/protected", - schemaPath: "#/properties/protected/type", - keyword: "type", + if (void 0 === data.classifier) return validate484.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", params: { - type: "boolean" + missingProperty: "classifier" } - } ], !1) : (validate114.errors = [], !0); -} - -validate114.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_RecordingBatchProtectionResult = validate115; - -function validate115(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate115.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate115.errors = [ { + } ], !1; + if (void 0 === data.model_version) return validate484.errors = [ { instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "model_version" } } ], !1; - if (void 0 === data.success_count) return validate115.errors = [ { + if (void 0 === data.label) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "success_count" + missingProperty: "label" } } ], !1; - if (void 0 === data.fail_count) return validate115.errors = [ { + if (void 0 === data.taxonomy_id) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "fail_count" + missingProperty: "taxonomy_id" } } ], !1; - if (void 0 === data.protected) return validate115.errors = [ { + if (void 0 === data.confidence) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "protected" + missingProperty: "confidence" } } ], !1; - if (void 0 === data.message) return validate115.errors = [ { + if (void 0 === data.classified_at) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "message" + missingProperty: "classified_at" } } ], !1; - if (void 0 !== data.success_count) { - let data0 = data.success_count, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate115.errors = [ { - instancePath: instancePath + "/success_count", - schemaPath: "#/properties/success_count/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate115.errors = [ { - instancePath: instancePath + "/success_count", - schemaPath: "#/properties/success_count/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.fail_count) { - let data1 = data.fail_count, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate115.errors = [ { - instancePath: instancePath + "/fail_count", - schemaPath: "#/properties/fail_count/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate115.errors = [ { - instancePath: instancePath + "/fail_count", - schemaPath: "#/properties/fail_count/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.protected) { - let _errs6 = 0; - if ("boolean" != typeof data.protected) return validate115.errors = [ { - instancePath: instancePath + "/protected", - schemaPath: "#/properties/protected/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2 && void 0 !== data.message && "string" != typeof data.message) return validate115.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; + if (void 0 === data.input_asset_id) return validate484.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "input_asset_id" } - } - return validate115.errors = [], !0; -} - -validate115.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_RecordingBatchTagsRequest = validate117; - -function validate117(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate117.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate117.errors = [ { + } ], !1; + if (void 0 === data.input_revision) return validate484.errors = [ { instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", + schemaPath: "#/required", + keyword: "required", params: { - type: "object" + missingProperty: "input_revision" } } ], !1; - if (void 0 === data.ids) return validate117.errors = [ { + if (void 0 === data.kind) return validate484.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "ids" + missingProperty: "kind" } } ], !1; - if (void 0 !== data.ids) { - let data0 = data.ids, _errs2 = 0; + if (void 0 === data.annotation_revision) return validate484.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "annotation_revision" + } + } ], !1; + if (void 0 === data.created_at) return validate484.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "created_at" + } + } ], !1; + if (void 0 === data.updated_at) return validate484.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "updated_at" + } + } ], !1; + if (void 0 !== data.key) { + let data0 = data.key, _errs2 = 0; if (0 === _errs2) { - if (!Array.isArray(data0)) return validate117.errors = [ { - instancePath: instancePath + "/ids", - schemaPath: "#/properties/ids/type", + if ("string" != typeof data0) return validate484.errors = [ { + instancePath: instancePath + "/key", + schemaPath: "#/properties/key/type", keyword: "type", params: { - type: "array" + type: "string" } } ], !1; - if (data0.length > 1e4) return validate117.errors = [ { - instancePath: instancePath + "/ids", - schemaPath: "#/properties/ids/maxItems", - keyword: "maxItems", - params: { - limit: 1e4 - } - } ], !1; - if (data0.length < 1) return validate117.errors = [ { - instancePath: instancePath + "/ids", - schemaPath: "#/properties/ids/minItems", - keyword: "minItems", + if (!pattern160.test(data0)) return validate484.errors = [ { + instancePath: instancePath + "/key", + schemaPath: "#/properties/key/pattern", + keyword: "pattern", params: { - limit: 1 + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}/[a-z0-9][a-z0-9_.-]{0,31}$" } } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (0 === _errs4) { - if ("number" != typeof data1 || !isFinite(data1)) return validate117.errors = [ { - instancePath: instancePath + "/ids/" + i0, - schemaPath: "#/properties/ids/items/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - if (data1 <= 0 || isNaN(data1)) return validate117.errors = [ { - instancePath: instancePath + "/ids/" + i0, - schemaPath: "#/properties/ids/items/exclusiveMinimum", - keyword: "exclusiveMinimum", - params: { - comparison: ">", - limit: 0 - } - } ], !1; - } - if (0 !== _errs4) break; - } - } } var valid0 = 0 === _errs2; } else valid0 = !0; if (valid0) { - if (void 0 !== data.add) { - let _errs6 = 0; - if (0 === _errs6 && !Array.isArray(data.add)) return validate117.errors = [ { - instancePath: instancePath + "/add", - schemaPath: "#/properties/add/type", - keyword: "type", - params: { - type: "array" + if (void 0 !== data.writer_namespace) { + let data1 = data.writer_namespace, _errs4 = 0; + if (0 === _errs4) { + if ("string" != typeof data1) return validate484.errors = [ { + instancePath: instancePath + "/writer_namespace", + schemaPath: "#/properties/writer_namespace/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern161.test(data1)) return validate484.errors = [ { + instancePath: instancePath + "/writer_namespace", + schemaPath: "#/properties/writer_namespace/pattern", + keyword: "pattern", + params: { + pattern: "^[a-z0-9][a-z0-9_.-]{0,31}$" + } + } ], !1; + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.classifier) { + let data2 = data.classifier, _errs6 = 0; + if (0 === _errs6) { + if ("string" != typeof data2) return validate484.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 64) return validate484.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + if (func1(data2) < 1) return validate484.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.model_version) { + let data3 = data.model_version, _errs8 = 0; + if ("string" != typeof data3 && null !== data3) return validate484.errors = [ { + instancePath: instancePath + "/model_version", + schemaPath: "#/properties/model_version/type", + keyword: "type", + params: { + type: schema427.properties.model_version.type + } + } ], !1; + if (0 === _errs8 && "string" == typeof data3 && func1(data3) > 64) return validate484.errors = [ { + instancePath: instancePath + "/model_version", + schemaPath: "#/properties/model_version/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.label) { + let data4 = data.label, _errs10 = 0; + if (0 === _errs10) { + if ("string" != typeof data4) return validate484.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data4) > 128) return validate484.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/maxLength", + keyword: "maxLength", + params: { + limit: 128 + } + } ], !1; + if (func1(data4) < 1) return validate484.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.taxonomy_id) { + let data5 = data.taxonomy_id, _errs12 = 0; + if ("string" != typeof data5 && null !== data5) return validate484.errors = [ { + instancePath: instancePath + "/taxonomy_id", + schemaPath: "#/properties/taxonomy_id/type", + keyword: "type", + params: { + type: schema427.properties.taxonomy_id.type + } + } ], !1; + if (0 === _errs12 && "string" == typeof data5 && func1(data5) > 64) return validate484.errors = [ { + instancePath: instancePath + "/taxonomy_id", + schemaPath: "#/properties/taxonomy_id/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.confidence) { + let data6 = data.confidence, _errs14 = 0; + if (0 === _errs14) { + if ("number" != typeof data6 || !isFinite(data6)) return validate484.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data6 > 1 || isNaN(data6)) return validate484.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data6 < 0 || isNaN(data6)) return validate484.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.classified_at) { + let data7 = data.classified_at, _errs16 = 0; + if ("string" != typeof data7) return validate484.errors = [ { + instancePath: instancePath + "/classified_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!formats2.validate(data7)) return validate484.errors = [ { + instancePath: instancePath + "/classified_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.input_asset_id) { + let data8 = data.input_asset_id, _errs19 = 0; + if ("string" != typeof data8) return validate484.errors = [ { + instancePath: instancePath + "/input_asset_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data8)) return validate484.errors = [ { + instancePath: instancePath + "/input_asset_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + var valid9 = 0 === _errs19; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.input_revision) { + let data9 = data.input_revision, _errs22 = 0; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate484.errors = [ { + instancePath: instancePath + "/input_revision", + schemaPath: "#/properties/input_revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 1 || isNaN(data9))) return validate484.errors = [ { + instancePath: instancePath + "/input_revision", + schemaPath: "#/properties/input_revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid11 = 0 === _errs22; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.kind) { + let data10 = data.kind, _errs24 = 0; + if ("string" != typeof data10) return validate484.errors = [ { + instancePath: instancePath + "/kind", + schemaPath: "#/properties/kind/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("automated" !== data10 && "owner_confirmed" !== data10) return validate484.errors = [ { + instancePath: instancePath + "/kind", + schemaPath: "#/properties/kind/enum", + keyword: "enum", + params: { + allowedValues: schema427.properties.kind.enum + } + } ], !1; + var valid12 = 0 === _errs24; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data.annotation_revision) { + let data11 = data.annotation_revision, _errs26 = 0; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate484.errors = [ { + instancePath: instancePath + "/annotation_revision", + schemaPath: "#/properties/annotation_revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs26 && "number" == typeof data11 && isFinite(data11) && (data11 < 1 || isNaN(data11))) return validate484.errors = [ { + instancePath: instancePath + "/annotation_revision", + schemaPath: "#/properties/annotation_revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid13 = 0 === _errs26; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.created_at) { + let data12 = data.created_at, _errs28 = 0; + if ("string" != typeof data12) return validate484.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!formats2.validate(data12)) return validate484.errors = [ { + instancePath: instancePath + "/created_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; + var valid14 = 0 === _errs28; + } else valid14 = !0; + if (valid14 && void 0 !== data.updated_at) { + let data13 = data.updated_at; + if ("string" != typeof data13) return validate484.errors = [ { + instancePath: instancePath + "/updated_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!formats2.validate(data13)) return validate484.errors = [ { + instancePath: instancePath + "/updated_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + } ], !1; + } + } + } + } + } + } + } + } + } } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2 && void 0 !== data.remove && !Array.isArray(data.remove)) return validate117.errors = [ { - instancePath: instancePath + "/remove", - schemaPath: "#/properties/remove/type", - keyword: "type", - params: { - type: "array" } - } ], !1; + } } - return validate117.errors = [], !0; + return validate484.errors = [], !0; } -validate117.evaluated = { +validate484.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_RecordingBatchTagsResult = validate118; +var __contract_ObservationClassificationResult = validate489; -function validate118(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate118.evaluated; +function validate489(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate489.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate118.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.recordings_count) return validate118.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "recordings_count" - } - } ], !1; - if (void 0 === data.tags_added) return validate118.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "tags_added" - } - } ], !1; - if (void 0 === data.tags_removed) return validate118.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "tags_removed" - } - } ], !1; - if (void 0 !== data.recordings_count) { - let data0 = data.recordings_count, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate118.errors = [ { - instancePath: instancePath + "/recordings_count", - schemaPath: "#/properties/recordings_count/type", + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate489.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", keyword: "type", params: { - type: "integer" + type: "object" } } ], !1; - if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate118.errors = [ { - instancePath: instancePath + "/recordings_count", - schemaPath: "#/properties/recordings_count/minimum", - keyword: "minimum", + if (void 0 === data.changed) return validate489.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", params: { - comparison: ">=", - limit: 0 + missingProperty: "changed" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.tags_added) { - let data1 = data.tags_added, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate118.errors = [ { - instancePath: instancePath + "/tags_added", - schemaPath: "#/properties/tags_added/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate118.errors = [ { - instancePath: instancePath + "/tags_added", - schemaPath: "#/properties/tags_added/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1 && void 0 !== data.tags_removed) { - let data2 = data.tags_removed, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate118.errors = [ { - instancePath: instancePath + "/tags_removed", - schemaPath: "#/properties/tags_removed/type", + if (void 0 === data.observation_revision) return validate489.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "observation_revision" + } + } ], !1; + if (void 0 === data.annotation) return validate489.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "annotation" + } + } ], !1; + if (void 0 !== data.changed) { + let _errs2 = errors; + if ("boolean" != typeof data.changed) return validate489.errors = [ { + instancePath: instancePath + "/changed", + schemaPath: "#/properties/changed/type", keyword: "type", params: { - type: "integer" - } - } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate118.errors = [ { - instancePath: instancePath + "/tags_removed", - schemaPath: "#/properties/tags_removed/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 + type: "boolean" } } ], !1; + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.observation_revision) { + let data1 = data.observation_revision, _errs4 = errors; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate489.errors = [ { + instancePath: instancePath + "/observation_revision", + schemaPath: "#/properties/observation_revision/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate489.errors = [ { + instancePath: instancePath + "/observation_revision", + schemaPath: "#/properties/observation_revision/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid1 = _errs4 === errors; + } else valid1 = !0; + valid1 && void 0 !== data.annotation && (validate369(data.annotation, { + instancePath: instancePath + "/annotation", + parentData: data, + parentDataProperty: "annotation", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate369.errors), errors = vErrors.length)); } } - return validate118.errors = [], !0; + return validate489.errors = vErrors, 0 === errors; } -validate118.evaluated = { +validate489.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_RecordingDeletionFilter = validate120, schema127 = { +var __contract_ObservationClassificationWrite = validate488, schema596 = { type: "object", properties: { - stream_name: { + classifier: { type: "string", minLength: 1, - maxLength: 255 + maxLength: 64 }, - stream: { - type: "string", - minLength: 1, - maxLength: 255 + model_version: { + type: [ "string", "null" ], + maxLength: 64 }, - collection_uuid: { + label: { type: "string", minLength: 1, - maxLength: 36 + maxLength: 128 }, - start: { - type: "string", - minLength: 1, + taxonomy_id: { + type: [ "string", "null" ], maxLength: 64 }, - end: { - type: "string", - minLength: 1, - maxLength: 64 + confidence: { + type: "number", + minimum: 0, + maximum: 1 }, - detection: { + classified_at: { + $ref: "#/$defs/ObservationTime" + }, + input_asset_id: { + $ref: "#/$defs/ObservationUuid" + }, + input_revision: { type: "integer", - enum: [ -1, 0, 1 ] + minimum: 1 }, - detection_label: { + kind: { type: "string", - minLength: 1, - maxLength: 255 + enum: [ "automated", "owner_confirmed" ] }, - tag: { - type: "string", - minLength: 1, - maxLength: 511 + expected_revision: { + type: "integer", + minimum: 0, + description: "0 or absent means the key must not exist yet." }, - capture_method: { + writer_namespace: { type: "string", - minLength: 1, - maxLength: 127 + description: "Optional. Accepted and ignored when it names the principal's derived namespace; any other value returns 403 namespace_forbidden." }, - protected: { - type: "integer", - enum: [ -1, 0, 1 ] + writer: { + type: "string", + description: "Same rule as writer_namespace." } }, - required: [], + required: [ "classifier", "label", "confidence", "classified_at", "input_asset_id", "input_revision", "kind" ], additionalProperties: !1, - description: "stream_name (or stream fallback) selects comma-separated current camera names. Without a nonempty stream filter, all-fleet recording.delete authority is required. Authorized camera UUIDs and collection membership are captured before dispatch; caller-supplied camera UUID fields do not grant scope. Supplied start/end must be valid UTC YYYY-MM-DDTHH:mm:ss strings, optionally followed by fractional seconds and/or Z; fractions are truncated to whole seconds. Bounds must be after the Unix epoch and start must not exceed end. Invalid types, calendar dates, suffixes, or ranges return400 before job creation. Omitted bounds are unbounded. detection/protected must be -1, 0, or 1. Unknown keys, duplicate keys (including date-case aliases), simultaneous stream and stream_name, invalid types, and empty selectors return400. Date key spelling is case-insensitive; other keys are case-sensitive. Caller-supplied camera/stream UUID selectors are unsupported and rejected. In-progress rows are excluded.", - patternProperties: { - "^(?!start$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$": { - type: "string", - minLength: 1, - maxLength: 64 - } - } -}, pattern20 = new RegExp("^(?!start$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$", "u"); + description: "PUT body (contract §10.3). Closed: unknown fields return 400. The writer namespace is derived by the server from the authenticated user, never from this body." +}; -function validate120(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate120.evaluated; +function validate488(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate488.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate120.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate488.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -77347,9 +78335,65 @@ function validate120(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; + if (void 0 === data.classifier) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "classifier" + } + } ], !1; + if (void 0 === data.label) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "label" + } + } ], !1; + if (void 0 === data.confidence) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "confidence" + } + } ], !1; + if (void 0 === data.classified_at) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "classified_at" + } + } ], !1; + if (void 0 === data.input_asset_id) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "input_asset_id" + } + } ], !1; + if (void 0 === data.input_revision) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "input_revision" + } + } ], !1; + if (void 0 === data.kind) return validate488.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "kind" + } + } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema127.properties, key0) && !pattern20.test(key0)) return validate120.errors = [ { + for (let key0 in data) if (!func7.call(schema596.properties, key0)) return validate488.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -77358,28 +78402,28 @@ function validate120(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs1) { - if (void 0 !== data.stream_name) { - let data0 = data.stream_name, _errs2 = 0; + if (void 0 !== data.classifier) { + let data0 = data.classifier, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate120.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/type", + if ("string" != typeof data0) return validate488.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/type", keyword: "type", params: { type: "string" } } ], !1; - if (func1(data0) > 255) return validate120.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/maxLength", + if (func1(data0) > 64) return validate488.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/maxLength", keyword: "maxLength", params: { - limit: 255 + limit: 64 } } ], !1; - if (func1(data0) < 1) return validate120.errors = [ { - instancePath: instancePath + "/stream_name", - schemaPath: "#/properties/stream_name/minLength", + if (func1(data0) < 1) return validate488.errors = [ { + instancePath: instancePath + "/classifier", + schemaPath: "#/properties/classifier/minLength", keyword: "minLength", params: { limit: 1 @@ -77389,59 +78433,49 @@ function validate120(data, {instancePath: instancePath = "", parentData: parentD var valid0 = 0 === _errs2; } else valid0 = !0; if (valid0) { - if (void 0 !== data.stream) { - let data1 = data.stream, _errs4 = 0; - if (0 === _errs4) { - if ("string" != typeof data1) return validate120.errors = [ { - instancePath: instancePath + "/stream", - schemaPath: "#/properties/stream/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data1) > 255) return validate120.errors = [ { - instancePath: instancePath + "/stream", - schemaPath: "#/properties/stream/maxLength", - keyword: "maxLength", - params: { - limit: 255 - } - } ], !1; - if (func1(data1) < 1) return validate120.errors = [ { - instancePath: instancePath + "/stream", - schemaPath: "#/properties/stream/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } + if (void 0 !== data.model_version) { + let data1 = data.model_version, _errs4 = 0; + if ("string" != typeof data1 && null !== data1) return validate488.errors = [ { + instancePath: instancePath + "/model_version", + schemaPath: "#/properties/model_version/type", + keyword: "type", + params: { + type: schema596.properties.model_version.type + } + } ], !1; + if (0 === _errs4 && "string" == typeof data1 && func1(data1) > 64) return validate488.errors = [ { + instancePath: instancePath + "/model_version", + schemaPath: "#/properties/model_version/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; var valid1 = 0 === _errs4; } else valid1 = !0; if (valid1) { - if (void 0 !== data.collection_uuid) { - let data2 = data.collection_uuid, _errs6 = 0; + if (void 0 !== data.label) { + let data2 = data.label, _errs6 = 0; if (0 === _errs6) { - if ("string" != typeof data2) return validate120.errors = [ { - instancePath: instancePath + "/collection_uuid", - schemaPath: "#/properties/collection_uuid/type", + if ("string" != typeof data2) return validate488.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/type", keyword: "type", params: { type: "string" } } ], !1; - if (func1(data2) > 36) return validate120.errors = [ { - instancePath: instancePath + "/collection_uuid", - schemaPath: "#/properties/collection_uuid/maxLength", + if (func1(data2) > 128) return validate488.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/maxLength", keyword: "maxLength", params: { - limit: 36 + limit: 128 } } ], !1; - if (func1(data2) < 1) return validate120.errors = [ { - instancePath: instancePath + "/collection_uuid", - schemaPath: "#/properties/collection_uuid/minLength", + if (func1(data2) < 1) return validate488.errors = [ { + instancePath: instancePath + "/label", + schemaPath: "#/properties/label/minLength", keyword: "minLength", params: { limit: 1 @@ -77451,231 +78485,187 @@ function validate120(data, {instancePath: instancePath = "", parentData: parentD var valid2 = 0 === _errs6; } else valid2 = !0; if (valid2) { - if (void 0 !== data.start) { - let data3 = data.start, _errs8 = 0; - if (0 === _errs8) { - if ("string" != typeof data3) return validate120.errors = [ { - instancePath: instancePath + "/start", - schemaPath: "#/properties/start/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data3) > 64) return validate120.errors = [ { - instancePath: instancePath + "/start", - schemaPath: "#/properties/start/maxLength", - keyword: "maxLength", - params: { - limit: 64 - } - } ], !1; - if (func1(data3) < 1) return validate120.errors = [ { - instancePath: instancePath + "/start", - schemaPath: "#/properties/start/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } + if (void 0 !== data.taxonomy_id) { + let data3 = data.taxonomy_id, _errs8 = 0; + if ("string" != typeof data3 && null !== data3) return validate488.errors = [ { + instancePath: instancePath + "/taxonomy_id", + schemaPath: "#/properties/taxonomy_id/type", + keyword: "type", + params: { + type: schema596.properties.taxonomy_id.type + } + } ], !1; + if (0 === _errs8 && "string" == typeof data3 && func1(data3) > 64) return validate488.errors = [ { + instancePath: instancePath + "/taxonomy_id", + schemaPath: "#/properties/taxonomy_id/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; var valid3 = 0 === _errs8; } else valid3 = !0; if (valid3) { - if (void 0 !== data.end) { - let data4 = data.end, _errs10 = 0; + if (void 0 !== data.confidence) { + let data4 = data.confidence, _errs10 = 0; if (0 === _errs10) { - if ("string" != typeof data4) return validate120.errors = [ { - instancePath: instancePath + "/end", - schemaPath: "#/properties/end/type", + if ("number" != typeof data4 || !isFinite(data4)) return validate488.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/type", keyword: "type", params: { - type: "string" + type: "number" } } ], !1; - if (func1(data4) > 64) return validate120.errors = [ { - instancePath: instancePath + "/end", - schemaPath: "#/properties/end/maxLength", - keyword: "maxLength", + if (data4 > 1 || isNaN(data4)) return validate488.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/maximum", + keyword: "maximum", params: { - limit: 64 + comparison: "<=", + limit: 1 } } ], !1; - if (func1(data4) < 1) return validate120.errors = [ { - instancePath: instancePath + "/end", - schemaPath: "#/properties/end/minLength", - keyword: "minLength", + if (data4 < 0 || isNaN(data4)) return validate488.errors = [ { + instancePath: instancePath + "/confidence", + schemaPath: "#/properties/confidence/minimum", + keyword: "minimum", params: { - limit: 1 + comparison: ">=", + limit: 0 } } ], !1; } var valid4 = 0 === _errs10; } else valid4 = !0; if (valid4) { - if (void 0 !== data.detection) { - let data5 = data.detection, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate120.errors = [ { - instancePath: instancePath + "/detection", - schemaPath: "#/properties/detection/type", + if (void 0 !== data.classified_at) { + let data5 = data.classified_at, _errs12 = 0; + if ("string" != typeof data5) return validate488.errors = [ { + instancePath: instancePath + "/classified_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { - type: "integer" + type: "string" } } ], !1; - if (-1 !== data5 && 0 !== data5 && 1 !== data5) return validate120.errors = [ { - instancePath: instancePath + "/detection", - schemaPath: "#/properties/detection/enum", - keyword: "enum", + if (!formats2.validate(data5)) return validate488.errors = [ { + instancePath: instancePath + "/classified_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", params: { - allowedValues: schema127.properties.detection.enum + format: "date-time" } } ], !1; var valid5 = 0 === _errs12; } else valid5 = !0; if (valid5) { - if (void 0 !== data.detection_label) { - let data6 = data.detection_label, _errs14 = 0; - if (0 === _errs14) { - if ("string" != typeof data6) return validate120.errors = [ { - instancePath: instancePath + "/detection_label", - schemaPath: "#/properties/detection_label/type", + if (void 0 !== data.input_asset_id) { + let data6 = data.input_asset_id, _errs15 = 0; + if ("string" != typeof data6) return validate488.errors = [ { + instancePath: instancePath + "/input_asset_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data6)) return validate488.errors = [ { + instancePath: instancePath + "/input_asset_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + var valid7 = 0 === _errs15; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.input_revision) { + let data7 = data.input_revision, _errs18 = 0; + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate488.errors = [ { + instancePath: instancePath + "/input_revision", + schemaPath: "#/properties/input_revision/type", keyword: "type", params: { - type: "string" - } - } ], !1; - if (func1(data6) > 255) return validate120.errors = [ { - instancePath: instancePath + "/detection_label", - schemaPath: "#/properties/detection_label/maxLength", - keyword: "maxLength", - params: { - limit: 255 + type: "integer" } } ], !1; - if (func1(data6) < 1) return validate120.errors = [ { - instancePath: instancePath + "/detection_label", - schemaPath: "#/properties/detection_label/minLength", - keyword: "minLength", + if (0 === _errs18 && "number" == typeof data7 && isFinite(data7) && (data7 < 1 || isNaN(data7))) return validate488.errors = [ { + instancePath: instancePath + "/input_revision", + schemaPath: "#/properties/input_revision/minimum", + keyword: "minimum", params: { + comparison: ">=", limit: 1 } } ], !1; - } - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.tag) { - let data7 = data.tag, _errs16 = 0; - if (0 === _errs16) { - if ("string" != typeof data7) return validate120.errors = [ { - instancePath: instancePath + "/tag", - schemaPath: "#/properties/tag/type", + var valid9 = 0 === _errs18; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.kind) { + let data8 = data.kind, _errs20 = 0; + if ("string" != typeof data8) return validate488.errors = [ { + instancePath: instancePath + "/kind", + schemaPath: "#/properties/kind/type", keyword: "type", params: { type: "string" } } ], !1; - if (func1(data7) > 511) return validate120.errors = [ { - instancePath: instancePath + "/tag", - schemaPath: "#/properties/tag/maxLength", - keyword: "maxLength", - params: { - limit: 511 - } - } ], !1; - if (func1(data7) < 1) return validate120.errors = [ { - instancePath: instancePath + "/tag", - schemaPath: "#/properties/tag/minLength", - keyword: "minLength", + if ("automated" !== data8 && "owner_confirmed" !== data8) return validate488.errors = [ { + instancePath: instancePath + "/kind", + schemaPath: "#/properties/kind/enum", + keyword: "enum", params: { - limit: 1 + allowedValues: schema596.properties.kind.enum } } ], !1; - } - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.capture_method) { - let data8 = data.capture_method, _errs18 = 0; - if (0 === _errs18) { - if ("string" != typeof data8) return validate120.errors = [ { - instancePath: instancePath + "/capture_method", - schemaPath: "#/properties/capture_method/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data8) > 127) return validate120.errors = [ { - instancePath: instancePath + "/capture_method", - schemaPath: "#/properties/capture_method/maxLength", - keyword: "maxLength", - params: { - limit: 127 - } - } ], !1; - if (func1(data8) < 1) return validate120.errors = [ { - instancePath: instancePath + "/capture_method", - schemaPath: "#/properties/capture_method/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data.protected) { - let data9 = data.protected, _errs20 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate120.errors = [ { - instancePath: instancePath + "/protected", - schemaPath: "#/properties/protected/type", + var valid10 = 0 === _errs20; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.expected_revision) { + let data9 = data.expected_revision, _errs22 = 0; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate488.errors = [ { + instancePath: instancePath + "/expected_revision", + schemaPath: "#/properties/expected_revision/type", keyword: "type", params: { type: "integer" } } ], !1; - if (-1 !== data9 && 0 !== data9 && 1 !== data9) return validate120.errors = [ { - instancePath: instancePath + "/protected", - schemaPath: "#/properties/protected/enum", - keyword: "enum", + if (0 === _errs22 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate488.errors = [ { + instancePath: instancePath + "/expected_revision", + schemaPath: "#/properties/expected_revision/minimum", + keyword: "minimum", params: { - allowedValues: schema127.properties.protected.enum + comparison: ">=", + limit: 0 } } ], !1; - var valid9 = 0 === _errs20; - } else valid9 = !0; - if (valid9) for (let key1 in data) if (pattern20.test(key1)) { - let data10 = data[key1], _errs22 = 0; - if (0 === _errs22) { - if ("string" != typeof data10) return validate120.errors = [ { - instancePath: instancePath + "/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), - schemaPath: "#/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/type", + var valid11 = 0 === _errs22; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.writer_namespace) { + let _errs24 = 0; + if ("string" != typeof data.writer_namespace) return validate488.errors = [ { + instancePath: instancePath + "/writer_namespace", + schemaPath: "#/properties/writer_namespace/type", keyword: "type", params: { type: "string" } } ], !1; - if (func1(data10) > 64) return validate120.errors = [ { - instancePath: instancePath + "/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), - schemaPath: "#/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/maxLength", - keyword: "maxLength", - params: { - limit: 64 - } - } ], !1; - if (func1(data10) < 1) return validate120.errors = [ { - instancePath: instancePath + "/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), - schemaPath: "#/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - if (0 !== _errs22) break; + var valid12 = 0 === _errs24; + } else valid12 = !0; + if (valid12 && void 0 !== data.writer && "string" != typeof data.writer) return validate488.errors = [ { + instancePath: instancePath + "/writer", + schemaPath: "#/properties/writer/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; } } } @@ -77688,377 +78678,416 @@ function validate120(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate120.errors = [], !0; -} - -validate120.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_RecordingDeletionJobAccepted = validate122; - -function validate122(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate122.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate122.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.job_id) return validate122.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "job_id" - } - } ], !1; - if (void 0 === data.status) return validate122.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "status" - } - } ], !1; - if (void 0 !== data.job_id) { - let _errs2 = 0; - if ("string" != typeof data.job_id) return validate122.errors = [ { - instancePath: instancePath + "/job_id", - schemaPath: "#/properties/job_id/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - return valid0 && void 0 !== data.status && "started" !== data.status ? (validate122.errors = [ { - instancePath: instancePath + "/status", - schemaPath: "#/properties/status/const", - keyword: "const", - params: { - allowedValue: "started" - } - } ], !1) : (validate122.errors = [], !0); + return validate488.errors = [], !0; } -validate122.evaluated = { +validate488.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_RecordingDeletionProgress = validate123, schema131 = { +var __contract_ObservationError = validate498, schema618 = { type: "object", properties: { - job_id: { + error: { type: "string" }, - status: { + code: { type: "string", - enum: [ "pending", "running", "complete", "error", "unknown" ] + enum: [ "invalid_request", "invalid_cursor", "forbidden", "namespace_forbidden", "observation_not_found", "evidence_unavailable", "evidence_pending", "evidence_expired", "cursor_expired", "revision_conflict", "owner_decision_locked", "annotation_limit", "evidence_mismatch", "not_implemented", "service_unavailable", "internal_error" ] }, - total: { - type: "integer", - minimum: 0 + field: { + type: "string" }, - current: { - type: "integer", - minimum: 0 + reason: { + $ref: "#/$defs/ObservationEvidenceReason" }, - succeeded: { + retry_after_seconds: { type: "integer", - minimum: 0 + minimum: 1, + maximum: 30 }, - pending_deletions: { - type: "integer", - minimum: 0 + expired_at: { + anyOf: [ { + $ref: "#/$defs/ObservationTime" + }, { + type: "null" + } ] }, - failed: { - type: "integer", - minimum: 0 + oldest_cursor: { + type: "string", + minLength: 1, + maxLength: 256 }, - status_message: { - type: "string" + oldest_observed_at: { + anyOf: [ { + $ref: "#/$defs/ObservationTime" + }, { + type: "null" + } ] }, - complete: { - type: "boolean" + current: { + $ref: "#/$defs/ObservationClassification" }, - error_message: { - type: "string" + limit: { + type: "integer", + minimum: 1 + }, + observation_asset_id: { + $ref: "#/$defs/ObservationUuid" + }, + evidence_state: { + $ref: "#/$defs/ObservationEvidenceState" } }, - required: [ "job_id", "status", "total", "current", "succeeded", "pending_deletions", "failed", "status_message", "complete" ], + required: [ "error", "code" ], additionalProperties: !0, - description: "complete is true for both complete and error terminal states. A complete job can have failed members or queued pending deletions; it does not imply every file is already removed. SQL failure is error; an empty successful filter completes with zero counts. No cancellation endpoint/state exists." + description: "Coded error (contract §11, D2): a valid ApiError with a string error and a stable code. Clients branch on status and code, never on the message." }; -function validate123(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate123.evaluated; +function validate498(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate498.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.job_id) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "job_id" - } - } ], !1; - if (void 0 === data.status) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "status" - } - } ], !1; - if (void 0 === data.total) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "total" - } - } ], !1; - if (void 0 === data.current) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "current" - } - } ], !1; - if (void 0 === data.succeeded) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "succeeded" - } - } ], !1; - if (void 0 === data.pending_deletions) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "pending_deletions" - } - } ], !1; - if (void 0 === data.failed) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "failed" - } - } ], !1; - if (void 0 === data.status_message) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "status_message" - } - } ], !1; - if (void 0 === data.complete) return validate123.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "complete" - } - } ], !1; - if (void 0 !== data.job_id) { - let _errs2 = 0; - if ("string" != typeof data.job_id) return validate123.errors = [ { - instancePath: instancePath + "/job_id", - schemaPath: "#/properties/job_id/type", + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate498.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", keyword: "type", params: { - type: "string" + type: "object" } } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.status) { - let data1 = data.status, _errs4 = 0; - if ("string" != typeof data1) return validate123.errors = [ { - instancePath: instancePath + "/status", - schemaPath: "#/properties/status/type", + if (void 0 === data.error) return validate498.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "error" + } + } ], !1; + if (void 0 === data.code) return validate498.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "code" + } + } ], !1; + if (void 0 !== data.error) { + let _errs2 = errors; + if ("string" != typeof data.error) return validate498.errors = [ { + instancePath: instancePath + "/error", + schemaPath: "#/properties/error/type", keyword: "type", params: { type: "string" } } ], !1; - if ("pending" !== data1 && "running" !== data1 && "complete" !== data1 && "error" !== data1 && "unknown" !== data1) return validate123.errors = [ { - instancePath: instancePath + "/status", - schemaPath: "#/properties/status/enum", - keyword: "enum", - params: { - allowedValues: schema131.properties.status.enum - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.total) { - let data2 = data.total, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate123.errors = [ { - instancePath: instancePath + "/total", - schemaPath: "#/properties/total/type", + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.code) { + let data1 = data.code, _errs4 = errors; + if ("string" != typeof data1) return validate498.errors = [ { + instancePath: instancePath + "/code", + schemaPath: "#/properties/code/type", keyword: "type", params: { - type: "integer" + type: "string" } } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate123.errors = [ { - instancePath: instancePath + "/total", - schemaPath: "#/properties/total/minimum", - keyword: "minimum", + if ("invalid_request" !== data1 && "invalid_cursor" !== data1 && "forbidden" !== data1 && "namespace_forbidden" !== data1 && "observation_not_found" !== data1 && "evidence_unavailable" !== data1 && "evidence_pending" !== data1 && "evidence_expired" !== data1 && "cursor_expired" !== data1 && "revision_conflict" !== data1 && "owner_decision_locked" !== data1 && "annotation_limit" !== data1 && "evidence_mismatch" !== data1 && "not_implemented" !== data1 && "service_unavailable" !== data1 && "internal_error" !== data1) return validate498.errors = [ { + instancePath: instancePath + "/code", + schemaPath: "#/properties/code/enum", + keyword: "enum", params: { - comparison: ">=", - limit: 0 + allowedValues: schema618.properties.code.enum } } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2) { - if (void 0 !== data.current) { - let data3 = data.current, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate123.errors = [ { - instancePath: instancePath + "/current", - schemaPath: "#/properties/current/type", + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.field) { + let _errs6 = errors; + if ("string" != typeof data.field) return validate498.errors = [ { + instancePath: instancePath + "/field", + schemaPath: "#/properties/field/type", keyword: "type", params: { - type: "integer" - } - } ], !1; - if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate123.errors = [ { - instancePath: instancePath + "/current", - schemaPath: "#/properties/current/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 + type: "string" } } ], !1; - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data.succeeded) { - let data4 = data.succeeded, _errs10 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate123.errors = [ { - instancePath: instancePath + "/succeeded", - schemaPath: "#/properties/succeeded/type", + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.reason) { + let data3 = data.reason, _errs8 = errors; + if ("string" != typeof data3) return validate498.errors = [ { + instancePath: instancePath + "/reason", + schemaPath: "#/$defs/ObservationEvidenceReason/type", keyword: "type", params: { - type: "integer" + type: "string" } } ], !1; - if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate123.errors = [ { - instancePath: instancePath + "/succeeded", - schemaPath: "#/properties/succeeded/minimum", - keyword: "minimum", + if ("queue_full" !== data3 && "budget_exhausted" !== data3 && "encode_failed" !== data3 && "write_failed" !== data3 && "interrupted" !== data3 && "no_matching_media" !== data3 && "disabled" !== data3 && "retention_expired" !== data3 && "deleted" !== data3) return validate498.errors = [ { + instancePath: instancePath + "/reason", + schemaPath: "#/$defs/ObservationEvidenceReason/enum", + keyword: "enum", params: { - comparison: ">=", - limit: 0 + allowedValues: schema575.enum } } ], !1; - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data.pending_deletions) { - let data5 = data.pending_deletions, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate123.errors = [ { - instancePath: instancePath + "/pending_deletions", - schemaPath: "#/properties/pending_deletions/type", + var valid3 = _errs8 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.retry_after_seconds) { + let data4 = data.retry_after_seconds, _errs11 = errors; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate498.errors = [ { + instancePath: instancePath + "/retry_after_seconds", + schemaPath: "#/properties/retry_after_seconds/type", keyword: "type", params: { type: "integer" } } ], !1; - if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate123.errors = [ { - instancePath: instancePath + "/pending_deletions", - schemaPath: "#/properties/pending_deletions/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data.failed) { - let data6 = data.failed, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate123.errors = [ { - instancePath: instancePath + "/failed", - schemaPath: "#/properties/failed/type", - keyword: "type", + if (errors === _errs11 && "number" == typeof data4 && isFinite(data4)) { + if (data4 > 30 || isNaN(data4)) return validate498.errors = [ { + instancePath: instancePath + "/retry_after_seconds", + schemaPath: "#/properties/retry_after_seconds/maximum", + keyword: "maximum", params: { - type: "integer" + comparison: "<=", + limit: 30 } } ], !1; - if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate123.errors = [ { - instancePath: instancePath + "/failed", - schemaPath: "#/properties/failed/minimum", + if (data4 < 1 || isNaN(data4)) return validate498.errors = [ { + instancePath: instancePath + "/retry_after_seconds", + schemaPath: "#/properties/retry_after_seconds/minimum", keyword: "minimum", params: { comparison: ">=", - limit: 0 + limit: 1 } } ], !1; - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data.status_message) { - let _errs16 = 0; - if ("string" != typeof data.status_message) return validate123.errors = [ { - instancePath: instancePath + "/status_message", - schemaPath: "#/properties/status_message/type", + } + var valid5 = _errs11 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.expired_at) { + let data5 = data.expired_at, _errs13 = errors, _errs14 = errors, valid7 = !1, _errs15 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data5) { + if (!formats2.validate(data5)) { + let err0 = { + instancePath: instancePath + "/expired_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err0), errors++; + } + } else { + let err1 = { + instancePath: instancePath + "/expired_at", + schemaPath: "#/$defs/ObservationTime/type", keyword: "type", params: { type: "string" } - } ], !1; - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data.complete) { - let _errs18 = 0; - if ("boolean" != typeof data.complete) return validate123.errors = [ { - instancePath: instancePath + "/complete", - schemaPath: "#/properties/complete/type", + }; + vErrors.push(err1), errors++; + } + var _valid0 = _errs15 === errors; + valid7 = valid7 || _valid0; + let _errs18 = errors; + if (null !== data5) { + let err2 = { + instancePath: instancePath + "/expired_at", + schemaPath: "#/properties/expired_at/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err2), errors++; + } + if (_valid0 = _errs18 === errors, valid7 = valid7 || _valid0, !valid7) { + let err3 = { + instancePath: instancePath + "/expired_at", + schemaPath: "#/properties/expired_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err3), errors++, validate498.errors = vErrors, !1; + } + errors = _errs14, vErrors.length = _errs14; + var valid6 = _errs13 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.oldest_cursor) { + let data6 = data.oldest_cursor, _errs20 = errors; + if (errors === _errs20) { + if ("string" != typeof data6) return validate498.errors = [ { + instancePath: instancePath + "/oldest_cursor", + schemaPath: "#/properties/oldest_cursor/type", keyword: "type", params: { - type: "boolean" + type: "string" } } ], !1; - var valid8 = 0 === _errs18; - } else valid8 = !0; - if (valid8 && void 0 !== data.error_message && "string" != typeof data.error_message) return validate123.errors = [ { - instancePath: instancePath + "/error_message", - schemaPath: "#/properties/error_message/type", - keyword: "type", - params: { - type: "string" + if (func1(data6) > 256) return validate498.errors = [ { + instancePath: instancePath + "/oldest_cursor", + schemaPath: "#/properties/oldest_cursor/maxLength", + keyword: "maxLength", + params: { + limit: 256 + } + } ], !1; + if (func1(data6) < 1) return validate498.errors = [ { + instancePath: instancePath + "/oldest_cursor", + schemaPath: "#/properties/oldest_cursor/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid9 = _errs20 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.oldest_observed_at) { + let data7 = data.oldest_observed_at, _errs22 = errors, _errs23 = errors, valid11 = !1, _errs24 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data7) { + if (!formats2.validate(data7)) { + let err4 = { + instancePath: instancePath + "/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err4), errors++; + } + } else { + let err5 = { + instancePath: instancePath + "/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err5), errors++; } - } ], !1; + var _valid1 = _errs24 === errors; + valid11 = valid11 || _valid1; + let _errs27 = errors; + if (null !== data7) { + let err6 = { + instancePath: instancePath + "/oldest_observed_at", + schemaPath: "#/properties/oldest_observed_at/anyOf/1/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err6), errors++; + } + if (_valid1 = _errs27 === errors, valid11 = valid11 || _valid1, !valid11) { + let err7 = { + instancePath: instancePath + "/oldest_observed_at", + schemaPath: "#/properties/oldest_observed_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err7), errors++, validate498.errors = vErrors, !1; + } + errors = _errs23, vErrors.length = _errs23; + var valid10 = _errs22 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.current) { + let _errs29 = errors; + validate369(data.current, { + instancePath: instancePath + "/current", + parentData: data, + parentDataProperty: "current", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate369.errors), errors = vErrors.length); + var valid13 = _errs29 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.limit) { + let data9 = data.limit, _errs30 = errors; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate498.errors = [ { + instancePath: instancePath + "/limit", + schemaPath: "#/properties/limit/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs30 && "number" == typeof data9 && isFinite(data9) && (data9 < 1 || isNaN(data9))) return validate498.errors = [ { + instancePath: instancePath + "/limit", + schemaPath: "#/properties/limit/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid14 = _errs30 === errors; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.observation_asset_id) { + let data10 = data.observation_asset_id, _errs32 = errors; + if (errors == errors) { + if ("string" != typeof data10) return validate498.errors = [ { + instancePath: instancePath + "/observation_asset_id", + schemaPath: "#/$defs/ObservationUuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern159.test(data10)) return validate498.errors = [ { + instancePath: instancePath + "/observation_asset_id", + schemaPath: "#/$defs/ObservationUuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + } + } ], !1; + } + var valid15 = _errs32 === errors; + } else valid15 = !0; + if (valid15 && void 0 !== data.evidence_state) { + let data11 = data.evidence_state; + if ("string" != typeof data11) return validate498.errors = [ { + instancePath: instancePath + "/evidence_state", + schemaPath: "#/$defs/ObservationEvidenceState/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("pending" !== data11 && "ready" !== data11 && "unavailable" !== data11 && "expired" !== data11) return validate498.errors = [ { + instancePath: instancePath + "/evidence_state", + schemaPath: "#/$defs/ObservationEvidenceState/enum", + keyword: "enum", + params: { + allowedValues: schema574.enum + } + } ], !1; + } + } + } + } } } } @@ -78067,527 +79096,8137 @@ function validate123(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate123.errors = [], !0; + return validate498.errors = vErrors, 0 === errors; } -validate123.evaluated = { +validate498.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_RecordingDeletionRequest = validate121, schema128 = { - anyOf: [ { - type: "object", - properties: { - ids: { - type: "array", - items: {}, - minItems: 1 +var __contract_ObservationEvidence = validate483, __contract_ObservationPage = validate495; + +function validate495(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate495.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate495.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" } - }, - required: [ "ids" ], - additionalProperties: !0, - description: "IDs take precedence over filter. IDs accept safe positive integer JSON numbers or exact positive decimal uint64 strings. Invalid, unsafe numeric, or missing members become failed counts; a forbidden existing member rejects the whole request before job creation." - }, { - type: "object", - properties: { - filter: { - $ref: "#/$defs/RecordingDeletionFilter" - }, - ids: { - anyOf: [ { - type: "array", - items: {}, - minItems: 1 - }, { - type: [ "null", "boolean", "object", "number", "string" ] - } ], - description: "Array-valued ids take precedence over filter and must be nonempty. Other ids values fall through to the filter handler." + } ], !1; + if (void 0 === data.items) return validate495.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "items" } - }, - required: [ "filter" ], - additionalProperties: !0 - } ] -}; - -function validate121(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate121.evaluated; - evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); - let _errs0 = errors, valid0 = !1, _errs1 = errors; - if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) { - if (void 0 === data.ids) { - let err0 = { - instancePath: instancePath, - schemaPath: "#/anyOf/0/required", - keyword: "required", - params: { - missingProperty: "ids" - } - }; - vErrors.push(err0), errors++; - } else if (void 0 !== data.ids) { - let data0 = data.ids; - if (errors == errors) if (Array.isArray(data0)) { - if (data0.length < 1) { - let err1 = { - instancePath: instancePath + "/ids", - schemaPath: "#/anyOf/0/properties/ids/minItems", - keyword: "minItems", - params: { - limit: 1 - } - }; - vErrors.push(err1), errors++; - } - } else { - let err2 = { - instancePath: instancePath + "/ids", - schemaPath: "#/anyOf/0/properties/ids/type", + } ], !1; + if (void 0 === data.next_cursor) return validate495.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "next_cursor" + } + } ], !1; + if (void 0 === data.has_more) return validate495.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_more" + } + } ], !1; + if (void 0 !== data.items) { + let data0 = data.items, _errs2 = errors; + if (errors === _errs2) { + if (!Array.isArray(data0)) return validate495.errors = [ { + instancePath: instancePath + "/items", + schemaPath: "#/properties/items/type", keyword: "type", params: { type: "array" } - }; - vErrors.push(err2), errors++; + } ], !1; + if (data0.length > 500) return validate495.errors = [ { + instancePath: instancePath + "/items", + schemaPath: "#/properties/items/maxItems", + keyword: "maxItems", + params: { + limit: 500 + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs4 = errors; + if (validate491(data0[i0], { + instancePath: instancePath + "/items/" + i0, + parentData: data0, + parentDataProperty: i0, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate491.errors), errors = vErrors.length), _errs4 !== errors) break; + } + } } + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.next_cursor) { + let data2 = data.next_cursor, _errs5 = errors; + if (errors === _errs5) { + if ("string" != typeof data2) return validate495.errors = [ { + instancePath: instancePath + "/next_cursor", + schemaPath: "#/properties/next_cursor/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 256) return validate495.errors = [ { + instancePath: instancePath + "/next_cursor", + schemaPath: "#/properties/next_cursor/maxLength", + keyword: "maxLength", + params: { + limit: 256 + } + } ], !1; + if (func1(data2) < 1) return validate495.errors = [ { + instancePath: instancePath + "/next_cursor", + schemaPath: "#/properties/next_cursor/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2 && void 0 !== data.has_more && "boolean" != typeof data.has_more) return validate495.errors = [ { + instancePath: instancePath + "/has_more", + schemaPath: "#/properties/has_more/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; } - } else { - let err3 = { + } + return validate495.errors = vErrors, 0 === errors; +} + +validate495.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_ObservationPipelineDiagnostics = validate266; + +function validate266(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate266.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate266.errors = [ { instancePath: instancePath, - schemaPath: "#/anyOf/0/type", + schemaPath: "#/type", keyword: "type", params: { type: "object" } - }; - vErrors.push(err3), errors++; - } - var _valid0 = _errs1 === errors; - if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; - let _errs6 = errors; - if (errors === _errs6) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.filter) { - let err4 = { + } ], !1; + if (void 0 === data.active) return validate266.errors = [ { instancePath: instancePath, - schemaPath: "#/anyOf/1/required", + schemaPath: "#/required", keyword: "required", params: { - missingProperty: "filter" + missingProperty: "active" } - }; - vErrors.push(err4), errors++; - } else { - if (void 0 !== data.filter) { - let data1 = data.filter, _errs9 = errors; - if (errors == errors) if (data1 && "object" == typeof data1 && !Array.isArray(data1)) { - let _errs12 = errors; - for (let key0 in data1) if (!func5.call(schema127.properties, key0) && !pattern20.test(key0)) { - let err5 = { - instancePath: instancePath + "/filter", - schemaPath: "#/$defs/RecordingDeletionFilter/additionalProperties", - keyword: "additionalProperties", - params: { - additionalProperty: key0 - } - }; - vErrors.push(err5), errors++; - break; + } ], !1; + if (void 0 === data.accepted) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "accepted" + } + } ], !1; + if (void 0 === data.dropped) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "dropped" + } + } ], !1; + if (void 0 === data.evidence) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "evidence" + } + } ], !1; + if (void 0 === data.history) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "history" + } + } ], !1; + if (void 0 === data.publications) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "publications" + } + } ], !1; + if (void 0 === data.budget_exhausted_cameras) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "budget_exhausted_cameras" + } + } ], !1; + if (void 0 === data.cameras) return validate266.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "cameras" + } + } ], !1; + if (void 0 !== data.active) { + let _errs2 = errors; + if ("boolean" != typeof data.active) return validate266.errors = [ { + instancePath: instancePath + "/active", + schemaPath: "#/properties/active/type", + keyword: "type", + params: { + type: "boolean" } - if (_errs12 === errors) { - if (void 0 !== data1.stream_name) { - let data2 = data1.stream_name, _errs13 = errors; - if (errors === _errs13) if ("string" == typeof data2) { - if (func1(data2) > 255) { - let err6 = { - instancePath: instancePath + "/filter/stream_name", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream_name/maxLength", - keyword: "maxLength", - params: { - limit: 255 - } - }; - vErrors.push(err6), errors++; - } else if (func1(data2) < 1) { - let err7 = { - instancePath: instancePath + "/filter/stream_name", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream_name/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err7), errors++; + } ], !1; + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.accepted) { + let data1 = data.accepted, _errs4 = errors; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate266.errors = [ { + instancePath: instancePath + "/accepted", + schemaPath: "#/properties/accepted/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate266.errors = [ { + instancePath: instancePath + "/accepted", + schemaPath: "#/properties/accepted/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.dropped) { + let data2 = data.dropped, _errs6 = errors; + if (errors === _errs6) { + if (!data2 || "object" != typeof data2 || Array.isArray(data2)) return validate266.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/type", + keyword: "type", + params: { + type: "object" } - } else { - let err8 = { - instancePath: instancePath + "/filter/stream_name", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream_name/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err8), errors++; - } - var valid4 = _errs13 === errors; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data1.stream) { - let data3 = data1.stream, _errs15 = errors; - if (errors === _errs15) if ("string" == typeof data3) { - if (func1(data3) > 255) { - let err9 = { - instancePath: instancePath + "/filter/stream", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream/maxLength", - keyword: "maxLength", + } ], !1; + if (void 0 === data2.queue_full) return validate266.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", + params: { + missingProperty: "queue_full" + } + } ], !1; + if (void 0 === data2.budget_exhausted) return validate266.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", + params: { + missingProperty: "budget_exhausted" + } + } ], !1; + if (void 0 === data2.disabled) return validate266.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", + params: { + missingProperty: "disabled" + } + } ], !1; + if (void 0 === data2.untracked) return validate266.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", + params: { + missingProperty: "untracked" + } + } ], !1; + if (void 0 !== data2.queue_full) { + let data3 = data2.queue_full, _errs9 = errors; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate266.errors = [ { + instancePath: instancePath + "/dropped/queue_full", + schemaPath: "#/properties/dropped/properties/queue_full/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate266.errors = [ { + instancePath: instancePath + "/dropped/queue_full", + schemaPath: "#/properties/dropped/properties/queue_full/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid3 = _errs9 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data2.budget_exhausted) { + let data4 = data2.budget_exhausted, _errs11 = errors; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate266.errors = [ { + instancePath: instancePath + "/dropped/budget_exhausted", + schemaPath: "#/properties/dropped/properties/budget_exhausted/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate266.errors = [ { + instancePath: instancePath + "/dropped/budget_exhausted", + schemaPath: "#/properties/dropped/properties/budget_exhausted/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid4 = _errs11 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data2.disabled) { + let data5 = data2.disabled, _errs13 = errors; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate266.errors = [ { + instancePath: instancePath + "/dropped/disabled", + schemaPath: "#/properties/dropped/properties/disabled/type", + keyword: "type", params: { - limit: 255 + type: "integer" } - }; - vErrors.push(err9), errors++; - } else if (func1(data3) < 1) { - let err10 = { - instancePath: instancePath + "/filter/stream", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream/minLength", - keyword: "minLength", + } ], !1; + if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate266.errors = [ { + instancePath: instancePath + "/dropped/disabled", + schemaPath: "#/properties/dropped/properties/disabled/minimum", + keyword: "minimum", params: { - limit: 1 + comparison: ">=", + limit: 0 } - }; - vErrors.push(err10), errors++; + } ], !1; + var valid5 = _errs13 === errors; + } else valid5 = !0; + if (valid5 && void 0 !== data2.untracked) { + let data6 = data2.untracked, _errs15 = errors; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate266.errors = [ { + instancePath: instancePath + "/dropped/untracked", + schemaPath: "#/properties/dropped/properties/untracked/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate266.errors = [ { + instancePath: instancePath + "/dropped/untracked", + schemaPath: "#/properties/dropped/properties/untracked/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; } - } else { - let err11 = { - instancePath: instancePath + "/filter/stream", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream/type", + } + } + } + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.evidence) { + let data7 = data.evidence, _errs17 = errors; + if (errors === _errs17) { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data7.queue_allocated) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "queue_allocated" + } + } ], !1; + if (void 0 === data7.worker_running) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "worker_running" + } + } ], !1; + if (void 0 === data7.backlog_items) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "backlog_items" + } + } ], !1; + if (void 0 === data7.backlog_bytes) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "backlog_bytes" + } + } ], !1; + if (void 0 === data7.high_water_items) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "high_water_items" + } + } ], !1; + if (void 0 === data7.high_water_bytes) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "high_water_bytes" + } + } ], !1; + if (void 0 === data7.drop_ring_depth) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "drop_ring_depth" + } + } ], !1; + if (void 0 === data7.ready) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "ready" + } + } ], !1; + if (void 0 === data7.unavailable_submitted) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "unavailable_submitted" + } + } ], !1; + if (void 0 === data7.bytes_written) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "bytes_written" + } + } ], !1; + if (void 0 === data7.failures) return validate266.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "failures" + } + } ], !1; + if (void 0 !== data7.queue_allocated) { + let _errs20 = errors; + if ("boolean" != typeof data7.queue_allocated) return validate266.errors = [ { + instancePath: instancePath + "/evidence/queue_allocated", + schemaPath: "#/properties/evidence/properties/queue_allocated/type", keyword: "type", params: { - type: "string" - } - }; - vErrors.push(err11), errors++; - } - var valid5 = _errs15 === errors; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data1.collection_uuid) { - let data4 = data1.collection_uuid, _errs17 = errors; - if (errors === _errs17) if ("string" == typeof data4) { - if (func1(data4) > 36) { - let err12 = { - instancePath: instancePath + "/filter/collection_uuid", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/collection_uuid/maxLength", - keyword: "maxLength", - params: { - limit: 36 - } - }; - vErrors.push(err12), errors++; - } else if (func1(data4) < 1) { - let err13 = { - instancePath: instancePath + "/filter/collection_uuid", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/collection_uuid/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err13), errors++; + type: "boolean" } - } else { - let err14 = { - instancePath: instancePath + "/filter/collection_uuid", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/collection_uuid/type", + } ], !1; + var valid8 = _errs20 === errors; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data7.worker_running) { + let _errs22 = errors; + if ("boolean" != typeof data7.worker_running) return validate266.errors = [ { + instancePath: instancePath + "/evidence/worker_running", + schemaPath: "#/properties/evidence/properties/worker_running/type", keyword: "type", params: { - type: "string" - } - }; - vErrors.push(err14), errors++; - } - var valid6 = _errs17 === errors; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data1.start) { - let data5 = data1.start, _errs19 = errors; - if (errors === _errs19) if ("string" == typeof data5) { - if (func1(data5) > 64) { - let err15 = { - instancePath: instancePath + "/filter/start", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/start/maxLength", - keyword: "maxLength", - params: { - limit: 64 - } - }; - vErrors.push(err15), errors++; - } else if (func1(data5) < 1) { - let err16 = { - instancePath: instancePath + "/filter/start", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/start/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err16), errors++; + type: "boolean" } - } else { - let err17 = { - instancePath: instancePath + "/filter/start", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/start/type", + } ], !1; + var valid9 = _errs22 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data7.backlog_items) { + let data10 = data7.backlog_items, _errs24 = errors; + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/backlog_items", + schemaPath: "#/properties/evidence/properties/backlog_items/type", keyword: "type", params: { - type: "string" + type: "integer" } - }; - vErrors.push(err17), errors++; - } - var valid7 = _errs19 === errors; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data1.end) { - let data6 = data1.end, _errs21 = errors; - if (errors === _errs21) if ("string" == typeof data6) { - if (func1(data6) > 64) { - let err18 = { - instancePath: instancePath + "/filter/end", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/end/maxLength", - keyword: "maxLength", - params: { - limit: 64 - } - }; - vErrors.push(err18), errors++; - } else if (func1(data6) < 1) { - let err19 = { - instancePath: instancePath + "/filter/end", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/end/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err19), errors++; + } ], !1; + if (errors === _errs24 && "number" == typeof data10 && isFinite(data10) && (data10 < 0 || isNaN(data10))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/backlog_items", + schemaPath: "#/properties/evidence/properties/backlog_items/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 } - } else { - let err20 = { - instancePath: instancePath + "/filter/end", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/end/type", + } ], !1; + var valid10 = _errs24 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data7.backlog_bytes) { + let data11 = data7.backlog_bytes, _errs26 = errors; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/backlog_bytes", + schemaPath: "#/properties/evidence/properties/backlog_bytes/type", keyword: "type", params: { - type: "string" + type: "integer" } - }; - vErrors.push(err20), errors++; - } - var valid8 = _errs21 === errors; - } else valid8 = !0; - if (valid8) { - if (void 0 !== data1.detection) { - let data7 = data1.detection, _errs23 = errors; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) { - let err21 = { - instancePath: instancePath + "/filter/detection", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection/type", + } ], !1; + if (errors === _errs26 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/backlog_bytes", + schemaPath: "#/properties/evidence/properties/backlog_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid11 = _errs26 === errors; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data7.high_water_items) { + let data12 = data7.high_water_items, _errs28 = errors; + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/high_water_items", + schemaPath: "#/properties/evidence/properties/high_water_items/type", keyword: "type", params: { type: "integer" } - }; - vErrors.push(err21), errors++; - } - if (-1 !== data7 && 0 !== data7 && 1 !== data7) { - let err22 = { - instancePath: instancePath + "/filter/detection", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection/enum", - keyword: "enum", + } ], !1; + if (errors === _errs28 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/high_water_items", + schemaPath: "#/properties/evidence/properties/high_water_items/minimum", + keyword: "minimum", params: { - allowedValues: schema127.properties.detection.enum - } - }; - vErrors.push(err22), errors++; - } - var valid9 = _errs23 === errors; - } else valid9 = !0; - if (valid9) { - if (void 0 !== data1.detection_label) { - let data8 = data1.detection_label, _errs25 = errors; - if (errors === _errs25) if ("string" == typeof data8) { - if (func1(data8) > 255) { - let err23 = { - instancePath: instancePath + "/filter/detection_label", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection_label/maxLength", - keyword: "maxLength", - params: { - limit: 255 - } - }; - vErrors.push(err23), errors++; - } else if (func1(data8) < 1) { - let err24 = { - instancePath: instancePath + "/filter/detection_label", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection_label/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err24), errors++; + comparison: ">=", + limit: 0 } - } else { - let err25 = { - instancePath: instancePath + "/filter/detection_label", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection_label/type", + } ], !1; + var valid12 = _errs28 === errors; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data7.high_water_bytes) { + let data13 = data7.high_water_bytes, _errs30 = errors; + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/high_water_bytes", + schemaPath: "#/properties/evidence/properties/high_water_bytes/type", keyword: "type", params: { - type: "string" + type: "integer" } - }; - vErrors.push(err25), errors++; - } - var valid10 = _errs25 === errors; - } else valid10 = !0; - if (valid10) { - if (void 0 !== data1.tag) { - let data9 = data1.tag, _errs27 = errors; - if (errors === _errs27) if ("string" == typeof data9) { - if (func1(data9) > 511) { - let err26 = { - instancePath: instancePath + "/filter/tag", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/tag/maxLength", - keyword: "maxLength", - params: { - limit: 511 - } - }; - vErrors.push(err26), errors++; - } else if (func1(data9) < 1) { - let err27 = { - instancePath: instancePath + "/filter/tag", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/tag/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err27), errors++; + } ], !1; + if (errors === _errs30 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/high_water_bytes", + schemaPath: "#/properties/evidence/properties/high_water_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 } - } else { - let err28 = { - instancePath: instancePath + "/filter/tag", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/tag/type", + } ], !1; + var valid13 = _errs30 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data7.drop_ring_depth) { + let data14 = data7.drop_ring_depth, _errs32 = errors; + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/drop_ring_depth", + schemaPath: "#/properties/evidence/properties/drop_ring_depth/type", keyword: "type", params: { - type: "string" + type: "integer" } - }; - vErrors.push(err28), errors++; - } - var valid11 = _errs27 === errors; - } else valid11 = !0; - if (valid11) { - if (void 0 !== data1.capture_method) { - let data10 = data1.capture_method, _errs29 = errors; - if (errors === _errs29) if ("string" == typeof data10) { - if (func1(data10) > 127) { - let err29 = { - instancePath: instancePath + "/filter/capture_method", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/capture_method/maxLength", - keyword: "maxLength", - params: { - limit: 127 - } - }; - vErrors.push(err29), errors++; - } else if (func1(data10) < 1) { - let err30 = { - instancePath: instancePath + "/filter/capture_method", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/capture_method/minLength", - keyword: "minLength", - params: { - limit: 1 - } - }; - vErrors.push(err30), errors++; + } ], !1; + if (errors === _errs32 && "number" == typeof data14 && isFinite(data14) && (data14 < 0 || isNaN(data14))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/drop_ring_depth", + schemaPath: "#/properties/evidence/properties/drop_ring_depth/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 } - } else { - let err31 = { - instancePath: instancePath + "/filter/capture_method", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/capture_method/type", + } ], !1; + var valid14 = _errs32 === errors; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data7.ready) { + let data15 = data7.ready, _errs34 = errors; + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/ready", + schemaPath: "#/properties/evidence/properties/ready/type", keyword: "type", params: { - type: "string" + type: "integer" } - }; - vErrors.push(err31), errors++; - } - var valid12 = _errs29 === errors; - } else valid12 = !0; - if (valid12) { - if (void 0 !== data1.protected) { - let data11 = data1.protected, _errs31 = errors; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) { - let err32 = { - instancePath: instancePath + "/filter/protected", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/protected/type", + } ], !1; + if (errors === _errs34 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/ready", + schemaPath: "#/properties/evidence/properties/ready/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid15 = _errs34 === errors; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data7.unavailable_submitted) { + let data16 = data7.unavailable_submitted, _errs36 = errors; + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/unavailable_submitted", + schemaPath: "#/properties/evidence/properties/unavailable_submitted/type", keyword: "type", params: { type: "integer" } - }; - vErrors.push(err32), errors++; - } - if (-1 !== data11 && 0 !== data11 && 1 !== data11) { - let err33 = { - instancePath: instancePath + "/filter/protected", - schemaPath: "#/$defs/RecordingDeletionFilter/properties/protected/enum", - keyword: "enum", + } ], !1; + if (errors === _errs36 && "number" == typeof data16 && isFinite(data16) && (data16 < 0 || isNaN(data16))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/unavailable_submitted", + schemaPath: "#/properties/evidence/properties/unavailable_submitted/minimum", + keyword: "minimum", params: { - allowedValues: schema127.properties.protected.enum + comparison: ">=", + limit: 0 } - }; - vErrors.push(err33), errors++; - } - var valid13 = _errs31 === errors; - } else valid13 = !0; - if (valid13) for (let key1 in data1) if (pattern20.test(key1)) { - let data12 = data1[key1], _errs33 = errors; - if (errors === _errs33) if ("string" == typeof data12) { - if (func1(data12) > 64) { - let err34 = { - instancePath: instancePath + "/filter/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), - schemaPath: "#/$defs/RecordingDeletionFilter/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/maxLength", - keyword: "maxLength", + } ], !1; + var valid16 = _errs36 === errors; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data7.bytes_written) { + let data17 = data7.bytes_written, _errs38 = errors; + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/bytes_written", + schemaPath: "#/properties/evidence/properties/bytes_written/type", + keyword: "type", params: { - limit: 64 + type: "integer" } - }; - vErrors.push(err34), errors++; - } else if (func1(data12) < 1) { - let err35 = { - instancePath: instancePath + "/filter/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), - schemaPath: "#/$defs/RecordingDeletionFilter/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/minLength", - keyword: "minLength", + } ], !1; + if (errors === _errs38 && "number" == typeof data17 && isFinite(data17) && (data17 < 0 || isNaN(data17))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/bytes_written", + schemaPath: "#/properties/evidence/properties/bytes_written/minimum", + keyword: "minimum", params: { - limit: 1 + comparison: ">=", + limit: 0 } - }; - vErrors.push(err35), errors++; - } - } else { - let err36 = { - instancePath: instancePath + "/filter/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), - schemaPath: "#/$defs/RecordingDeletionFilter/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/type", - keyword: "type", - params: { - type: "string" - } - }; - vErrors.push(err36), errors++; - } - if (_errs33 !== errors) break; - } + } ], !1; + var valid17 = _errs38 === errors; + } else valid17 = !0; + if (valid17 && void 0 !== data7.failures) { + let data18 = data7.failures; + if (errors == errors) { + if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data18.encode_failed) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "encode_failed" + } + } ], !1; + if (void 0 === data18.write_failed) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "write_failed" + } + } ], !1; + if (void 0 === data18.db_error) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "db_error" + } + } ], !1; + if (void 0 === data18.disabled) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "disabled" + } + } ], !1; + if (void 0 === data18.interrupted) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "interrupted" + } + } ], !1; + if (void 0 !== data18.encode_failed) { + let data19 = data18.encode_failed, _errs43 = errors; + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/encode_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/encode_failed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs43 && "number" == typeof data19 && isFinite(data19) && (data19 < 0 || isNaN(data19))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/encode_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/encode_failed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid19 = _errs43 === errors; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data18.write_failed) { + let data20 = data18.write_failed, _errs45 = errors; + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/write_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/write_failed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs45 && "number" == typeof data20 && isFinite(data20) && (data20 < 0 || isNaN(data20))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/write_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/write_failed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid20 = _errs45 === errors; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data18.db_error) { + let data21 = data18.db_error, _errs47 = errors; + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/db_error", + schemaPath: "#/properties/evidence/properties/failures/properties/db_error/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs47 && "number" == typeof data21 && isFinite(data21) && (data21 < 0 || isNaN(data21))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/db_error", + schemaPath: "#/properties/evidence/properties/failures/properties/db_error/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid21 = _errs47 === errors; + } else valid21 = !0; + if (valid21) { + if (void 0 !== data18.disabled) { + let data22 = data18.disabled, _errs49 = errors; + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/disabled", + schemaPath: "#/properties/evidence/properties/failures/properties/disabled/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs49 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/disabled", + schemaPath: "#/properties/evidence/properties/failures/properties/disabled/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid22 = _errs49 === errors; + } else valid22 = !0; + if (valid22 && void 0 !== data18.interrupted) { + let data23 = data18.interrupted, _errs51 = errors; + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/interrupted", + schemaPath: "#/properties/evidence/properties/failures/properties/interrupted/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs51 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate266.errors = [ { + instancePath: instancePath + "/evidence/failures/interrupted", + schemaPath: "#/properties/evidence/properties/failures/properties/interrupted/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + var valid7 = _errs17 === errors; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.history) { + let data24 = data.history, _errs53 = errors; + if (errors === _errs53) { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data24.available) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "available" + } + } ], !1; + if (void 0 === data24.observations) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "observations" + } + } ], !1; + if (void 0 === data24.oldest_seq) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "oldest_seq" + } + } ], !1; + if (void 0 === data24.head_seq) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "head_seq" + } + } ], !1; + if (void 0 === data24.oldest_observed_at) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "oldest_observed_at" + } + } ], !1; + if (void 0 === data24.epoch_regenerations) return validate266.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "epoch_regenerations" + } + } ], !1; + if (void 0 !== data24.available) { + let _errs56 = errors; + if ("boolean" != typeof data24.available) return validate266.errors = [ { + instancePath: instancePath + "/history/available", + schemaPath: "#/properties/history/properties/available/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid25 = _errs56 === errors; + } else valid25 = !0; + if (valid25) { + if (void 0 !== data24.observations) { + let data26 = data24.observations, _errs58 = errors; + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate266.errors = [ { + instancePath: instancePath + "/history/observations", + schemaPath: "#/properties/history/properties/observations/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs58 && "number" == typeof data26 && isFinite(data26) && (data26 < 0 || isNaN(data26))) return validate266.errors = [ { + instancePath: instancePath + "/history/observations", + schemaPath: "#/properties/history/properties/observations/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid26 = _errs58 === errors; + } else valid26 = !0; + if (valid26) { + if (void 0 !== data24.oldest_seq) { + let data27 = data24.oldest_seq, _errs60 = errors; + if ("number" != typeof data27 || data27 % 1 || isNaN(data27) || !isFinite(data27)) return validate266.errors = [ { + instancePath: instancePath + "/history/oldest_seq", + schemaPath: "#/properties/history/properties/oldest_seq/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs60 && "number" == typeof data27 && isFinite(data27) && (data27 < 0 || isNaN(data27))) return validate266.errors = [ { + instancePath: instancePath + "/history/oldest_seq", + schemaPath: "#/properties/history/properties/oldest_seq/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid27 = _errs60 === errors; + } else valid27 = !0; + if (valid27) { + if (void 0 !== data24.head_seq) { + let data28 = data24.head_seq, _errs62 = errors; + if ("number" != typeof data28 || data28 % 1 || isNaN(data28) || !isFinite(data28)) return validate266.errors = [ { + instancePath: instancePath + "/history/head_seq", + schemaPath: "#/properties/history/properties/head_seq/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs62 && "number" == typeof data28 && isFinite(data28) && (data28 < 0 || isNaN(data28))) return validate266.errors = [ { + instancePath: instancePath + "/history/head_seq", + schemaPath: "#/properties/history/properties/head_seq/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid28 = _errs62 === errors; + } else valid28 = !0; + if (valid28) { + if (void 0 !== data24.oldest_observed_at) { + let data29 = data24.oldest_observed_at, _errs64 = errors, _errs65 = errors, valid30 = !1, _errs66 = errors; + if (null !== data29) { + let err0 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/properties/history/properties/oldest_observed_at/anyOf/0/type", + keyword: "type", + params: { + type: "null" + } + }; + vErrors.push(err0), errors++; + } + var _valid0 = _errs66 === errors; + valid30 = valid30 || _valid0; + let _errs68 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data29) { + if (!formats2.validate(data29)) { + let err1 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err1), errors++; + } + } else { + let err2 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err2), errors++; + } + if (_valid0 = _errs68 === errors, valid30 = valid30 || _valid0, !valid30) { + let err3 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/properties/history/properties/oldest_observed_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err3), errors++, validate266.errors = vErrors, !1; + } + errors = _errs65, vErrors.length = _errs65; + var valid29 = _errs64 === errors; + } else valid29 = !0; + if (valid29 && void 0 !== data24.epoch_regenerations) { + let data30 = data24.epoch_regenerations, _errs71 = errors; + if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate266.errors = [ { + instancePath: instancePath + "/history/epoch_regenerations", + schemaPath: "#/properties/history/properties/epoch_regenerations/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs71 && "number" == typeof data30 && isFinite(data30) && (data30 < 0 || isNaN(data30))) return validate266.errors = [ { + instancePath: instancePath + "/history/epoch_regenerations", + schemaPath: "#/properties/history/properties/epoch_regenerations/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + } + } + var valid24 = _errs53 === errors; + } else valid24 = !0; + if (valid24) { + if (void 0 !== data.publications) { + let data31 = data.publications, _errs73 = errors; + if (errors === _errs73) { + if (!data31 || "object" != typeof data31 || Array.isArray(data31)) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data31.pending) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "pending" + } + } ], !1; + if (void 0 === data31.failed) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "failed" + } + } ], !1; + if (void 0 === data31.expired) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "expired" + } + } ], !1; + if (void 0 === data31.relay_lag_seconds) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relay_lag_seconds" + } + } ], !1; + if (void 0 === data31.relay_running) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relay_running" + } + } ], !1; + if (void 0 === data31.relayed) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relayed" + } + } ], !1; + if (void 0 === data31.relayed_no_route) return validate266.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relayed_no_route" + } + } ], !1; + if (void 0 !== data31.pending) { + let data32 = data31.pending, _errs76 = errors; + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate266.errors = [ { + instancePath: instancePath + "/publications/pending", + schemaPath: "#/properties/publications/properties/pending/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs76 && "number" == typeof data32 && isFinite(data32) && (data32 < 0 || isNaN(data32))) return validate266.errors = [ { + instancePath: instancePath + "/publications/pending", + schemaPath: "#/properties/publications/properties/pending/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid34 = _errs76 === errors; + } else valid34 = !0; + if (valid34) { + if (void 0 !== data31.failed) { + let data33 = data31.failed, _errs78 = errors; + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate266.errors = [ { + instancePath: instancePath + "/publications/failed", + schemaPath: "#/properties/publications/properties/failed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs78 && "number" == typeof data33 && isFinite(data33) && (data33 < 0 || isNaN(data33))) return validate266.errors = [ { + instancePath: instancePath + "/publications/failed", + schemaPath: "#/properties/publications/properties/failed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid35 = _errs78 === errors; + } else valid35 = !0; + if (valid35) { + if (void 0 !== data31.expired) { + let data34 = data31.expired, _errs80 = errors; + if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate266.errors = [ { + instancePath: instancePath + "/publications/expired", + schemaPath: "#/properties/publications/properties/expired/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs80 && "number" == typeof data34 && isFinite(data34) && (data34 < 0 || isNaN(data34))) return validate266.errors = [ { + instancePath: instancePath + "/publications/expired", + schemaPath: "#/properties/publications/properties/expired/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid36 = _errs80 === errors; + } else valid36 = !0; + if (valid36) { + if (void 0 !== data31.relay_lag_seconds) { + let data35 = data31.relay_lag_seconds, _errs82 = errors; + if (errors === _errs82) { + if ("number" != typeof data35 || !isFinite(data35)) return validate266.errors = [ { + instancePath: instancePath + "/publications/relay_lag_seconds", + schemaPath: "#/properties/publications/properties/relay_lag_seconds/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data35 < 0 || isNaN(data35)) return validate266.errors = [ { + instancePath: instancePath + "/publications/relay_lag_seconds", + schemaPath: "#/properties/publications/properties/relay_lag_seconds/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid37 = _errs82 === errors; + } else valid37 = !0; + if (valid37) { + if (void 0 !== data31.relay_running) { + let _errs84 = errors; + if ("boolean" != typeof data31.relay_running) return validate266.errors = [ { + instancePath: instancePath + "/publications/relay_running", + schemaPath: "#/properties/publications/properties/relay_running/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid38 = _errs84 === errors; + } else valid38 = !0; + if (valid38) { + if (void 0 !== data31.relayed) { + let data37 = data31.relayed, _errs86 = errors; + if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate266.errors = [ { + instancePath: instancePath + "/publications/relayed", + schemaPath: "#/properties/publications/properties/relayed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs86 && "number" == typeof data37 && isFinite(data37) && (data37 < 0 || isNaN(data37))) return validate266.errors = [ { + instancePath: instancePath + "/publications/relayed", + schemaPath: "#/properties/publications/properties/relayed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid39 = _errs86 === errors; + } else valid39 = !0; + if (valid39 && void 0 !== data31.relayed_no_route) { + let data38 = data31.relayed_no_route, _errs88 = errors; + if ("number" != typeof data38 || data38 % 1 || isNaN(data38) || !isFinite(data38)) return validate266.errors = [ { + instancePath: instancePath + "/publications/relayed_no_route", + schemaPath: "#/properties/publications/properties/relayed_no_route/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs88 && "number" == typeof data38 && isFinite(data38) && (data38 < 0 || isNaN(data38))) return validate266.errors = [ { + instancePath: instancePath + "/publications/relayed_no_route", + schemaPath: "#/properties/publications/properties/relayed_no_route/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + } + } + } + var valid33 = _errs73 === errors; + } else valid33 = !0; + if (valid33) { + if (void 0 !== data.budget_exhausted_cameras) { + let data39 = data.budget_exhausted_cameras, _errs90 = errors; + if ("number" != typeof data39 || data39 % 1 || isNaN(data39) || !isFinite(data39)) return validate266.errors = [ { + instancePath: instancePath + "/budget_exhausted_cameras", + schemaPath: "#/properties/budget_exhausted_cameras/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs90 && "number" == typeof data39 && isFinite(data39) && (data39 < 0 || isNaN(data39))) return validate266.errors = [ { + instancePath: instancePath + "/budget_exhausted_cameras", + schemaPath: "#/properties/budget_exhausted_cameras/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid41 = _errs90 === errors; + } else valid41 = !0; + if (valid41 && void 0 !== data.cameras) { + let data40 = data.cameras; + if (errors == errors) { + if (!Array.isArray(data40)) return validate266.errors = [ { + instancePath: instancePath + "/cameras", + schemaPath: "#/properties/cameras/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data40.length; + for (let i0 = 0; i0 < len0; i0++) { + let data41 = data40[i0], _errs94 = errors; + if (errors === _errs94) { + if (!data41 || "object" != typeof data41 || Array.isArray(data41)) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data41.camera_uuid) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "camera_uuid" + } + } ], !1; + if (void 0 === data41.retained_bytes) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "retained_bytes" + } + } ], !1; + if (void 0 === data41.max_bytes) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "max_bytes" + } + } ], !1; + if (void 0 === data41.max_age_days) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "max_age_days" + } + } ], !1; + if (void 0 === data41.budget_exhausted) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "budget_exhausted" + } + } ], !1; + if (void 0 !== data41.camera_uuid) { + let data42 = data41.camera_uuid, _errs97 = errors; + if (errors === _errs97) { + if ("string" != typeof data42) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/camera_uuid", + schemaPath: "#/properties/cameras/items/properties/camera_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern4.test(data42)) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/camera_uuid", + schemaPath: "#/properties/cameras/items/properties/camera_uuid/pattern", + keyword: "pattern", + params: { + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" + } + } ], !1; + } + var valid44 = _errs97 === errors; + } else valid44 = !0; + if (valid44) { + if (void 0 !== data41.retained_bytes) { + let data43 = data41.retained_bytes, _errs99 = errors; + if ("number" != typeof data43 || data43 % 1 || isNaN(data43) || !isFinite(data43)) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/retained_bytes", + schemaPath: "#/properties/cameras/items/properties/retained_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs99 && "number" == typeof data43 && isFinite(data43) && (data43 < 0 || isNaN(data43))) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/retained_bytes", + schemaPath: "#/properties/cameras/items/properties/retained_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid45 = _errs99 === errors; + } else valid45 = !0; + if (valid45) { + if (void 0 !== data41.max_bytes) { + let data44 = data41.max_bytes, _errs101 = errors; + if ("number" != typeof data44 || data44 % 1 || isNaN(data44) || !isFinite(data44)) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_bytes", + schemaPath: "#/properties/cameras/items/properties/max_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs101 && "number" == typeof data44 && isFinite(data44) && (data44 < 0 || isNaN(data44))) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_bytes", + schemaPath: "#/properties/cameras/items/properties/max_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid46 = _errs101 === errors; + } else valid46 = !0; + if (valid46) { + if (void 0 !== data41.max_age_days) { + let data45 = data41.max_age_days, _errs103 = errors; + if ("number" != typeof data45 || data45 % 1 || isNaN(data45) || !isFinite(data45)) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_age_days", + schemaPath: "#/properties/cameras/items/properties/max_age_days/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs103 && "number" == typeof data45 && isFinite(data45) && (data45 < 0 || isNaN(data45))) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_age_days", + schemaPath: "#/properties/cameras/items/properties/max_age_days/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid47 = _errs103 === errors; + } else valid47 = !0; + if (valid47 && void 0 !== data41.budget_exhausted && "boolean" != typeof data41.budget_exhausted) return validate266.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/budget_exhausted", + schemaPath: "#/properties/cameras/items/properties/budget_exhausted/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + } + } + } + } + if (_errs94 !== errors) break; + } + } + } + } + } + } + } + } + } + } + } + return validate266.errors = vErrors, 0 === errors; +} + +validate266.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_ObservationTransform = validate479; + +function validate479(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate479.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.mode) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "mode" + } + } ], !1; + if (void 0 === data.source_width) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "source_width" + } + } ], !1; + if (void 0 === data.source_height) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "source_height" + } + } ], !1; + if (void 0 === data.inference_width) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "inference_width" + } + } ], !1; + if (void 0 === data.inference_height) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "inference_height" + } + } ], !1; + if (void 0 === data.scale_x) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "scale_x" + } + } ], !1; + if (void 0 === data.scale_y) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "scale_y" + } + } ], !1; + if (void 0 === data.pad_x) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "pad_x" + } + } ], !1; + if (void 0 === data.pad_y) return validate479.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "pad_y" + } + } ], !1; + if (void 0 !== data.mode) { + let data0 = data.mode, _errs2 = 0; + if ("string" != typeof data0) return validate479.errors = [ { + instancePath: instancePath + "/mode", + schemaPath: "#/properties/mode/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("none" !== data0 && "resize" !== data0 && "letterbox" !== data0) return validate479.errors = [ { + instancePath: instancePath + "/mode", + schemaPath: "#/properties/mode/enum", + keyword: "enum", + params: { + allowedValues: schema577.properties.mode.enum + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.source_width) { + let data1 = data.source_width, _errs4 = 0; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate479.errors = [ { + instancePath: instancePath + "/source_width", + schemaPath: "#/properties/source_width/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate479.errors = [ { + instancePath: instancePath + "/source_width", + schemaPath: "#/properties/source_width/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.source_height) { + let data2 = data.source_height, _errs6 = 0; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate479.errors = [ { + instancePath: instancePath + "/source_height", + schemaPath: "#/properties/source_height/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 1 || isNaN(data2))) return validate479.errors = [ { + instancePath: instancePath + "/source_height", + schemaPath: "#/properties/source_height/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.inference_width) { + let data3 = data.inference_width, _errs8 = 0; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate479.errors = [ { + instancePath: instancePath + "/inference_width", + schemaPath: "#/properties/inference_width/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate479.errors = [ { + instancePath: instancePath + "/inference_width", + schemaPath: "#/properties/inference_width/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.inference_height) { + let data4 = data.inference_height, _errs10 = 0; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate479.errors = [ { + instancePath: instancePath + "/inference_height", + schemaPath: "#/properties/inference_height/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 1 || isNaN(data4))) return validate479.errors = [ { + instancePath: instancePath + "/inference_height", + schemaPath: "#/properties/inference_height/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.scale_x) { + let data5 = data.scale_x, _errs12 = 0; + if (0 === _errs12) { + if ("number" != typeof data5 || !isFinite(data5)) return validate479.errors = [ { + instancePath: instancePath + "/scale_x", + schemaPath: "#/properties/scale_x/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data5 <= 0 || isNaN(data5)) return validate479.errors = [ { + instancePath: instancePath + "/scale_x", + schemaPath: "#/properties/scale_x/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + } ], !1; + } + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.scale_y) { + let data6 = data.scale_y, _errs14 = 0; + if (0 === _errs14) { + if ("number" != typeof data6 || !isFinite(data6)) return validate479.errors = [ { + instancePath: instancePath + "/scale_y", + schemaPath: "#/properties/scale_y/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data6 <= 0 || isNaN(data6)) return validate479.errors = [ { + instancePath: instancePath + "/scale_y", + schemaPath: "#/properties/scale_y/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + } ], !1; + } + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.pad_x) { + let data7 = data.pad_x, _errs16 = 0; + if (0 === _errs16) { + if ("number" != typeof data7 || !isFinite(data7)) return validate479.errors = [ { + instancePath: instancePath + "/pad_x", + schemaPath: "#/properties/pad_x/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data7 < 0 || isNaN(data7)) return validate479.errors = [ { + instancePath: instancePath + "/pad_x", + schemaPath: "#/properties/pad_x/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7 && void 0 !== data.pad_y) { + let data8 = data.pad_y; + if ("number" != typeof data8 || !isFinite(data8)) return validate479.errors = [ { + instancePath: instancePath + "/pad_y", + schemaPath: "#/properties/pad_y/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data8 < 0 || isNaN(data8)) return validate479.errors = [ { + instancePath: instancePath + "/pad_y", + schemaPath: "#/properties/pad_y/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + } + } + } + } + return validate479.errors = [], !0; +} + +validate479.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifAddRequest = validate446; + +function validate446(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate446.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate446.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.device_url) return validate446.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "device_url" + } + } ], !1; + if (void 0 === data.profile_token) return validate446.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "profile_token" + } + } ], !1; + if (void 0 === data.stream_name) return validate446.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream_name" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("device_url" !== key0 && "profile_token" !== key0 && "stream_name" !== key0 && "username" !== key0 && "password" !== key0) return validate446.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.device_url) { + let data0 = data.device_url, _errs2 = 0; + if (0 === _errs2) { + if ("string" != typeof data0) return validate446.errors = [ { + instancePath: instancePath + "/device_url", + schemaPath: "#/properties/device_url/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 511) return validate446.errors = [ { + instancePath: instancePath + "/device_url", + schemaPath: "#/properties/device_url/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + } ], !1; + if (func1(data0) < 1) return validate446.errors = [ { + instancePath: instancePath + "/device_url", + schemaPath: "#/properties/device_url/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.profile_token) { + let data1 = data.profile_token, _errs4 = 0; + if (0 === _errs4) { + if ("string" != typeof data1) return validate446.errors = [ { + instancePath: instancePath + "/profile_token", + schemaPath: "#/properties/profile_token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data1) > 63) return validate446.errors = [ { + instancePath: instancePath + "/profile_token", + schemaPath: "#/properties/profile_token/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + if (func1(data1) < 1) return validate446.errors = [ { + instancePath: instancePath + "/profile_token", + schemaPath: "#/properties/profile_token/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.stream_name) { + let data2 = data.stream_name, _errs6 = 0; + if (0 === _errs6) { + if ("string" != typeof data2) return validate446.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 255) return validate446.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + if (func1(data2) < 1) return validate446.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.username) { + let data3 = data.username, _errs8 = 0; + if (0 === _errs8) { + if ("string" != typeof data3) return validate446.errors = [ { + instancePath: instancePath + "/username", + schemaPath: "#/properties/username/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data3) > 63) return validate446.errors = [ { + instancePath: instancePath + "/username", + schemaPath: "#/properties/username/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3 && void 0 !== data.password) { + let data4 = data.password; + if ("string" != typeof data4) return validate446.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data4) > 63) return validate446.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + } + } + } + } + } + return validate446.errors = [], !0; +} + +validate446.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifAddResult = validate447; + +function validate447(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate447.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate447.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate447.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.message) return validate447.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 === data.stream_name) return validate447.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream_name" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("success" !== key0 && "message" !== key0 && "stream_name" !== key0) return validate447.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate447.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.message) { + let _errs3 = 0; + if ("string" != typeof data.message) return validate447.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs3; + } else valid1 = !0; + if (valid1 && void 0 !== data.stream_name && "string" != typeof data.stream_name) return validate447.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + return validate447.errors = [], !0; +} + +validate447.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifClaimRequest = validate448; + +function validate448(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate448.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate448.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.inventory_uuid) return validate448.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "inventory_uuid" + } + } ], !1; + if (void 0 === data.stream_name) return validate448.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream_name" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("inventory_uuid" !== key0 && "stream_name" !== key0) return validate448.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.inventory_uuid) { + let data0 = data.inventory_uuid, _errs2 = 0; + if (0 === _errs2) { + if ("string" != typeof data0) return validate448.errors = [ { + instancePath: instancePath + "/inventory_uuid", + schemaPath: "#/properties/inventory_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!formats6.test(data0)) return validate448.errors = [ { + instancePath: instancePath + "/inventory_uuid", + schemaPath: "#/properties/inventory_uuid/format", + keyword: "format", + params: { + format: "uuid" + } + } ], !1; + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0 && void 0 !== data.stream_name) { + let data1 = data.stream_name; + if ("string" != typeof data1) return validate448.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data1) > 255) return validate448.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + if (func1(data1) < 1) return validate448.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + } + } + return validate448.errors = [], !0; +} + +validate448.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifDevice = validate440, schema531 = { + type: "object", + properties: { + endpoint: { + type: "string" + }, + device_service: { + type: "string" + }, + media_service: { + type: "string" + }, + ptz_service: { + type: "string" + }, + imaging_service: { + type: "string" + }, + manufacturer: { + type: "string" + }, + model: { + type: "string" + }, + firmware_version: { + type: "string" + }, + serial_number: { + type: "string" + }, + hardware_id: { + type: "string" + }, + ip_address: { + type: "string" + }, + mac_address: { + type: "string" + }, + inventory_uuid: { + type: "string" + }, + last_scan_network: { + type: "string" + }, + claim_state: { + type: "string" + }, + claimed_camera_uuid: { + type: "string" + }, + discovery_time: { + type: "integer" + }, + first_seen_at: { + type: "integer" + }, + last_seen_at: { + type: "integer" + }, + online: { + type: "boolean" + }, + duplicate_suspected: { + type: "boolean" + }, + addresses: { + type: "array", + items: { + type: "string" + } + } + }, + required: [ "endpoint", "device_service", "media_service", "ptz_service", "imaging_service", "manufacturer", "model", "firmware_version", "serial_number", "hardware_id", "ip_address", "mac_address", "inventory_uuid", "last_scan_network", "claim_state", "claimed_camera_uuid", "discovery_time", "first_seen_at", "last_seen_at", "online", "duplicate_suspected", "addresses" ], + additionalProperties: !1 +}; + +function validate440(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate440.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.endpoint) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "endpoint" + } + } ], !1; + if (void 0 === data.device_service) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "device_service" + } + } ], !1; + if (void 0 === data.media_service) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "media_service" + } + } ], !1; + if (void 0 === data.ptz_service) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ptz_service" + } + } ], !1; + if (void 0 === data.imaging_service) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "imaging_service" + } + } ], !1; + if (void 0 === data.manufacturer) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "manufacturer" + } + } ], !1; + if (void 0 === data.model) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "model" + } + } ], !1; + if (void 0 === data.firmware_version) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "firmware_version" + } + } ], !1; + if (void 0 === data.serial_number) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "serial_number" + } + } ], !1; + if (void 0 === data.hardware_id) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "hardware_id" + } + } ], !1; + if (void 0 === data.ip_address) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ip_address" + } + } ], !1; + if (void 0 === data.mac_address) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "mac_address" + } + } ], !1; + if (void 0 === data.inventory_uuid) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "inventory_uuid" + } + } ], !1; + if (void 0 === data.last_scan_network) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "last_scan_network" + } + } ], !1; + if (void 0 === data.claim_state) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "claim_state" + } + } ], !1; + if (void 0 === data.claimed_camera_uuid) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "claimed_camera_uuid" + } + } ], !1; + if (void 0 === data.discovery_time) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "discovery_time" + } + } ], !1; + if (void 0 === data.first_seen_at) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "first_seen_at" + } + } ], !1; + if (void 0 === data.last_seen_at) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "last_seen_at" + } + } ], !1; + if (void 0 === data.online) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "online" + } + } ], !1; + if (void 0 === data.duplicate_suspected) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "duplicate_suspected" + } + } ], !1; + if (void 0 === data.addresses) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "addresses" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if (!func7.call(schema531.properties, key0)) return validate440.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.endpoint) { + let _errs2 = 0; + if ("string" != typeof data.endpoint) return validate440.errors = [ { + instancePath: instancePath + "/endpoint", + schemaPath: "#/properties/endpoint/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.device_service) { + let _errs4 = 0; + if ("string" != typeof data.device_service) return validate440.errors = [ { + instancePath: instancePath + "/device_service", + schemaPath: "#/properties/device_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.media_service) { + let _errs6 = 0; + if ("string" != typeof data.media_service) return validate440.errors = [ { + instancePath: instancePath + "/media_service", + schemaPath: "#/properties/media_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.ptz_service) { + let _errs8 = 0; + if ("string" != typeof data.ptz_service) return validate440.errors = [ { + instancePath: instancePath + "/ptz_service", + schemaPath: "#/properties/ptz_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.imaging_service) { + let _errs10 = 0; + if ("string" != typeof data.imaging_service) return validate440.errors = [ { + instancePath: instancePath + "/imaging_service", + schemaPath: "#/properties/imaging_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.manufacturer) { + let _errs12 = 0; + if ("string" != typeof data.manufacturer) return validate440.errors = [ { + instancePath: instancePath + "/manufacturer", + schemaPath: "#/properties/manufacturer/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.model) { + let _errs14 = 0; + if ("string" != typeof data.model) return validate440.errors = [ { + instancePath: instancePath + "/model", + schemaPath: "#/properties/model/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.firmware_version) { + let _errs16 = 0; + if ("string" != typeof data.firmware_version) return validate440.errors = [ { + instancePath: instancePath + "/firmware_version", + schemaPath: "#/properties/firmware_version/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.serial_number) { + let _errs18 = 0; + if ("string" != typeof data.serial_number) return validate440.errors = [ { + instancePath: instancePath + "/serial_number", + schemaPath: "#/properties/serial_number/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.hardware_id) { + let _errs20 = 0; + if ("string" != typeof data.hardware_id) return validate440.errors = [ { + instancePath: instancePath + "/hardware_id", + schemaPath: "#/properties/hardware_id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.ip_address) { + let _errs22 = 0; + if ("string" != typeof data.ip_address) return validate440.errors = [ { + instancePath: instancePath + "/ip_address", + schemaPath: "#/properties/ip_address/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid10 = 0 === _errs22; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.mac_address) { + let _errs24 = 0; + if ("string" != typeof data.mac_address) return validate440.errors = [ { + instancePath: instancePath + "/mac_address", + schemaPath: "#/properties/mac_address/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid11 = 0 === _errs24; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data.inventory_uuid) { + let _errs26 = 0; + if ("string" != typeof data.inventory_uuid) return validate440.errors = [ { + instancePath: instancePath + "/inventory_uuid", + schemaPath: "#/properties/inventory_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid12 = 0 === _errs26; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data.last_scan_network) { + let _errs28 = 0; + if ("string" != typeof data.last_scan_network) return validate440.errors = [ { + instancePath: instancePath + "/last_scan_network", + schemaPath: "#/properties/last_scan_network/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid13 = 0 === _errs28; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data.claim_state) { + let _errs30 = 0; + if ("string" != typeof data.claim_state) return validate440.errors = [ { + instancePath: instancePath + "/claim_state", + schemaPath: "#/properties/claim_state/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid14 = 0 === _errs30; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.claimed_camera_uuid) { + let _errs32 = 0; + if ("string" != typeof data.claimed_camera_uuid) return validate440.errors = [ { + instancePath: instancePath + "/claimed_camera_uuid", + schemaPath: "#/properties/claimed_camera_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid15 = 0 === _errs32; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data.discovery_time) { + let data16 = data.discovery_time, _errs34 = 0; + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate440.errors = [ { + instancePath: instancePath + "/discovery_time", + schemaPath: "#/properties/discovery_time/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid16 = 0 === _errs34; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data.first_seen_at) { + let data17 = data.first_seen_at, _errs36 = 0; + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate440.errors = [ { + instancePath: instancePath + "/first_seen_at", + schemaPath: "#/properties/first_seen_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid17 = 0 === _errs36; + } else valid17 = !0; + if (valid17) { + if (void 0 !== data.last_seen_at) { + let data18 = data.last_seen_at, _errs38 = 0; + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate440.errors = [ { + instancePath: instancePath + "/last_seen_at", + schemaPath: "#/properties/last_seen_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid18 = 0 === _errs38; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data.online) { + let _errs40 = 0; + if ("boolean" != typeof data.online) return validate440.errors = [ { + instancePath: instancePath + "/online", + schemaPath: "#/properties/online/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid19 = 0 === _errs40; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data.duplicate_suspected) { + let _errs42 = 0; + if ("boolean" != typeof data.duplicate_suspected) return validate440.errors = [ { + instancePath: instancePath + "/duplicate_suspected", + schemaPath: "#/properties/duplicate_suspected/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid20 = 0 === _errs42; + } else valid20 = !0; + if (valid20 && void 0 !== data.addresses) { + let data21 = data.addresses; + if (!Array.isArray(data21)) return validate440.errors = [ { + instancePath: instancePath + "/addresses", + schemaPath: "#/properties/addresses/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data21.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs46 = 0; + if ("string" != typeof data21[i0]) return validate440.errors = [ { + instancePath: instancePath + "/addresses/" + i0, + schemaPath: "#/properties/addresses/items/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (0 !== _errs46) break; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return validate440.errors = [], !0; +} + +validate440.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifDevicesResponse = validate441; + +function validate441(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate441.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate441.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.devices) return validate441.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "devices" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("devices" !== key0) return validate441.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1 && void 0 !== data.devices) { + let data0 = data.devices; + if (!Array.isArray(data0)) return validate441.errors = [ { + instancePath: instancePath + "/devices", + schemaPath: "#/properties/devices/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.endpoint) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "endpoint" + } + } ], !1; + if (void 0 === data1.device_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "device_service" + } + } ], !1; + if (void 0 === data1.media_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "media_service" + } + } ], !1; + if (void 0 === data1.ptz_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "ptz_service" + } + } ], !1; + if (void 0 === data1.imaging_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "imaging_service" + } + } ], !1; + if (void 0 === data1.manufacturer) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "manufacturer" + } + } ], !1; + if (void 0 === data1.model) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "model" + } + } ], !1; + if (void 0 === data1.firmware_version) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "firmware_version" + } + } ], !1; + if (void 0 === data1.serial_number) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "serial_number" + } + } ], !1; + if (void 0 === data1.hardware_id) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "hardware_id" + } + } ], !1; + if (void 0 === data1.ip_address) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "ip_address" + } + } ], !1; + if (void 0 === data1.mac_address) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "mac_address" + } + } ], !1; + if (void 0 === data1.inventory_uuid) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "inventory_uuid" + } + } ], !1; + if (void 0 === data1.last_scan_network) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "last_scan_network" + } + } ], !1; + if (void 0 === data1.claim_state) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "claim_state" + } + } ], !1; + if (void 0 === data1.claimed_camera_uuid) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "claimed_camera_uuid" + } + } ], !1; + if (void 0 === data1.discovery_time) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "discovery_time" + } + } ], !1; + if (void 0 === data1.first_seen_at) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "first_seen_at" + } + } ], !1; + if (void 0 === data1.last_seen_at) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "last_seen_at" + } + } ], !1; + if (void 0 === data1.online) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "online" + } + } ], !1; + if (void 0 === data1.duplicate_suspected) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "duplicate_suspected" + } + } ], !1; + if (void 0 === data1.addresses) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/required", + keyword: "required", + params: { + missingProperty: "addresses" + } + } ], !1; + { + let _errs7 = 0; + for (let key1 in data1) if (!func7.call(schema531.properties, key1)) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0, + schemaPath: "#/$defs/OnvifDevice/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key1 + } + } ], !1; + if (0 === _errs7) { + if (void 0 !== data1.endpoint) { + let _errs8 = 0; + if ("string" != typeof data1.endpoint) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/endpoint", + schemaPath: "#/$defs/OnvifDevice/properties/endpoint/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data1.device_service) { + let _errs10 = 0; + if ("string" != typeof data1.device_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/device_service", + schemaPath: "#/$defs/OnvifDevice/properties/device_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data1.media_service) { + let _errs12 = 0; + if ("string" != typeof data1.media_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/media_service", + schemaPath: "#/$defs/OnvifDevice/properties/media_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data1.ptz_service) { + let _errs14 = 0; + if ("string" != typeof data1.ptz_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/ptz_service", + schemaPath: "#/$defs/OnvifDevice/properties/ptz_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data1.imaging_service) { + let _errs16 = 0; + if ("string" != typeof data1.imaging_service) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/imaging_service", + schemaPath: "#/$defs/OnvifDevice/properties/imaging_service/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data1.manufacturer) { + let _errs18 = 0; + if ("string" != typeof data1.manufacturer) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/manufacturer", + schemaPath: "#/$defs/OnvifDevice/properties/manufacturer/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data1.model) { + let _errs20 = 0; + if ("string" != typeof data1.model) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/model", + schemaPath: "#/$defs/OnvifDevice/properties/model/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data1.firmware_version) { + let _errs22 = 0; + if ("string" != typeof data1.firmware_version) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/firmware_version", + schemaPath: "#/$defs/OnvifDevice/properties/firmware_version/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid10 = 0 === _errs22; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data1.serial_number) { + let _errs24 = 0; + if ("string" != typeof data1.serial_number) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/serial_number", + schemaPath: "#/$defs/OnvifDevice/properties/serial_number/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid11 = 0 === _errs24; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data1.hardware_id) { + let _errs26 = 0; + if ("string" != typeof data1.hardware_id) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/hardware_id", + schemaPath: "#/$defs/OnvifDevice/properties/hardware_id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid12 = 0 === _errs26; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data1.ip_address) { + let _errs28 = 0; + if ("string" != typeof data1.ip_address) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/ip_address", + schemaPath: "#/$defs/OnvifDevice/properties/ip_address/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid13 = 0 === _errs28; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data1.mac_address) { + let _errs30 = 0; + if ("string" != typeof data1.mac_address) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/mac_address", + schemaPath: "#/$defs/OnvifDevice/properties/mac_address/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid14 = 0 === _errs30; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data1.inventory_uuid) { + let _errs32 = 0; + if ("string" != typeof data1.inventory_uuid) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/inventory_uuid", + schemaPath: "#/$defs/OnvifDevice/properties/inventory_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid15 = 0 === _errs32; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data1.last_scan_network) { + let _errs34 = 0; + if ("string" != typeof data1.last_scan_network) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/last_scan_network", + schemaPath: "#/$defs/OnvifDevice/properties/last_scan_network/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid16 = 0 === _errs34; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data1.claim_state) { + let _errs36 = 0; + if ("string" != typeof data1.claim_state) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/claim_state", + schemaPath: "#/$defs/OnvifDevice/properties/claim_state/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid17 = 0 === _errs36; + } else valid17 = !0; + if (valid17) { + if (void 0 !== data1.claimed_camera_uuid) { + let _errs38 = 0; + if ("string" != typeof data1.claimed_camera_uuid) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/claimed_camera_uuid", + schemaPath: "#/$defs/OnvifDevice/properties/claimed_camera_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid18 = 0 === _errs38; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data1.discovery_time) { + let data18 = data1.discovery_time, _errs40 = 0; + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/discovery_time", + schemaPath: "#/$defs/OnvifDevice/properties/discovery_time/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid19 = 0 === _errs40; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data1.first_seen_at) { + let data19 = data1.first_seen_at, _errs42 = 0; + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/first_seen_at", + schemaPath: "#/$defs/OnvifDevice/properties/first_seen_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid20 = 0 === _errs42; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data1.last_seen_at) { + let data20 = data1.last_seen_at, _errs44 = 0; + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/last_seen_at", + schemaPath: "#/$defs/OnvifDevice/properties/last_seen_at/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid21 = 0 === _errs44; + } else valid21 = !0; + if (valid21) { + if (void 0 !== data1.online) { + let _errs46 = 0; + if ("boolean" != typeof data1.online) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/online", + schemaPath: "#/$defs/OnvifDevice/properties/online/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid22 = 0 === _errs46; + } else valid22 = !0; + if (valid22) { + if (void 0 !== data1.duplicate_suspected) { + let _errs48 = 0; + if ("boolean" != typeof data1.duplicate_suspected) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/duplicate_suspected", + schemaPath: "#/$defs/OnvifDevice/properties/duplicate_suspected/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid23 = 0 === _errs48; + } else valid23 = !0; + if (valid23 && void 0 !== data1.addresses) { + let data23 = data1.addresses; + if (!Array.isArray(data23)) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/addresses", + schemaPath: "#/$defs/OnvifDevice/properties/addresses/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len1 = data23.length; + for (let i1 = 0; i1 < len1; i1++) { + let _errs52 = 0; + if ("string" != typeof data23[i1]) return validate441.errors = [ { + instancePath: instancePath + "/devices/" + i0 + "/addresses/" + i1, + schemaPath: "#/$defs/OnvifDevice/properties/addresses/items/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (0 !== _errs52) break; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + if (0 !== _errs4) break; + } + } + } + } + return validate441.errors = [], !0; +} + +validate441.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifDiscoveryRequest = validate442; + +function validate442(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate442.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate442.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("network" !== key0) return validate442.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1 && void 0 !== data.network) { + let data0 = data.network; + if ("string" != typeof data0) return validate442.errors = [ { + instancePath: instancePath + "/network", + schemaPath: "#/properties/network/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 255) return validate442.errors = [ { + instancePath: instancePath + "/network", + schemaPath: "#/properties/network/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + } + } + return validate442.errors = [], !0; +} + +validate442.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifDiscoveryStatus = validate439; + +function validate439(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate439.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate439.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.enabled) return validate439.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "enabled" + } + } ], !1; + if (void 0 === data.network) return validate439.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "network" + } + } ], !1; + if (void 0 === data.interval) return validate439.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "interval" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("enabled" !== key0 && "network" !== key0 && "interval" !== key0) return validate439.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.enabled) { + let _errs2 = 0; + if ("boolean" != typeof data.enabled) return validate439.errors = [ { + instancePath: instancePath + "/enabled", + schemaPath: "#/properties/enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.network) { + let _errs4 = 0; + if ("string" != typeof data.network) return validate439.errors = [ { + instancePath: instancePath + "/network", + schemaPath: "#/properties/network/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1 && void 0 !== data.interval) { + let data2 = data.interval; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate439.errors = [ { + instancePath: instancePath + "/interval", + schemaPath: "#/properties/interval/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + } + } + } + } + return validate439.errors = [], !0; +} + +validate439.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifProfile = validate443, schema535 = { + type: "object", + properties: { + token: { + type: "string" + }, + name: { + type: "string" + }, + video_source_token: { + type: "string" + }, + ptz_configuration_token: { + type: "string" + }, + snapshot_uri: { + type: "string" + }, + stream_uri: { + type: "string" + }, + encoding: { + type: "string" + }, + width: { + type: "integer" + }, + height: { + type: "integer" + }, + fps: { + type: "integer" + }, + bitrate: { + type: "integer" + }, + ptz_supported: { + type: "boolean" + } + }, + required: [ "token", "name", "video_source_token", "ptz_configuration_token", "snapshot_uri", "stream_uri", "encoding", "width", "height", "fps", "bitrate", "ptz_supported" ], + additionalProperties: !1 +}; + +function validate443(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate443.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.token) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "token" + } + } ], !1; + if (void 0 === data.name) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "name" + } + } ], !1; + if (void 0 === data.video_source_token) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "video_source_token" + } + } ], !1; + if (void 0 === data.ptz_configuration_token) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ptz_configuration_token" + } + } ], !1; + if (void 0 === data.snapshot_uri) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "snapshot_uri" + } + } ], !1; + if (void 0 === data.stream_uri) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream_uri" + } + } ], !1; + if (void 0 === data.encoding) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "encoding" + } + } ], !1; + if (void 0 === data.width) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "width" + } + } ], !1; + if (void 0 === data.height) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "height" + } + } ], !1; + if (void 0 === data.fps) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "fps" + } + } ], !1; + if (void 0 === data.bitrate) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "bitrate" + } + } ], !1; + if (void 0 === data.ptz_supported) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ptz_supported" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if (!func7.call(schema535.properties, key0)) return validate443.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.token) { + let _errs2 = 0; + if ("string" != typeof data.token) return validate443.errors = [ { + instancePath: instancePath + "/token", + schemaPath: "#/properties/token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.name) { + let _errs4 = 0; + if ("string" != typeof data.name) return validate443.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.video_source_token) { + let _errs6 = 0; + if ("string" != typeof data.video_source_token) return validate443.errors = [ { + instancePath: instancePath + "/video_source_token", + schemaPath: "#/properties/video_source_token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.ptz_configuration_token) { + let _errs8 = 0; + if ("string" != typeof data.ptz_configuration_token) return validate443.errors = [ { + instancePath: instancePath + "/ptz_configuration_token", + schemaPath: "#/properties/ptz_configuration_token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.snapshot_uri) { + let _errs10 = 0; + if ("string" != typeof data.snapshot_uri) return validate443.errors = [ { + instancePath: instancePath + "/snapshot_uri", + schemaPath: "#/properties/snapshot_uri/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.stream_uri) { + let _errs12 = 0; + if ("string" != typeof data.stream_uri) return validate443.errors = [ { + instancePath: instancePath + "/stream_uri", + schemaPath: "#/properties/stream_uri/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.encoding) { + let _errs14 = 0; + if ("string" != typeof data.encoding) return validate443.errors = [ { + instancePath: instancePath + "/encoding", + schemaPath: "#/properties/encoding/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.width) { + let data7 = data.width, _errs16 = 0; + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate443.errors = [ { + instancePath: instancePath + "/width", + schemaPath: "#/properties/width/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.height) { + let data8 = data.height, _errs18 = 0; + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate443.errors = [ { + instancePath: instancePath + "/height", + schemaPath: "#/properties/height/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.fps) { + let data9 = data.fps, _errs20 = 0; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate443.errors = [ { + instancePath: instancePath + "/fps", + schemaPath: "#/properties/fps/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.bitrate) { + let data10 = data.bitrate, _errs22 = 0; + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate443.errors = [ { + instancePath: instancePath + "/bitrate", + schemaPath: "#/properties/bitrate/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid10 = 0 === _errs22; + } else valid10 = !0; + if (valid10 && void 0 !== data.ptz_supported && "boolean" != typeof data.ptz_supported) return validate443.errors = [ { + instancePath: instancePath + "/ptz_supported", + schemaPath: "#/properties/ptz_supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + } + } + } + } + } + } + } + } + } + } + } + } + return validate443.errors = [], !0; +} + +validate443.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifProfilesResponse = validate444; + +function validate444(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate444.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate444.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.profiles) return validate444.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "profiles" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("profiles" !== key0) return validate444.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1 && void 0 !== data.profiles) { + let data0 = data.profiles; + if (!Array.isArray(data0)) return validate444.errors = [ { + instancePath: instancePath + "/profiles", + schemaPath: "#/properties/profiles/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data0.length > 16) return validate444.errors = [ { + instancePath: instancePath + "/profiles", + schemaPath: "#/properties/profiles/maxItems", + keyword: "maxItems", + params: { + limit: 16 + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.token) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "token" + } + } ], !1; + if (void 0 === data1.name) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "name" + } + } ], !1; + if (void 0 === data1.video_source_token) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "video_source_token" + } + } ], !1; + if (void 0 === data1.ptz_configuration_token) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "ptz_configuration_token" + } + } ], !1; + if (void 0 === data1.snapshot_uri) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "snapshot_uri" + } + } ], !1; + if (void 0 === data1.stream_uri) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "stream_uri" + } + } ], !1; + if (void 0 === data1.encoding) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "encoding" + } + } ], !1; + if (void 0 === data1.width) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "width" + } + } ], !1; + if (void 0 === data1.height) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "height" + } + } ], !1; + if (void 0 === data1.fps) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "fps" + } + } ], !1; + if (void 0 === data1.bitrate) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "bitrate" + } + } ], !1; + if (void 0 === data1.ptz_supported) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/required", + keyword: "required", + params: { + missingProperty: "ptz_supported" + } + } ], !1; + { + let _errs7 = 0; + for (let key1 in data1) if (!func7.call(schema535.properties, key1)) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0, + schemaPath: "#/$defs/OnvifProfile/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key1 + } + } ], !1; + if (0 === _errs7) { + if (void 0 !== data1.token) { + let _errs8 = 0; + if ("string" != typeof data1.token) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/token", + schemaPath: "#/$defs/OnvifProfile/properties/token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data1.name) { + let _errs10 = 0; + if ("string" != typeof data1.name) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/name", + schemaPath: "#/$defs/OnvifProfile/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data1.video_source_token) { + let _errs12 = 0; + if ("string" != typeof data1.video_source_token) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/video_source_token", + schemaPath: "#/$defs/OnvifProfile/properties/video_source_token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data1.ptz_configuration_token) { + let _errs14 = 0; + if ("string" != typeof data1.ptz_configuration_token) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/ptz_configuration_token", + schemaPath: "#/$defs/OnvifProfile/properties/ptz_configuration_token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data1.snapshot_uri) { + let _errs16 = 0; + if ("string" != typeof data1.snapshot_uri) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/snapshot_uri", + schemaPath: "#/$defs/OnvifProfile/properties/snapshot_uri/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data1.stream_uri) { + let _errs18 = 0; + if ("string" != typeof data1.stream_uri) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/stream_uri", + schemaPath: "#/$defs/OnvifProfile/properties/stream_uri/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data1.encoding) { + let _errs20 = 0; + if ("string" != typeof data1.encoding) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/encoding", + schemaPath: "#/$defs/OnvifProfile/properties/encoding/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data1.width) { + let data9 = data1.width, _errs22 = 0; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/width", + schemaPath: "#/$defs/OnvifProfile/properties/width/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid10 = 0 === _errs22; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data1.height) { + let data10 = data1.height, _errs24 = 0; + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/height", + schemaPath: "#/$defs/OnvifProfile/properties/height/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid11 = 0 === _errs24; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data1.fps) { + let data11 = data1.fps, _errs26 = 0; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/fps", + schemaPath: "#/$defs/OnvifProfile/properties/fps/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid12 = 0 === _errs26; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data1.bitrate) { + let data12 = data1.bitrate, _errs28 = 0; + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/bitrate", + schemaPath: "#/$defs/OnvifProfile/properties/bitrate/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid13 = 0 === _errs28; + } else valid13 = !0; + if (valid13 && void 0 !== data1.ptz_supported && "boolean" != typeof data1.ptz_supported) return validate444.errors = [ { + instancePath: instancePath + "/profiles/" + i0 + "/ptz_supported", + schemaPath: "#/$defs/OnvifProfile/properties/ptz_supported/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + } + } + } + } + } + } + } + } + } + } + } + } + if (0 !== _errs4) break; + } + } + } + } + return validate444.errors = [], !0; +} + +validate444.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OnvifTestRequest = validate445; + +function validate445(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate445.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate445.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.url) return validate445.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "url" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("url" !== key0 && "username" !== key0 && "password" !== key0 && "insecure" !== key0) return validate445.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.url) { + let data0 = data.url, _errs2 = 0; + if (0 === _errs2) { + if ("string" != typeof data0) return validate445.errors = [ { + instancePath: instancePath + "/url", + schemaPath: "#/properties/url/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 511) return validate445.errors = [ { + instancePath: instancePath + "/url", + schemaPath: "#/properties/url/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + } ], !1; + if (func1(data0) < 1) return validate445.errors = [ { + instancePath: instancePath + "/url", + schemaPath: "#/properties/url/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.username) { + let data1 = data.username, _errs4 = 0; + if (0 === _errs4) { + if ("string" != typeof data1) return validate445.errors = [ { + instancePath: instancePath + "/username", + schemaPath: "#/properties/username/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data1) > 63) return validate445.errors = [ { + instancePath: instancePath + "/username", + schemaPath: "#/properties/username/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.password) { + let data2 = data.password, _errs6 = 0; + if (0 === _errs6) { + if ("string" != typeof data2) return validate445.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 63) return validate445.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2 && void 0 !== data.insecure && "boolean" != typeof data.insecure) return validate445.errors = [ { + instancePath: instancePath + "/insecure", + schemaPath: "#/properties/insecure/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + } + } + } + } + return validate445.errors = [], !0; +} + +validate445.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_OpenApiDocument = validate69; + +function validate69(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate69.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate69.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.openapi) return validate69.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "openapi" + } + } ], !1; + if (void 0 === data.info) return validate69.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "info" + } + } ], !1; + if (void 0 === data.paths) return validate69.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "paths" + } + } ], !1; + if (void 0 === data["x-lightnvr-coverage"]) return validate69.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "x-lightnvr-coverage" + } + } ], !1; + if (void 0 !== data.openapi) { + let data0 = data.openapi, _errs2 = 0; + if ("string" != typeof data0) return validate69.errors = [ { + instancePath: instancePath + "/openapi", + schemaPath: "#/properties/openapi/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("3.1.1" !== data0) return validate69.errors = [ { + instancePath: instancePath + "/openapi", + schemaPath: "#/properties/openapi/const", + keyword: "const", + params: { + allowedValue: "3.1.1" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.info) { + let data1 = data.info, _errs4 = 0; + if (0 === _errs4) { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate69.errors = [ { + instancePath: instancePath + "/info", + schemaPath: "#/properties/info/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.title) return validate69.errors = [ { + instancePath: instancePath + "/info", + schemaPath: "#/properties/info/required", + keyword: "required", + params: { + missingProperty: "title" + } + } ], !1; + if (void 0 === data1.version) return validate69.errors = [ { + instancePath: instancePath + "/info", + schemaPath: "#/properties/info/required", + keyword: "required", + params: { + missingProperty: "version" + } + } ], !1; + if (void 0 !== data1.title) { + let _errs7 = 0; + if ("string" != typeof data1.title) return validate69.errors = [ { + instancePath: instancePath + "/info/title", + schemaPath: "#/properties/info/properties/title/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid2 = 0 === _errs7; + } else valid2 = !0; + if (valid2 && void 0 !== data1.version && "string" != typeof data1.version) return validate69.errors = [ { + instancePath: instancePath + "/info/version", + schemaPath: "#/properties/info/properties/version/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.paths) { + let data4 = data.paths, _errs11 = 0; + if (0 === _errs11 && (!data4 || "object" != typeof data4 || Array.isArray(data4))) return validate69.errors = [ { + instancePath: instancePath + "/paths", + schemaPath: "#/properties/paths/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + var valid4 = 0 === _errs11; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.components) { + let data5 = data.components, _errs14 = 0; + if (0 === _errs14 && (!data5 || "object" != typeof data5 || Array.isArray(data5))) return validate69.errors = [ { + instancePath: instancePath + "/components", + schemaPath: "#/properties/components/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + var valid5 = 0 === _errs14; + } else valid5 = !0; + if (valid5 && void 0 !== data["x-lightnvr-coverage"]) { + let data6 = data["x-lightnvr-coverage"]; + if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage", + schemaPath: "#/$defs/OpenApiCoverage/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data6.partial) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage", + schemaPath: "#/$defs/OpenApiCoverage/required", + keyword: "required", + params: { + missingProperty: "partial" + } + } ], !1; + if (void 0 === data6.canonical_revision) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage", + schemaPath: "#/$defs/OpenApiCoverage/required", + keyword: "required", + params: { + missingProperty: "canonical_revision" + } + } ], !1; + if (void 0 === data6.covered_operations) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage", + schemaPath: "#/$defs/OpenApiCoverage/required", + keyword: "required", + params: { + missingProperty: "covered_operations" + } + } ], !1; + if (void 0 !== data6.partial) { + let data7 = data6.partial, _errs21 = 0; + if ("boolean" != typeof data7) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage/partial", + schemaPath: "#/$defs/OpenApiCoverage/properties/partial/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data7) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage/partial", + schemaPath: "#/$defs/OpenApiCoverage/properties/partial/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid8 = 0 === _errs21; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data6.canonical_revision) { + let _errs23 = 0; + if ("string" != typeof data6.canonical_revision) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage/canonical_revision", + schemaPath: "#/$defs/OpenApiCoverage/properties/canonical_revision/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid9 = 0 === _errs23; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data6.covered_operations) { + let data9 = data6.covered_operations, _errs25 = 0; + if (0 === _errs25) { + if (!Array.isArray(data9)) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage/covered_operations", + schemaPath: "#/$defs/OpenApiCoverage/properties/covered_operations/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data9.length; + for (let i0 = 0; i0 < len0; i0++) { + let _errs27 = 0; + if ("string" != typeof data9[i0]) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage/covered_operations/" + i0, + schemaPath: "#/$defs/OpenApiCoverage/properties/covered_operations/items/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (0 !== _errs27) break; + } + } + } + var valid10 = 0 === _errs25; + } else valid10 = !0; + if (valid10 && void 0 !== data6.description && "string" != typeof data6.description) return validate69.errors = [ { + instancePath: instancePath + "/x-lightnvr-coverage/description", + schemaPath: "#/$defs/OpenApiCoverage/properties/description/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + } + } + } + return validate69.errors = [], !0; +} + +validate69.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PasswordAdminReset = validate139; + +function validate139(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate139.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate139.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.new_password) return validate139.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "new_password" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("old_password" !== key0 && "new_password" !== key0) return validate139.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.old_password) { + let _errs2 = 0; + if ("string" != typeof data.old_password) return validate139.errors = [ { + instancePath: instancePath + "/old_password", + schemaPath: "#/properties/old_password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0 && void 0 !== data.new_password && "string" != typeof data.new_password) return validate139.errors = [ { + instancePath: instancePath + "/new_password", + schemaPath: "#/properties/new_password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + return validate139.errors = [], !0; +} + +validate139.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PasswordChangeRequest = validate140; + +function validate140(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate140.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs0 = errors, valid0 = !1, _errs1 = errors; + if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.old_password) { + let err0 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordSelfChange/required", + keyword: "required", + params: { + missingProperty: "old_password" + } + }; + vErrors.push(err0), errors++; + } else if (void 0 === data.new_password) { + let err1 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordSelfChange/required", + keyword: "required", + params: { + missingProperty: "new_password" + } + }; + vErrors.push(err1), errors++; + } else { + let _errs4 = errors; + for (let key0 in data) if ("old_password" !== key0 && "new_password" !== key0) { + let err2 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordSelfChange/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + }; + vErrors.push(err2), errors++; + break; + } + if (_errs4 === errors) { + if (void 0 !== data.old_password) { + let _errs5 = errors; + if ("string" != typeof data.old_password) { + let err3 = { + instancePath: instancePath + "/old_password", + schemaPath: "#/$defs/PasswordSelfChange/properties/old_password/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err3), errors++; + } + var valid2 = _errs5 === errors; + } else valid2 = !0; + if (valid2 && void 0 !== data.new_password && "string" != typeof data.new_password) { + let err4 = { + instancePath: instancePath + "/new_password", + schemaPath: "#/$defs/PasswordSelfChange/properties/new_password/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err4), errors++; + } + } + } else { + let err5 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordSelfChange/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err5), errors++; + } + var _valid0 = _errs1 === errors; + if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; + let _errs9 = errors; + if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.new_password) { + let err6 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordAdminReset/required", + keyword: "required", + params: { + missingProperty: "new_password" + } + }; + vErrors.push(err6), errors++; + } else { + let _errs12 = errors; + for (let key1 in data) if ("old_password" !== key1 && "new_password" !== key1) { + let err7 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordAdminReset/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key1 + } + }; + vErrors.push(err7), errors++; + break; + } + if (_errs12 === errors) { + if (void 0 !== data.old_password) { + let _errs13 = errors; + if ("string" != typeof data.old_password) { + let err8 = { + instancePath: instancePath + "/old_password", + schemaPath: "#/$defs/PasswordAdminReset/properties/old_password/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err8), errors++; + } + var valid5 = _errs13 === errors; + } else valid5 = !0; + if (valid5 && void 0 !== data.new_password && "string" != typeof data.new_password) { + let err9 = { + instancePath: instancePath + "/new_password", + schemaPath: "#/$defs/PasswordAdminReset/properties/new_password/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err9), errors++; + } + } + } else { + let err10 = { + instancePath: instancePath, + schemaPath: "#/$defs/PasswordAdminReset/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err10), errors++; + } + if (_valid0 = _errs9 === errors, valid0 = valid0 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), + !valid0) { + let err11 = { + instancePath: instancePath, + schemaPath: "#/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err11), errors++, validate140.errors = vErrors, !1; + } + return errors = _errs0, vErrors.length = _errs0, validate140.errors = vErrors, evaluated0.props = props0, + 0 === errors; +} + +validate140.evaluated = { + dynamicProps: !0, + dynamicItems: !1 +}; + +var __contract_PasswordChangedResponse = validate141; + +function validate141(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate141.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate141.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate141.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.must_change_password) return validate141.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "must_change_password" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("success" !== key0 && "must_change_password" !== key0) return validate141.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate141.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0 && void 0 !== data.must_change_password && !1 !== data.must_change_password) return validate141.errors = [ { + instancePath: instancePath + "/must_change_password", + schemaPath: "#/properties/must_change_password/const", + keyword: "const", + params: { + allowedValue: !1 + } + } ], !1; + } + } + return validate141.errors = [], !0; +} + +validate141.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PasswordLockRequest = validate180; + +function validate180(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate180.evaluated; + return evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data) ? (validate180.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1) : void 0 === data.locked ? (validate180.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "locked" + } + } ], !1) : void 0 !== data.locked && "boolean" != typeof data.locked ? (validate180.errors = [ { + instancePath: instancePath + "/locked", + schemaPath: "#/properties/locked/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1) : (validate180.errors = [], !0); +} + +validate180.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PasswordSelfChange = validate138; + +function validate138(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate138.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate138.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.old_password) return validate138.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "old_password" + } + } ], !1; + if (void 0 === data.new_password) return validate138.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "new_password" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("old_password" !== key0 && "new_password" !== key0) return validate138.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.old_password) { + let _errs2 = 0; + if ("string" != typeof data.old_password) return validate138.errors = [ { + instancePath: instancePath + "/old_password", + schemaPath: "#/properties/old_password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0 && void 0 !== data.new_password && "string" != typeof data.new_password) return validate138.errors = [ { + instancePath: instancePath + "/new_password", + schemaPath: "#/properties/new_password/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + return validate138.errors = [], !0; +} + +validate138.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PlayerTelemetry = validate287; + +function validate287(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate287.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate287.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.stream_name) return validate287.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "stream_name" + } + } ], !1; + if (void 0 !== data.stream_name) { + let _errs2 = 0; + if ("string" != typeof data.stream_name) return validate287.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.session_id) { + let _errs4 = 0; + if ("string" != typeof data.session_id) return validate287.errors = [ { + instancePath: instancePath + "/session_id", + schemaPath: "#/properties/session_id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.transport) { + let _errs6 = 0; + if ("string" != typeof data.transport) return validate287.errors = [ { + instancePath: instancePath + "/transport", + schemaPath: "#/properties/transport/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.ttff_ms) { + let data3 = data.ttff_ms, _errs8 = 0; + if ("number" != typeof data3 || !isFinite(data3)) return validate287.errors = [ { + instancePath: instancePath + "/ttff_ms", + schemaPath: "#/properties/ttff_ms/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.rebuffer_count) { + let data4 = data.rebuffer_count, _errs10 = 0; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate287.errors = [ { + instancePath: instancePath + "/rebuffer_count", + schemaPath: "#/properties/rebuffer_count/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.rebuffer_duration_ms) { + let data5 = data.rebuffer_duration_ms, _errs12 = 0; + if ("number" != typeof data5 || !isFinite(data5)) return validate287.errors = [ { + instancePath: instancePath + "/rebuffer_duration_ms", + schemaPath: "#/properties/rebuffer_duration_ms/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.resolution_switches) { + let data6 = data.resolution_switches, _errs14 = 0; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate287.errors = [ { + instancePath: instancePath + "/resolution_switches", + schemaPath: "#/properties/resolution_switches/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6 && void 0 !== data.webrtc_rtt_ms) { + let data7 = data.webrtc_rtt_ms; + if ("number" != typeof data7 || !isFinite(data7)) return validate287.errors = [ { + instancePath: instancePath + "/webrtc_rtt_ms", + schemaPath: "#/properties/webrtc_rtt_ms/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + } + } + } + } + } + } + } + return validate287.errors = [], !0; +} + +validate287.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_ProcessLiveness = validate283; + +function validate283(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate283.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.healthy) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "healthy" + } + } ], !1; + if (void 0 === data.uptime) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "uptime" + } + } ], !1; + if (void 0 === data.totalRequests) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "totalRequests" + } + } ], !1; + if (void 0 === data.failedRequests) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "failedRequests" + } + } ], !1; + if (void 0 === data.timestamp) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "timestamp" + } + } ], !1; + if (void 0 === data.liveness) return validate283.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "liveness" + } + } ], !1; + if (void 0 !== data.healthy) { + let _errs2 = 0; + if ("boolean" != typeof data.healthy) return validate283.errors = [ { + instancePath: instancePath + "/healthy", + schemaPath: "#/properties/healthy/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.uptime) { + let data1 = data.uptime, _errs4 = 0; + if ("number" != typeof data1 || !isFinite(data1)) return validate283.errors = [ { + instancePath: instancePath + "/uptime", + schemaPath: "#/properties/uptime/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.totalRequests) { + let data2 = data.totalRequests, _errs6 = 0; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate283.errors = [ { + instancePath: instancePath + "/totalRequests", + schemaPath: "#/properties/totalRequests/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.failedRequests) { + let data3 = data.failedRequests, _errs8 = 0; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate283.errors = [ { + instancePath: instancePath + "/failedRequests", + schemaPath: "#/properties/failedRequests/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.timestamp) { + let _errs10 = 0; + if ("string" != typeof data.timestamp) return validate283.errors = [ { + instancePath: instancePath + "/timestamp", + schemaPath: "#/properties/timestamp/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4 && void 0 !== data.liveness) { + let data5 = data.liveness; + if ("boolean" != typeof data5) return validate283.errors = [ { + instancePath: instancePath + "/liveness", + schemaPath: "#/properties/liveness/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data5) return validate283.errors = [ { + instancePath: instancePath + "/liveness", + schemaPath: "#/properties/liveness/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + } + } + } + } + } + return validate283.errors = [], !0; +} + +validate283.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PtzAxesRequest = validate416; + +function validate416(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate416.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate416.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (Object.keys(data).length < 1) return validate416.errors = [ { + instancePath: instancePath, + schemaPath: "#/minProperties", + keyword: "minProperties", + params: { + limit: 1 + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("pan" !== key0 && "tilt" !== key0 && "zoom" !== key0) return validate416.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.pan) { + let data0 = data.pan, _errs2 = 0; + if (0 === _errs2) { + if ("number" != typeof data0 || !isFinite(data0)) return validate416.errors = [ { + instancePath: instancePath + "/pan", + schemaPath: "#/properties/pan/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data0 > 34028234663852886e22 || isNaN(data0)) return validate416.errors = [ { + instancePath: instancePath + "/pan", + schemaPath: "#/properties/pan/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 34028234663852886e22 + } + } ], !1; + if (data0 < -34028234663852886e22 || isNaN(data0)) return validate416.errors = [ { + instancePath: instancePath + "/pan", + schemaPath: "#/properties/pan/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: -34028234663852886e22 + } + } ], !1; + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.tilt) { + let data1 = data.tilt, _errs4 = 0; + if (0 === _errs4) { + if ("number" != typeof data1 || !isFinite(data1)) return validate416.errors = [ { + instancePath: instancePath + "/tilt", + schemaPath: "#/properties/tilt/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data1 > 34028234663852886e22 || isNaN(data1)) return validate416.errors = [ { + instancePath: instancePath + "/tilt", + schemaPath: "#/properties/tilt/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 34028234663852886e22 + } + } ], !1; + if (data1 < -34028234663852886e22 || isNaN(data1)) return validate416.errors = [ { + instancePath: instancePath + "/tilt", + schemaPath: "#/properties/tilt/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: -34028234663852886e22 + } + } ], !1; + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1 && void 0 !== data.zoom) { + let data2 = data.zoom; + if ("number" != typeof data2 || !isFinite(data2)) return validate416.errors = [ { + instancePath: instancePath + "/zoom", + schemaPath: "#/properties/zoom/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data2 > 34028234663852886e22 || isNaN(data2)) return validate416.errors = [ { + instancePath: instancePath + "/zoom", + schemaPath: "#/properties/zoom/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 34028234663852886e22 + } + } ], !1; + if (data2 < -34028234663852886e22 || isNaN(data2)) return validate416.errors = [ { + instancePath: instancePath + "/zoom", + schemaPath: "#/properties/zoom/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: -34028234663852886e22 + } + } ], !1; + } + } + } + } + return validate416.errors = [], !0; +} + +validate416.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PtzCapabilities = validate421, schema489 = { + type: "object", + properties: { + ptz_enabled: { + type: "boolean" + }, + onvif_queried: { + type: "boolean" + }, + has_pan_tilt: { + type: "boolean" + }, + has_zoom: { + type: "boolean" + }, + has_continuous_move: { + type: "boolean" + }, + has_absolute_move: { + type: "boolean" + }, + has_relative_move: { + type: "boolean" + }, + has_home_position: { + type: "boolean" + }, + has_presets: { + type: "boolean" + }, + has_home: { + type: "boolean" + }, + pan_range: { + $ref: "#/$defs/DeviceFloatRange" + }, + tilt_range: { + $ref: "#/$defs/DeviceFloatRange" + }, + zoom_range: { + $ref: "#/$defs/DeviceFloatRange" + }, + max_x: { + type: "number" + }, + max_y: { + type: "number" + }, + max_z: { + type: "number" + }, + max_presets: { + type: "integer" + }, + ptz_service_url: { + type: "string" + } + }, + required: [ "ptz_enabled", "onvif_queried", "has_pan_tilt", "has_zoom", "has_continuous_move", "has_absolute_move", "has_relative_move", "has_home_position", "has_presets", "has_home", "pan_range", "tilt_range", "zoom_range", "max_x", "max_y", "max_z", "max_presets", "ptz_service_url" ], + additionalProperties: !1 +}; + +function validate421(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate421.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.ptz_enabled) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ptz_enabled" + } + } ], !1; + if (void 0 === data.onvif_queried) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "onvif_queried" + } + } ], !1; + if (void 0 === data.has_pan_tilt) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_pan_tilt" + } + } ], !1; + if (void 0 === data.has_zoom) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_zoom" + } + } ], !1; + if (void 0 === data.has_continuous_move) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_continuous_move" + } + } ], !1; + if (void 0 === data.has_absolute_move) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_absolute_move" + } + } ], !1; + if (void 0 === data.has_relative_move) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_relative_move" + } + } ], !1; + if (void 0 === data.has_home_position) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_home_position" + } + } ], !1; + if (void 0 === data.has_presets) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_presets" + } + } ], !1; + if (void 0 === data.has_home) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "has_home" + } + } ], !1; + if (void 0 === data.pan_range) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "pan_range" + } + } ], !1; + if (void 0 === data.tilt_range) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "tilt_range" + } + } ], !1; + if (void 0 === data.zoom_range) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "zoom_range" + } + } ], !1; + if (void 0 === data.max_x) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "max_x" + } + } ], !1; + if (void 0 === data.max_y) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "max_y" + } + } ], !1; + if (void 0 === data.max_z) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "max_z" + } + } ], !1; + if (void 0 === data.max_presets) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "max_presets" + } + } ], !1; + if (void 0 === data.ptz_service_url) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ptz_service_url" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if (!func7.call(schema489.properties, key0)) return validate421.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.ptz_enabled) { + let _errs2 = 0; + if ("boolean" != typeof data.ptz_enabled) return validate421.errors = [ { + instancePath: instancePath + "/ptz_enabled", + schemaPath: "#/properties/ptz_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.onvif_queried) { + let _errs4 = 0; + if ("boolean" != typeof data.onvif_queried) return validate421.errors = [ { + instancePath: instancePath + "/onvif_queried", + schemaPath: "#/properties/onvif_queried/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.has_pan_tilt) { + let _errs6 = 0; + if ("boolean" != typeof data.has_pan_tilt) return validate421.errors = [ { + instancePath: instancePath + "/has_pan_tilt", + schemaPath: "#/properties/has_pan_tilt/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.has_zoom) { + let _errs8 = 0; + if ("boolean" != typeof data.has_zoom) return validate421.errors = [ { + instancePath: instancePath + "/has_zoom", + schemaPath: "#/properties/has_zoom/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.has_continuous_move) { + let _errs10 = 0; + if ("boolean" != typeof data.has_continuous_move) return validate421.errors = [ { + instancePath: instancePath + "/has_continuous_move", + schemaPath: "#/properties/has_continuous_move/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.has_absolute_move) { + let _errs12 = 0; + if ("boolean" != typeof data.has_absolute_move) return validate421.errors = [ { + instancePath: instancePath + "/has_absolute_move", + schemaPath: "#/properties/has_absolute_move/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.has_relative_move) { + let _errs14 = 0; + if ("boolean" != typeof data.has_relative_move) return validate421.errors = [ { + instancePath: instancePath + "/has_relative_move", + schemaPath: "#/properties/has_relative_move/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.has_home_position) { + let _errs16 = 0; + if ("boolean" != typeof data.has_home_position) return validate421.errors = [ { + instancePath: instancePath + "/has_home_position", + schemaPath: "#/properties/has_home_position/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.has_presets) { + let _errs18 = 0; + if ("boolean" != typeof data.has_presets) return validate421.errors = [ { + instancePath: instancePath + "/has_presets", + schemaPath: "#/properties/has_presets/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.has_home) { + let _errs20 = 0; + if ("boolean" != typeof data.has_home) return validate421.errors = [ { + instancePath: instancePath + "/has_home", + schemaPath: "#/properties/has_home/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data.pan_range) { + let data10 = data.pan_range, _errs22 = 0; + if (!data10 || "object" != typeof data10 || Array.isArray(data10)) return validate421.errors = [ { + instancePath: instancePath + "/pan_range", + schemaPath: "#/$defs/DeviceFloatRange/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data10.min) return validate421.errors = [ { + instancePath: instancePath + "/pan_range", + schemaPath: "#/$defs/DeviceFloatRange/required", + keyword: "required", + params: { + missingProperty: "min" + } + } ], !1; + if (void 0 === data10.max) return validate421.errors = [ { + instancePath: instancePath + "/pan_range", + schemaPath: "#/$defs/DeviceFloatRange/required", + keyword: "required", + params: { + missingProperty: "max" + } + } ], !1; + { + let _errs25 = 0; + for (let key1 in data10) if ("min" !== key1 && "max" !== key1) return validate421.errors = [ { + instancePath: instancePath + "/pan_range", + schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key1 + } + } ], !1; + if (0 === _errs25) { + if (void 0 !== data10.min) { + let data11 = data10.min, _errs26 = 0; + if ("number" != typeof data11 || !isFinite(data11)) return validate421.errors = [ { + instancePath: instancePath + "/pan_range/min", + schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid12 = 0 === _errs26; + } else valid12 = !0; + if (valid12 && void 0 !== data10.max) { + let data12 = data10.max; + if ("number" != typeof data12 || !isFinite(data12)) return validate421.errors = [ { + instancePath: instancePath + "/pan_range/max", + schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + } + } + } + var valid10 = 0 === _errs22; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data.tilt_range) { + let data13 = data.tilt_range, _errs30 = 0; + if (!data13 || "object" != typeof data13 || Array.isArray(data13)) return validate421.errors = [ { + instancePath: instancePath + "/tilt_range", + schemaPath: "#/$defs/DeviceFloatRange/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data13.min) return validate421.errors = [ { + instancePath: instancePath + "/tilt_range", + schemaPath: "#/$defs/DeviceFloatRange/required", + keyword: "required", + params: { + missingProperty: "min" + } + } ], !1; + if (void 0 === data13.max) return validate421.errors = [ { + instancePath: instancePath + "/tilt_range", + schemaPath: "#/$defs/DeviceFloatRange/required", + keyword: "required", + params: { + missingProperty: "max" + } + } ], !1; + { + let _errs33 = 0; + for (let key2 in data13) if ("min" !== key2 && "max" !== key2) return validate421.errors = [ { + instancePath: instancePath + "/tilt_range", + schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key2 + } + } ], !1; + if (0 === _errs33) { + if (void 0 !== data13.min) { + let data14 = data13.min, _errs34 = 0; + if ("number" != typeof data14 || !isFinite(data14)) return validate421.errors = [ { + instancePath: instancePath + "/tilt_range/min", + schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid16 = 0 === _errs34; + } else valid16 = !0; + if (valid16 && void 0 !== data13.max) { + let data15 = data13.max; + if ("number" != typeof data15 || !isFinite(data15)) return validate421.errors = [ { + instancePath: instancePath + "/tilt_range/max", + schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + } + } + } + var valid14 = 0 === _errs30; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data.zoom_range) { + let data16 = data.zoom_range, _errs38 = 0; + if (!data16 || "object" != typeof data16 || Array.isArray(data16)) return validate421.errors = [ { + instancePath: instancePath + "/zoom_range", + schemaPath: "#/$defs/DeviceFloatRange/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data16.min) return validate421.errors = [ { + instancePath: instancePath + "/zoom_range", + schemaPath: "#/$defs/DeviceFloatRange/required", + keyword: "required", + params: { + missingProperty: "min" + } + } ], !1; + if (void 0 === data16.max) return validate421.errors = [ { + instancePath: instancePath + "/zoom_range", + schemaPath: "#/$defs/DeviceFloatRange/required", + keyword: "required", + params: { + missingProperty: "max" + } + } ], !1; + { + let _errs41 = 0; + for (let key3 in data16) if ("min" !== key3 && "max" !== key3) return validate421.errors = [ { + instancePath: instancePath + "/zoom_range", + schemaPath: "#/$defs/DeviceFloatRange/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key3 + } + } ], !1; + if (0 === _errs41) { + if (void 0 !== data16.min) { + let data17 = data16.min, _errs42 = 0; + if ("number" != typeof data17 || !isFinite(data17)) return validate421.errors = [ { + instancePath: instancePath + "/zoom_range/min", + schemaPath: "#/$defs/DeviceFloatRange/properties/min/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid20 = 0 === _errs42; + } else valid20 = !0; + if (valid20 && void 0 !== data16.max) { + let data18 = data16.max; + if ("number" != typeof data18 || !isFinite(data18)) return validate421.errors = [ { + instancePath: instancePath + "/zoom_range/max", + schemaPath: "#/$defs/DeviceFloatRange/properties/max/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + } + } + } + var valid18 = 0 === _errs38; + } else valid18 = !0; + if (valid18) { + if (void 0 !== data.max_x) { + let data19 = data.max_x, _errs46 = 0; + if ("number" != typeof data19 || !isFinite(data19)) return validate421.errors = [ { + instancePath: instancePath + "/max_x", + schemaPath: "#/properties/max_x/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid22 = 0 === _errs46; + } else valid22 = !0; + if (valid22) { + if (void 0 !== data.max_y) { + let data20 = data.max_y, _errs48 = 0; + if ("number" != typeof data20 || !isFinite(data20)) return validate421.errors = [ { + instancePath: instancePath + "/max_y", + schemaPath: "#/properties/max_y/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid23 = 0 === _errs48; + } else valid23 = !0; + if (valid23) { + if (void 0 !== data.max_z) { + let data21 = data.max_z, _errs50 = 0; + if ("number" != typeof data21 || !isFinite(data21)) return validate421.errors = [ { + instancePath: instancePath + "/max_z", + schemaPath: "#/properties/max_z/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + var valid24 = 0 === _errs50; + } else valid24 = !0; + if (valid24) { + if (void 0 !== data.max_presets) { + let data22 = data.max_presets, _errs52 = 0; + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate421.errors = [ { + instancePath: instancePath + "/max_presets", + schemaPath: "#/properties/max_presets/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + var valid25 = 0 === _errs52; + } else valid25 = !0; + if (valid25 && void 0 !== data.ptz_service_url && "string" != typeof data.ptz_service_url) return validate421.errors = [ { + instancePath: instancePath + "/ptz_service_url", + schemaPath: "#/properties/ptz_service_url/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + return validate421.errors = [], !0; +} + +validate421.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PtzGotoPresetRequest = validate418; + +function validate418(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate418.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate418.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.token) return validate418.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "token" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("token" !== key0) return validate418.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1 && void 0 !== data.token) { + let data0 = data.token; + if ("string" != typeof data0) return validate418.errors = [ { + instancePath: instancePath + "/token", + schemaPath: "#/properties/token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 63) return validate418.errors = [ { + instancePath: instancePath + "/token", + schemaPath: "#/properties/token/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + if (func1(data0) < 1) return validate418.errors = [ { + instancePath: instancePath + "/token", + schemaPath: "#/properties/token/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + } + return validate418.errors = [], !0; +} + +validate418.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PtzPresetRequest = validate417; + +function validate417(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate417.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate417.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("name" !== key0) return validate417.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1 && void 0 !== data.name) { + let data0 = data.name; + if ("string" != typeof data0) return validate417.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 63) return validate417.errors = [ { + instancePath: instancePath + "/name", + schemaPath: "#/properties/name/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + } + return validate417.errors = [], !0; +} + +validate417.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PtzPresetResult = validate419; + +function validate419(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate419.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate419.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate419.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.message) return validate419.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 === data.token) return validate419.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "token" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("success" !== key0 && "message" !== key0 && "token" !== key0) return validate419.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate419.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.message) { + let _errs3 = 0; + if ("string" != typeof data.message) return validate419.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs3; + } else valid1 = !0; + if (valid1 && void 0 !== data.token && "string" != typeof data.token) return validate419.errors = [ { + instancePath: instancePath + "/token", + schemaPath: "#/properties/token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + return validate419.errors = [], !0; +} + +validate419.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_PtzPresets = validate420; + +function validate420(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate420.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate420.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.presets) return validate420.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "presets" + } + } ], !1; + if (void 0 === data.count) return validate420.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "count" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if ("presets" !== key0 && "count" !== key0) return validate420.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.presets) { + let data0 = data.presets, _errs2 = 0; + if (0 === _errs2) { + if (!Array.isArray(data0)) return validate420.errors = [ { + instancePath: instancePath + "/presets", + schemaPath: "#/properties/presets/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data0.length > 32) return validate420.errors = [ { + instancePath: instancePath + "/presets", + schemaPath: "#/properties/presets/maxItems", + keyword: "maxItems", + params: { + limit: 32 + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (0 === _errs4) { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate420.errors = [ { + instancePath: instancePath + "/presets/" + i0, + schemaPath: "#/properties/presets/items/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.token) return validate420.errors = [ { + instancePath: instancePath + "/presets/" + i0, + schemaPath: "#/properties/presets/items/required", + keyword: "required", + params: { + missingProperty: "token" + } + } ], !1; + if (void 0 === data1.name) return validate420.errors = [ { + instancePath: instancePath + "/presets/" + i0, + schemaPath: "#/properties/presets/items/required", + keyword: "required", + params: { + missingProperty: "name" + } + } ], !1; + { + let _errs6 = 0; + for (let key1 in data1) if ("token" !== key1 && "name" !== key1) return validate420.errors = [ { + instancePath: instancePath + "/presets/" + i0, + schemaPath: "#/properties/presets/items/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key1 + } + } ], !1; + if (0 === _errs6) { + if (void 0 !== data1.token) { + let _errs7 = 0; + if ("string" != typeof data1.token) return validate420.errors = [ { + instancePath: instancePath + "/presets/" + i0 + "/token", + schemaPath: "#/properties/presets/items/properties/token/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid2 = 0 === _errs7; + } else valid2 = !0; + if (valid2 && void 0 !== data1.name && "string" != typeof data1.name) return validate420.errors = [ { + instancePath: instancePath + "/presets/" + i0 + "/name", + schemaPath: "#/properties/presets/items/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + if (0 !== _errs4) break; + } + } + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0 && void 0 !== data.count) { + let data4 = data.count; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate420.errors = [ { + instancePath: instancePath + "/count", + schemaPath: "#/properties/count/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + } + } + } + return validate420.errors = [], !0; +} + +validate420.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingBatchProtectionRequest = validate114; + +function validate114(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate114.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate114.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.ids) return validate114.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ids" + } + } ], !1; + if (void 0 === data.protected) return validate114.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "protected" + } + } ], !1; + if (void 0 !== data.ids) { + let _errs2 = 0; + if (0 === _errs2 && !Array.isArray(data.ids)) return validate114.errors = [ { + instancePath: instancePath + "/ids", + schemaPath: "#/properties/ids/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + return valid0 && void 0 !== data.protected && "boolean" != typeof data.protected ? (validate114.errors = [ { + instancePath: instancePath + "/protected", + schemaPath: "#/properties/protected/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1) : (validate114.errors = [], !0); +} + +validate114.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingBatchProtectionResult = validate115; + +function validate115(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate115.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate115.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success_count) return validate115.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success_count" + } + } ], !1; + if (void 0 === data.fail_count) return validate115.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "fail_count" + } + } ], !1; + if (void 0 === data.protected) return validate115.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "protected" + } + } ], !1; + if (void 0 === data.message) return validate115.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 !== data.success_count) { + let data0 = data.success_count, _errs2 = 0; + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate115.errors = [ { + instancePath: instancePath + "/success_count", + schemaPath: "#/properties/success_count/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate115.errors = [ { + instancePath: instancePath + "/success_count", + schemaPath: "#/properties/success_count/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.fail_count) { + let data1 = data.fail_count, _errs4 = 0; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate115.errors = [ { + instancePath: instancePath + "/fail_count", + schemaPath: "#/properties/fail_count/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate115.errors = [ { + instancePath: instancePath + "/fail_count", + schemaPath: "#/properties/fail_count/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.protected) { + let _errs6 = 0; + if ("boolean" != typeof data.protected) return validate115.errors = [ { + instancePath: instancePath + "/protected", + schemaPath: "#/properties/protected/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2 && void 0 !== data.message && "string" != typeof data.message) return validate115.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + return validate115.errors = [], !0; +} + +validate115.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingBatchTagsRequest = validate117; + +function validate117(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate117.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate117.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.ids) return validate117.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "ids" + } + } ], !1; + if (void 0 !== data.ids) { + let data0 = data.ids, _errs2 = 0; + if (0 === _errs2) { + if (!Array.isArray(data0)) return validate117.errors = [ { + instancePath: instancePath + "/ids", + schemaPath: "#/properties/ids/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data0.length > 1e4) return validate117.errors = [ { + instancePath: instancePath + "/ids", + schemaPath: "#/properties/ids/maxItems", + keyword: "maxItems", + params: { + limit: 1e4 + } + } ], !1; + if (data0.length < 1) return validate117.errors = [ { + instancePath: instancePath + "/ids", + schemaPath: "#/properties/ids/minItems", + keyword: "minItems", + params: { + limit: 1 + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (0 === _errs4) { + if ("number" != typeof data1 || !isFinite(data1)) return validate117.errors = [ { + instancePath: instancePath + "/ids/" + i0, + schemaPath: "#/properties/ids/items/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data1 <= 0 || isNaN(data1)) return validate117.errors = [ { + instancePath: instancePath + "/ids/" + i0, + schemaPath: "#/properties/ids/items/exclusiveMinimum", + keyword: "exclusiveMinimum", + params: { + comparison: ">", + limit: 0 + } + } ], !1; + } + if (0 !== _errs4) break; + } + } + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.add) { + let _errs6 = 0; + if (0 === _errs6 && !Array.isArray(data.add)) return validate117.errors = [ { + instancePath: instancePath + "/add", + schemaPath: "#/properties/add/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2 && void 0 !== data.remove && !Array.isArray(data.remove)) return validate117.errors = [ { + instancePath: instancePath + "/remove", + schemaPath: "#/properties/remove/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + } + return validate117.errors = [], !0; +} + +validate117.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingBatchTagsResult = validate118; + +function validate118(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate118.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate118.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.recordings_count) return validate118.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "recordings_count" + } + } ], !1; + if (void 0 === data.tags_added) return validate118.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "tags_added" + } + } ], !1; + if (void 0 === data.tags_removed) return validate118.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "tags_removed" + } + } ], !1; + if (void 0 !== data.recordings_count) { + let data0 = data.recordings_count, _errs2 = 0; + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate118.errors = [ { + instancePath: instancePath + "/recordings_count", + schemaPath: "#/properties/recordings_count/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate118.errors = [ { + instancePath: instancePath + "/recordings_count", + schemaPath: "#/properties/recordings_count/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.tags_added) { + let data1 = data.tags_added, _errs4 = 0; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate118.errors = [ { + instancePath: instancePath + "/tags_added", + schemaPath: "#/properties/tags_added/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate118.errors = [ { + instancePath: instancePath + "/tags_added", + schemaPath: "#/properties/tags_added/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1 && void 0 !== data.tags_removed) { + let data2 = data.tags_removed, _errs6 = 0; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate118.errors = [ { + instancePath: instancePath + "/tags_removed", + schemaPath: "#/properties/tags_removed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate118.errors = [ { + instancePath: instancePath + "/tags_removed", + schemaPath: "#/properties/tags_removed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + return validate118.errors = [], !0; +} + +validate118.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingDeletionFilter = validate120, schema127 = { + type: "object", + properties: { + stream_name: { + type: "string", + minLength: 1, + maxLength: 255 + }, + stream: { + type: "string", + minLength: 1, + maxLength: 255 + }, + collection_uuid: { + type: "string", + minLength: 1, + maxLength: 36 + }, + start: { + type: "string", + minLength: 1, + maxLength: 64 + }, + end: { + type: "string", + minLength: 1, + maxLength: 64 + }, + detection: { + type: "integer", + enum: [ -1, 0, 1 ] + }, + detection_label: { + type: "string", + minLength: 1, + maxLength: 255 + }, + tag: { + type: "string", + minLength: 1, + maxLength: 511 + }, + capture_method: { + type: "string", + minLength: 1, + maxLength: 127 + }, + protected: { + type: "integer", + enum: [ -1, 0, 1 ] + } + }, + required: [], + additionalProperties: !1, + description: "stream_name (or stream fallback) selects comma-separated current camera names. Without a nonempty stream filter, all-fleet recording.delete authority is required. Authorized camera UUIDs and collection membership are captured before dispatch; caller-supplied camera UUID fields do not grant scope. Supplied start/end must be valid UTC YYYY-MM-DDTHH:mm:ss strings, optionally followed by fractional seconds and/or Z; fractions are truncated to whole seconds. Bounds must be after the Unix epoch and start must not exceed end. Invalid types, calendar dates, suffixes, or ranges return400 before job creation. Omitted bounds are unbounded. detection/protected must be -1, 0, or 1. Unknown keys, duplicate keys (including date-case aliases), simultaneous stream and stream_name, invalid types, and empty selectors return400. Date key spelling is case-insensitive; other keys are case-sensitive. Caller-supplied camera/stream UUID selectors are unsupported and rejected. In-progress rows are excluded.", + patternProperties: { + "^(?!start$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$": { + type: "string", + minLength: 1, + maxLength: 64 + } + } +}, pattern20 = new RegExp("^(?!start$|end$)([sS][tT][aA][rR][tT]|[eE][nN][dD])$", "u"); + +function validate120(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate120.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate120.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + { + let _errs1 = 0; + for (let key0 in data) if (!func7.call(schema127.properties, key0) && !pattern20.test(key0)) return validate120.errors = [ { + instancePath: instancePath, + schemaPath: "#/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + } ], !1; + if (0 === _errs1) { + if (void 0 !== data.stream_name) { + let data0 = data.stream_name, _errs2 = 0; + if (0 === _errs2) { + if ("string" != typeof data0) return validate120.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data0) > 255) return validate120.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + if (func1(data0) < 1) return validate120.errors = [ { + instancePath: instancePath + "/stream_name", + schemaPath: "#/properties/stream_name/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.stream) { + let data1 = data.stream, _errs4 = 0; + if (0 === _errs4) { + if ("string" != typeof data1) return validate120.errors = [ { + instancePath: instancePath + "/stream", + schemaPath: "#/properties/stream/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data1) > 255) return validate120.errors = [ { + instancePath: instancePath + "/stream", + schemaPath: "#/properties/stream/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + if (func1(data1) < 1) return validate120.errors = [ { + instancePath: instancePath + "/stream", + schemaPath: "#/properties/stream/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.collection_uuid) { + let data2 = data.collection_uuid, _errs6 = 0; + if (0 === _errs6) { + if ("string" != typeof data2) return validate120.errors = [ { + instancePath: instancePath + "/collection_uuid", + schemaPath: "#/properties/collection_uuid/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 36) return validate120.errors = [ { + instancePath: instancePath + "/collection_uuid", + schemaPath: "#/properties/collection_uuid/maxLength", + keyword: "maxLength", + params: { + limit: 36 + } + } ], !1; + if (func1(data2) < 1) return validate120.errors = [ { + instancePath: instancePath + "/collection_uuid", + schemaPath: "#/properties/collection_uuid/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.start) { + let data3 = data.start, _errs8 = 0; + if (0 === _errs8) { + if ("string" != typeof data3) return validate120.errors = [ { + instancePath: instancePath + "/start", + schemaPath: "#/properties/start/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data3) > 64) return validate120.errors = [ { + instancePath: instancePath + "/start", + schemaPath: "#/properties/start/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + if (func1(data3) < 1) return validate120.errors = [ { + instancePath: instancePath + "/start", + schemaPath: "#/properties/start/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.end) { + let data4 = data.end, _errs10 = 0; + if (0 === _errs10) { + if ("string" != typeof data4) return validate120.errors = [ { + instancePath: instancePath + "/end", + schemaPath: "#/properties/end/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data4) > 64) return validate120.errors = [ { + instancePath: instancePath + "/end", + schemaPath: "#/properties/end/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + if (func1(data4) < 1) return validate120.errors = [ { + instancePath: instancePath + "/end", + schemaPath: "#/properties/end/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.detection) { + let data5 = data.detection, _errs12 = 0; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate120.errors = [ { + instancePath: instancePath + "/detection", + schemaPath: "#/properties/detection/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (-1 !== data5 && 0 !== data5 && 1 !== data5) return validate120.errors = [ { + instancePath: instancePath + "/detection", + schemaPath: "#/properties/detection/enum", + keyword: "enum", + params: { + allowedValues: schema127.properties.detection.enum + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.detection_label) { + let data6 = data.detection_label, _errs14 = 0; + if (0 === _errs14) { + if ("string" != typeof data6) return validate120.errors = [ { + instancePath: instancePath + "/detection_label", + schemaPath: "#/properties/detection_label/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data6) > 255) return validate120.errors = [ { + instancePath: instancePath + "/detection_label", + schemaPath: "#/properties/detection_label/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + if (func1(data6) < 1) return validate120.errors = [ { + instancePath: instancePath + "/detection_label", + schemaPath: "#/properties/detection_label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.tag) { + let data7 = data.tag, _errs16 = 0; + if (0 === _errs16) { + if ("string" != typeof data7) return validate120.errors = [ { + instancePath: instancePath + "/tag", + schemaPath: "#/properties/tag/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data7) > 511) return validate120.errors = [ { + instancePath: instancePath + "/tag", + schemaPath: "#/properties/tag/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + } ], !1; + if (func1(data7) < 1) return validate120.errors = [ { + instancePath: instancePath + "/tag", + schemaPath: "#/properties/tag/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.capture_method) { + let data8 = data.capture_method, _errs18 = 0; + if (0 === _errs18) { + if ("string" != typeof data8) return validate120.errors = [ { + instancePath: instancePath + "/capture_method", + schemaPath: "#/properties/capture_method/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data8) > 127) return validate120.errors = [ { + instancePath: instancePath + "/capture_method", + schemaPath: "#/properties/capture_method/maxLength", + keyword: "maxLength", + params: { + limit: 127 + } + } ], !1; + if (func1(data8) < 1) return validate120.errors = [ { + instancePath: instancePath + "/capture_method", + schemaPath: "#/properties/capture_method/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data.protected) { + let data9 = data.protected, _errs20 = 0; + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate120.errors = [ { + instancePath: instancePath + "/protected", + schemaPath: "#/properties/protected/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (-1 !== data9 && 0 !== data9 && 1 !== data9) return validate120.errors = [ { + instancePath: instancePath + "/protected", + schemaPath: "#/properties/protected/enum", + keyword: "enum", + params: { + allowedValues: schema127.properties.protected.enum + } + } ], !1; + var valid9 = 0 === _errs20; + } else valid9 = !0; + if (valid9) for (let key1 in data) if (pattern20.test(key1)) { + let data10 = data[key1], _errs22 = 0; + if (0 === _errs22) { + if ("string" != typeof data10) return validate120.errors = [ { + instancePath: instancePath + "/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data10) > 64) return validate120.errors = [ { + instancePath: instancePath + "/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + } ], !1; + if (func1(data10) < 1) return validate120.errors = [ { + instancePath: instancePath + "/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + if (0 !== _errs22) break; + } + } + } + } + } + } + } + } + } + } + } + } + return validate120.errors = [], !0; +} + +validate120.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingDeletionJobAccepted = validate122; + +function validate122(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate122.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate122.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.job_id) return validate122.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "job_id" + } + } ], !1; + if (void 0 === data.status) return validate122.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "status" + } + } ], !1; + if (void 0 !== data.job_id) { + let _errs2 = 0; + if ("string" != typeof data.job_id) return validate122.errors = [ { + instancePath: instancePath + "/job_id", + schemaPath: "#/properties/job_id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + return valid0 && void 0 !== data.status && "started" !== data.status ? (validate122.errors = [ { + instancePath: instancePath + "/status", + schemaPath: "#/properties/status/const", + keyword: "const", + params: { + allowedValue: "started" + } + } ], !1) : (validate122.errors = [], !0); +} + +validate122.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingDeletionProgress = validate123, schema131 = { + type: "object", + properties: { + job_id: { + type: "string" + }, + status: { + type: "string", + enum: [ "pending", "running", "complete", "error", "unknown" ] + }, + total: { + type: "integer", + minimum: 0 + }, + current: { + type: "integer", + minimum: 0 + }, + succeeded: { + type: "integer", + minimum: 0 + }, + pending_deletions: { + type: "integer", + minimum: 0 + }, + failed: { + type: "integer", + minimum: 0 + }, + status_message: { + type: "string" + }, + complete: { + type: "boolean" + }, + error_message: { + type: "string" + } + }, + required: [ "job_id", "status", "total", "current", "succeeded", "pending_deletions", "failed", "status_message", "complete" ], + additionalProperties: !0, + description: "complete is true for both complete and error terminal states. A complete job can have failed members or queued pending deletions; it does not imply every file is already removed. SQL failure is error; an empty successful filter completes with zero counts. No cancellation endpoint/state exists." +}; + +function validate123(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate123.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.job_id) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "job_id" + } + } ], !1; + if (void 0 === data.status) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "status" + } + } ], !1; + if (void 0 === data.total) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "total" + } + } ], !1; + if (void 0 === data.current) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "current" + } + } ], !1; + if (void 0 === data.succeeded) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "succeeded" + } + } ], !1; + if (void 0 === data.pending_deletions) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "pending_deletions" + } + } ], !1; + if (void 0 === data.failed) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "failed" + } + } ], !1; + if (void 0 === data.status_message) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "status_message" + } + } ], !1; + if (void 0 === data.complete) return validate123.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "complete" + } + } ], !1; + if (void 0 !== data.job_id) { + let _errs2 = 0; + if ("string" != typeof data.job_id) return validate123.errors = [ { + instancePath: instancePath + "/job_id", + schemaPath: "#/properties/job_id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.status) { + let data1 = data.status, _errs4 = 0; + if ("string" != typeof data1) return validate123.errors = [ { + instancePath: instancePath + "/status", + schemaPath: "#/properties/status/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if ("pending" !== data1 && "running" !== data1 && "complete" !== data1 && "error" !== data1 && "unknown" !== data1) return validate123.errors = [ { + instancePath: instancePath + "/status", + schemaPath: "#/properties/status/enum", + keyword: "enum", + params: { + allowedValues: schema131.properties.status.enum + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.total) { + let data2 = data.total, _errs6 = 0; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate123.errors = [ { + instancePath: instancePath + "/total", + schemaPath: "#/properties/total/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate123.errors = [ { + instancePath: instancePath + "/total", + schemaPath: "#/properties/total/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.current) { + let data3 = data.current, _errs8 = 0; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate123.errors = [ { + instancePath: instancePath + "/current", + schemaPath: "#/properties/current/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate123.errors = [ { + instancePath: instancePath + "/current", + schemaPath: "#/properties/current/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data.succeeded) { + let data4 = data.succeeded, _errs10 = 0; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate123.errors = [ { + instancePath: instancePath + "/succeeded", + schemaPath: "#/properties/succeeded/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate123.errors = [ { + instancePath: instancePath + "/succeeded", + schemaPath: "#/properties/succeeded/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data.pending_deletions) { + let data5 = data.pending_deletions, _errs12 = 0; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate123.errors = [ { + instancePath: instancePath + "/pending_deletions", + schemaPath: "#/properties/pending_deletions/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate123.errors = [ { + instancePath: instancePath + "/pending_deletions", + schemaPath: "#/properties/pending_deletions/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data.failed) { + let data6 = data.failed, _errs14 = 0; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate123.errors = [ { + instancePath: instancePath + "/failed", + schemaPath: "#/properties/failed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate123.errors = [ { + instancePath: instancePath + "/failed", + schemaPath: "#/properties/failed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data.status_message) { + let _errs16 = 0; + if ("string" != typeof data.status_message) return validate123.errors = [ { + instancePath: instancePath + "/status_message", + schemaPath: "#/properties/status_message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.complete) { + let _errs18 = 0; + if ("boolean" != typeof data.complete) return validate123.errors = [ { + instancePath: instancePath + "/complete", + schemaPath: "#/properties/complete/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid8 = 0 === _errs18; + } else valid8 = !0; + if (valid8 && void 0 !== data.error_message && "string" != typeof data.error_message) return validate123.errors = [ { + instancePath: instancePath + "/error_message", + schemaPath: "#/properties/error_message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + } + } + } + } + } + return validate123.errors = [], !0; +} + +validate123.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_RecordingDeletionRequest = validate121, schema128 = { + anyOf: [ { + type: "object", + properties: { + ids: { + type: "array", + items: {}, + minItems: 1 + } + }, + required: [ "ids" ], + additionalProperties: !0, + description: "IDs take precedence over filter. IDs accept safe positive integer JSON numbers or exact positive decimal uint64 strings. Invalid, unsafe numeric, or missing members become failed counts; a forbidden existing member rejects the whole request before job creation." + }, { + type: "object", + properties: { + filter: { + $ref: "#/$defs/RecordingDeletionFilter" + }, + ids: { + anyOf: [ { + type: "array", + items: {}, + minItems: 1 + }, { + type: [ "null", "boolean", "object", "number", "string" ] + } ], + description: "Array-valued ids take precedence over filter and must be nonempty. Other ids values fall through to the filter handler." + } + }, + required: [ "filter" ], + additionalProperties: !0 + } ] +}; + +function validate121(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate121.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs0 = errors, valid0 = !1, _errs1 = errors; + if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) { + if (void 0 === data.ids) { + let err0 = { + instancePath: instancePath, + schemaPath: "#/anyOf/0/required", + keyword: "required", + params: { + missingProperty: "ids" + } + }; + vErrors.push(err0), errors++; + } else if (void 0 !== data.ids) { + let data0 = data.ids; + if (errors == errors) if (Array.isArray(data0)) { + if (data0.length < 1) { + let err1 = { + instancePath: instancePath + "/ids", + schemaPath: "#/anyOf/0/properties/ids/minItems", + keyword: "minItems", + params: { + limit: 1 + } + }; + vErrors.push(err1), errors++; + } + } else { + let err2 = { + instancePath: instancePath + "/ids", + schemaPath: "#/anyOf/0/properties/ids/type", + keyword: "type", + params: { + type: "array" + } + }; + vErrors.push(err2), errors++; + } + } + } else { + let err3 = { + instancePath: instancePath, + schemaPath: "#/anyOf/0/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err3), errors++; + } + var _valid0 = _errs1 === errors; + if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; + let _errs6 = errors; + if (errors === _errs6) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.filter) { + let err4 = { + instancePath: instancePath, + schemaPath: "#/anyOf/1/required", + keyword: "required", + params: { + missingProperty: "filter" + } + }; + vErrors.push(err4), errors++; + } else { + if (void 0 !== data.filter) { + let data1 = data.filter, _errs9 = errors; + if (errors == errors) if (data1 && "object" == typeof data1 && !Array.isArray(data1)) { + let _errs12 = errors; + for (let key0 in data1) if (!func7.call(schema127.properties, key0) && !pattern20.test(key0)) { + let err5 = { + instancePath: instancePath + "/filter", + schemaPath: "#/$defs/RecordingDeletionFilter/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + }; + vErrors.push(err5), errors++; + break; + } + if (_errs12 === errors) { + if (void 0 !== data1.stream_name) { + let data2 = data1.stream_name, _errs13 = errors; + if (errors === _errs13) if ("string" == typeof data2) { + if (func1(data2) > 255) { + let err6 = { + instancePath: instancePath + "/filter/stream_name", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream_name/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + }; + vErrors.push(err6), errors++; + } else if (func1(data2) < 1) { + let err7 = { + instancePath: instancePath + "/filter/stream_name", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream_name/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err7), errors++; + } + } else { + let err8 = { + instancePath: instancePath + "/filter/stream_name", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err8), errors++; + } + var valid4 = _errs13 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data1.stream) { + let data3 = data1.stream, _errs15 = errors; + if (errors === _errs15) if ("string" == typeof data3) { + if (func1(data3) > 255) { + let err9 = { + instancePath: instancePath + "/filter/stream", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + }; + vErrors.push(err9), errors++; + } else if (func1(data3) < 1) { + let err10 = { + instancePath: instancePath + "/filter/stream", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err10), errors++; + } + } else { + let err11 = { + instancePath: instancePath + "/filter/stream", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/stream/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err11), errors++; + } + var valid5 = _errs15 === errors; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data1.collection_uuid) { + let data4 = data1.collection_uuid, _errs17 = errors; + if (errors === _errs17) if ("string" == typeof data4) { + if (func1(data4) > 36) { + let err12 = { + instancePath: instancePath + "/filter/collection_uuid", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/collection_uuid/maxLength", + keyword: "maxLength", + params: { + limit: 36 + } + }; + vErrors.push(err12), errors++; + } else if (func1(data4) < 1) { + let err13 = { + instancePath: instancePath + "/filter/collection_uuid", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/collection_uuid/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err13), errors++; + } + } else { + let err14 = { + instancePath: instancePath + "/filter/collection_uuid", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/collection_uuid/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err14), errors++; + } + var valid6 = _errs17 === errors; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data1.start) { + let data5 = data1.start, _errs19 = errors; + if (errors === _errs19) if ("string" == typeof data5) { + if (func1(data5) > 64) { + let err15 = { + instancePath: instancePath + "/filter/start", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/start/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + }; + vErrors.push(err15), errors++; + } else if (func1(data5) < 1) { + let err16 = { + instancePath: instancePath + "/filter/start", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/start/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err16), errors++; + } + } else { + let err17 = { + instancePath: instancePath + "/filter/start", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/start/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err17), errors++; + } + var valid7 = _errs19 === errors; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data1.end) { + let data6 = data1.end, _errs21 = errors; + if (errors === _errs21) if ("string" == typeof data6) { + if (func1(data6) > 64) { + let err18 = { + instancePath: instancePath + "/filter/end", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/end/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + }; + vErrors.push(err18), errors++; + } else if (func1(data6) < 1) { + let err19 = { + instancePath: instancePath + "/filter/end", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/end/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err19), errors++; + } + } else { + let err20 = { + instancePath: instancePath + "/filter/end", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/end/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err20), errors++; + } + var valid8 = _errs21 === errors; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data1.detection) { + let data7 = data1.detection, _errs23 = errors; + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) { + let err21 = { + instancePath: instancePath + "/filter/detection", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection/type", + keyword: "type", + params: { + type: "integer" + } + }; + vErrors.push(err21), errors++; + } + if (-1 !== data7 && 0 !== data7 && 1 !== data7) { + let err22 = { + instancePath: instancePath + "/filter/detection", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection/enum", + keyword: "enum", + params: { + allowedValues: schema127.properties.detection.enum + } + }; + vErrors.push(err22), errors++; + } + var valid9 = _errs23 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data1.detection_label) { + let data8 = data1.detection_label, _errs25 = errors; + if (errors === _errs25) if ("string" == typeof data8) { + if (func1(data8) > 255) { + let err23 = { + instancePath: instancePath + "/filter/detection_label", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection_label/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + }; + vErrors.push(err23), errors++; + } else if (func1(data8) < 1) { + let err24 = { + instancePath: instancePath + "/filter/detection_label", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection_label/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err24), errors++; + } + } else { + let err25 = { + instancePath: instancePath + "/filter/detection_label", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/detection_label/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err25), errors++; + } + var valid10 = _errs25 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data1.tag) { + let data9 = data1.tag, _errs27 = errors; + if (errors === _errs27) if ("string" == typeof data9) { + if (func1(data9) > 511) { + let err26 = { + instancePath: instancePath + "/filter/tag", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/tag/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + }; + vErrors.push(err26), errors++; + } else if (func1(data9) < 1) { + let err27 = { + instancePath: instancePath + "/filter/tag", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/tag/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err27), errors++; + } + } else { + let err28 = { + instancePath: instancePath + "/filter/tag", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/tag/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err28), errors++; + } + var valid11 = _errs27 === errors; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data1.capture_method) { + let data10 = data1.capture_method, _errs29 = errors; + if (errors === _errs29) if ("string" == typeof data10) { + if (func1(data10) > 127) { + let err29 = { + instancePath: instancePath + "/filter/capture_method", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/capture_method/maxLength", + keyword: "maxLength", + params: { + limit: 127 + } + }; + vErrors.push(err29), errors++; + } else if (func1(data10) < 1) { + let err30 = { + instancePath: instancePath + "/filter/capture_method", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/capture_method/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err30), errors++; + } + } else { + let err31 = { + instancePath: instancePath + "/filter/capture_method", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/capture_method/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err31), errors++; + } + var valid12 = _errs29 === errors; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data1.protected) { + let data11 = data1.protected, _errs31 = errors; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) { + let err32 = { + instancePath: instancePath + "/filter/protected", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/protected/type", + keyword: "type", + params: { + type: "integer" + } + }; + vErrors.push(err32), errors++; + } + if (-1 !== data11 && 0 !== data11 && 1 !== data11) { + let err33 = { + instancePath: instancePath + "/filter/protected", + schemaPath: "#/$defs/RecordingDeletionFilter/properties/protected/enum", + keyword: "enum", + params: { + allowedValues: schema127.properties.protected.enum + } + }; + vErrors.push(err33), errors++; + } + var valid13 = _errs31 === errors; + } else valid13 = !0; + if (valid13) for (let key1 in data1) if (pattern20.test(key1)) { + let data12 = data1[key1], _errs33 = errors; + if (errors === _errs33) if ("string" == typeof data12) { + if (func1(data12) > 64) { + let err34 = { + instancePath: instancePath + "/filter/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/$defs/RecordingDeletionFilter/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/maxLength", + keyword: "maxLength", + params: { + limit: 64 + } + }; + vErrors.push(err34), errors++; + } else if (func1(data12) < 1) { + let err35 = { + instancePath: instancePath + "/filter/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/$defs/RecordingDeletionFilter/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/minLength", + keyword: "minLength", + params: { + limit: 1 + } + }; + vErrors.push(err35), errors++; + } + } else { + let err36 = { + instancePath: instancePath + "/filter/" + key1.replace(/~/g, "~0").replace(/\//g, "~1"), + schemaPath: "#/$defs/RecordingDeletionFilter/patternProperties/%5E(%3F!start%24%7Cend%24)(%5BsS%5D%5BtT%5D%5BaA%5D%5BrR%5D%5BtT%5D%7C%5BeE%5D%5BnN%5D%5BdD%5D)%24/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err36), errors++; + } + if (_errs33 !== errors) break; + } } } } @@ -82586,12 +91225,12 @@ validate101.evaluated = { dynamicItems: !1 }; -var __contract_SetupStatus = validate271; +var __contract_SetupStatus = validate274; -function validate271(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate271.evaluated; +function validate274(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate274.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate271.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate274.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -82599,7 +91238,7 @@ function validate271(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.complete) return validate271.errors = [ { + if (void 0 === data.complete) return validate274.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82607,7 +91246,7 @@ function validate271(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "complete" } } ], !1; - if (void 0 === data.setup_completed_at) return validate271.errors = [ { + if (void 0 === data.setup_completed_at) return validate274.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82617,7 +91256,7 @@ function validate271(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.complete) { let _errs2 = 0; - if ("boolean" != typeof data.complete) return validate271.errors = [ { + if ("boolean" != typeof data.complete) return validate274.errors = [ { instancePath: instancePath + "/complete", schemaPath: "#/properties/complete/type", keyword: "type", @@ -82629,7 +91268,7 @@ function validate271(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.setup_completed_at) { let data1 = data.setup_completed_at; - if ("number" != typeof data1 || !isFinite(data1)) return validate271.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate274.errors = [ { instancePath: instancePath + "/setup_completed_at", schemaPath: "#/properties/setup_completed_at/type", keyword: "type", @@ -82638,49 +91277,49 @@ function validate271(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate271.errors = [], !0; + return validate274.errors = [], !0; } -validate271.evaluated = { +validate274.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_SetupUpdate = validate272; +var __contract_SetupUpdate = validate275; -function validate272(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate272.evaluated; +function validate275(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate275.evaluated; return evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data) ? (validate272.errors = [ { + !data || "object" != typeof data || Array.isArray(data) ? (validate275.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" } - } ], !1) : void 0 !== data.complete && "boolean" != typeof data.complete ? (validate272.errors = [ { + } ], !1) : void 0 !== data.complete && "boolean" != typeof data.complete ? (validate275.errors = [ { instancePath: instancePath + "/complete", schemaPath: "#/properties/complete/type", keyword: "type", params: { type: "boolean" } - } ], !1) : (validate272.errors = [], !0); + } ], !1) : (validate275.errors = [], !0); } -validate272.evaluated = { +validate275.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_SetupUpdated = validate273; +var __contract_SetupUpdated = validate276; -function validate273(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate273.evaluated; +function validate276(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate276.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate273.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate276.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -82688,7 +91327,7 @@ function validate273(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate273.errors = [ { + if (void 0 === data.success) return validate276.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82698,7 +91337,7 @@ function validate273(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.success) { let data0 = data.success; - if ("boolean" != typeof data0) return validate273.errors = [ { + if ("boolean" != typeof data0) return validate276.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -82706,7 +91345,7 @@ function validate273(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate273.errors = [ { + if (!0 !== data0) return validate276.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -82715,21 +91354,21 @@ function validate273(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate273.errors = [], !0; + return validate276.errors = [], !0; } -validate273.evaluated = { +validate276.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageArchiveDeletion = validate349; +var __contract_StorageArchiveDeletion = validate352; -function validate349(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate349.evaluated; +function validate352(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate352.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate349.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -82737,7 +91376,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate349.errors = [ { + if (void 0 === data.uuid) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82745,7 +91384,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.recording_id) return validate349.errors = [ { + if (void 0 === data.recording_id) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82753,7 +91392,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.created_at) return validate349.errors = [ { + if (void 0 === data.created_at) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82761,7 +91400,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.remaining_objects) return validate349.errors = [ { + if (void 0 === data.remaining_objects) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82769,7 +91408,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "remaining_objects" } } ], !1; - if (void 0 === data.attempts) return validate349.errors = [ { + if (void 0 === data.attempts) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82777,7 +91416,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "attempts" } } ], !1; - if (void 0 === data.next_attempt_at) return validate349.errors = [ { + if (void 0 === data.next_attempt_at) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82785,7 +91424,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "next_attempt_at" } } ], !1; - if (void 0 === data.last_error) return validate349.errors = [ { + if (void 0 === data.last_error) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82795,7 +91434,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("uuid" !== key0 && "recording_id" !== key0 && "created_at" !== key0 && "remaining_objects" !== key0 && "attempts" !== key0 && "next_attempt_at" !== key0 && "last_error" !== key0) return validate349.errors = [ { + for (let key0 in data) if ("uuid" !== key0 && "recording_id" !== key0 && "created_at" !== key0 && "remaining_objects" !== key0 && "attempts" !== key0 && "next_attempt_at" !== key0 && "last_error" !== key0) return validate352.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -82806,7 +91445,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = 0; - if ("string" != typeof data0) return validate349.errors = [ { + if ("string" != typeof data0) return validate352.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -82814,7 +91453,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate349.errors = [ { + if (!pattern4.test(data0)) return validate352.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -82827,7 +91466,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.recording_id) { let data1 = data.recording_id, _errs5 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate349.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate352.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/type", keyword: "type", @@ -82835,7 +91474,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate349.errors = [ { + if (0 === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 1 || isNaN(data1))) return validate352.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/minimum", keyword: "minimum", @@ -82849,7 +91488,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.created_at) { let data2 = data.created_at, _errs7 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate349.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate352.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -82857,7 +91496,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate349.errors = [ { + if (0 === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate352.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -82871,7 +91510,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.remaining_objects) { let data3 = data.remaining_objects, _errs9 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate349.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate352.errors = [ { instancePath: instancePath + "/remaining_objects", schemaPath: "#/properties/remaining_objects/type", keyword: "type", @@ -82879,7 +91518,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate349.errors = [ { + if (0 === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate352.errors = [ { instancePath: instancePath + "/remaining_objects", schemaPath: "#/properties/remaining_objects/minimum", keyword: "minimum", @@ -82893,7 +91532,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.attempts) { let data4 = data.attempts, _errs11 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate349.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate352.errors = [ { instancePath: instancePath + "/attempts", schemaPath: "#/properties/attempts/type", keyword: "type", @@ -82901,7 +91540,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate349.errors = [ { + if (0 === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate352.errors = [ { instancePath: instancePath + "/attempts", schemaPath: "#/properties/attempts/minimum", keyword: "minimum", @@ -82915,7 +91554,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.next_attempt_at) { let data5 = data.next_attempt_at, _errs13 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate349.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate352.errors = [ { instancePath: instancePath + "/next_attempt_at", schemaPath: "#/properties/next_attempt_at/type", keyword: "type", @@ -82923,7 +91562,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate349.errors = [ { + if (0 === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate352.errors = [ { instancePath: instancePath + "/next_attempt_at", schemaPath: "#/properties/next_attempt_at/minimum", keyword: "minimum", @@ -82934,7 +91573,7 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid6 = 0 === _errs13; } else valid6 = !0; - if (valid6 && void 0 !== data.last_error && "string" != typeof data.last_error) return validate349.errors = [ { + if (valid6 && void 0 !== data.last_error && "string" != typeof data.last_error) return validate352.errors = [ { instancePath: instancePath + "/last_error", schemaPath: "#/properties/last_error/type", keyword: "type", @@ -82949,22 +91588,22 @@ function validate349(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate349.errors = [], !0; + return validate352.errors = [], !0; } -validate349.evaluated = { +validate352.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageArchiveResponse = validate350; +var __contract_StorageArchiveResponse = validate353; -function validate350(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate350.evaluated; +function validate353(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate353.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate350.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -82972,7 +91611,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.s3_enabled) return validate350.errors = [ { + if (void 0 === data.s3_enabled) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82980,7 +91619,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "s3_enabled" } } ], !1; - if (void 0 === data.recovery_read_only) return validate350.errors = [ { + if (void 0 === data.recovery_read_only) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82988,7 +91627,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recovery_read_only" } } ], !1; - if (void 0 === data.archive_bytes) return validate350.errors = [ { + if (void 0 === data.archive_bytes) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -82996,7 +91635,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archive_bytes" } } ], !1; - if (void 0 === data.transfer_backlog_bytes) return validate350.errors = [ { + if (void 0 === data.transfer_backlog_bytes) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83004,7 +91643,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "transfer_backlog_bytes" } } ], !1; - if (void 0 === data.retrieval_cache_bytes) return validate350.errors = [ { + if (void 0 === data.retrieval_cache_bytes) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83012,7 +91651,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "retrieval_cache_bytes" } } ], !1; - if (void 0 === data.retrieval_jobs) return validate350.errors = [ { + if (void 0 === data.retrieval_jobs) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83020,7 +91659,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "retrieval_jobs" } } ], !1; - if (void 0 === data.pending_deletion_count) return validate350.errors = [ { + if (void 0 === data.pending_deletion_count) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83028,7 +91667,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pending_deletion_count" } } ], !1; - if (void 0 === data.pending_deletions) return validate350.errors = [ { + if (void 0 === data.pending_deletions) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83038,7 +91677,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("s3_enabled" !== key0 && "recovery_read_only" !== key0 && "archive_bytes" !== key0 && "transfer_backlog_bytes" !== key0 && "retrieval_cache_bytes" !== key0 && "retrieval_jobs" !== key0 && "pending_deletion_count" !== key0 && "pending_deletions" !== key0) return validate350.errors = [ { + for (let key0 in data) if ("s3_enabled" !== key0 && "recovery_read_only" !== key0 && "archive_bytes" !== key0 && "transfer_backlog_bytes" !== key0 && "retrieval_cache_bytes" !== key0 && "retrieval_jobs" !== key0 && "pending_deletion_count" !== key0 && "pending_deletions" !== key0) return validate353.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -83049,7 +91688,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.s3_enabled) { let _errs2 = errors; - if ("boolean" != typeof data.s3_enabled) return validate350.errors = [ { + if ("boolean" != typeof data.s3_enabled) return validate353.errors = [ { instancePath: instancePath + "/s3_enabled", schemaPath: "#/properties/s3_enabled/type", keyword: "type", @@ -83062,7 +91701,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.recovery_read_only) { let _errs4 = errors; - if ("boolean" != typeof data.recovery_read_only) return validate350.errors = [ { + if ("boolean" != typeof data.recovery_read_only) return validate353.errors = [ { instancePath: instancePath + "/recovery_read_only", schemaPath: "#/properties/recovery_read_only/type", keyword: "type", @@ -83075,7 +91714,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.archive_bytes) { let data2 = data.archive_bytes, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate350.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate353.errors = [ { instancePath: instancePath + "/archive_bytes", schemaPath: "#/properties/archive_bytes/type", keyword: "type", @@ -83083,7 +91722,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate350.errors = [ { + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate353.errors = [ { instancePath: instancePath + "/archive_bytes", schemaPath: "#/properties/archive_bytes/minimum", keyword: "minimum", @@ -83097,7 +91736,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.transfer_backlog_bytes) { let data3 = data.transfer_backlog_bytes, _errs8 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate350.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate353.errors = [ { instancePath: instancePath + "/transfer_backlog_bytes", schemaPath: "#/properties/transfer_backlog_bytes/type", keyword: "type", @@ -83105,7 +91744,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate350.errors = [ { + if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate353.errors = [ { instancePath: instancePath + "/transfer_backlog_bytes", schemaPath: "#/properties/transfer_backlog_bytes/minimum", keyword: "minimum", @@ -83119,7 +91758,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.retrieval_cache_bytes) { let data4 = data.retrieval_cache_bytes, _errs10 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate350.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate353.errors = [ { instancePath: instancePath + "/retrieval_cache_bytes", schemaPath: "#/properties/retrieval_cache_bytes/type", keyword: "type", @@ -83127,7 +91766,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate350.errors = [ { + if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate353.errors = [ { instancePath: instancePath + "/retrieval_cache_bytes", schemaPath: "#/properties/retrieval_cache_bytes/minimum", keyword: "minimum", @@ -83141,7 +91780,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.retrieval_jobs) { let data5 = data.retrieval_jobs, _errs12 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate350.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate353.errors = [ { instancePath: instancePath + "/retrieval_jobs", schemaPath: "#/properties/retrieval_jobs/type", keyword: "type", @@ -83149,7 +91788,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate350.errors = [ { + if (errors === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate353.errors = [ { instancePath: instancePath + "/retrieval_jobs", schemaPath: "#/properties/retrieval_jobs/minimum", keyword: "minimum", @@ -83163,7 +91802,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.pending_deletion_count) { let data6 = data.pending_deletion_count, _errs14 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate350.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate353.errors = [ { instancePath: instancePath + "/pending_deletion_count", schemaPath: "#/properties/pending_deletion_count/type", keyword: "type", @@ -83171,7 +91810,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate350.errors = [ { + if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate353.errors = [ { instancePath: instancePath + "/pending_deletion_count", schemaPath: "#/properties/pending_deletion_count/minimum", keyword: "minimum", @@ -83185,7 +91824,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD if (valid6 && void 0 !== data.pending_deletions) { let data7 = data.pending_deletions; if (errors == errors) { - if (!Array.isArray(data7)) return validate350.errors = [ { + if (!Array.isArray(data7)) return validate353.errors = [ { instancePath: instancePath + "/pending_deletions", schemaPath: "#/properties/pending_deletions/type", keyword: "type", @@ -83193,7 +91832,7 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data7.length > 100) return validate350.errors = [ { + if (data7.length > 100) return validate353.errors = [ { instancePath: instancePath + "/pending_deletions", schemaPath: "#/properties/pending_deletions/maxItems", keyword: "maxItems", @@ -83205,13 +91844,13 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD let len0 = data7.length; for (let i0 = 0; i0 < len0; i0++) { let _errs18 = errors; - if (validate349(data7[i0], { + if (validate352(data7[i0], { instancePath: instancePath + "/pending_deletions/" + i0, parentData: data7, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate349.errors), errors = vErrors.length), _errs18 !== errors) break; + }) || (vErrors = vErrors.concat(validate352.errors), errors = vErrors.length), _errs18 !== errors) break; } } } @@ -83225,21 +91864,21 @@ function validate350(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate350.errors = vErrors, 0 === errors; + return validate353.errors = vErrors, 0 === errors; } -validate350.evaluated = { +validate353.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageArchiveRetryRequest = validate352; +var __contract_StorageArchiveRetryRequest = validate355; -function validate352(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate352.evaluated; +function validate355(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate355.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate352.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate355.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -83247,7 +91886,7 @@ function validate352(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.deletion_uuid) return validate352.errors = [ { + if (void 0 === data.deletion_uuid) return validate355.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83257,7 +91896,7 @@ function validate352(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("deletion_uuid" !== key0) return validate352.errors = [ { + for (let key0 in data) if ("deletion_uuid" !== key0) return validate355.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -83267,7 +91906,7 @@ function validate352(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (0 === _errs1 && void 0 !== data.deletion_uuid) { let data0 = data.deletion_uuid; - if ("string" != typeof data0) return validate352.errors = [ { + if ("string" != typeof data0) return validate355.errors = [ { instancePath: instancePath + "/deletion_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -83275,7 +91914,7 @@ function validate352(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate352.errors = [ { + if (!pattern4.test(data0)) return validate355.errors = [ { instancePath: instancePath + "/deletion_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -83285,21 +91924,21 @@ function validate352(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate352.errors = [], !0; + return validate355.errors = [], !0; } -validate352.evaluated = { +validate355.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageArchiveRetryResponse = validate353; +var __contract_StorageArchiveRetryResponse = validate356; -function validate353(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate353.evaluated; +function validate356(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate356.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate353.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate356.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -83307,7 +91946,7 @@ function validate353(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.status) return validate353.errors = [ { + if (void 0 === data.status) return validate356.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83317,7 +91956,7 @@ function validate353(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("status" !== key0) return validate353.errors = [ { + for (let key0 in data) if ("status" !== key0) return validate356.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -83327,7 +91966,7 @@ function validate353(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (0 === _errs1 && void 0 !== data.status) { let data0 = data.status; - if ("string" != typeof data0) return validate353.errors = [ { + if ("string" != typeof data0) return validate356.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -83335,7 +91974,7 @@ function validate353(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("retry_queued" !== data0) return validate353.errors = [ { + if ("retry_queued" !== data0) return validate356.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/const", keyword: "const", @@ -83345,21 +91984,21 @@ function validate353(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate353.errors = [], !0; + return validate356.errors = [], !0; } -validate353.evaluated = { +validate356.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageCleanupRequest = validate355; +var __contract_StorageCleanupRequest = validate358; -function validate355(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate355.evaluated; +function validate358(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate358.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate355.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate358.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -83369,7 +92008,7 @@ function validate355(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("aggressive" !== key0) return validate355.errors = [ { + for (let key0 in data) if ("aggressive" !== key0) return validate358.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -83377,7 +92016,7 @@ function validate355(data, {instancePath: instancePath = "", parentData: parentD additionalProperty: key0 } } ], !1; - if (0 === _errs1 && void 0 !== data.aggressive && "boolean" != typeof data.aggressive) return validate355.errors = [ { + if (0 === _errs1 && void 0 !== data.aggressive && "boolean" != typeof data.aggressive) return validate358.errors = [ { instancePath: instancePath + "/aggressive", schemaPath: "#/properties/aggressive/type", keyword: "type", @@ -83386,21 +92025,21 @@ function validate355(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate355.errors = [], !0; + return validate358.errors = [], !0; } -validate355.evaluated = { +validate358.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageCleanupResponse = validate356; +var __contract_StorageCleanupResponse = validate359; -function validate356(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate356.evaluated; +function validate359(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate359.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate356.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate359.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -83408,7 +92047,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate356.errors = [ { + if (void 0 === data.success) return validate359.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83416,7 +92055,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.aggressive) return validate356.errors = [ { + if (void 0 === data.aggressive) return validate359.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83424,7 +92063,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "aggressive" } } ], !1; - if (void 0 === data.message) return validate356.errors = [ { + if (void 0 === data.message) return validate359.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83434,7 +92073,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("success" !== key0 && "aggressive" !== key0 && "message" !== key0) return validate356.errors = [ { + for (let key0 in data) if ("success" !== key0 && "aggressive" !== key0 && "message" !== key0) return validate359.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -83445,7 +92084,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.success) { let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate356.errors = [ { + if ("boolean" != typeof data0) return validate359.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -83453,7 +92092,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate356.errors = [ { + if (!0 !== data0) return validate359.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -83466,7 +92105,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.aggressive) { let _errs4 = 0; - if ("boolean" != typeof data.aggressive) return validate356.errors = [ { + if ("boolean" != typeof data.aggressive) return validate359.errors = [ { instancePath: instancePath + "/aggressive", schemaPath: "#/properties/aggressive/type", keyword: "type", @@ -83478,7 +92117,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.message) { let data2 = data.message; - if ("string" != typeof data2) return validate356.errors = [ { + if ("string" != typeof data2) return validate359.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", @@ -83486,7 +92125,7 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("Cleanup triggered successfully" !== data2) return validate356.errors = [ { + if ("Cleanup triggered successfully" !== data2) return validate359.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/const", keyword: "const", @@ -83498,16 +92137,16 @@ function validate356(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate356.errors = [], !0; + return validate359.errors = [], !0; } -validate356.evaluated = { +validate359.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageCompliancePolicy = validate358, schema414 = { +var __contract_StorageCompliancePolicy = validate361, schema418 = { type: "object", properties: { policy_uuid: { @@ -83577,11 +92216,11 @@ var __contract_StorageCompliancePolicy = validate358, schema414 = { additionalProperties: !1 }; -function validate358(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate358.evaluated; +function validate361(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate361.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate358.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -83589,7 +92228,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.policy_uuid) return validate358.errors = [ { + if (void 0 === data.policy_uuid) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83597,7 +92236,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_uuid" } } ], !1; - if (void 0 === data.policy_name) return validate358.errors = [ { + if (void 0 === data.policy_name) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83605,7 +92244,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_name" } } ], !1; - if (void 0 === data.recording_count) return validate358.errors = [ { + if (void 0 === data.recording_count) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83613,7 +92252,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_count" } } ], !1; - if (void 0 === data.recording_bytes) return validate358.errors = [ { + if (void 0 === data.recording_bytes) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83621,7 +92260,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_bytes" } } ], !1; - if (void 0 === data.minimum_retention_days) return validate358.errors = [ { + if (void 0 === data.minimum_retention_days) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83629,7 +92268,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "minimum_retention_days" } } ], !1; - if (void 0 === data.desired_retention_days) return validate358.errors = [ { + if (void 0 === data.desired_retention_days) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83637,7 +92276,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "desired_retention_days" } } ], !1; - if (void 0 === data.maximum_retention_days) return validate358.errors = [ { + if (void 0 === data.maximum_retention_days) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83645,7 +92284,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "maximum_retention_days" } } ], !1; - if (void 0 === data.required_copy_count) return validate358.errors = [ { + if (void 0 === data.required_copy_count) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83653,7 +92292,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "required_copy_count" } } ], !1; - if (void 0 === data.copy_deficit_recordings) return validate358.errors = [ { + if (void 0 === data.copy_deficit_recordings) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83661,7 +92300,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "copy_deficit_recordings" } } ], !1; - if (void 0 === data.active_violation_count) return validate358.errors = [ { + if (void 0 === data.active_violation_count) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83669,7 +92308,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "active_violation_count" } } ], !1; - if (void 0 === data.sample_window_days) return validate358.errors = [ { + if (void 0 === data.sample_window_days) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83677,7 +92316,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample_window_days" } } ], !1; - if (void 0 === data.observed_daily_bytes) return validate358.errors = [ { + if (void 0 === data.observed_daily_bytes) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83685,7 +92324,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observed_daily_bytes" } } ], !1; - if (void 0 === data.achieved_retention_days) return validate358.errors = [ { + if (void 0 === data.achieved_retention_days) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83693,7 +92332,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "achieved_retention_days" } } ], !1; - if (void 0 === data.confidence) return validate358.errors = [ { + if (void 0 === data.confidence) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83701,7 +92340,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "confidence" } } ], !1; - if (void 0 === data.expected_retention_days) return validate358.errors = [ { + if (void 0 === data.expected_retention_days) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83709,7 +92348,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "expected_retention_days" } } ], !1; - if (void 0 === data.minimum_forecast_met) return validate358.errors = [ { + if (void 0 === data.minimum_forecast_met) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -83719,7 +92358,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema414.properties, key0)) return validate358.errors = [ { + for (let key0 in data) if (!func7.call(schema418.properties, key0)) return validate361.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -83731,7 +92370,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.policy_uuid) { let data0 = data.policy_uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate358.errors = [ { + if ("string" != typeof data0) return validate361.errors = [ { instancePath: instancePath + "/policy_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -83739,7 +92378,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate358.errors = [ { + if (!pattern4.test(data0)) return validate361.errors = [ { instancePath: instancePath + "/policy_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -83753,7 +92392,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.policy_name) { let _errs5 = errors; - if ("string" != typeof data.policy_name) return validate358.errors = [ { + if ("string" != typeof data.policy_name) return validate361.errors = [ { instancePath: instancePath + "/policy_name", schemaPath: "#/properties/policy_name/type", keyword: "type", @@ -83766,7 +92405,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.recording_count) { let data2 = data.recording_count, _errs7 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate358.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate361.errors = [ { instancePath: instancePath + "/recording_count", schemaPath: "#/properties/recording_count/type", keyword: "type", @@ -83774,7 +92413,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate358.errors = [ { + if (errors === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate361.errors = [ { instancePath: instancePath + "/recording_count", schemaPath: "#/properties/recording_count/minimum", keyword: "minimum", @@ -83788,7 +92427,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.recording_bytes) { let data3 = data.recording_bytes, _errs9 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate358.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate361.errors = [ { instancePath: instancePath + "/recording_bytes", schemaPath: "#/properties/recording_bytes/type", keyword: "type", @@ -83796,7 +92435,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate358.errors = [ { + if (errors === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate361.errors = [ { instancePath: instancePath + "/recording_bytes", schemaPath: "#/properties/recording_bytes/minimum", keyword: "minimum", @@ -83810,7 +92449,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.minimum_retention_days) { let data4 = data.minimum_retention_days, _errs11 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate358.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate361.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/type", keyword: "type", @@ -83818,7 +92457,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate358.errors = [ { + if (errors === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate361.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/minimum", keyword: "minimum", @@ -83832,7 +92471,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.desired_retention_days) { let data5 = data.desired_retention_days, _errs13 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate358.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate361.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/type", keyword: "type", @@ -83840,7 +92479,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate358.errors = [ { + if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate361.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/minimum", keyword: "minimum", @@ -83854,7 +92493,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.maximum_retention_days) { let data6 = data.maximum_retention_days, _errs15 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate358.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate361.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/type", keyword: "type", @@ -83862,7 +92501,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate358.errors = [ { + if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate361.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/minimum", keyword: "minimum", @@ -83876,7 +92515,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.required_copy_count) { let data7 = data.required_copy_count, _errs17 = errors; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate358.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate361.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/type", keyword: "type", @@ -83884,7 +92523,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs17 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate358.errors = [ { + if (errors === _errs17 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate361.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/minimum", keyword: "minimum", @@ -83898,7 +92537,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.copy_deficit_recordings) { let data8 = data.copy_deficit_recordings, _errs19 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate358.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate361.errors = [ { instancePath: instancePath + "/copy_deficit_recordings", schemaPath: "#/properties/copy_deficit_recordings/type", keyword: "type", @@ -83906,7 +92545,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs19 && "number" == typeof data8 && isFinite(data8) && (data8 < 0 || isNaN(data8))) return validate358.errors = [ { + if (errors === _errs19 && "number" == typeof data8 && isFinite(data8) && (data8 < 0 || isNaN(data8))) return validate361.errors = [ { instancePath: instancePath + "/copy_deficit_recordings", schemaPath: "#/properties/copy_deficit_recordings/minimum", keyword: "minimum", @@ -83920,7 +92559,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.active_violation_count) { let data9 = data.active_violation_count, _errs21 = errors; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate358.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate361.errors = [ { instancePath: instancePath + "/active_violation_count", schemaPath: "#/properties/active_violation_count/type", keyword: "type", @@ -83928,7 +92567,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs21 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate358.errors = [ { + if (errors === _errs21 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate361.errors = [ { instancePath: instancePath + "/active_violation_count", schemaPath: "#/properties/active_violation_count/minimum", keyword: "minimum", @@ -83942,7 +92581,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.sample_window_days) { let data10 = data.sample_window_days, _errs23 = errors; - if ("number" != typeof data10 || !isFinite(data10)) return validate358.errors = [ { + if ("number" != typeof data10 || !isFinite(data10)) return validate361.errors = [ { instancePath: instancePath + "/sample_window_days", schemaPath: "#/properties/sample_window_days/type", keyword: "type", @@ -83955,7 +92594,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.observed_daily_bytes) { let data11 = data.observed_daily_bytes, _errs25 = errors; - if ("number" != typeof data11 || !isFinite(data11)) return validate358.errors = [ { + if ("number" != typeof data11 || !isFinite(data11)) return validate361.errors = [ { instancePath: instancePath + "/observed_daily_bytes", schemaPath: "#/properties/observed_daily_bytes/type", keyword: "type", @@ -83968,7 +92607,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data.achieved_retention_days) { let data12 = data.achieved_retention_days, _errs27 = errors; - if ("number" != typeof data12 || !isFinite(data12)) return validate358.errors = [ { + if ("number" != typeof data12 || !isFinite(data12)) return validate361.errors = [ { instancePath: instancePath + "/achieved_retention_days", schemaPath: "#/properties/achieved_retention_days/type", keyword: "type", @@ -83981,7 +92620,7 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data.confidence) { let data13 = data.confidence, _errs29 = errors; - if ("string" != typeof data13) return validate358.errors = [ { + if ("string" != typeof data13) return validate361.errors = [ { instancePath: instancePath + "/confidence", schemaPath: "#/properties/confidence/type", keyword: "type", @@ -83989,12 +92628,12 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("low" !== data13 && "medium" !== data13 && "high" !== data13) return validate358.errors = [ { + if ("low" !== data13 && "medium" !== data13 && "high" !== data13) return validate361.errors = [ { instancePath: instancePath + "/confidence", schemaPath: "#/properties/confidence/enum", keyword: "enum", params: { - allowedValues: schema414.properties.confidence.enum + allowedValues: schema418.properties.confidence.enum } } ], !1; var valid14 = _errs29 === errors; @@ -84047,12 +92686,12 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate358.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate361.errors = vErrors, !1; } errors = _errs32, vErrors.length = _errs32; var valid15 = _errs31 === errors; } else valid15 = !0; - if (valid15 && void 0 !== data.minimum_forecast_met && "boolean" != typeof data.minimum_forecast_met) return validate358.errors = [ { + if (valid15 && void 0 !== data.minimum_forecast_met && "boolean" != typeof data.minimum_forecast_met) return validate361.errors = [ { instancePath: instancePath + "/minimum_forecast_met", schemaPath: "#/properties/minimum_forecast_met/type", keyword: "type", @@ -84077,16 +92716,16 @@ function validate358(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate358.errors = vErrors, 0 === errors; + return validate361.errors = vErrors, 0 === errors; } -validate358.evaluated = { +validate361.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageComplianceResponse = validate360, schema412 = { +var __contract_StorageComplianceResponse = validate363, schema416 = { type: "object", properties: { target_uuid: { @@ -84136,11 +92775,11 @@ var __contract_StorageComplianceResponse = validate360, schema412 = { additionalProperties: !1 }; -function validate357(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate357.evaluated; +function validate360(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate360.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate357.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -84148,7 +92787,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.target_uuid) return validate357.errors = [ { + if (void 0 === data.target_uuid) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84156,7 +92795,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "target_uuid" } } ], !1; - if (void 0 === data.target_name) return validate357.errors = [ { + if (void 0 === data.target_name) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84164,7 +92803,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "target_name" } } ], !1; - if (void 0 === data.sample_window_days) return validate357.errors = [ { + if (void 0 === data.sample_window_days) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84172,7 +92811,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample_window_days" } } ], !1; - if (void 0 === data.observed_daily_bytes) return validate357.errors = [ { + if (void 0 === data.observed_daily_bytes) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84180,7 +92819,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observed_daily_bytes" } } ], !1; - if (void 0 === data.sample_count) return validate357.errors = [ { + if (void 0 === data.sample_count) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84188,7 +92827,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample_count" } } ], !1; - if (void 0 === data.observed_bytes) return validate357.errors = [ { + if (void 0 === data.observed_bytes) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84196,7 +92835,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observed_bytes" } } ], !1; - if (void 0 === data.confidence) return validate357.errors = [ { + if (void 0 === data.confidence) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84204,7 +92843,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "confidence" } } ], !1; - if (void 0 === data.days_to_high_watermark) return validate357.errors = [ { + if (void 0 === data.days_to_high_watermark) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84212,7 +92851,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "days_to_high_watermark" } } ], !1; - if (void 0 === data.expected_retention_days) return validate357.errors = [ { + if (void 0 === data.expected_retention_days) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84222,7 +92861,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema412.properties, key0)) return validate357.errors = [ { + for (let key0 in data) if (!func7.call(schema416.properties, key0)) return validate360.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -84234,7 +92873,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.target_uuid) { let data0 = data.target_uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate357.errors = [ { + if ("string" != typeof data0) return validate360.errors = [ { instancePath: instancePath + "/target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -84242,7 +92881,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate357.errors = [ { + if (!pattern4.test(data0)) return validate360.errors = [ { instancePath: instancePath + "/target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -84256,7 +92895,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.target_name) { let _errs5 = errors; - if ("string" != typeof data.target_name) return validate357.errors = [ { + if ("string" != typeof data.target_name) return validate360.errors = [ { instancePath: instancePath + "/target_name", schemaPath: "#/properties/target_name/type", keyword: "type", @@ -84270,7 +92909,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.sample_window_days) { let data2 = data.sample_window_days, _errs7 = errors; if (errors === _errs7) { - if ("number" != typeof data2 || !isFinite(data2)) return validate357.errors = [ { + if ("number" != typeof data2 || !isFinite(data2)) return validate360.errors = [ { instancePath: instancePath + "/sample_window_days", schemaPath: "#/properties/sample_window_days/type", keyword: "type", @@ -84278,7 +92917,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data2 < 0 || isNaN(data2)) return validate357.errors = [ { + if (data2 < 0 || isNaN(data2)) return validate360.errors = [ { instancePath: instancePath + "/sample_window_days", schemaPath: "#/properties/sample_window_days/minimum", keyword: "minimum", @@ -84294,7 +92933,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.observed_daily_bytes) { let data3 = data.observed_daily_bytes, _errs9 = errors; if (errors === _errs9) { - if ("number" != typeof data3 || !isFinite(data3)) return validate357.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate360.errors = [ { instancePath: instancePath + "/observed_daily_bytes", schemaPath: "#/properties/observed_daily_bytes/type", keyword: "type", @@ -84302,7 +92941,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data3 < 0 || isNaN(data3)) return validate357.errors = [ { + if (data3 < 0 || isNaN(data3)) return validate360.errors = [ { instancePath: instancePath + "/observed_daily_bytes", schemaPath: "#/properties/observed_daily_bytes/minimum", keyword: "minimum", @@ -84317,7 +92956,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.sample_count) { let data4 = data.sample_count, _errs11 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate357.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate360.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/type", keyword: "type", @@ -84325,7 +92964,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate357.errors = [ { + if (errors === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate360.errors = [ { instancePath: instancePath + "/sample_count", schemaPath: "#/properties/sample_count/minimum", keyword: "minimum", @@ -84339,7 +92978,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.observed_bytes) { let data5 = data.observed_bytes, _errs13 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate357.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate360.errors = [ { instancePath: instancePath + "/observed_bytes", schemaPath: "#/properties/observed_bytes/type", keyword: "type", @@ -84347,7 +92986,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate357.errors = [ { + if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate360.errors = [ { instancePath: instancePath + "/observed_bytes", schemaPath: "#/properties/observed_bytes/minimum", keyword: "minimum", @@ -84361,7 +93000,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.confidence) { let data6 = data.confidence, _errs15 = errors; - if ("string" != typeof data6) return validate357.errors = [ { + if ("string" != typeof data6) return validate360.errors = [ { instancePath: instancePath + "/confidence", schemaPath: "#/properties/confidence/type", keyword: "type", @@ -84369,12 +93008,12 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("low" !== data6 && "medium" !== data6 && "high" !== data6) return validate357.errors = [ { + if ("low" !== data6 && "medium" !== data6 && "high" !== data6) return validate360.errors = [ { instancePath: instancePath + "/confidence", schemaPath: "#/properties/confidence/enum", keyword: "enum", params: { - allowedValues: schema412.properties.confidence.enum + allowedValues: schema416.properties.confidence.enum } } ], !1; var valid7 = _errs15 === errors; @@ -84427,7 +93066,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate357.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate360.errors = vErrors, !1; } errors = _errs18, vErrors.length = _errs18; var valid8 = _errs17 === errors; @@ -84479,7 +93118,7 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate357.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate360.errors = vErrors, !1; } errors = _errs24, vErrors.length = _errs24; } @@ -84493,14 +93132,14 @@ function validate357(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate357.errors = vErrors, 0 === errors; + return validate360.errors = vErrors, 0 === errors; } -function validate359(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate359.evaluated; +function validate362(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate362.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate359.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -84508,7 +93147,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate359.errors = [ { + if (void 0 === data.uuid) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84516,7 +93155,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.policy_uuid) return validate359.errors = [ { + if (void 0 === data.policy_uuid) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84524,7 +93163,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_uuid" } } ], !1; - if (void 0 === data.policy_name) return validate359.errors = [ { + if (void 0 === data.policy_name) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84532,7 +93171,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_name" } } ], !1; - if (void 0 === data.recording_id) return validate359.errors = [ { + if (void 0 === data.recording_id) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84540,7 +93179,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.type) return validate359.errors = [ { + if (void 0 === data.type) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84548,7 +93187,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data.details) return validate359.errors = [ { + if (void 0 === data.details) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84556,7 +93195,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "details" } } ], !1; - if (void 0 === data.first_seen_at) return validate359.errors = [ { + if (void 0 === data.first_seen_at) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84564,7 +93203,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "first_seen_at" } } ], !1; - if (void 0 === data.last_seen_at) return validate359.errors = [ { + if (void 0 === data.last_seen_at) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84574,7 +93213,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("uuid" !== key0 && "policy_uuid" !== key0 && "policy_name" !== key0 && "recording_id" !== key0 && "type" !== key0 && "details" !== key0 && "first_seen_at" !== key0 && "last_seen_at" !== key0) return validate359.errors = [ { + for (let key0 in data) if ("uuid" !== key0 && "policy_uuid" !== key0 && "policy_name" !== key0 && "recording_id" !== key0 && "type" !== key0 && "details" !== key0 && "first_seen_at" !== key0 && "last_seen_at" !== key0) return validate362.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -84586,7 +93225,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate359.errors = [ { + if ("string" != typeof data0) return validate362.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -84594,7 +93233,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate359.errors = [ { + if (!pattern4.test(data0)) return validate362.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -84609,7 +93248,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.policy_uuid) { let data1 = data.policy_uuid, _errs5 = errors; if (errors == errors) { - if ("string" != typeof data1) return validate359.errors = [ { + if ("string" != typeof data1) return validate362.errors = [ { instancePath: instancePath + "/policy_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -84617,7 +93256,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data1)) return validate359.errors = [ { + if (!pattern4.test(data1)) return validate362.errors = [ { instancePath: instancePath + "/policy_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -84631,7 +93270,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.policy_name) { let _errs8 = errors; - if ("string" != typeof data.policy_name) return validate359.errors = [ { + if ("string" != typeof data.policy_name) return validate362.errors = [ { instancePath: instancePath + "/policy_name", schemaPath: "#/properties/policy_name/type", keyword: "type", @@ -84688,7 +93327,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate359.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate362.errors = vErrors, !1; } errors = _errs11, vErrors.length = _errs11; var valid5 = _errs10 === errors; @@ -84696,7 +93335,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.type) { let _errs16 = errors; - if ("string" != typeof data.type) return validate359.errors = [ { + if ("string" != typeof data.type) return validate362.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -84709,7 +93348,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.details) { let _errs18 = errors; - if ("string" != typeof data.details) return validate359.errors = [ { + if ("string" != typeof data.details) return validate362.errors = [ { instancePath: instancePath + "/details", schemaPath: "#/properties/details/type", keyword: "type", @@ -84722,7 +93361,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.first_seen_at) { let data6 = data.first_seen_at, _errs20 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate359.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate362.errors = [ { instancePath: instancePath + "/first_seen_at", schemaPath: "#/properties/first_seen_at/type", keyword: "type", @@ -84730,7 +93369,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs20 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate359.errors = [ { + if (errors === _errs20 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate362.errors = [ { instancePath: instancePath + "/first_seen_at", schemaPath: "#/properties/first_seen_at/minimum", keyword: "minimum", @@ -84743,7 +93382,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD } else valid9 = !0; if (valid9 && void 0 !== data.last_seen_at) { let data7 = data.last_seen_at, _errs22 = errors; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate359.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate362.errors = [ { instancePath: instancePath + "/last_seen_at", schemaPath: "#/properties/last_seen_at/type", keyword: "type", @@ -84751,7 +93390,7 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs22 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate359.errors = [ { + if (errors === _errs22 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate362.errors = [ { instancePath: instancePath + "/last_seen_at", schemaPath: "#/properties/last_seen_at/minimum", keyword: "minimum", @@ -84770,14 +93409,14 @@ function validate359(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate359.errors = vErrors, 0 === errors; + return validate362.errors = vErrors, 0 === errors; } -function validate360(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate360.evaluated; +function validate363(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate363.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate360.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -84785,7 +93424,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.forecast_window_days) return validate360.errors = [ { + if (void 0 === data.forecast_window_days) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84793,7 +93432,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "forecast_window_days" } } ], !1; - if (void 0 === data.forecast_note) return validate360.errors = [ { + if (void 0 === data.forecast_note) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84801,7 +93440,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "forecast_note" } } ], !1; - if (void 0 === data.targets) return validate360.errors = [ { + if (void 0 === data.targets) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84809,7 +93448,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "targets" } } ], !1; - if (void 0 === data.policies) return validate360.errors = [ { + if (void 0 === data.policies) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84817,7 +93456,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policies" } } ], !1; - if (void 0 === data.active_violations) return validate360.errors = [ { + if (void 0 === data.active_violations) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -84827,7 +93466,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("forecast_window_days" !== key0 && "forecast_note" !== key0 && "targets" !== key0 && "policies" !== key0 && "active_violations" !== key0) return validate360.errors = [ { + for (let key0 in data) if ("forecast_window_days" !== key0 && "forecast_note" !== key0 && "targets" !== key0 && "policies" !== key0 && "active_violations" !== key0) return validate363.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -84838,7 +93477,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.forecast_window_days) { let data0 = data.forecast_window_days, _errs2 = errors; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate360.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate363.errors = [ { instancePath: instancePath + "/forecast_window_days", schemaPath: "#/properties/forecast_window_days/type", keyword: "type", @@ -84846,7 +93485,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate360.errors = [ { + if (errors === _errs2 && "number" == typeof data0 && isFinite(data0) && (data0 < 0 || isNaN(data0))) return validate363.errors = [ { instancePath: instancePath + "/forecast_window_days", schemaPath: "#/properties/forecast_window_days/minimum", keyword: "minimum", @@ -84860,7 +93499,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.forecast_note) { let _errs4 = errors; - if ("string" != typeof data.forecast_note) return validate360.errors = [ { + if ("string" != typeof data.forecast_note) return validate363.errors = [ { instancePath: instancePath + "/forecast_note", schemaPath: "#/properties/forecast_note/type", keyword: "type", @@ -84874,7 +93513,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.targets) { let data2 = data.targets, _errs6 = errors; if (errors === _errs6) { - if (!Array.isArray(data2)) return validate360.errors = [ { + if (!Array.isArray(data2)) return validate363.errors = [ { instancePath: instancePath + "/targets", schemaPath: "#/properties/targets/type", keyword: "type", @@ -84886,13 +93525,13 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD let len0 = data2.length; for (let i0 = 0; i0 < len0; i0++) { let _errs8 = errors; - if (validate357(data2[i0], { + if (validate360(data2[i0], { instancePath: instancePath + "/targets/" + i0, parentData: data2, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate357.errors), errors = vErrors.length), _errs8 !== errors) break; + }) || (vErrors = vErrors.concat(validate360.errors), errors = vErrors.length), _errs8 !== errors) break; } } } @@ -84902,7 +93541,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.policies) { let data4 = data.policies, _errs9 = errors; if (errors === _errs9) { - if (!Array.isArray(data4)) return validate360.errors = [ { + if (!Array.isArray(data4)) return validate363.errors = [ { instancePath: instancePath + "/policies", schemaPath: "#/properties/policies/type", keyword: "type", @@ -84914,13 +93553,13 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD let len1 = data4.length; for (let i1 = 0; i1 < len1; i1++) { let _errs11 = errors; - if (validate358(data4[i1], { + if (validate361(data4[i1], { instancePath: instancePath + "/policies/" + i1, parentData: data4, parentDataProperty: i1, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate358.errors), errors = vErrors.length), _errs11 !== errors) break; + }) || (vErrors = vErrors.concat(validate361.errors), errors = vErrors.length), _errs11 !== errors) break; } } } @@ -84929,7 +93568,7 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD if (valid4 && void 0 !== data.active_violations) { let data6 = data.active_violations; if (errors == errors) { - if (!Array.isArray(data6)) return validate360.errors = [ { + if (!Array.isArray(data6)) return validate363.errors = [ { instancePath: instancePath + "/active_violations", schemaPath: "#/properties/active_violations/type", keyword: "type", @@ -84941,13 +93580,13 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD let len2 = data6.length; for (let i2 = 0; i2 < len2; i2++) { let _errs14 = errors; - if (validate359(data6[i2], { + if (validate362(data6[i2], { instancePath: instancePath + "/active_violations/" + i2, parentData: data6, parentDataProperty: i2, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate359.errors), errors = vErrors.length), _errs14 !== errors) break; + }) || (vErrors = vErrors.concat(validate362.errors), errors = vErrors.length), _errs14 !== errors) break; } } } @@ -84958,24 +93597,24 @@ function validate360(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate360.errors = vErrors, 0 === errors; + return validate363.errors = vErrors, 0 === errors; } -validate357.evaluated = { +validate360.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate359.evaluated = { +}, validate362.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate360.evaluated = { +}, validate363.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageComplianceTarget = validate357, __contract_StorageComplianceViolation = validate359, __contract_StorageHealthResponse = validate354, schema409 = { +var __contract_StorageComplianceTarget = validate360, __contract_StorageComplianceViolation = validate362, __contract_StorageHealthResponse = validate357, schema413 = { type: "object", properties: { pressure_level: { @@ -85035,10 +93674,10 @@ var __contract_StorageComplianceTarget = validate357, __contract_StorageComplian additionalProperties: !1 }; -function validate354(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate354.evaluated; +function validate357(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate357.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate354.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -85046,7 +93685,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.pressure_level) return validate354.errors = [ { + if (void 0 === data.pressure_level) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85054,7 +93693,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pressure_level" } } ], !1; - if (void 0 === data.pressure_level_num) return validate354.errors = [ { + if (void 0 === data.pressure_level_num) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85062,7 +93701,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pressure_level_num" } } ], !1; - if (void 0 === data.free_space_pct) return validate354.errors = [ { + if (void 0 === data.free_space_pct) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85070,7 +93709,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "free_space_pct" } } ], !1; - if (void 0 === data.fill_rate_bytes_per_day) return validate354.errors = [ { + if (void 0 === data.fill_rate_bytes_per_day) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85078,7 +93717,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "fill_rate_bytes_per_day" } } ], !1; - if (void 0 === data.projected_seconds_to_target) return validate354.errors = [ { + if (void 0 === data.projected_seconds_to_target) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85086,7 +93725,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "projected_seconds_to_target" } } ], !1; - if (void 0 === data.free_space_bytes) return validate354.errors = [ { + if (void 0 === data.free_space_bytes) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85094,7 +93733,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "free_space_bytes" } } ], !1; - if (void 0 === data.total_space_bytes) return validate354.errors = [ { + if (void 0 === data.total_space_bytes) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85102,7 +93741,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "total_space_bytes" } } ], !1; - if (void 0 === data.used_space_bytes) return validate354.errors = [ { + if (void 0 === data.used_space_bytes) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85110,7 +93749,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "used_space_bytes" } } ], !1; - if (void 0 === data.last_check_time) return validate354.errors = [ { + if (void 0 === data.last_check_time) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85118,7 +93757,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_check_time" } } ], !1; - if (void 0 === data.last_cleanup_time) return validate354.errors = [ { + if (void 0 === data.last_cleanup_time) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85126,7 +93765,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_cleanup_time" } } ], !1; - if (void 0 === data.last_deep_time) return validate354.errors = [ { + if (void 0 === data.last_deep_time) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85134,7 +93773,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_deep_time" } } ], !1; - if (void 0 === data.last_cleanup_deleted) return validate354.errors = [ { + if (void 0 === data.last_cleanup_deleted) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85142,7 +93781,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_cleanup_deleted" } } ], !1; - if (void 0 === data.last_cleanup_freed) return validate354.errors = [ { + if (void 0 === data.last_cleanup_freed) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85150,7 +93789,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_cleanup_freed" } } ], !1; - if (void 0 === data.capacity_target_free_pct) return validate354.errors = [ { + if (void 0 === data.capacity_target_free_pct) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85160,7 +93799,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema409.properties, key0)) return validate354.errors = [ { + for (let key0 in data) if (!func7.call(schema413.properties, key0)) return validate357.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -85171,7 +93810,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.pressure_level) { let _errs2 = 0; - if ("string" != typeof data.pressure_level) return validate354.errors = [ { + if ("string" != typeof data.pressure_level) return validate357.errors = [ { instancePath: instancePath + "/pressure_level", schemaPath: "#/properties/pressure_level/type", keyword: "type", @@ -85184,7 +93823,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.pressure_level_num) { let data1 = data.pressure_level_num, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate354.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate357.errors = [ { instancePath: instancePath + "/pressure_level_num", schemaPath: "#/properties/pressure_level_num/type", keyword: "type", @@ -85192,7 +93831,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate354.errors = [ { + if (0 === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate357.errors = [ { instancePath: instancePath + "/pressure_level_num", schemaPath: "#/properties/pressure_level_num/minimum", keyword: "minimum", @@ -85206,7 +93845,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.free_space_pct) { let data2 = data.free_space_pct, _errs6 = 0; - if ("number" != typeof data2 || !isFinite(data2)) return validate354.errors = [ { + if ("number" != typeof data2 || !isFinite(data2)) return validate357.errors = [ { instancePath: instancePath + "/free_space_pct", schemaPath: "#/properties/free_space_pct/type", keyword: "type", @@ -85219,7 +93858,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.fill_rate_bytes_per_day) { let data3 = data.fill_rate_bytes_per_day, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate354.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate357.errors = [ { instancePath: instancePath + "/fill_rate_bytes_per_day", schemaPath: "#/properties/fill_rate_bytes_per_day/type", keyword: "type", @@ -85232,7 +93871,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.projected_seconds_to_target) { let data4 = data.projected_seconds_to_target, _errs10 = 0; - if ("number" != typeof data4 || !isFinite(data4)) return validate354.errors = [ { + if ("number" != typeof data4 || !isFinite(data4)) return validate357.errors = [ { instancePath: instancePath + "/projected_seconds_to_target", schemaPath: "#/properties/projected_seconds_to_target/type", keyword: "type", @@ -85245,7 +93884,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.free_space_bytes) { let data5 = data.free_space_bytes, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate354.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate357.errors = [ { instancePath: instancePath + "/free_space_bytes", schemaPath: "#/properties/free_space_bytes/type", keyword: "type", @@ -85253,7 +93892,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate354.errors = [ { + if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate357.errors = [ { instancePath: instancePath + "/free_space_bytes", schemaPath: "#/properties/free_space_bytes/minimum", keyword: "minimum", @@ -85267,7 +93906,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.total_space_bytes) { let data6 = data.total_space_bytes, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate354.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate357.errors = [ { instancePath: instancePath + "/total_space_bytes", schemaPath: "#/properties/total_space_bytes/type", keyword: "type", @@ -85275,7 +93914,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate354.errors = [ { + if (0 === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate357.errors = [ { instancePath: instancePath + "/total_space_bytes", schemaPath: "#/properties/total_space_bytes/minimum", keyword: "minimum", @@ -85289,7 +93928,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.used_space_bytes) { let data7 = data.used_space_bytes, _errs16 = 0; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate354.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate357.errors = [ { instancePath: instancePath + "/used_space_bytes", schemaPath: "#/properties/used_space_bytes/type", keyword: "type", @@ -85297,7 +93936,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs16 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate354.errors = [ { + if (0 === _errs16 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate357.errors = [ { instancePath: instancePath + "/used_space_bytes", schemaPath: "#/properties/used_space_bytes/minimum", keyword: "minimum", @@ -85311,7 +93950,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.last_check_time) { let data8 = data.last_check_time, _errs18 = 0; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate354.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate357.errors = [ { instancePath: instancePath + "/last_check_time", schemaPath: "#/properties/last_check_time/type", keyword: "type", @@ -85319,7 +93958,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs18 && "number" == typeof data8 && isFinite(data8) && (data8 < 0 || isNaN(data8))) return validate354.errors = [ { + if (0 === _errs18 && "number" == typeof data8 && isFinite(data8) && (data8 < 0 || isNaN(data8))) return validate357.errors = [ { instancePath: instancePath + "/last_check_time", schemaPath: "#/properties/last_check_time/minimum", keyword: "minimum", @@ -85333,7 +93972,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.last_cleanup_time) { let data9 = data.last_cleanup_time, _errs20 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate354.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate357.errors = [ { instancePath: instancePath + "/last_cleanup_time", schemaPath: "#/properties/last_cleanup_time/type", keyword: "type", @@ -85341,7 +93980,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs20 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate354.errors = [ { + if (0 === _errs20 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate357.errors = [ { instancePath: instancePath + "/last_cleanup_time", schemaPath: "#/properties/last_cleanup_time/minimum", keyword: "minimum", @@ -85355,7 +93994,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.last_deep_time) { let data10 = data.last_deep_time, _errs22 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate354.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate357.errors = [ { instancePath: instancePath + "/last_deep_time", schemaPath: "#/properties/last_deep_time/type", keyword: "type", @@ -85363,7 +94002,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs22 && "number" == typeof data10 && isFinite(data10) && (data10 < 0 || isNaN(data10))) return validate354.errors = [ { + if (0 === _errs22 && "number" == typeof data10 && isFinite(data10) && (data10 < 0 || isNaN(data10))) return validate357.errors = [ { instancePath: instancePath + "/last_deep_time", schemaPath: "#/properties/last_deep_time/minimum", keyword: "minimum", @@ -85377,7 +94016,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.last_cleanup_deleted) { let data11 = data.last_cleanup_deleted, _errs24 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate354.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate357.errors = [ { instancePath: instancePath + "/last_cleanup_deleted", schemaPath: "#/properties/last_cleanup_deleted/type", keyword: "type", @@ -85385,7 +94024,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs24 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate354.errors = [ { + if (0 === _errs24 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate357.errors = [ { instancePath: instancePath + "/last_cleanup_deleted", schemaPath: "#/properties/last_cleanup_deleted/minimum", keyword: "minimum", @@ -85399,7 +94038,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.last_cleanup_freed) { let data12 = data.last_cleanup_freed, _errs26 = 0; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate354.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate357.errors = [ { instancePath: instancePath + "/last_cleanup_freed", schemaPath: "#/properties/last_cleanup_freed/type", keyword: "type", @@ -85407,7 +94046,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs26 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate354.errors = [ { + if (0 === _errs26 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate357.errors = [ { instancePath: instancePath + "/last_cleanup_freed", schemaPath: "#/properties/last_cleanup_freed/minimum", keyword: "minimum", @@ -85420,7 +94059,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD } else valid12 = !0; if (valid12 && void 0 !== data.capacity_target_free_pct) { let data13 = data.capacity_target_free_pct, _errs28 = 0; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate354.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate357.errors = [ { instancePath: instancePath + "/capacity_target_free_pct", schemaPath: "#/properties/capacity_target_free_pct/type", keyword: "type", @@ -85428,7 +94067,7 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs28 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate354.errors = [ { + if (0 === _errs28 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate357.errors = [ { instancePath: instancePath + "/capacity_target_free_pct", schemaPath: "#/properties/capacity_target_free_pct/minimum", keyword: "minimum", @@ -85452,16 +94091,16 @@ function validate354(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate354.errors = [], !0; + return validate357.errors = [], !0; } -validate354.evaluated = { +validate357.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageMigration = validate345, schema396 = { +var __contract_StorageMigration = validate348, schema400 = { type: "object", properties: { uuid: { @@ -85583,13 +94222,13 @@ var __contract_StorageMigration = validate345, schema396 = { }, required: [ "uuid", "source_target_uuid", "destination_target_uuid", "recording_id", "operation", "source_object_key", "destination_object_key", "last_error", "state", "checksum_mode", "checksum", "bytes_total", "bytes_copied", "attempt_count", "max_attempts", "bandwidth_limit_bps", "progress", "next_attempt_at", "cancel_requested", "window_start_minute", "window_end_minute", "revision", "created_at", "updated_at", "started_at", "completed_at" ], additionalProperties: !1 -}, pattern149 = new RegExp("^[0-9a-f]{64}$", "u"); +}, pattern151 = new RegExp("^[0-9a-f]{64}$", "u"); -function validate345(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate345.evaluated; +function validate348(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate348.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate345.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -85597,7 +94236,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate345.errors = [ { + if (void 0 === data.uuid) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85605,7 +94244,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.source_target_uuid) return validate345.errors = [ { + if (void 0 === data.source_target_uuid) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85613,7 +94252,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source_target_uuid" } } ], !1; - if (void 0 === data.destination_target_uuid) return validate345.errors = [ { + if (void 0 === data.destination_target_uuid) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85621,7 +94260,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "destination_target_uuid" } } ], !1; - if (void 0 === data.recording_id) return validate345.errors = [ { + if (void 0 === data.recording_id) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85629,7 +94268,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.operation) return validate345.errors = [ { + if (void 0 === data.operation) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85637,7 +94276,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "operation" } } ], !1; - if (void 0 === data.source_object_key) return validate345.errors = [ { + if (void 0 === data.source_object_key) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85645,7 +94284,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source_object_key" } } ], !1; - if (void 0 === data.destination_object_key) return validate345.errors = [ { + if (void 0 === data.destination_object_key) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85653,7 +94292,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "destination_object_key" } } ], !1; - if (void 0 === data.last_error) return validate345.errors = [ { + if (void 0 === data.last_error) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85661,7 +94300,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_error" } } ], !1; - if (void 0 === data.state) return validate345.errors = [ { + if (void 0 === data.state) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85669,7 +94308,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "state" } } ], !1; - if (void 0 === data.checksum_mode) return validate345.errors = [ { + if (void 0 === data.checksum_mode) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85677,7 +94316,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "checksum_mode" } } ], !1; - if (void 0 === data.checksum) return validate345.errors = [ { + if (void 0 === data.checksum) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85685,7 +94324,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "checksum" } } ], !1; - if (void 0 === data.bytes_total) return validate345.errors = [ { + if (void 0 === data.bytes_total) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85693,7 +94332,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bytes_total" } } ], !1; - if (void 0 === data.bytes_copied) return validate345.errors = [ { + if (void 0 === data.bytes_copied) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85701,7 +94340,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bytes_copied" } } ], !1; - if (void 0 === data.attempt_count) return validate345.errors = [ { + if (void 0 === data.attempt_count) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85709,7 +94348,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "attempt_count" } } ], !1; - if (void 0 === data.max_attempts) return validate345.errors = [ { + if (void 0 === data.max_attempts) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85717,7 +94356,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "max_attempts" } } ], !1; - if (void 0 === data.bandwidth_limit_bps) return validate345.errors = [ { + if (void 0 === data.bandwidth_limit_bps) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85725,7 +94364,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bandwidth_limit_bps" } } ], !1; - if (void 0 === data.progress) return validate345.errors = [ { + if (void 0 === data.progress) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85733,7 +94372,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "progress" } } ], !1; - if (void 0 === data.next_attempt_at) return validate345.errors = [ { + if (void 0 === data.next_attempt_at) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85741,7 +94380,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "next_attempt_at" } } ], !1; - if (void 0 === data.cancel_requested) return validate345.errors = [ { + if (void 0 === data.cancel_requested) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85749,7 +94388,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cancel_requested" } } ], !1; - if (void 0 === data.window_start_minute) return validate345.errors = [ { + if (void 0 === data.window_start_minute) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85757,7 +94396,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "window_start_minute" } } ], !1; - if (void 0 === data.window_end_minute) return validate345.errors = [ { + if (void 0 === data.window_end_minute) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85765,7 +94404,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "window_end_minute" } } ], !1; - if (void 0 === data.revision) return validate345.errors = [ { + if (void 0 === data.revision) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85773,7 +94412,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate345.errors = [ { + if (void 0 === data.created_at) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85781,7 +94420,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate345.errors = [ { + if (void 0 === data.updated_at) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85789,7 +94428,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "updated_at" } } ], !1; - if (void 0 === data.started_at) return validate345.errors = [ { + if (void 0 === data.started_at) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85797,7 +94436,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "started_at" } } ], !1; - if (void 0 === data.completed_at) return validate345.errors = [ { + if (void 0 === data.completed_at) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -85807,7 +94446,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema396.properties, key0)) return validate345.errors = [ { + for (let key0 in data) if (!func7.call(schema400.properties, key0)) return validate348.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -85819,7 +94458,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate345.errors = [ { + if ("string" != typeof data0) return validate348.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -85827,7 +94466,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate345.errors = [ { + if (!pattern4.test(data0)) return validate348.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -85842,7 +94481,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.source_target_uuid) { let data1 = data.source_target_uuid, _errs5 = errors; if (errors == errors) { - if ("string" != typeof data1) return validate345.errors = [ { + if ("string" != typeof data1) return validate348.errors = [ { instancePath: instancePath + "/source_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -85850,7 +94489,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data1)) return validate345.errors = [ { + if (!pattern4.test(data1)) return validate348.errors = [ { instancePath: instancePath + "/source_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -85865,7 +94504,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.destination_target_uuid) { let data2 = data.destination_target_uuid, _errs8 = errors; if (errors == errors) { - if ("string" != typeof data2) return validate345.errors = [ { + if ("string" != typeof data2) return validate348.errors = [ { instancePath: instancePath + "/destination_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -85873,7 +94512,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data2)) return validate345.errors = [ { + if (!pattern4.test(data2)) return validate348.errors = [ { instancePath: instancePath + "/destination_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -85887,7 +94526,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.recording_id) { let data3 = data.recording_id, _errs11 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate345.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate348.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/type", keyword: "type", @@ -85895,7 +94534,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs11 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate345.errors = [ { + if (errors === _errs11 && "number" == typeof data3 && isFinite(data3) && (data3 < 1 || isNaN(data3))) return validate348.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/minimum", keyword: "minimum", @@ -85909,7 +94548,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.operation) { let data4 = data.operation, _errs13 = errors; - if ("string" != typeof data4) return validate345.errors = [ { + if ("string" != typeof data4) return validate348.errors = [ { instancePath: instancePath + "/operation", schemaPath: "#/properties/operation/type", keyword: "type", @@ -85917,12 +94556,12 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("move" !== data4 && "copy" !== data4) return validate345.errors = [ { + if ("move" !== data4 && "copy" !== data4) return validate348.errors = [ { instancePath: instancePath + "/operation", schemaPath: "#/properties/operation/enum", keyword: "enum", params: { - allowedValues: schema396.properties.operation.enum + allowedValues: schema400.properties.operation.enum } } ], !1; var valid7 = _errs13 === errors; @@ -85930,7 +94569,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.source_object_key) { let _errs15 = errors; - if ("string" != typeof data.source_object_key) return validate345.errors = [ { + if ("string" != typeof data.source_object_key) return validate348.errors = [ { instancePath: instancePath + "/source_object_key", schemaPath: "#/properties/source_object_key/type", keyword: "type", @@ -85943,7 +94582,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.destination_object_key) { let _errs17 = errors; - if ("string" != typeof data.destination_object_key) return validate345.errors = [ { + if ("string" != typeof data.destination_object_key) return validate348.errors = [ { instancePath: instancePath + "/destination_object_key", schemaPath: "#/properties/destination_object_key/type", keyword: "type", @@ -85956,7 +94595,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.last_error) { let _errs19 = errors; - if ("string" != typeof data.last_error) return validate345.errors = [ { + if ("string" != typeof data.last_error) return validate348.errors = [ { instancePath: instancePath + "/last_error", schemaPath: "#/properties/last_error/type", keyword: "type", @@ -85969,7 +94608,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.state) { let data8 = data.state, _errs21 = errors; - if ("string" != typeof data8) return validate345.errors = [ { + if ("string" != typeof data8) return validate348.errors = [ { instancePath: instancePath + "/state", schemaPath: "#/properties/state/type", keyword: "type", @@ -85977,12 +94616,12 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("queued" !== data8 && "copying" !== data8 && "verifying" !== data8 && "committing" !== data8 && "retry_wait" !== data8 && "cleanup_pending" !== data8 && "completed" !== data8 && "failed" !== data8 && "cancelled" !== data8) return validate345.errors = [ { + if ("queued" !== data8 && "copying" !== data8 && "verifying" !== data8 && "committing" !== data8 && "retry_wait" !== data8 && "cleanup_pending" !== data8 && "completed" !== data8 && "failed" !== data8 && "cancelled" !== data8) return validate348.errors = [ { instancePath: instancePath + "/state", schemaPath: "#/properties/state/enum", keyword: "enum", params: { - allowedValues: schema396.properties.state.enum + allowedValues: schema400.properties.state.enum } } ], !1; var valid11 = _errs21 === errors; @@ -85990,7 +94629,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.checksum_mode) { let data9 = data.checksum_mode, _errs23 = errors; - if ("string" != typeof data9) return validate345.errors = [ { + if ("string" != typeof data9) return validate348.errors = [ { instancePath: instancePath + "/checksum_mode", schemaPath: "#/properties/checksum_mode/type", keyword: "type", @@ -85998,7 +94637,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("sha256" !== data9) return validate345.errors = [ { + if ("sha256" !== data9) return validate348.errors = [ { instancePath: instancePath + "/checksum_mode", schemaPath: "#/properties/checksum_mode/const", keyword: "const", @@ -86012,7 +94651,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.checksum) { let data10 = data.checksum, _errs25 = errors, _errs26 = errors, valid14 = !1, _errs27 = errors; if (errors === _errs27) if ("string" == typeof data10) { - if (!pattern149.test(data10)) { + if (!pattern151.test(data10)) { let err0 = { instancePath: instancePath + "/checksum", schemaPath: "#/properties/checksum/anyOf/0/pattern", @@ -86055,7 +94694,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate345.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate348.errors = vErrors, !1; } errors = _errs26, vErrors.length = _errs26; var valid13 = _errs25 === errors; @@ -86063,7 +94702,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data.bytes_total) { let data11 = data.bytes_total, _errs31 = errors; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate345.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate348.errors = [ { instancePath: instancePath + "/bytes_total", schemaPath: "#/properties/bytes_total/type", keyword: "type", @@ -86071,7 +94710,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs31 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate345.errors = [ { + if (errors === _errs31 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate348.errors = [ { instancePath: instancePath + "/bytes_total", schemaPath: "#/properties/bytes_total/minimum", keyword: "minimum", @@ -86085,7 +94724,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data.bytes_copied) { let data12 = data.bytes_copied, _errs33 = errors; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate345.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate348.errors = [ { instancePath: instancePath + "/bytes_copied", schemaPath: "#/properties/bytes_copied/type", keyword: "type", @@ -86093,7 +94732,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs33 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate345.errors = [ { + if (errors === _errs33 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate348.errors = [ { instancePath: instancePath + "/bytes_copied", schemaPath: "#/properties/bytes_copied/minimum", keyword: "minimum", @@ -86107,7 +94746,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data.attempt_count) { let data13 = data.attempt_count, _errs35 = errors; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate345.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate348.errors = [ { instancePath: instancePath + "/attempt_count", schemaPath: "#/properties/attempt_count/type", keyword: "type", @@ -86115,7 +94754,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs35 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate345.errors = [ { + if (errors === _errs35 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate348.errors = [ { instancePath: instancePath + "/attempt_count", schemaPath: "#/properties/attempt_count/minimum", keyword: "minimum", @@ -86129,7 +94768,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data.max_attempts) { let data14 = data.max_attempts, _errs37 = errors; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate345.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate348.errors = [ { instancePath: instancePath + "/max_attempts", schemaPath: "#/properties/max_attempts/type", keyword: "type", @@ -86137,7 +94776,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs37 && "number" == typeof data14 && isFinite(data14) && (data14 < 0 || isNaN(data14))) return validate345.errors = [ { + if (errors === _errs37 && "number" == typeof data14 && isFinite(data14) && (data14 < 0 || isNaN(data14))) return validate348.errors = [ { instancePath: instancePath + "/max_attempts", schemaPath: "#/properties/max_attempts/minimum", keyword: "minimum", @@ -86151,7 +94790,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data.bandwidth_limit_bps) { let data15 = data.bandwidth_limit_bps, _errs39 = errors; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate345.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate348.errors = [ { instancePath: instancePath + "/bandwidth_limit_bps", schemaPath: "#/properties/bandwidth_limit_bps/type", keyword: "type", @@ -86159,7 +94798,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs39 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate345.errors = [ { + if (errors === _errs39 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate348.errors = [ { instancePath: instancePath + "/bandwidth_limit_bps", schemaPath: "#/properties/bandwidth_limit_bps/minimum", keyword: "minimum", @@ -86174,7 +94813,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.progress) { let data16 = data.progress, _errs41 = errors; if (errors === _errs41) { - if ("number" != typeof data16 || !isFinite(data16)) return validate345.errors = [ { + if ("number" != typeof data16 || !isFinite(data16)) return validate348.errors = [ { instancePath: instancePath + "/progress", schemaPath: "#/properties/progress/type", keyword: "type", @@ -86182,7 +94821,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data16 < 0 || isNaN(data16)) return validate345.errors = [ { + if (data16 < 0 || isNaN(data16)) return validate348.errors = [ { instancePath: instancePath + "/progress", schemaPath: "#/properties/progress/minimum", keyword: "minimum", @@ -86241,7 +94880,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate345.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate348.errors = vErrors, !1; } errors = _errs44, vErrors.length = _errs44; var valid21 = _errs43 === errors; @@ -86249,7 +94888,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data.cancel_requested) { let _errs49 = errors; - if ("boolean" != typeof data.cancel_requested) return validate345.errors = [ { + if ("boolean" != typeof data.cancel_requested) return validate348.errors = [ { instancePath: instancePath + "/cancel_requested", schemaPath: "#/properties/cancel_requested/type", keyword: "type", @@ -86262,7 +94901,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data.window_start_minute) { let data19 = data.window_start_minute, _errs51 = errors; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate345.errors = [ { + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate348.errors = [ { instancePath: instancePath + "/window_start_minute", schemaPath: "#/properties/window_start_minute/type", keyword: "type", @@ -86271,7 +94910,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs51 && "number" == typeof data19 && isFinite(data19)) { - if (data19 > 1439 || isNaN(data19)) return validate345.errors = [ { + if (data19 > 1439 || isNaN(data19)) return validate348.errors = [ { instancePath: instancePath + "/window_start_minute", schemaPath: "#/properties/window_start_minute/maximum", keyword: "maximum", @@ -86280,7 +94919,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data19 < 0 || isNaN(data19)) return validate345.errors = [ { + if (data19 < 0 || isNaN(data19)) return validate348.errors = [ { instancePath: instancePath + "/window_start_minute", schemaPath: "#/properties/window_start_minute/minimum", keyword: "minimum", @@ -86295,7 +94934,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data.window_end_minute) { let data20 = data.window_end_minute, _errs53 = errors; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate345.errors = [ { + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate348.errors = [ { instancePath: instancePath + "/window_end_minute", schemaPath: "#/properties/window_end_minute/type", keyword: "type", @@ -86304,7 +94943,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs53 && "number" == typeof data20 && isFinite(data20)) { - if (data20 > 1439 || isNaN(data20)) return validate345.errors = [ { + if (data20 > 1439 || isNaN(data20)) return validate348.errors = [ { instancePath: instancePath + "/window_end_minute", schemaPath: "#/properties/window_end_minute/maximum", keyword: "maximum", @@ -86313,7 +94952,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data20 < 0 || isNaN(data20)) return validate345.errors = [ { + if (data20 < 0 || isNaN(data20)) return validate348.errors = [ { instancePath: instancePath + "/window_end_minute", schemaPath: "#/properties/window_end_minute/minimum", keyword: "minimum", @@ -86328,7 +94967,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data.revision) { let data21 = data.revision, _errs55 = errors; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate345.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate348.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -86337,7 +94976,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs55 && "number" == typeof data21 && isFinite(data21)) { - if (data21 > 9007199254740991 || isNaN(data21)) return validate345.errors = [ { + if (data21 > 9007199254740991 || isNaN(data21)) return validate348.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -86346,7 +94985,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data21 < 1 || isNaN(data21)) return validate345.errors = [ { + if (data21 < 1 || isNaN(data21)) return validate348.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -86361,7 +95000,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data.created_at) { let data22 = data.created_at, _errs57 = errors; - if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate345.errors = [ { + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate348.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -86369,7 +95008,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs57 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate345.errors = [ { + if (errors === _errs57 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate348.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -86383,7 +95022,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data.updated_at) { let data23 = data.updated_at, _errs59 = errors; - if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate345.errors = [ { + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate348.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -86391,7 +95030,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs59 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate345.errors = [ { + if (errors === _errs59 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate348.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/minimum", keyword: "minimum", @@ -86449,7 +95088,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err11), errors++, validate345.errors = vErrors, !1; + return vErrors.push(err11), errors++, validate348.errors = vErrors, !1; } errors = _errs62, vErrors.length = _errs62; var valid29 = _errs61 === errors; @@ -86500,7 +95139,7 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err15), errors++, validate345.errors = vErrors, !1; + return vErrors.push(err15), errors++, validate348.errors = vErrors, !1; } errors = _errs68, vErrors.length = _errs68; } @@ -86531,16 +95170,16 @@ function validate345(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate345.errors = vErrors, 0 === errors; + return validate348.errors = vErrors, 0 === errors; } -validate345.evaluated = { +validate348.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageMigrationCreate = validate348, schema401 = { +var __contract_StorageMigrationCreate = validate351, schema405 = { type: "object", properties: { recording_id: { @@ -86560,10 +95199,10 @@ var __contract_StorageMigrationCreate = validate348, schema401 = { additionalProperties: !1 }; -function validate348(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate348.evaluated; +function validate351(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate351.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate348.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate351.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -86571,7 +95210,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.recording_id) return validate348.errors = [ { + if (void 0 === data.recording_id) return validate351.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86579,7 +95218,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.destination_target_uuid) return validate348.errors = [ { + if (void 0 === data.destination_target_uuid) return validate351.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86589,7 +95228,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("recording_id" !== key0 && "destination_target_uuid" !== key0 && "operation" !== key0) return validate348.errors = [ { + for (let key0 in data) if ("recording_id" !== key0 && "destination_target_uuid" !== key0 && "operation" !== key0) return validate351.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -86600,7 +95239,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.recording_id) { let data0 = data.recording_id, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate348.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate351.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/type", keyword: "type", @@ -86609,7 +95248,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs2 && "number" == typeof data0 && isFinite(data0)) { - if (data0 > 9007199254740991 || isNaN(data0)) return validate348.errors = [ { + if (data0 > 9007199254740991 || isNaN(data0)) return validate351.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/maximum", keyword: "maximum", @@ -86618,7 +95257,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data0 < 1 || isNaN(data0)) return validate348.errors = [ { + if (data0 < 1 || isNaN(data0)) return validate351.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/minimum", keyword: "minimum", @@ -86633,7 +95272,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.destination_target_uuid) { let data1 = data.destination_target_uuid, _errs4 = 0; - if ("string" != typeof data1) return validate348.errors = [ { + if ("string" != typeof data1) return validate351.errors = [ { instancePath: instancePath + "/destination_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -86641,7 +95280,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data1)) return validate348.errors = [ { + if (!pattern4.test(data1)) return validate351.errors = [ { instancePath: instancePath + "/destination_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -86653,7 +95292,7 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.operation) { let data2 = data.operation; - if ("string" != typeof data2) return validate348.errors = [ { + if ("string" != typeof data2) return validate351.errors = [ { instancePath: instancePath + "/operation", schemaPath: "#/properties/operation/type", keyword: "type", @@ -86661,34 +95300,34 @@ function validate348(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("move" !== data2 && "copy" !== data2) return validate348.errors = [ { + if ("move" !== data2 && "copy" !== data2) return validate351.errors = [ { instancePath: instancePath + "/operation", schemaPath: "#/properties/operation/enum", keyword: "enum", params: { - allowedValues: schema401.properties.operation.enum + allowedValues: schema405.properties.operation.enum } } ], !1; } } } } - return validate348.errors = [], !0; + return validate351.errors = [], !0; } -validate348.evaluated = { +validate351.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageMigrationsResponse = validate346; +var __contract_StorageMigrationsResponse = validate349; -function validate346(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate346.evaluated; +function validate349(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate349.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate346.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate349.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -86696,7 +95335,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.jobs) return validate346.errors = [ { + if (void 0 === data.jobs) return validate349.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86704,7 +95343,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "jobs" } } ], !1; - if (void 0 === data.count) return validate346.errors = [ { + if (void 0 === data.count) return validate349.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86712,7 +95351,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "count" } } ], !1; - if (void 0 === data.worker_concurrency) return validate346.errors = [ { + if (void 0 === data.worker_concurrency) return validate349.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86722,7 +95361,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("jobs" !== key0 && "count" !== key0 && "worker_concurrency" !== key0) return validate346.errors = [ { + for (let key0 in data) if ("jobs" !== key0 && "count" !== key0 && "worker_concurrency" !== key0) return validate349.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -86734,7 +95373,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.jobs) { let data0 = data.jobs, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate346.errors = [ { + if (!Array.isArray(data0)) return validate349.errors = [ { instancePath: instancePath + "/jobs", schemaPath: "#/properties/jobs/type", keyword: "type", @@ -86746,13 +95385,13 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = errors; - if (validate345(data0[i0], { + if (validate348(data0[i0], { instancePath: instancePath + "/jobs/" + i0, parentData: data0, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate345.errors), errors = vErrors.length), _errs4 !== errors) break; + }) || (vErrors = vErrors.concat(validate348.errors), errors = vErrors.length), _errs4 !== errors) break; } } } @@ -86761,7 +95400,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.count) { let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate346.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate349.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -86769,7 +95408,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate346.errors = [ { + if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate349.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -86782,7 +95421,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD } else valid2 = !0; if (valid2 && void 0 !== data.worker_concurrency) { let data3 = data.worker_concurrency; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate346.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate349.errors = [ { instancePath: instancePath + "/worker_concurrency", schemaPath: "#/properties/worker_concurrency/type", keyword: "type", @@ -86790,7 +95429,7 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (1 !== data3) return validate346.errors = [ { + if (1 !== data3) return validate349.errors = [ { instancePath: instancePath + "/worker_concurrency", schemaPath: "#/properties/worker_concurrency/const", keyword: "const", @@ -86803,16 +95442,16 @@ function validate346(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate346.errors = vErrors, 0 === errors; + return validate349.errors = vErrors, 0 === errors; } -validate346.evaluated = { +validate349.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePoliciesResponse = validate326, schema352 = { +var __contract_StoragePoliciesResponse = validate329, schema356 = { type: "object", properties: { uuid: { @@ -86933,11 +95572,11 @@ var __contract_StoragePoliciesResponse = validate326, schema352 = { additionalProperties: !1 }; -function validate324(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate324.evaluated; +function validate327(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate327.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate324.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -86945,7 +95584,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate324.errors = [ { + if (void 0 === data.uuid) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86953,7 +95592,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.name) return validate324.errors = [ { + if (void 0 === data.name) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86961,7 +95600,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.enabled) return validate324.errors = [ { + if (void 0 === data.enabled) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86969,7 +95608,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.priority) return validate324.errors = [ { + if (void 0 === data.priority) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86977,7 +95616,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "priority" } } ], !1; - if (void 0 === data.selector) return validate324.errors = [ { + if (void 0 === data.selector) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86985,7 +95624,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "selector" } } ], !1; - if (void 0 === data.primary_target_uuid) return validate324.errors = [ { + if (void 0 === data.primary_target_uuid) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -86993,7 +95632,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "primary_target_uuid" } } ], !1; - if (void 0 === data.primary_pool_uuid) return validate324.errors = [ { + if (void 0 === data.primary_pool_uuid) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87001,7 +95640,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "primary_pool_uuid" } } ], !1; - if (void 0 === data.fallback_target_uuid) return validate324.errors = [ { + if (void 0 === data.fallback_target_uuid) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87009,7 +95648,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "fallback_target_uuid" } } ], !1; - if (void 0 === data.replication_pool_uuid) return validate324.errors = [ { + if (void 0 === data.replication_pool_uuid) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87017,7 +95656,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "replication_pool_uuid" } } ], !1; - if (void 0 === data.migration_target_uuid) return validate324.errors = [ { + if (void 0 === data.migration_target_uuid) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87025,7 +95664,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "migration_target_uuid" } } ], !1; - if (void 0 === data.fallback_mode) return validate324.errors = [ { + if (void 0 === data.fallback_mode) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87033,7 +95672,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "fallback_mode" } } ], !1; - if (void 0 === data.minimum_retention_days) return validate324.errors = [ { + if (void 0 === data.minimum_retention_days) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87041,7 +95680,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "minimum_retention_days" } } ], !1; - if (void 0 === data.desired_retention_days) return validate324.errors = [ { + if (void 0 === data.desired_retention_days) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87049,7 +95688,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "desired_retention_days" } } ], !1; - if (void 0 === data.maximum_retention_days) return validate324.errors = [ { + if (void 0 === data.maximum_retention_days) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87057,7 +95696,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "maximum_retention_days" } } ], !1; - if (void 0 === data.migration_after_days) return validate324.errors = [ { + if (void 0 === data.migration_after_days) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87065,7 +95704,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "migration_after_days" } } ], !1; - if (void 0 === data.required_copy_count) return validate324.errors = [ { + if (void 0 === data.required_copy_count) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87073,7 +95712,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "required_copy_count" } } ], !1; - if (void 0 === data.pressure_priority) return validate324.errors = [ { + if (void 0 === data.pressure_priority) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87081,7 +95720,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pressure_priority" } } ], !1; - if (void 0 === data.archive_after_seconds) return validate324.errors = [ { + if (void 0 === data.archive_after_seconds) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87089,7 +95728,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archive_after_seconds" } } ], !1; - if (void 0 === data.hot_residency_seconds) return validate324.errors = [ { + if (void 0 === data.hot_residency_seconds) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87097,7 +95736,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "hot_residency_seconds" } } ], !1; - if (void 0 === data.archive_protected) return validate324.errors = [ { + if (void 0 === data.archive_protected) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87105,7 +95744,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archive_protected" } } ], !1; - if (void 0 === data.archive_on_pressure) return validate324.errors = [ { + if (void 0 === data.archive_on_pressure) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87113,7 +95752,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archive_on_pressure" } } ], !1; - if (void 0 === data.revision) return validate324.errors = [ { + if (void 0 === data.revision) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87121,7 +95760,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate324.errors = [ { + if (void 0 === data.created_at) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87129,7 +95768,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate324.errors = [ { + if (void 0 === data.updated_at) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87139,7 +95778,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema352.properties, key0)) return validate324.errors = [ { + for (let key0 in data) if (!func7.call(schema356.properties, key0)) return validate327.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -87151,7 +95790,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate324.errors = [ { + if ("string" != typeof data0) return validate327.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -87159,7 +95798,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate324.errors = [ { + if (!pattern4.test(data0)) return validate327.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -87174,7 +95813,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data1 = data.name, _errs5 = errors; if (errors === _errs5) { - if ("string" != typeof data1) return validate324.errors = [ { + if ("string" != typeof data1) return validate327.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -87182,7 +95821,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 127) return validate324.errors = [ { + if (func1(data1) > 127) return validate327.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -87190,7 +95829,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data1) < 1) return validate324.errors = [ { + if (func1(data1) < 1) return validate327.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -87204,7 +95843,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.enabled) { let _errs7 = errors; - if ("boolean" != typeof data.enabled) return validate324.errors = [ { + if ("boolean" != typeof data.enabled) return validate327.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -87217,7 +95856,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.priority) { let data3 = data.priority, _errs9 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate324.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate327.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", @@ -87226,7 +95865,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs9 && "number" == typeof data3 && isFinite(data3)) { - if (data3 > 1e6 || isNaN(data3)) return validate324.errors = [ { + if (data3 > 1e6 || isNaN(data3)) return validate327.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/maximum", keyword: "maximum", @@ -87235,7 +95874,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data3 < -1e6 || isNaN(data3)) return validate324.errors = [ { + if (data3 < -1e6 || isNaN(data3)) return validate327.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/minimum", keyword: "minimum", @@ -87263,7 +95902,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_target_uuid) { let data5 = data.primary_target_uuid, _errs12 = errors; if (errors == errors) { - if ("string" != typeof data5) return validate324.errors = [ { + if ("string" != typeof data5) return validate327.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -87271,7 +95910,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data5)) return validate324.errors = [ { + if (!pattern4.test(data5)) return validate327.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -87329,7 +95968,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate324.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate327.errors = vErrors, !1; } errors = _errs16, vErrors.length = _errs16; var valid8 = _errs15 === errors; @@ -87381,7 +96020,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate324.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate327.errors = vErrors, !1; } errors = _errs23, vErrors.length = _errs23; var valid11 = _errs22 === errors; @@ -87433,7 +96072,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err11), errors++, validate324.errors = vErrors, !1; + return vErrors.push(err11), errors++, validate327.errors = vErrors, !1; } errors = _errs30, vErrors.length = _errs30; var valid14 = _errs29 === errors; @@ -87485,7 +96124,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err15), errors++, validate324.errors = vErrors, !1; + return vErrors.push(err15), errors++, validate327.errors = vErrors, !1; } errors = _errs37, vErrors.length = _errs37; var valid17 = _errs36 === errors; @@ -87493,7 +96132,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data.fallback_mode) { let data10 = data.fallback_mode, _errs43 = errors; - if ("string" != typeof data10) return validate324.errors = [ { + if ("string" != typeof data10) return validate327.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/type", keyword: "type", @@ -87501,12 +96140,12 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("default" !== data10 && "target" !== data10 && "pause" !== data10 && "fail" !== data10) return validate324.errors = [ { + if ("default" !== data10 && "target" !== data10 && "pause" !== data10 && "fail" !== data10) return validate327.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/enum", keyword: "enum", params: { - allowedValues: schema352.properties.fallback_mode.enum + allowedValues: schema356.properties.fallback_mode.enum } } ], !1; var valid20 = _errs43 === errors; @@ -87514,7 +96153,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data.minimum_retention_days) { let data11 = data.minimum_retention_days, _errs45 = errors; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate324.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate327.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/type", keyword: "type", @@ -87523,7 +96162,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs45 && "number" == typeof data11 && isFinite(data11)) { - if (data11 > 36500 || isNaN(data11)) return validate324.errors = [ { + if (data11 > 36500 || isNaN(data11)) return validate327.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/maximum", keyword: "maximum", @@ -87532,7 +96171,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data11 < 0 || isNaN(data11)) return validate324.errors = [ { + if (data11 < 0 || isNaN(data11)) return validate327.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/minimum", keyword: "minimum", @@ -87547,7 +96186,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data.desired_retention_days) { let data12 = data.desired_retention_days, _errs47 = errors; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate324.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate327.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/type", keyword: "type", @@ -87556,7 +96195,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs47 && "number" == typeof data12 && isFinite(data12)) { - if (data12 > 36500 || isNaN(data12)) return validate324.errors = [ { + if (data12 > 36500 || isNaN(data12)) return validate327.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/maximum", keyword: "maximum", @@ -87565,7 +96204,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data12 < 0 || isNaN(data12)) return validate324.errors = [ { + if (data12 < 0 || isNaN(data12)) return validate327.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/minimum", keyword: "minimum", @@ -87580,7 +96219,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data.maximum_retention_days) { let data13 = data.maximum_retention_days, _errs49 = errors; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate324.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate327.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/type", keyword: "type", @@ -87589,7 +96228,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs49 && "number" == typeof data13 && isFinite(data13)) { - if (data13 > 36500 || isNaN(data13)) return validate324.errors = [ { + if (data13 > 36500 || isNaN(data13)) return validate327.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/maximum", keyword: "maximum", @@ -87598,7 +96237,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data13 < 0 || isNaN(data13)) return validate324.errors = [ { + if (data13 < 0 || isNaN(data13)) return validate327.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/minimum", keyword: "minimum", @@ -87613,7 +96252,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data.migration_after_days) { let data14 = data.migration_after_days, _errs51 = errors; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate324.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate327.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/type", keyword: "type", @@ -87622,7 +96261,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs51 && "number" == typeof data14 && isFinite(data14)) { - if (data14 > 36500 || isNaN(data14)) return validate324.errors = [ { + if (data14 > 36500 || isNaN(data14)) return validate327.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/maximum", keyword: "maximum", @@ -87631,7 +96270,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate324.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate327.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/minimum", keyword: "minimum", @@ -87646,7 +96285,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data.required_copy_count) { let data15 = data.required_copy_count, _errs53 = errors; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate324.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate327.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/type", keyword: "type", @@ -87655,7 +96294,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs53 && "number" == typeof data15 && isFinite(data15)) { - if (data15 > 8 || isNaN(data15)) return validate324.errors = [ { + if (data15 > 8 || isNaN(data15)) return validate327.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/maximum", keyword: "maximum", @@ -87664,7 +96303,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 8 } } ], !1; - if (data15 < 1 || isNaN(data15)) return validate324.errors = [ { + if (data15 < 1 || isNaN(data15)) return validate327.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/minimum", keyword: "minimum", @@ -87679,7 +96318,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data.pressure_priority) { let data16 = data.pressure_priority, _errs55 = errors; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate324.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate327.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/type", keyword: "type", @@ -87688,7 +96327,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs55 && "number" == typeof data16 && isFinite(data16)) { - if (data16 > 1e6 || isNaN(data16)) return validate324.errors = [ { + if (data16 > 1e6 || isNaN(data16)) return validate327.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/maximum", keyword: "maximum", @@ -87697,7 +96336,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data16 < -1e6 || isNaN(data16)) return validate324.errors = [ { + if (data16 < -1e6 || isNaN(data16)) return validate327.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/minimum", keyword: "minimum", @@ -87712,7 +96351,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data.archive_after_seconds) { let data17 = data.archive_after_seconds, _errs57 = errors; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate324.errors = [ { + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate327.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/type", keyword: "type", @@ -87721,7 +96360,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs57 && "number" == typeof data17 && isFinite(data17)) { - if (data17 > 2147483647 || isNaN(data17)) return validate324.errors = [ { + if (data17 > 2147483647 || isNaN(data17)) return validate327.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/maximum", keyword: "maximum", @@ -87730,7 +96369,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data17 < -1 || isNaN(data17)) return validate324.errors = [ { + if (data17 < -1 || isNaN(data17)) return validate327.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/minimum", keyword: "minimum", @@ -87745,7 +96384,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data.hot_residency_seconds) { let data18 = data.hot_residency_seconds, _errs59 = errors; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate324.errors = [ { + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate327.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/type", keyword: "type", @@ -87754,7 +96393,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs59 && "number" == typeof data18 && isFinite(data18)) { - if (data18 > 2147483647 || isNaN(data18)) return validate324.errors = [ { + if (data18 > 2147483647 || isNaN(data18)) return validate327.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/maximum", keyword: "maximum", @@ -87763,7 +96402,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data18 < -1 || isNaN(data18)) return validate324.errors = [ { + if (data18 < -1 || isNaN(data18)) return validate327.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/minimum", keyword: "minimum", @@ -87778,7 +96417,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid28) { if (void 0 !== data.archive_protected) { let _errs61 = errors; - if ("boolean" != typeof data.archive_protected) return validate324.errors = [ { + if ("boolean" != typeof data.archive_protected) return validate327.errors = [ { instancePath: instancePath + "/archive_protected", schemaPath: "#/properties/archive_protected/type", keyword: "type", @@ -87791,7 +96430,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid29) { if (void 0 !== data.archive_on_pressure) { let _errs63 = errors; - if ("boolean" != typeof data.archive_on_pressure) return validate324.errors = [ { + if ("boolean" != typeof data.archive_on_pressure) return validate327.errors = [ { instancePath: instancePath + "/archive_on_pressure", schemaPath: "#/properties/archive_on_pressure/type", keyword: "type", @@ -87804,7 +96443,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid30) { if (void 0 !== data.revision) { let data21 = data.revision, _errs65 = errors; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate324.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate327.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -87813,7 +96452,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs65 && "number" == typeof data21 && isFinite(data21)) { - if (data21 > 9007199254740991 || isNaN(data21)) return validate324.errors = [ { + if (data21 > 9007199254740991 || isNaN(data21)) return validate327.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -87822,7 +96461,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data21 < 1 || isNaN(data21)) return validate324.errors = [ { + if (data21 < 1 || isNaN(data21)) return validate327.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -87837,7 +96476,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD if (valid31) { if (void 0 !== data.created_at) { let data22 = data.created_at, _errs67 = errors; - if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate324.errors = [ { + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate327.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -87845,7 +96484,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs67 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate324.errors = [ { + if (errors === _errs67 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate327.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -87858,7 +96497,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } else valid32 = !0; if (valid32 && void 0 !== data.updated_at) { let data23 = data.updated_at, _errs69 = errors; - if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate324.errors = [ { + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate327.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -87866,7 +96505,7 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs69 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate324.errors = [ { + if (errors === _errs69 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate327.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/minimum", keyword: "minimum", @@ -87901,14 +96540,14 @@ function validate324(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate324.errors = vErrors, 0 === errors; + return validate327.errors = vErrors, 0 === errors; } -function validate326(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate326.evaluated; +function validate329(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate329.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate326.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate329.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -87916,7 +96555,7 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.policies) return validate326.errors = [ { + if (void 0 === data.policies) return validate329.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87924,7 +96563,7 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policies" } } ], !1; - if (void 0 === data.count) return validate326.errors = [ { + if (void 0 === data.count) return validate329.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -87934,7 +96573,7 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("policies" !== key0 && "count" !== key0) return validate326.errors = [ { + for (let key0 in data) if ("policies" !== key0 && "count" !== key0) return validate329.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -87946,7 +96585,7 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.policies) { let data0 = data.policies, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate326.errors = [ { + if (!Array.isArray(data0)) return validate329.errors = [ { instancePath: instancePath + "/policies", schemaPath: "#/properties/policies/type", keyword: "type", @@ -87958,13 +96597,13 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = errors; - if (validate324(data0[i0], { + if (validate327(data0[i0], { instancePath: instancePath + "/policies/" + i0, parentData: data0, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate324.errors), errors = vErrors.length), _errs4 !== errors) break; + }) || (vErrors = vErrors.concat(validate327.errors), errors = vErrors.length), _errs4 !== errors) break; } } } @@ -87972,7 +96611,7 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.count) { let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate326.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate329.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -87980,7 +96619,7 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate326.errors = [ { + if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate329.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -87993,25 +96632,25 @@ function validate326(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate326.errors = vErrors, 0 === errors; + return validate329.errors = vErrors, 0 === errors; } -validate324.evaluated = { +validate327.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate326.evaluated = { +}, validate329.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicy = validate324, __contract_StoragePolicyApplyRequest = validate343; +var __contract_StoragePolicy = validate327, __contract_StoragePolicyApplyRequest = validate346; -function validate343(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate343.evaluated; +function validate346(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate346.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate343.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate346.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -88019,7 +96658,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate343.errors = [ { + if (void 0 === data.revision) return validate346.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88027,7 +96666,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.preview) return validate343.errors = [ { + if (void 0 === data.preview) return validate346.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88037,7 +96676,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("revision" !== key0 && "preview" !== key0) return validate343.errors = [ { + for (let key0 in data) if ("revision" !== key0 && "preview" !== key0) return validate346.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -88048,7 +96687,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.revision) { let data0 = data.revision, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate343.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate346.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -88057,7 +96696,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs2 && "number" == typeof data0 && isFinite(data0)) { - if (data0 > 9007199254740991 || isNaN(data0)) return validate343.errors = [ { + if (data0 > 9007199254740991 || isNaN(data0)) return validate346.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -88066,7 +96705,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data0 < 1 || isNaN(data0)) return validate343.errors = [ { + if (data0 < 1 || isNaN(data0)) return validate346.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -88078,7 +96717,7 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD } var valid0 = 0 === _errs2; } else valid0 = !0; - if (valid0 && void 0 !== data.preview && "boolean" != typeof data.preview) return validate343.errors = [ { + if (valid0 && void 0 !== data.preview && "boolean" != typeof data.preview) return validate346.errors = [ { instancePath: instancePath + "/preview", schemaPath: "#/properties/preview/type", keyword: "type", @@ -88088,21 +96727,21 @@ function validate343(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate343.errors = [], !0; + return validate346.errors = [], !0; } -validate343.evaluated = { +validate346.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyApplyResponse = validate344; +var __contract_StoragePolicyApplyResponse = validate347; -function validate344(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate344.evaluated; +function validate347(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate347.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate344.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -88110,7 +96749,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate344.errors = [ { + if (void 0 === data.revision) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88118,7 +96757,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.preview) return validate344.errors = [ { + if (void 0 === data.preview) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88126,7 +96765,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "preview" } } ], !1; - if (void 0 === data.recordings) return validate344.errors = [ { + if (void 0 === data.recordings) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88134,7 +96773,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recordings" } } ], !1; - if (void 0 === data.bytes) return validate344.errors = [ { + if (void 0 === data.bytes) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88142,7 +96781,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bytes" } } ], !1; - if (void 0 === data.past_new_retention_limit) return validate344.errors = [ { + if (void 0 === data.past_new_retention_limit) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88150,7 +96789,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "past_new_retention_limit" } } ], !1; - if (void 0 === data.unfinished_transfers) return validate344.errors = [ { + if (void 0 === data.unfinished_transfers) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88160,7 +96799,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("revision" !== key0 && "preview" !== key0 && "recordings" !== key0 && "bytes" !== key0 && "past_new_retention_limit" !== key0 && "unfinished_transfers" !== key0) return validate344.errors = [ { + for (let key0 in data) if ("revision" !== key0 && "preview" !== key0 && "recordings" !== key0 && "bytes" !== key0 && "past_new_retention_limit" !== key0 && "unfinished_transfers" !== key0) return validate347.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -88171,7 +96810,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.revision) { let data0 = data.revision, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate344.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate347.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -88180,7 +96819,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs2 && "number" == typeof data0 && isFinite(data0)) { - if (data0 > 9007199254740991 || isNaN(data0)) return validate344.errors = [ { + if (data0 > 9007199254740991 || isNaN(data0)) return validate347.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -88189,7 +96828,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data0 < 1 || isNaN(data0)) return validate344.errors = [ { + if (data0 < 1 || isNaN(data0)) return validate347.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -88204,7 +96843,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.preview) { let _errs4 = 0; - if ("boolean" != typeof data.preview) return validate344.errors = [ { + if ("boolean" != typeof data.preview) return validate347.errors = [ { instancePath: instancePath + "/preview", schemaPath: "#/properties/preview/type", keyword: "type", @@ -88217,7 +96856,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.recordings) { let data2 = data.recordings, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate344.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate347.errors = [ { instancePath: instancePath + "/recordings", schemaPath: "#/properties/recordings/type", keyword: "type", @@ -88225,7 +96864,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate344.errors = [ { + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate347.errors = [ { instancePath: instancePath + "/recordings", schemaPath: "#/properties/recordings/minimum", keyword: "minimum", @@ -88239,7 +96878,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.bytes) { let data3 = data.bytes, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate344.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate347.errors = [ { instancePath: instancePath + "/bytes", schemaPath: "#/properties/bytes/type", keyword: "type", @@ -88247,7 +96886,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate344.errors = [ { + if (0 === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate347.errors = [ { instancePath: instancePath + "/bytes", schemaPath: "#/properties/bytes/minimum", keyword: "minimum", @@ -88261,7 +96900,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.past_new_retention_limit) { let data4 = data.past_new_retention_limit, _errs10 = 0; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate344.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate347.errors = [ { instancePath: instancePath + "/past_new_retention_limit", schemaPath: "#/properties/past_new_retention_limit/type", keyword: "type", @@ -88269,7 +96908,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate344.errors = [ { + if (0 === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate347.errors = [ { instancePath: instancePath + "/past_new_retention_limit", schemaPath: "#/properties/past_new_retention_limit/minimum", keyword: "minimum", @@ -88282,7 +96921,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD } else valid4 = !0; if (valid4 && void 0 !== data.unfinished_transfers) { let data5 = data.unfinished_transfers, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate344.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate347.errors = [ { instancePath: instancePath + "/unfinished_transfers", schemaPath: "#/properties/unfinished_transfers/type", keyword: "type", @@ -88290,7 +96929,7 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate344.errors = [ { + if (0 === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate347.errors = [ { instancePath: instancePath + "/unfinished_transfers", schemaPath: "#/properties/unfinished_transfers/minimum", keyword: "minimum", @@ -88306,21 +96945,21 @@ function validate344(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate344.errors = [], !0; + return validate347.errors = [], !0; } -validate344.evaluated = { +validate347.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyConflict = validate339; +var __contract_StoragePolicyConflict = validate342; -function validate339(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate339.evaluated; +function validate342(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate342.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate339.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -88328,7 +96967,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.policy_uuid) return validate339.errors = [ { + if (void 0 === data.policy_uuid) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88336,7 +96975,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_uuid" } } ], !1; - if (void 0 === data.policy_name) return validate339.errors = [ { + if (void 0 === data.policy_name) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88344,7 +96983,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_name" } } ], !1; - if (void 0 === data.priority) return validate339.errors = [ { + if (void 0 === data.priority) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88352,7 +96991,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "priority" } } ], !1; - if (void 0 === data.overlap_camera_count) return validate339.errors = [ { + if (void 0 === data.overlap_camera_count) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88360,7 +96999,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "overlap_camera_count" } } ], !1; - if (void 0 === data.draft_precedes) return validate339.errors = [ { + if (void 0 === data.draft_precedes) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88370,7 +97009,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("policy_uuid" !== key0 && "policy_name" !== key0 && "priority" !== key0 && "overlap_camera_count" !== key0 && "draft_precedes" !== key0) return validate339.errors = [ { + for (let key0 in data) if ("policy_uuid" !== key0 && "policy_name" !== key0 && "priority" !== key0 && "overlap_camera_count" !== key0 && "draft_precedes" !== key0) return validate342.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -88381,7 +97020,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.policy_uuid) { let data0 = data.policy_uuid, _errs2 = 0; - if ("string" != typeof data0) return validate339.errors = [ { + if ("string" != typeof data0) return validate342.errors = [ { instancePath: instancePath + "/policy_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -88389,7 +97028,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate339.errors = [ { + if (!pattern4.test(data0)) return validate342.errors = [ { instancePath: instancePath + "/policy_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -88402,7 +97041,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.policy_name) { let _errs5 = 0; - if ("string" != typeof data.policy_name) return validate339.errors = [ { + if ("string" != typeof data.policy_name) return validate342.errors = [ { instancePath: instancePath + "/policy_name", schemaPath: "#/properties/policy_name/type", keyword: "type", @@ -88415,7 +97054,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.priority) { let data2 = data.priority, _errs7 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate339.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate342.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", @@ -88423,7 +97062,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < -1e6 || isNaN(data2))) return validate339.errors = [ { + if (0 === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < -1e6 || isNaN(data2))) return validate342.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/minimum", keyword: "minimum", @@ -88437,7 +97076,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.overlap_camera_count) { let data3 = data.overlap_camera_count, _errs9 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate339.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate342.errors = [ { instancePath: instancePath + "/overlap_camera_count", schemaPath: "#/properties/overlap_camera_count/type", keyword: "type", @@ -88445,7 +97084,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate339.errors = [ { + if (0 === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate342.errors = [ { instancePath: instancePath + "/overlap_camera_count", schemaPath: "#/properties/overlap_camera_count/minimum", keyword: "minimum", @@ -88456,7 +97095,7 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid4 = 0 === _errs9; } else valid4 = !0; - if (valid4 && void 0 !== data.draft_precedes && "boolean" != typeof data.draft_precedes) return validate339.errors = [ { + if (valid4 && void 0 !== data.draft_precedes && "boolean" != typeof data.draft_precedes) return validate342.errors = [ { instancePath: instancePath + "/draft_precedes", schemaPath: "#/properties/draft_precedes/type", keyword: "type", @@ -88469,16 +97108,16 @@ function validate339(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate339.errors = [], !0; + return validate342.errors = [], !0; } -validate339.evaluated = { +validate342.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyCreate = validate328, schema360 = { +var __contract_StoragePolicyCreate = validate331, schema364 = { type: "object", properties: { name: { @@ -88602,8 +97241,8 @@ var __contract_StoragePolicyCreate = validate328, schema360 = { } ] }; -function validate328(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate328.evaluated; +function validate331(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate331.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs1 = errors, valid0 = !1, _errs2 = errors; if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.primary_target_uuid) { @@ -88712,10 +97351,10 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err8), errors++, validate328.errors = vErrors, !1; + return vErrors.push(err8), errors++, validate331.errors = vErrors, !1; } if (errors = _errs1, vErrors.length = _errs1, 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate328.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate331.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -88723,7 +97362,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate328.errors = [ { + if (void 0 === data.name) return validate331.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88731,7 +97370,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.selector) return validate328.errors = [ { + if (void 0 === data.selector) return validate331.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -88741,7 +97380,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs14 = errors; - for (let key0 in data) if (!func5.call(schema360.properties, key0)) return validate328.errors = [ { + for (let key0 in data) if (!func7.call(schema364.properties, key0)) return validate331.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -88753,7 +97392,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data2 = data.name, _errs15 = errors; if (errors === _errs15) { - if ("string" != typeof data2) return validate328.errors = [ { + if ("string" != typeof data2) return validate331.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -88761,7 +97400,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 127) return validate328.errors = [ { + if (func1(data2) > 127) return validate331.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -88769,7 +97408,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data2) < 1) return validate328.errors = [ { + if (func1(data2) < 1) return validate331.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -88783,7 +97422,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.enabled) { let _errs17 = errors; - if ("boolean" != typeof data.enabled) return validate328.errors = [ { + if ("boolean" != typeof data.enabled) return validate331.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -88796,7 +97435,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.priority) { let data4 = data.priority, _errs19 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate328.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate331.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", @@ -88805,7 +97444,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs19 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 1e6 || isNaN(data4)) return validate328.errors = [ { + if (data4 > 1e6 || isNaN(data4)) return validate331.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/maximum", keyword: "maximum", @@ -88814,7 +97453,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data4 < -1e6 || isNaN(data4)) return validate328.errors = [ { + if (data4 < -1e6 || isNaN(data4)) return validate331.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/minimum", keyword: "minimum", @@ -88842,7 +97481,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_target_uuid) { let data6 = data.primary_target_uuid, _errs22 = errors; if (errors == errors) { - if ("string" != typeof data6) return validate328.errors = [ { + if ("string" != typeof data6) return validate331.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -88850,7 +97489,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data6)) return validate328.errors = [ { + if (!pattern4.test(data6)) return validate331.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -88908,7 +97547,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err12), errors++, validate328.errors = vErrors, !1; + return vErrors.push(err12), errors++, validate331.errors = vErrors, !1; } errors = _errs26, vErrors.length = _errs26; var valid12 = _errs25 === errors; @@ -88960,7 +97599,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err16), errors++, validate328.errors = vErrors, !1; + return vErrors.push(err16), errors++, validate331.errors = vErrors, !1; } errors = _errs33, vErrors.length = _errs33; var valid15 = _errs32 === errors; @@ -89012,7 +97651,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err20), errors++, validate328.errors = vErrors, !1; + return vErrors.push(err20), errors++, validate331.errors = vErrors, !1; } errors = _errs40, vErrors.length = _errs40; var valid18 = _errs39 === errors; @@ -89064,7 +97703,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err24), errors++, validate328.errors = vErrors, !1; + return vErrors.push(err24), errors++, validate331.errors = vErrors, !1; } errors = _errs47, vErrors.length = _errs47; var valid21 = _errs46 === errors; @@ -89072,7 +97711,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data.fallback_mode) { let data11 = data.fallback_mode, _errs53 = errors; - if ("string" != typeof data11) return validate328.errors = [ { + if ("string" != typeof data11) return validate331.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/type", keyword: "type", @@ -89080,12 +97719,12 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("default" !== data11 && "target" !== data11 && "pause" !== data11 && "fail" !== data11) return validate328.errors = [ { + if ("default" !== data11 && "target" !== data11 && "pause" !== data11 && "fail" !== data11) return validate331.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/enum", keyword: "enum", params: { - allowedValues: schema360.properties.fallback_mode.enum + allowedValues: schema364.properties.fallback_mode.enum } } ], !1; var valid24 = _errs53 === errors; @@ -89093,7 +97732,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data.minimum_retention_days) { let data12 = data.minimum_retention_days, _errs55 = errors; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate328.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate331.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/type", keyword: "type", @@ -89102,7 +97741,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs55 && "number" == typeof data12 && isFinite(data12)) { - if (data12 > 36500 || isNaN(data12)) return validate328.errors = [ { + if (data12 > 36500 || isNaN(data12)) return validate331.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/maximum", keyword: "maximum", @@ -89111,7 +97750,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data12 < 0 || isNaN(data12)) return validate328.errors = [ { + if (data12 < 0 || isNaN(data12)) return validate331.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/minimum", keyword: "minimum", @@ -89126,7 +97765,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data.desired_retention_days) { let data13 = data.desired_retention_days, _errs57 = errors; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate328.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate331.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/type", keyword: "type", @@ -89135,7 +97774,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs57 && "number" == typeof data13 && isFinite(data13)) { - if (data13 > 36500 || isNaN(data13)) return validate328.errors = [ { + if (data13 > 36500 || isNaN(data13)) return validate331.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/maximum", keyword: "maximum", @@ -89144,7 +97783,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data13 < 0 || isNaN(data13)) return validate328.errors = [ { + if (data13 < 0 || isNaN(data13)) return validate331.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/minimum", keyword: "minimum", @@ -89159,7 +97798,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data.maximum_retention_days) { let data14 = data.maximum_retention_days, _errs59 = errors; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate328.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate331.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/type", keyword: "type", @@ -89168,7 +97807,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs59 && "number" == typeof data14 && isFinite(data14)) { - if (data14 > 36500 || isNaN(data14)) return validate328.errors = [ { + if (data14 > 36500 || isNaN(data14)) return validate331.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/maximum", keyword: "maximum", @@ -89177,7 +97816,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate328.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate331.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/minimum", keyword: "minimum", @@ -89192,7 +97831,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data.migration_after_days) { let data15 = data.migration_after_days, _errs61 = errors; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate328.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate331.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/type", keyword: "type", @@ -89201,7 +97840,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs61 && "number" == typeof data15 && isFinite(data15)) { - if (data15 > 36500 || isNaN(data15)) return validate328.errors = [ { + if (data15 > 36500 || isNaN(data15)) return validate331.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/maximum", keyword: "maximum", @@ -89210,7 +97849,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data15 < 0 || isNaN(data15)) return validate328.errors = [ { + if (data15 < 0 || isNaN(data15)) return validate331.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/minimum", keyword: "minimum", @@ -89225,7 +97864,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid28) { if (void 0 !== data.required_copy_count) { let data16 = data.required_copy_count, _errs63 = errors; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate328.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate331.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/type", keyword: "type", @@ -89234,7 +97873,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs63 && "number" == typeof data16 && isFinite(data16)) { - if (data16 > 8 || isNaN(data16)) return validate328.errors = [ { + if (data16 > 8 || isNaN(data16)) return validate331.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/maximum", keyword: "maximum", @@ -89243,7 +97882,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 8 } } ], !1; - if (data16 < 1 || isNaN(data16)) return validate328.errors = [ { + if (data16 < 1 || isNaN(data16)) return validate331.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/minimum", keyword: "minimum", @@ -89258,7 +97897,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid29) { if (void 0 !== data.pressure_priority) { let data17 = data.pressure_priority, _errs65 = errors; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate328.errors = [ { + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate331.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/type", keyword: "type", @@ -89267,7 +97906,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs65 && "number" == typeof data17 && isFinite(data17)) { - if (data17 > 1e6 || isNaN(data17)) return validate328.errors = [ { + if (data17 > 1e6 || isNaN(data17)) return validate331.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/maximum", keyword: "maximum", @@ -89276,7 +97915,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data17 < -1e6 || isNaN(data17)) return validate328.errors = [ { + if (data17 < -1e6 || isNaN(data17)) return validate331.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/minimum", keyword: "minimum", @@ -89291,7 +97930,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid30) { if (void 0 !== data.archive_after_seconds) { let data18 = data.archive_after_seconds, _errs67 = errors; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate328.errors = [ { + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate331.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/type", keyword: "type", @@ -89300,7 +97939,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs67 && "number" == typeof data18 && isFinite(data18)) { - if (data18 > 2147483647 || isNaN(data18)) return validate328.errors = [ { + if (data18 > 2147483647 || isNaN(data18)) return validate331.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/maximum", keyword: "maximum", @@ -89309,7 +97948,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data18 < -1 || isNaN(data18)) return validate328.errors = [ { + if (data18 < -1 || isNaN(data18)) return validate331.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/minimum", keyword: "minimum", @@ -89324,7 +97963,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid31) { if (void 0 !== data.hot_residency_seconds) { let data19 = data.hot_residency_seconds, _errs69 = errors; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate328.errors = [ { + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate331.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/type", keyword: "type", @@ -89333,7 +97972,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs69 && "number" == typeof data19 && isFinite(data19)) { - if (data19 > 2147483647 || isNaN(data19)) return validate328.errors = [ { + if (data19 > 2147483647 || isNaN(data19)) return validate331.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/maximum", keyword: "maximum", @@ -89342,7 +97981,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data19 < -1 || isNaN(data19)) return validate328.errors = [ { + if (data19 < -1 || isNaN(data19)) return validate331.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/minimum", keyword: "minimum", @@ -89357,7 +97996,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data.archive_protected) { let _errs71 = errors; - if ("boolean" != typeof data.archive_protected) return validate328.errors = [ { + if ("boolean" != typeof data.archive_protected) return validate331.errors = [ { instancePath: instancePath + "/archive_protected", schemaPath: "#/properties/archive_protected/type", keyword: "type", @@ -89367,7 +98006,7 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid33 = _errs71 === errors; } else valid33 = !0; - if (valid33 && void 0 !== data.archive_on_pressure && "boolean" != typeof data.archive_on_pressure) return validate328.errors = [ { + if (valid33 && void 0 !== data.archive_on_pressure && "boolean" != typeof data.archive_on_pressure) return validate331.errors = [ { instancePath: instancePath + "/archive_on_pressure", schemaPath: "#/properties/archive_on_pressure/type", keyword: "type", @@ -89396,16 +98035,16 @@ function validate328(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate328.errors = vErrors, 0 === errors; + return validate331.errors = vErrors, 0 === errors; } -validate328.evaluated = { +validate331.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyPreview = validate340, schema393 = { +var __contract_StoragePolicyPreview = validate343, schema397 = { type: "object", properties: { draft_enabled: { @@ -89450,7 +98089,7 @@ var __contract_StoragePolicyPreview = validate340, schema393 = { }, required: [ "draft_enabled", "sample_truncated", "matched_camera_count", "effective_camera_count", "shadowed_camera_count", "conflict_camera_count", "conflict_policy_count", "sample", "conflicts" ], additionalProperties: !1 -}, schema384 = { +}, schema388 = { type: "object", properties: { draft: { @@ -89496,11 +98135,11 @@ var __contract_StoragePolicyPreview = validate340, schema393 = { additionalProperties: !1 }; -function validate336(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate336.evaluated; +function validate339(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate339.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate336.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -89508,7 +98147,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.draft) return validate336.errors = [ { + if (void 0 === data.draft) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89516,7 +98155,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "draft" } } ], !1; - if (void 0 === data.policy_uuid) return validate336.errors = [ { + if (void 0 === data.policy_uuid) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89524,7 +98163,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_uuid" } } ], !1; - if (void 0 === data.policy_name) return validate336.errors = [ { + if (void 0 === data.policy_name) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89532,7 +98171,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "policy_name" } } ], !1; - if (void 0 === data.priority) return validate336.errors = [ { + if (void 0 === data.priority) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89540,7 +98179,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "priority" } } ], !1; - if (void 0 === data.primary_target_uuid) return validate336.errors = [ { + if (void 0 === data.primary_target_uuid) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89548,7 +98187,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "primary_target_uuid" } } ], !1; - if (void 0 === data.primary_pool_uuid) return validate336.errors = [ { + if (void 0 === data.primary_pool_uuid) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89556,7 +98195,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "primary_pool_uuid" } } ], !1; - if (void 0 === data.fallback_mode) return validate336.errors = [ { + if (void 0 === data.fallback_mode) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89564,7 +98203,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "fallback_mode" } } ], !1; - if (void 0 === data.fallback_target_uuid) return validate336.errors = [ { + if (void 0 === data.fallback_target_uuid) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89574,7 +98213,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("draft" !== key0 && "policy_uuid" !== key0 && "policy_name" !== key0 && "priority" !== key0 && "primary_target_uuid" !== key0 && "primary_pool_uuid" !== key0 && "fallback_mode" !== key0 && "fallback_target_uuid" !== key0) return validate336.errors = [ { + for (let key0 in data) if ("draft" !== key0 && "policy_uuid" !== key0 && "policy_name" !== key0 && "priority" !== key0 && "primary_target_uuid" !== key0 && "primary_pool_uuid" !== key0 && "fallback_mode" !== key0 && "fallback_target_uuid" !== key0) return validate339.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -89585,7 +98224,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.draft) { let _errs2 = errors; - if ("boolean" != typeof data.draft) return validate336.errors = [ { + if ("boolean" != typeof data.draft) return validate339.errors = [ { instancePath: instancePath + "/draft", schemaPath: "#/properties/draft/type", keyword: "type", @@ -89642,7 +98281,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate336.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate339.errors = vErrors, !1; } errors = _errs5, vErrors.length = _errs5; var valid1 = _errs4 === errors; @@ -89650,7 +98289,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.policy_name) { let _errs11 = errors; - if ("string" != typeof data.policy_name) return validate336.errors = [ { + if ("string" != typeof data.policy_name) return validate339.errors = [ { instancePath: instancePath + "/policy_name", schemaPath: "#/properties/policy_name/type", keyword: "type", @@ -89663,7 +98302,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.priority) { let data3 = data.priority, _errs13 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate336.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate339.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", @@ -89671,7 +98310,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs13 && "number" == typeof data3 && isFinite(data3) && (data3 < -1e6 || isNaN(data3))) return validate336.errors = [ { + if (errors === _errs13 && "number" == typeof data3 && isFinite(data3) && (data3 < -1e6 || isNaN(data3))) return validate339.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/minimum", keyword: "minimum", @@ -89686,7 +98325,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_target_uuid) { let data4 = data.primary_target_uuid, _errs15 = errors; if (errors == errors) { - if ("string" != typeof data4) return validate336.errors = [ { + if ("string" != typeof data4) return validate339.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -89694,7 +98333,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data4)) return validate336.errors = [ { + if (!pattern4.test(data4)) return validate339.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -89752,7 +98391,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate336.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate339.errors = vErrors, !1; } errors = _errs19, vErrors.length = _errs19; var valid8 = _errs18 === errors; @@ -89760,7 +98399,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.fallback_mode) { let data6 = data.fallback_mode, _errs25 = errors; - if ("string" != typeof data6) return validate336.errors = [ { + if ("string" != typeof data6) return validate339.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/type", keyword: "type", @@ -89768,12 +98407,12 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("default" !== data6 && "target" !== data6 && "pause" !== data6 && "fail" !== data6) return validate336.errors = [ { + if ("default" !== data6 && "target" !== data6 && "pause" !== data6 && "fail" !== data6) return validate339.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/enum", keyword: "enum", params: { - allowedValues: schema384.properties.fallback_mode.enum + allowedValues: schema388.properties.fallback_mode.enum } } ], !1; var valid11 = _errs25 === errors; @@ -89824,7 +98463,7 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err11), errors++, validate336.errors = vErrors, !1; + return vErrors.push(err11), errors++, validate339.errors = vErrors, !1; } errors = _errs28, vErrors.length = _errs28; } @@ -89837,14 +98476,14 @@ function validate336(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate336.errors = vErrors, 0 === errors; + return validate339.errors = vErrors, 0 === errors; } -function validate337(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate337.evaluated; +function validate340(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate340.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate337.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate340.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -89852,7 +98491,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.camera_uuid) return validate337.errors = [ { + if (void 0 === data.camera_uuid) return validate340.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89860,7 +98499,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.camera_name) return validate337.errors = [ { + if (void 0 === data.camera_name) return validate340.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89868,7 +98507,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_name" } } ], !1; - if (void 0 === data.overlapping_policy_count) return validate337.errors = [ { + if (void 0 === data.overlapping_policy_count) return validate340.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89876,7 +98515,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "overlapping_policy_count" } } ], !1; - if (void 0 === data.effective_policy) return validate337.errors = [ { + if (void 0 === data.effective_policy) return validate340.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -89886,7 +98525,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("camera_uuid" !== key0 && "camera_name" !== key0 && "overlapping_policy_count" !== key0 && "effective_policy" !== key0) return validate337.errors = [ { + for (let key0 in data) if ("camera_uuid" !== key0 && "camera_name" !== key0 && "overlapping_policy_count" !== key0 && "effective_policy" !== key0) return validate340.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -89898,7 +98537,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.camera_uuid) { let data0 = data.camera_uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate337.errors = [ { + if ("string" != typeof data0) return validate340.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -89906,7 +98545,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate337.errors = [ { + if (!pattern4.test(data0)) return validate340.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -89920,7 +98559,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.camera_name) { let _errs5 = errors; - if ("string" != typeof data.camera_name) return validate337.errors = [ { + if ("string" != typeof data.camera_name) return validate340.errors = [ { instancePath: instancePath + "/camera_name", schemaPath: "#/properties/camera_name/type", keyword: "type", @@ -89933,7 +98572,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.overlapping_policy_count) { let data2 = data.overlapping_policy_count, _errs7 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate337.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate340.errors = [ { instancePath: instancePath + "/overlapping_policy_count", schemaPath: "#/properties/overlapping_policy_count/type", keyword: "type", @@ -89941,7 +98580,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate337.errors = [ { + if (errors === _errs7 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate340.errors = [ { instancePath: instancePath + "/overlapping_policy_count", schemaPath: "#/properties/overlapping_policy_count/minimum", keyword: "minimum", @@ -89954,13 +98593,13 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD } else valid3 = !0; if (valid3 && void 0 !== data.effective_policy) { let data3 = data.effective_policy, _errs10 = errors, valid5 = !1, _errs11 = errors; - validate336(data3, { + validate339(data3, { instancePath: instancePath + "/effective_policy", parentData: data, parentDataProperty: "effective_policy", rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate336.errors), errors = vErrors.length); + }) || (vErrors = vErrors.concat(validate339.errors), errors = vErrors.length); var _valid0 = _errs11 === errors; valid5 = valid5 || _valid0; let _errs12 = errors; @@ -89982,7 +98621,7 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err1), errors++, validate337.errors = vErrors, !1; + return vErrors.push(err1), errors++, validate340.errors = vErrors, !1; } errors = _errs10, vErrors.length = _errs10; } @@ -89991,14 +98630,14 @@ function validate337(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate337.errors = vErrors, 0 === errors; + return validate340.errors = vErrors, 0 === errors; } -function validate340(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate340.evaluated; +function validate343(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate343.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate340.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -90006,7 +98645,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.draft_enabled) return validate340.errors = [ { + if (void 0 === data.draft_enabled) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90014,7 +98653,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "draft_enabled" } } ], !1; - if (void 0 === data.sample_truncated) return validate340.errors = [ { + if (void 0 === data.sample_truncated) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90022,7 +98661,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample_truncated" } } ], !1; - if (void 0 === data.matched_camera_count) return validate340.errors = [ { + if (void 0 === data.matched_camera_count) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90030,7 +98669,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "matched_camera_count" } } ], !1; - if (void 0 === data.effective_camera_count) return validate340.errors = [ { + if (void 0 === data.effective_camera_count) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90038,7 +98677,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "effective_camera_count" } } ], !1; - if (void 0 === data.shadowed_camera_count) return validate340.errors = [ { + if (void 0 === data.shadowed_camera_count) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90046,7 +98685,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "shadowed_camera_count" } } ], !1; - if (void 0 === data.conflict_camera_count) return validate340.errors = [ { + if (void 0 === data.conflict_camera_count) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90054,7 +98693,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "conflict_camera_count" } } ], !1; - if (void 0 === data.conflict_policy_count) return validate340.errors = [ { + if (void 0 === data.conflict_policy_count) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90062,7 +98701,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "conflict_policy_count" } } ], !1; - if (void 0 === data.sample) return validate340.errors = [ { + if (void 0 === data.sample) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90070,7 +98709,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sample" } } ], !1; - if (void 0 === data.conflicts) return validate340.errors = [ { + if (void 0 === data.conflicts) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -90080,7 +98719,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema393.properties, key0)) return validate340.errors = [ { + for (let key0 in data) if (!func7.call(schema397.properties, key0)) return validate343.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -90091,7 +98730,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.draft_enabled) { let _errs2 = errors; - if ("boolean" != typeof data.draft_enabled) return validate340.errors = [ { + if ("boolean" != typeof data.draft_enabled) return validate343.errors = [ { instancePath: instancePath + "/draft_enabled", schemaPath: "#/properties/draft_enabled/type", keyword: "type", @@ -90104,7 +98743,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.sample_truncated) { let _errs4 = errors; - if ("boolean" != typeof data.sample_truncated) return validate340.errors = [ { + if ("boolean" != typeof data.sample_truncated) return validate343.errors = [ { instancePath: instancePath + "/sample_truncated", schemaPath: "#/properties/sample_truncated/type", keyword: "type", @@ -90117,7 +98756,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.matched_camera_count) { let data2 = data.matched_camera_count, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate340.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate343.errors = [ { instancePath: instancePath + "/matched_camera_count", schemaPath: "#/properties/matched_camera_count/type", keyword: "type", @@ -90125,7 +98764,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate340.errors = [ { + if (errors === _errs6 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate343.errors = [ { instancePath: instancePath + "/matched_camera_count", schemaPath: "#/properties/matched_camera_count/minimum", keyword: "minimum", @@ -90139,7 +98778,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.effective_camera_count) { let data3 = data.effective_camera_count, _errs8 = errors; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate340.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate343.errors = [ { instancePath: instancePath + "/effective_camera_count", schemaPath: "#/properties/effective_camera_count/type", keyword: "type", @@ -90147,7 +98786,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate340.errors = [ { + if (errors === _errs8 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate343.errors = [ { instancePath: instancePath + "/effective_camera_count", schemaPath: "#/properties/effective_camera_count/minimum", keyword: "minimum", @@ -90161,7 +98800,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.shadowed_camera_count) { let data4 = data.shadowed_camera_count, _errs10 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate340.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate343.errors = [ { instancePath: instancePath + "/shadowed_camera_count", schemaPath: "#/properties/shadowed_camera_count/type", keyword: "type", @@ -90169,7 +98808,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate340.errors = [ { + if (errors === _errs10 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate343.errors = [ { instancePath: instancePath + "/shadowed_camera_count", schemaPath: "#/properties/shadowed_camera_count/minimum", keyword: "minimum", @@ -90183,7 +98822,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.conflict_camera_count) { let data5 = data.conflict_camera_count, _errs12 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate340.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate343.errors = [ { instancePath: instancePath + "/conflict_camera_count", schemaPath: "#/properties/conflict_camera_count/type", keyword: "type", @@ -90191,7 +98830,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate340.errors = [ { + if (errors === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate343.errors = [ { instancePath: instancePath + "/conflict_camera_count", schemaPath: "#/properties/conflict_camera_count/minimum", keyword: "minimum", @@ -90205,7 +98844,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.conflict_policy_count) { let data6 = data.conflict_policy_count, _errs14 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate340.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate343.errors = [ { instancePath: instancePath + "/conflict_policy_count", schemaPath: "#/properties/conflict_policy_count/type", keyword: "type", @@ -90213,7 +98852,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate340.errors = [ { + if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate343.errors = [ { instancePath: instancePath + "/conflict_policy_count", schemaPath: "#/properties/conflict_policy_count/minimum", keyword: "minimum", @@ -90228,7 +98867,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.sample) { let data7 = data.sample, _errs16 = errors; if (errors === _errs16) { - if (!Array.isArray(data7)) return validate340.errors = [ { + if (!Array.isArray(data7)) return validate343.errors = [ { instancePath: instancePath + "/sample", schemaPath: "#/properties/sample/type", keyword: "type", @@ -90236,7 +98875,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data7.length > 50) return validate340.errors = [ { + if (data7.length > 50) return validate343.errors = [ { instancePath: instancePath + "/sample", schemaPath: "#/properties/sample/maxItems", keyword: "maxItems", @@ -90248,13 +98887,13 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD let len0 = data7.length; for (let i0 = 0; i0 < len0; i0++) { let _errs18 = errors; - if (validate337(data7[i0], { + if (validate340(data7[i0], { instancePath: instancePath + "/sample/" + i0, parentData: data7, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate337.errors), errors = vErrors.length), _errs18 !== errors) break; + }) || (vErrors = vErrors.concat(validate340.errors), errors = vErrors.length), _errs18 !== errors) break; } } } @@ -90263,7 +98902,7 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD if (valid7 && void 0 !== data.conflicts) { let data9 = data.conflicts; if (errors == errors) { - if (!Array.isArray(data9)) return validate340.errors = [ { + if (!Array.isArray(data9)) return validate343.errors = [ { instancePath: instancePath + "/conflicts", schemaPath: "#/properties/conflicts/type", keyword: "type", @@ -90275,13 +98914,13 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD let len1 = data9.length; for (let i1 = 0; i1 < len1; i1++) { let _errs21 = errors; - if (validate339(data9[i1], { + if (validate342(data9[i1], { instancePath: instancePath + "/conflicts/" + i1, parentData: data9, parentDataProperty: i1, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate339.errors), errors = vErrors.length), _errs21 !== errors) break; + }) || (vErrors = vErrors.concat(validate342.errors), errors = vErrors.length), _errs21 !== errors) break; } } } @@ -90296,24 +98935,24 @@ function validate340(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate340.errors = vErrors, 0 === errors; + return validate343.errors = vErrors, 0 === errors; } -validate336.evaluated = { +validate339.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate337.evaluated = { +}, validate340.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate340.evaluated = { +}, validate343.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyPreviewRequest = validate332, schema374 = { +var __contract_StoragePolicyPreviewRequest = validate335, schema378 = { type: "object", properties: { name: { @@ -90473,8 +99112,8 @@ var __contract_StoragePolicyPreviewRequest = validate332, schema374 = { } ] }; -function validate332(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate332.evaluated; +function validate335(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate335.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs1 = errors, valid0 = !1, _errs2 = errors; if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.uuid) { @@ -90815,10 +99454,10 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err25), errors++, validate332.errors = vErrors, !1; + return vErrors.push(err25), errors++, validate335.errors = vErrors, !1; } if (errors = _errs1, vErrors.length = _errs1, 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate332.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate335.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -90828,7 +99467,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs26 = errors; - for (let key0 in data) if (!func5.call(schema374.properties, key0)) return validate332.errors = [ { + for (let key0 in data) if (!func7.call(schema378.properties, key0)) return validate335.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -90840,7 +99479,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data8 = data.name, _errs27 = errors; if (errors === _errs27) { - if ("string" != typeof data8) return validate332.errors = [ { + if ("string" != typeof data8) return validate335.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -90848,7 +99487,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data8) > 127) return validate332.errors = [ { + if (func1(data8) > 127) return validate335.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -90856,7 +99495,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data8) < 1) return validate332.errors = [ { + if (func1(data8) < 1) return validate335.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -90870,7 +99509,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data.enabled) { let _errs29 = errors; - if ("boolean" != typeof data.enabled) return validate332.errors = [ { + if ("boolean" != typeof data.enabled) return validate335.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -90883,7 +99522,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data.priority) { let data10 = data.priority, _errs31 = errors; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate332.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate335.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", @@ -90892,7 +99531,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs31 && "number" == typeof data10 && isFinite(data10)) { - if (data10 > 1e6 || isNaN(data10)) return validate332.errors = [ { + if (data10 > 1e6 || isNaN(data10)) return validate335.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/maximum", keyword: "maximum", @@ -90901,7 +99540,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data10 < -1e6 || isNaN(data10)) return validate332.errors = [ { + if (data10 < -1e6 || isNaN(data10)) return validate335.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/minimum", keyword: "minimum", @@ -90929,7 +99568,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_target_uuid) { let data12 = data.primary_target_uuid, _errs34 = errors; if (errors == errors) { - if ("string" != typeof data12) return validate332.errors = [ { + if ("string" != typeof data12) return validate335.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -90937,7 +99576,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data12)) return validate332.errors = [ { + if (!pattern4.test(data12)) return validate335.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -90995,7 +99634,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err29), errors++, validate332.errors = vErrors, !1; + return vErrors.push(err29), errors++, validate335.errors = vErrors, !1; } errors = _errs38, vErrors.length = _errs38; var valid19 = _errs37 === errors; @@ -91047,7 +99686,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err33), errors++, validate332.errors = vErrors, !1; + return vErrors.push(err33), errors++, validate335.errors = vErrors, !1; } errors = _errs45, vErrors.length = _errs45; var valid22 = _errs44 === errors; @@ -91099,7 +99738,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err37), errors++, validate332.errors = vErrors, !1; + return vErrors.push(err37), errors++, validate335.errors = vErrors, !1; } errors = _errs52, vErrors.length = _errs52; var valid25 = _errs51 === errors; @@ -91151,7 +99790,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err41), errors++, validate332.errors = vErrors, !1; + return vErrors.push(err41), errors++, validate335.errors = vErrors, !1; } errors = _errs59, vErrors.length = _errs59; var valid28 = _errs58 === errors; @@ -91159,7 +99798,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid28) { if (void 0 !== data.fallback_mode) { let data17 = data.fallback_mode, _errs65 = errors; - if ("string" != typeof data17) return validate332.errors = [ { + if ("string" != typeof data17) return validate335.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/type", keyword: "type", @@ -91167,12 +99806,12 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("default" !== data17 && "target" !== data17 && "pause" !== data17 && "fail" !== data17) return validate332.errors = [ { + if ("default" !== data17 && "target" !== data17 && "pause" !== data17 && "fail" !== data17) return validate335.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/enum", keyword: "enum", params: { - allowedValues: schema374.properties.fallback_mode.enum + allowedValues: schema378.properties.fallback_mode.enum } } ], !1; var valid31 = _errs65 === errors; @@ -91180,7 +99819,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid31) { if (void 0 !== data.minimum_retention_days) { let data18 = data.minimum_retention_days, _errs67 = errors; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate332.errors = [ { + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate335.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/type", keyword: "type", @@ -91189,7 +99828,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs67 && "number" == typeof data18 && isFinite(data18)) { - if (data18 > 36500 || isNaN(data18)) return validate332.errors = [ { + if (data18 > 36500 || isNaN(data18)) return validate335.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/maximum", keyword: "maximum", @@ -91198,7 +99837,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data18 < 0 || isNaN(data18)) return validate332.errors = [ { + if (data18 < 0 || isNaN(data18)) return validate335.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/minimum", keyword: "minimum", @@ -91213,7 +99852,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data.desired_retention_days) { let data19 = data.desired_retention_days, _errs69 = errors; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate332.errors = [ { + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate335.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/type", keyword: "type", @@ -91222,7 +99861,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs69 && "number" == typeof data19 && isFinite(data19)) { - if (data19 > 36500 || isNaN(data19)) return validate332.errors = [ { + if (data19 > 36500 || isNaN(data19)) return validate335.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/maximum", keyword: "maximum", @@ -91231,7 +99870,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data19 < 0 || isNaN(data19)) return validate332.errors = [ { + if (data19 < 0 || isNaN(data19)) return validate335.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/minimum", keyword: "minimum", @@ -91246,7 +99885,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid33) { if (void 0 !== data.maximum_retention_days) { let data20 = data.maximum_retention_days, _errs71 = errors; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate332.errors = [ { + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate335.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/type", keyword: "type", @@ -91255,7 +99894,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs71 && "number" == typeof data20 && isFinite(data20)) { - if (data20 > 36500 || isNaN(data20)) return validate332.errors = [ { + if (data20 > 36500 || isNaN(data20)) return validate335.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/maximum", keyword: "maximum", @@ -91264,7 +99903,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data20 < 0 || isNaN(data20)) return validate332.errors = [ { + if (data20 < 0 || isNaN(data20)) return validate335.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/minimum", keyword: "minimum", @@ -91279,7 +99918,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data.migration_after_days) { let data21 = data.migration_after_days, _errs73 = errors; - if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate332.errors = [ { + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate335.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/type", keyword: "type", @@ -91288,7 +99927,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs73 && "number" == typeof data21 && isFinite(data21)) { - if (data21 > 36500 || isNaN(data21)) return validate332.errors = [ { + if (data21 > 36500 || isNaN(data21)) return validate335.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/maximum", keyword: "maximum", @@ -91297,7 +99936,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data21 < 0 || isNaN(data21)) return validate332.errors = [ { + if (data21 < 0 || isNaN(data21)) return validate335.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/minimum", keyword: "minimum", @@ -91312,7 +99951,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data.required_copy_count) { let data22 = data.required_copy_count, _errs75 = errors; - if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate332.errors = [ { + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate335.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/type", keyword: "type", @@ -91321,7 +99960,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs75 && "number" == typeof data22 && isFinite(data22)) { - if (data22 > 8 || isNaN(data22)) return validate332.errors = [ { + if (data22 > 8 || isNaN(data22)) return validate335.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/maximum", keyword: "maximum", @@ -91330,7 +99969,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 8 } } ], !1; - if (data22 < 1 || isNaN(data22)) return validate332.errors = [ { + if (data22 < 1 || isNaN(data22)) return validate335.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/minimum", keyword: "minimum", @@ -91345,7 +99984,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid36) { if (void 0 !== data.pressure_priority) { let data23 = data.pressure_priority, _errs77 = errors; - if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate332.errors = [ { + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate335.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/type", keyword: "type", @@ -91354,7 +99993,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs77 && "number" == typeof data23 && isFinite(data23)) { - if (data23 > 1e6 || isNaN(data23)) return validate332.errors = [ { + if (data23 > 1e6 || isNaN(data23)) return validate335.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/maximum", keyword: "maximum", @@ -91363,7 +100002,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data23 < -1e6 || isNaN(data23)) return validate332.errors = [ { + if (data23 < -1e6 || isNaN(data23)) return validate335.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/minimum", keyword: "minimum", @@ -91378,7 +100017,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid37) { if (void 0 !== data.archive_after_seconds) { let data24 = data.archive_after_seconds, _errs79 = errors; - if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate332.errors = [ { + if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate335.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/type", keyword: "type", @@ -91387,7 +100026,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs79 && "number" == typeof data24 && isFinite(data24)) { - if (data24 > 2147483647 || isNaN(data24)) return validate332.errors = [ { + if (data24 > 2147483647 || isNaN(data24)) return validate335.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/maximum", keyword: "maximum", @@ -91396,7 +100035,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data24 < -1 || isNaN(data24)) return validate332.errors = [ { + if (data24 < -1 || isNaN(data24)) return validate335.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/minimum", keyword: "minimum", @@ -91411,7 +100050,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid38) { if (void 0 !== data.hot_residency_seconds) { let data25 = data.hot_residency_seconds, _errs81 = errors; - if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate332.errors = [ { + if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate335.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/type", keyword: "type", @@ -91420,7 +100059,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs81 && "number" == typeof data25 && isFinite(data25)) { - if (data25 > 2147483647 || isNaN(data25)) return validate332.errors = [ { + if (data25 > 2147483647 || isNaN(data25)) return validate335.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/maximum", keyword: "maximum", @@ -91429,7 +100068,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data25 < -1 || isNaN(data25)) return validate332.errors = [ { + if (data25 < -1 || isNaN(data25)) return validate335.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/minimum", keyword: "minimum", @@ -91444,7 +100083,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid39) { if (void 0 !== data.archive_protected) { let _errs83 = errors; - if ("boolean" != typeof data.archive_protected) return validate332.errors = [ { + if ("boolean" != typeof data.archive_protected) return validate335.errors = [ { instancePath: instancePath + "/archive_protected", schemaPath: "#/properties/archive_protected/type", keyword: "type", @@ -91457,7 +100096,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (valid40) { if (void 0 !== data.archive_on_pressure) { let _errs85 = errors; - if ("boolean" != typeof data.archive_on_pressure) return validate332.errors = [ { + if ("boolean" != typeof data.archive_on_pressure) return validate335.errors = [ { instancePath: instancePath + "/archive_on_pressure", schemaPath: "#/properties/archive_on_pressure/type", keyword: "type", @@ -91471,7 +100110,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data28 = data.uuid, _errs87 = errors; if (errors == errors) { - if ("string" != typeof data28) return validate332.errors = [ { + if ("string" != typeof data28) return validate335.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -91479,7 +100118,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data28)) return validate332.errors = [ { + if (!pattern4.test(data28)) return validate335.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -91492,7 +100131,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } else valid42 = !0; if (valid42 && void 0 !== data.revision) { let data29 = data.revision, _errs90 = errors; - if ("number" != typeof data29 || data29 % 1 || isNaN(data29) || !isFinite(data29)) return validate332.errors = [ { + if ("number" != typeof data29 || data29 % 1 || isNaN(data29) || !isFinite(data29)) return validate335.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -91501,7 +100140,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs90 && "number" == typeof data29 && isFinite(data29)) { - if (data29 > 9007199254740991 || isNaN(data29)) return validate332.errors = [ { + if (data29 > 9007199254740991 || isNaN(data29)) return validate335.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -91510,7 +100149,7 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data29 < 1 || isNaN(data29)) return validate332.errors = [ { + if (data29 < 1 || isNaN(data29)) return validate335.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -91544,16 +100183,16 @@ function validate332(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate332.errors = vErrors, 0 === errors; + return validate335.errors = vErrors, 0 === errors; } -validate332.evaluated = { +validate335.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyPreviewSample = validate337, __contract_StoragePolicyUpdate = validate330, schema368 = { +var __contract_StoragePolicyPreviewSample = validate340, __contract_StoragePolicyUpdate = validate333, schema372 = { type: "object", properties: { name: { @@ -91663,11 +100302,11 @@ var __contract_StoragePolicyPreviewSample = validate337, __contract_StoragePolic additionalProperties: !1 }; -function validate330(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate330.evaluated; +function validate333(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate333.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate330.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate333.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -91675,7 +100314,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate330.errors = [ { + if (void 0 === data.revision) return validate333.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -91685,7 +100324,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema368.properties, key0)) return validate330.errors = [ { + for (let key0 in data) if (!func7.call(schema372.properties, key0)) return validate333.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -91697,7 +100336,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate330.errors = [ { + if ("string" != typeof data0) return validate333.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -91705,7 +100344,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate330.errors = [ { + if (func1(data0) > 127) return validate333.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -91713,7 +100352,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate330.errors = [ { + if (func1(data0) < 1) return validate333.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -91727,7 +100366,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.enabled) { let _errs4 = errors; - if ("boolean" != typeof data.enabled) return validate330.errors = [ { + if ("boolean" != typeof data.enabled) return validate333.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -91740,7 +100379,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.priority) { let data2 = data.priority, _errs6 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate330.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate333.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", @@ -91749,7 +100388,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs6 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 1e6 || isNaN(data2)) return validate330.errors = [ { + if (data2 > 1e6 || isNaN(data2)) return validate333.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/maximum", keyword: "maximum", @@ -91758,7 +100397,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data2 < -1e6 || isNaN(data2)) return validate330.errors = [ { + if (data2 < -1e6 || isNaN(data2)) return validate333.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/minimum", keyword: "minimum", @@ -91786,7 +100425,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.primary_target_uuid) { let data4 = data.primary_target_uuid, _errs9 = errors; if (errors == errors) { - if ("string" != typeof data4) return validate330.errors = [ { + if ("string" != typeof data4) return validate333.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -91794,7 +100433,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data4)) return validate330.errors = [ { + if (!pattern4.test(data4)) return validate333.errors = [ { instancePath: instancePath + "/primary_target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -91852,7 +100491,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate330.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate333.errors = vErrors, !1; } errors = _errs13, vErrors.length = _errs13; var valid6 = _errs12 === errors; @@ -91904,7 +100543,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate330.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate333.errors = vErrors, !1; } errors = _errs20, vErrors.length = _errs20; var valid9 = _errs19 === errors; @@ -91956,7 +100595,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err11), errors++, validate330.errors = vErrors, !1; + return vErrors.push(err11), errors++, validate333.errors = vErrors, !1; } errors = _errs27, vErrors.length = _errs27; var valid12 = _errs26 === errors; @@ -92008,7 +100647,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err15), errors++, validate330.errors = vErrors, !1; + return vErrors.push(err15), errors++, validate333.errors = vErrors, !1; } errors = _errs34, vErrors.length = _errs34; var valid15 = _errs33 === errors; @@ -92016,7 +100655,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data.fallback_mode) { let data9 = data.fallback_mode, _errs40 = errors; - if ("string" != typeof data9) return validate330.errors = [ { + if ("string" != typeof data9) return validate333.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/type", keyword: "type", @@ -92024,12 +100663,12 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("default" !== data9 && "target" !== data9 && "pause" !== data9 && "fail" !== data9) return validate330.errors = [ { + if ("default" !== data9 && "target" !== data9 && "pause" !== data9 && "fail" !== data9) return validate333.errors = [ { instancePath: instancePath + "/fallback_mode", schemaPath: "#/properties/fallback_mode/enum", keyword: "enum", params: { - allowedValues: schema368.properties.fallback_mode.enum + allowedValues: schema372.properties.fallback_mode.enum } } ], !1; var valid18 = _errs40 === errors; @@ -92037,7 +100676,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data.minimum_retention_days) { let data10 = data.minimum_retention_days, _errs42 = errors; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate330.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate333.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/type", keyword: "type", @@ -92046,7 +100685,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs42 && "number" == typeof data10 && isFinite(data10)) { - if (data10 > 36500 || isNaN(data10)) return validate330.errors = [ { + if (data10 > 36500 || isNaN(data10)) return validate333.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/maximum", keyword: "maximum", @@ -92055,7 +100694,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data10 < 0 || isNaN(data10)) return validate330.errors = [ { + if (data10 < 0 || isNaN(data10)) return validate333.errors = [ { instancePath: instancePath + "/minimum_retention_days", schemaPath: "#/properties/minimum_retention_days/minimum", keyword: "minimum", @@ -92070,7 +100709,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data.desired_retention_days) { let data11 = data.desired_retention_days, _errs44 = errors; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate330.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate333.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/type", keyword: "type", @@ -92079,7 +100718,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs44 && "number" == typeof data11 && isFinite(data11)) { - if (data11 > 36500 || isNaN(data11)) return validate330.errors = [ { + if (data11 > 36500 || isNaN(data11)) return validate333.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/maximum", keyword: "maximum", @@ -92088,7 +100727,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data11 < 0 || isNaN(data11)) return validate330.errors = [ { + if (data11 < 0 || isNaN(data11)) return validate333.errors = [ { instancePath: instancePath + "/desired_retention_days", schemaPath: "#/properties/desired_retention_days/minimum", keyword: "minimum", @@ -92103,7 +100742,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data.maximum_retention_days) { let data12 = data.maximum_retention_days, _errs46 = errors; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate330.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate333.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/type", keyword: "type", @@ -92112,7 +100751,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs46 && "number" == typeof data12 && isFinite(data12)) { - if (data12 > 36500 || isNaN(data12)) return validate330.errors = [ { + if (data12 > 36500 || isNaN(data12)) return validate333.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/maximum", keyword: "maximum", @@ -92121,7 +100760,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data12 < 0 || isNaN(data12)) return validate330.errors = [ { + if (data12 < 0 || isNaN(data12)) return validate333.errors = [ { instancePath: instancePath + "/maximum_retention_days", schemaPath: "#/properties/maximum_retention_days/minimum", keyword: "minimum", @@ -92136,7 +100775,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data.migration_after_days) { let data13 = data.migration_after_days, _errs48 = errors; - if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate330.errors = [ { + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate333.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/type", keyword: "type", @@ -92145,7 +100784,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs48 && "number" == typeof data13 && isFinite(data13)) { - if (data13 > 36500 || isNaN(data13)) return validate330.errors = [ { + if (data13 > 36500 || isNaN(data13)) return validate333.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/maximum", keyword: "maximum", @@ -92154,7 +100793,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 36500 } } ], !1; - if (data13 < 0 || isNaN(data13)) return validate330.errors = [ { + if (data13 < 0 || isNaN(data13)) return validate333.errors = [ { instancePath: instancePath + "/migration_after_days", schemaPath: "#/properties/migration_after_days/minimum", keyword: "minimum", @@ -92169,7 +100808,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data.required_copy_count) { let data14 = data.required_copy_count, _errs50 = errors; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate330.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate333.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/type", keyword: "type", @@ -92178,7 +100817,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs50 && "number" == typeof data14 && isFinite(data14)) { - if (data14 > 8 || isNaN(data14)) return validate330.errors = [ { + if (data14 > 8 || isNaN(data14)) return validate333.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/maximum", keyword: "maximum", @@ -92187,7 +100826,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 8 } } ], !1; - if (data14 < 1 || isNaN(data14)) return validate330.errors = [ { + if (data14 < 1 || isNaN(data14)) return validate333.errors = [ { instancePath: instancePath + "/required_copy_count", schemaPath: "#/properties/required_copy_count/minimum", keyword: "minimum", @@ -92202,7 +100841,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data.pressure_priority) { let data15 = data.pressure_priority, _errs52 = errors; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate330.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate333.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/type", keyword: "type", @@ -92211,7 +100850,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs52 && "number" == typeof data15 && isFinite(data15)) { - if (data15 > 1e6 || isNaN(data15)) return validate330.errors = [ { + if (data15 > 1e6 || isNaN(data15)) return validate333.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/maximum", keyword: "maximum", @@ -92220,7 +100859,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 1e6 } } ], !1; - if (data15 < -1e6 || isNaN(data15)) return validate330.errors = [ { + if (data15 < -1e6 || isNaN(data15)) return validate333.errors = [ { instancePath: instancePath + "/pressure_priority", schemaPath: "#/properties/pressure_priority/minimum", keyword: "minimum", @@ -92235,7 +100874,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data.archive_after_seconds) { let data16 = data.archive_after_seconds, _errs54 = errors; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate330.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate333.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/type", keyword: "type", @@ -92244,7 +100883,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs54 && "number" == typeof data16 && isFinite(data16)) { - if (data16 > 2147483647 || isNaN(data16)) return validate330.errors = [ { + if (data16 > 2147483647 || isNaN(data16)) return validate333.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/maximum", keyword: "maximum", @@ -92253,7 +100892,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data16 < -1 || isNaN(data16)) return validate330.errors = [ { + if (data16 < -1 || isNaN(data16)) return validate333.errors = [ { instancePath: instancePath + "/archive_after_seconds", schemaPath: "#/properties/archive_after_seconds/minimum", keyword: "minimum", @@ -92268,7 +100907,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data.hot_residency_seconds) { let data17 = data.hot_residency_seconds, _errs56 = errors; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate330.errors = [ { + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate333.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/type", keyword: "type", @@ -92277,7 +100916,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs56 && "number" == typeof data17 && isFinite(data17)) { - if (data17 > 2147483647 || isNaN(data17)) return validate330.errors = [ { + if (data17 > 2147483647 || isNaN(data17)) return validate333.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/maximum", keyword: "maximum", @@ -92286,7 +100925,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data17 < -1 || isNaN(data17)) return validate330.errors = [ { + if (data17 < -1 || isNaN(data17)) return validate333.errors = [ { instancePath: instancePath + "/hot_residency_seconds", schemaPath: "#/properties/hot_residency_seconds/minimum", keyword: "minimum", @@ -92301,7 +100940,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data.archive_protected) { let _errs58 = errors; - if ("boolean" != typeof data.archive_protected) return validate330.errors = [ { + if ("boolean" != typeof data.archive_protected) return validate333.errors = [ { instancePath: instancePath + "/archive_protected", schemaPath: "#/properties/archive_protected/type", keyword: "type", @@ -92314,7 +100953,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data.archive_on_pressure) { let _errs60 = errors; - if ("boolean" != typeof data.archive_on_pressure) return validate330.errors = [ { + if ("boolean" != typeof data.archive_on_pressure) return validate333.errors = [ { instancePath: instancePath + "/archive_on_pressure", schemaPath: "#/properties/archive_on_pressure/type", keyword: "type", @@ -92326,7 +100965,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } else valid28 = !0; if (valid28 && void 0 !== data.revision) { let data20 = data.revision, _errs62 = errors; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate330.errors = [ { + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate333.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -92335,7 +100974,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs62 && "number" == typeof data20 && isFinite(data20)) { - if (data20 > 9007199254740991 || isNaN(data20)) return validate330.errors = [ { + if (data20 > 9007199254740991 || isNaN(data20)) return validate333.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -92344,7 +100983,7 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data20 < 1 || isNaN(data20)) return validate330.errors = [ { + if (data20 < 1 || isNaN(data20)) return validate333.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -92377,16 +101016,16 @@ function validate330(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate330.errors = vErrors, 0 === errors; + return validate333.errors = vErrors, 0 === errors; } -validate330.evaluated = { +validate333.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePolicyWinner = validate336, __contract_StoragePool = validate316, schema347 = { +var __contract_StoragePolicyWinner = validate339, __contract_StoragePool = validate319, schema351 = { type: "object", properties: { uuid: { @@ -92430,10 +101069,10 @@ var __contract_StoragePolicyWinner = validate336, __contract_StoragePool = valid additionalProperties: !1 }; -function validate314(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate314.evaluated; +function validate317(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate317.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate314.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate317.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -92441,7 +101080,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.target_uuid) return validate314.errors = [ { + if (void 0 === data.target_uuid) return validate317.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92449,7 +101088,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "target_uuid" } } ], !1; - if (void 0 === data.position) return validate314.errors = [ { + if (void 0 === data.position) return validate317.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92457,7 +101096,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "position" } } ], !1; - if (void 0 === data.weight) return validate314.errors = [ { + if (void 0 === data.weight) return validate317.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92467,7 +101106,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("target_uuid" !== key0 && "position" !== key0 && "weight" !== key0) return validate314.errors = [ { + for (let key0 in data) if ("target_uuid" !== key0 && "position" !== key0 && "weight" !== key0) return validate317.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -92478,7 +101117,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.target_uuid) { let data0 = data.target_uuid, _errs2 = 0; - if ("string" != typeof data0) return validate314.errors = [ { + if ("string" != typeof data0) return validate317.errors = [ { instancePath: instancePath + "/target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -92486,7 +101125,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate314.errors = [ { + if (!pattern4.test(data0)) return validate317.errors = [ { instancePath: instancePath + "/target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -92499,7 +101138,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.position) { let data1 = data.position, _errs5 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate314.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate317.errors = [ { instancePath: instancePath + "/position", schemaPath: "#/properties/position/type", keyword: "type", @@ -92507,7 +101146,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate314.errors = [ { + if (0 === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate317.errors = [ { instancePath: instancePath + "/position", schemaPath: "#/properties/position/minimum", keyword: "minimum", @@ -92520,7 +101159,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD } else valid2 = !0; if (valid2 && void 0 !== data.weight) { let data2 = data.weight, _errs7 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate314.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate317.errors = [ { instancePath: instancePath + "/weight", schemaPath: "#/properties/weight/type", keyword: "type", @@ -92529,7 +101168,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs7 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 1e3 || isNaN(data2)) return validate314.errors = [ { + if (data2 > 1e3 || isNaN(data2)) return validate317.errors = [ { instancePath: instancePath + "/weight", schemaPath: "#/properties/weight/maximum", keyword: "maximum", @@ -92538,7 +101177,7 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD limit: 1e3 } } ], !1; - if (data2 < 1 || isNaN(data2)) return validate314.errors = [ { + if (data2 < 1 || isNaN(data2)) return validate317.errors = [ { instancePath: instancePath + "/weight", schemaPath: "#/properties/weight/minimum", keyword: "minimum", @@ -92552,14 +101191,14 @@ function validate314(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate314.errors = [], !0; + return validate317.errors = [], !0; } -function validate316(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate316.evaluated; +function validate319(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate319.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate316.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -92567,7 +101206,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate316.errors = [ { + if (void 0 === data.uuid) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92575,7 +101214,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.name) return validate316.errors = [ { + if (void 0 === data.name) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92583,7 +101222,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.strategy) return validate316.errors = [ { + if (void 0 === data.strategy) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92591,7 +101230,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "strategy" } } ], !1; - if (void 0 === data.enabled) return validate316.errors = [ { + if (void 0 === data.enabled) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92599,7 +101238,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.revision) return validate316.errors = [ { + if (void 0 === data.revision) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92607,7 +101246,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate316.errors = [ { + if (void 0 === data.created_at) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92615,7 +101254,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate316.errors = [ { + if (void 0 === data.updated_at) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92623,7 +101262,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "updated_at" } } ], !1; - if (void 0 === data.members) return validate316.errors = [ { + if (void 0 === data.members) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92633,7 +101272,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("uuid" !== key0 && "name" !== key0 && "strategy" !== key0 && "enabled" !== key0 && "revision" !== key0 && "created_at" !== key0 && "updated_at" !== key0 && "members" !== key0) return validate316.errors = [ { + for (let key0 in data) if ("uuid" !== key0 && "name" !== key0 && "strategy" !== key0 && "enabled" !== key0 && "revision" !== key0 && "created_at" !== key0 && "updated_at" !== key0 && "members" !== key0) return validate319.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -92645,7 +101284,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate316.errors = [ { + if ("string" != typeof data0) return validate319.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -92653,7 +101292,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate316.errors = [ { + if (!pattern4.test(data0)) return validate319.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -92668,7 +101307,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data1 = data.name, _errs5 = errors; if (errors === _errs5) { - if ("string" != typeof data1) return validate316.errors = [ { + if ("string" != typeof data1) return validate319.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -92676,7 +101315,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data1) > 127) return validate316.errors = [ { + if (func1(data1) > 127) return validate319.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -92684,7 +101323,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data1) < 1) return validate316.errors = [ { + if (func1(data1) < 1) return validate319.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -92698,7 +101337,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.strategy) { let data2 = data.strategy, _errs7 = errors; - if ("string" != typeof data2) return validate316.errors = [ { + if ("string" != typeof data2) return validate319.errors = [ { instancePath: instancePath + "/strategy", schemaPath: "#/properties/strategy/type", keyword: "type", @@ -92706,12 +101345,12 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("most_free" !== data2 && "round_robin" !== data2 && "priority" !== data2) return validate316.errors = [ { + if ("most_free" !== data2 && "round_robin" !== data2 && "priority" !== data2) return validate319.errors = [ { instancePath: instancePath + "/strategy", schemaPath: "#/properties/strategy/enum", keyword: "enum", params: { - allowedValues: schema347.properties.strategy.enum + allowedValues: schema351.properties.strategy.enum } } ], !1; var valid3 = _errs7 === errors; @@ -92719,7 +101358,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.enabled) { let _errs9 = errors; - if ("boolean" != typeof data.enabled) return validate316.errors = [ { + if ("boolean" != typeof data.enabled) return validate319.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -92732,7 +101371,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.revision) { let data4 = data.revision, _errs11 = errors; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate316.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate319.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -92741,7 +101380,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs11 && "number" == typeof data4 && isFinite(data4)) { - if (data4 > 9007199254740991 || isNaN(data4)) return validate316.errors = [ { + if (data4 > 9007199254740991 || isNaN(data4)) return validate319.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -92750,7 +101389,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data4 < 1 || isNaN(data4)) return validate316.errors = [ { + if (data4 < 1 || isNaN(data4)) return validate319.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -92765,7 +101404,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.created_at) { let data5 = data.created_at, _errs13 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate316.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate319.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -92773,7 +101412,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate316.errors = [ { + if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate319.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -92787,7 +101426,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.updated_at) { let data6 = data.updated_at, _errs15 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate316.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate319.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -92795,7 +101434,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate316.errors = [ { + if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate319.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/minimum", keyword: "minimum", @@ -92809,7 +101448,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD if (valid7 && void 0 !== data.members) { let data7 = data.members; if (errors == errors) { - if (!Array.isArray(data7)) return validate316.errors = [ { + if (!Array.isArray(data7)) return validate319.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/type", keyword: "type", @@ -92817,7 +101456,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data7.length > 128) return validate316.errors = [ { + if (data7.length > 128) return validate319.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/maxItems", keyword: "maxItems", @@ -92825,7 +101464,7 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD limit: 128 } } ], !1; - if (data7.length < 1) return validate316.errors = [ { + if (data7.length < 1) return validate319.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/minItems", keyword: "minItems", @@ -92837,13 +101476,13 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD let len0 = data7.length; for (let i0 = 0; i0 < len0; i0++) { let _errs19 = errors; - if (validate314(data7[i0], { + if (validate317(data7[i0], { instancePath: instancePath + "/members/" + i0, parentData: data7, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate314.errors), errors = vErrors.length), _errs19 !== errors) break; + }) || (vErrors = vErrors.concat(validate317.errors), errors = vErrors.length), _errs19 !== errors) break; } } } @@ -92857,20 +101496,20 @@ function validate316(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate316.errors = vErrors, 0 === errors; + return validate319.errors = vErrors, 0 === errors; } -validate314.evaluated = { +validate317.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate316.evaluated = { +}, validate319.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePoolCreate = validate320, schema350 = { +var __contract_StoragePoolCreate = validate323, schema354 = { type: "object", properties: { name: { @@ -92898,10 +101537,10 @@ var __contract_StoragePoolCreate = validate320, schema350 = { additionalProperties: !1 }; -function validate315(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate315.evaluated; +function validate318(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate318.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate315.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate318.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -92909,7 +101548,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.target_uuid) return validate315.errors = [ { + if (void 0 === data.target_uuid) return validate318.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -92919,7 +101558,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if ("target_uuid" !== key0 && "position" !== key0 && "weight" !== key0) return validate315.errors = [ { + for (let key0 in data) if ("target_uuid" !== key0 && "position" !== key0 && "weight" !== key0) return validate318.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -92930,7 +101569,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs1) { if (void 0 !== data.target_uuid) { let data0 = data.target_uuid, _errs2 = 0; - if ("string" != typeof data0) return validate315.errors = [ { + if ("string" != typeof data0) return validate318.errors = [ { instancePath: instancePath + "/target_uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -92938,7 +101577,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate315.errors = [ { + if (!pattern4.test(data0)) return validate318.errors = [ { instancePath: instancePath + "/target_uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -92951,7 +101590,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.position) { let data1 = data.position, _errs5 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate315.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate318.errors = [ { instancePath: instancePath + "/position", schemaPath: "#/properties/position/type", keyword: "type", @@ -92959,7 +101598,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate315.errors = [ { + if (0 === _errs5 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate318.errors = [ { instancePath: instancePath + "/position", schemaPath: "#/properties/position/minimum", keyword: "minimum", @@ -92972,7 +101611,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD } else valid2 = !0; if (valid2 && void 0 !== data.weight) { let data2 = data.weight, _errs7 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate315.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate318.errors = [ { instancePath: instancePath + "/weight", schemaPath: "#/properties/weight/type", keyword: "type", @@ -92981,7 +101620,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs7 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 1e3 || isNaN(data2)) return validate315.errors = [ { + if (data2 > 1e3 || isNaN(data2)) return validate318.errors = [ { instancePath: instancePath + "/weight", schemaPath: "#/properties/weight/maximum", keyword: "maximum", @@ -92990,7 +101629,7 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD limit: 1e3 } } ], !1; - if (data2 < 1 || isNaN(data2)) return validate315.errors = [ { + if (data2 < 1 || isNaN(data2)) return validate318.errors = [ { instancePath: instancePath + "/weight", schemaPath: "#/properties/weight/minimum", keyword: "minimum", @@ -93004,14 +101643,14 @@ function validate315(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate315.errors = [], !0; + return validate318.errors = [], !0; } -function validate320(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate320.evaluated; +function validate323(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate323.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate320.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate323.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -93019,7 +101658,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate320.errors = [ { + if (void 0 === data.name) return validate323.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93027,7 +101666,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.members) return validate320.errors = [ { + if (void 0 === data.members) return validate323.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93037,7 +101676,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("name" !== key0 && "strategy" !== key0 && "enabled" !== key0 && "members" !== key0) return validate320.errors = [ { + for (let key0 in data) if ("name" !== key0 && "strategy" !== key0 && "enabled" !== key0 && "members" !== key0) return validate323.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -93049,7 +101688,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate320.errors = [ { + if ("string" != typeof data0) return validate323.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -93057,7 +101696,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate320.errors = [ { + if (func1(data0) > 127) return validate323.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -93065,7 +101704,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate320.errors = [ { + if (func1(data0) < 1) return validate323.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -93079,7 +101718,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.strategy) { let data1 = data.strategy, _errs4 = errors; - if ("string" != typeof data1) return validate320.errors = [ { + if ("string" != typeof data1) return validate323.errors = [ { instancePath: instancePath + "/strategy", schemaPath: "#/properties/strategy/type", keyword: "type", @@ -93087,12 +101726,12 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("most_free" !== data1 && "round_robin" !== data1 && "priority" !== data1) return validate320.errors = [ { + if ("most_free" !== data1 && "round_robin" !== data1 && "priority" !== data1) return validate323.errors = [ { instancePath: instancePath + "/strategy", schemaPath: "#/properties/strategy/enum", keyword: "enum", params: { - allowedValues: schema350.properties.strategy.enum + allowedValues: schema354.properties.strategy.enum } } ], !1; var valid1 = _errs4 === errors; @@ -93100,7 +101739,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = errors; - if ("boolean" != typeof data.enabled) return validate320.errors = [ { + if ("boolean" != typeof data.enabled) return validate323.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -93113,7 +101752,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD if (valid2 && void 0 !== data.members) { let data3 = data.members; if (errors == errors) { - if (!Array.isArray(data3)) return validate320.errors = [ { + if (!Array.isArray(data3)) return validate323.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/type", keyword: "type", @@ -93121,7 +101760,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data3.length > 128) return validate320.errors = [ { + if (data3.length > 128) return validate323.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/maxItems", keyword: "maxItems", @@ -93129,7 +101768,7 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD limit: 128 } } ], !1; - if (data3.length < 1) return validate320.errors = [ { + if (data3.length < 1) return validate323.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/minItems", keyword: "minItems", @@ -93141,13 +101780,13 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD let len0 = data3.length; for (let i0 = 0; i0 < len0; i0++) { let _errs10 = errors; - if (validate315(data3[i0], { + if (validate318(data3[i0], { instancePath: instancePath + "/members/" + i0, parentData: data3, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate315.errors), errors = vErrors.length), _errs10 !== errors) break; + }) || (vErrors = vErrors.concat(validate318.errors), errors = vErrors.length), _errs10 !== errors) break; } } } @@ -93157,20 +101796,20 @@ function validate320(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate320.errors = vErrors, 0 === errors; + return validate323.errors = vErrors, 0 === errors; } -validate315.evaluated = { +validate318.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate320.evaluated = { +}, validate323.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePoolMember = validate314, __contract_StoragePoolMemberRequest = validate315, __contract_StoragePoolUpdate = validate322, schema351 = { +var __contract_StoragePoolMember = validate317, __contract_StoragePoolMemberRequest = validate318, __contract_StoragePoolUpdate = validate325, schema355 = { type: "object", properties: { name: { @@ -93203,11 +101842,11 @@ var __contract_StoragePoolMember = validate314, __contract_StoragePoolMemberRequ additionalProperties: !1 }; -function validate322(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate322.evaluated; +function validate325(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate325.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate322.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate325.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -93215,7 +101854,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate322.errors = [ { + if (void 0 === data.revision) return validate325.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93225,7 +101864,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("name" !== key0 && "strategy" !== key0 && "enabled" !== key0 && "members" !== key0 && "revision" !== key0) return validate322.errors = [ { + for (let key0 in data) if ("name" !== key0 && "strategy" !== key0 && "enabled" !== key0 && "members" !== key0 && "revision" !== key0) return validate325.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -93237,7 +101876,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = errors; if (errors === _errs2) { - if ("string" != typeof data0) return validate322.errors = [ { + if ("string" != typeof data0) return validate325.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -93245,7 +101884,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate322.errors = [ { + if (func1(data0) > 127) return validate325.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -93253,7 +101892,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate322.errors = [ { + if (func1(data0) < 1) return validate325.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -93267,7 +101906,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.strategy) { let data1 = data.strategy, _errs4 = errors; - if ("string" != typeof data1) return validate322.errors = [ { + if ("string" != typeof data1) return validate325.errors = [ { instancePath: instancePath + "/strategy", schemaPath: "#/properties/strategy/type", keyword: "type", @@ -93275,12 +101914,12 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("most_free" !== data1 && "round_robin" !== data1 && "priority" !== data1) return validate322.errors = [ { + if ("most_free" !== data1 && "round_robin" !== data1 && "priority" !== data1) return validate325.errors = [ { instancePath: instancePath + "/strategy", schemaPath: "#/properties/strategy/enum", keyword: "enum", params: { - allowedValues: schema351.properties.strategy.enum + allowedValues: schema355.properties.strategy.enum } } ], !1; var valid1 = _errs4 === errors; @@ -93288,7 +101927,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.enabled) { let _errs6 = errors; - if ("boolean" != typeof data.enabled) return validate322.errors = [ { + if ("boolean" != typeof data.enabled) return validate325.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -93302,7 +101941,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.members) { let data3 = data.members, _errs8 = errors; if (errors === _errs8) { - if (!Array.isArray(data3)) return validate322.errors = [ { + if (!Array.isArray(data3)) return validate325.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/type", keyword: "type", @@ -93310,7 +101949,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data3.length > 128) return validate322.errors = [ { + if (data3.length > 128) return validate325.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/maxItems", keyword: "maxItems", @@ -93318,7 +101957,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD limit: 128 } } ], !1; - if (data3.length < 1) return validate322.errors = [ { + if (data3.length < 1) return validate325.errors = [ { instancePath: instancePath + "/members", schemaPath: "#/properties/members/minItems", keyword: "minItems", @@ -93330,13 +101969,13 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD let len0 = data3.length; for (let i0 = 0; i0 < len0; i0++) { let _errs10 = errors; - if (validate315(data3[i0], { + if (validate318(data3[i0], { instancePath: instancePath + "/members/" + i0, parentData: data3, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate315.errors), errors = vErrors.length), _errs10 !== errors) break; + }) || (vErrors = vErrors.concat(validate318.errors), errors = vErrors.length), _errs10 !== errors) break; } } } @@ -93344,7 +101983,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD } else valid3 = !0; if (valid3 && void 0 !== data.revision) { let data5 = data.revision, _errs11 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate322.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate325.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -93353,7 +101992,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs11 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 9007199254740991 || isNaN(data5)) return validate322.errors = [ { + if (data5 > 9007199254740991 || isNaN(data5)) return validate325.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -93362,7 +102001,7 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data5 < 1 || isNaN(data5)) return validate322.errors = [ { + if (data5 < 1 || isNaN(data5)) return validate325.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -93379,22 +102018,22 @@ function validate322(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate322.errors = vErrors, 0 === errors; + return validate325.errors = vErrors, 0 === errors; } -validate322.evaluated = { +validate325.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StoragePoolsResponse = validate318; +var __contract_StoragePoolsResponse = validate321; -function validate318(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate318.evaluated; +function validate321(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate321.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate318.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate321.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -93402,7 +102041,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.pools) return validate318.errors = [ { + if (void 0 === data.pools) return validate321.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93410,7 +102049,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pools" } } ], !1; - if (void 0 === data.count) return validate318.errors = [ { + if (void 0 === data.count) return validate321.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93420,7 +102059,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("pools" !== key0 && "count" !== key0) return validate318.errors = [ { + for (let key0 in data) if ("pools" !== key0 && "count" !== key0) return validate321.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -93432,7 +102071,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.pools) { let data0 = data.pools, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate318.errors = [ { + if (!Array.isArray(data0)) return validate321.errors = [ { instancePath: instancePath + "/pools", schemaPath: "#/properties/pools/type", keyword: "type", @@ -93440,7 +102079,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data0.length > 64) return validate318.errors = [ { + if (data0.length > 64) return validate321.errors = [ { instancePath: instancePath + "/pools", schemaPath: "#/properties/pools/maxItems", keyword: "maxItems", @@ -93452,13 +102091,13 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = errors; - if (validate316(data0[i0], { + if (validate319(data0[i0], { instancePath: instancePath + "/pools/" + i0, parentData: data0, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate316.errors), errors = vErrors.length), _errs4 !== errors) break; + }) || (vErrors = vErrors.concat(validate319.errors), errors = vErrors.length), _errs4 !== errors) break; } } } @@ -93466,7 +102105,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.count) { let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate318.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate321.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -93474,7 +102113,7 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate318.errors = [ { + if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate321.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -93487,16 +102126,16 @@ function validate318(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate318.errors = vErrors, 0 === errors; + return validate321.errors = vErrors, 0 === errors; } -validate318.evaluated = { +validate321.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageTarget = validate309, schema337 = { +var __contract_StorageTarget = validate312, schema341 = { type: "object", properties: { uuid: { @@ -93610,7 +102249,7 @@ var __contract_StorageTarget = validate309, schema337 = { }, required: [ "uuid", "name", "root_path", "endpoint", "region", "bucket", "credential_ref", "target_type", "archive_only", "enabled", "is_default", "mount_required", "mount_guard_path", "storage_class", "archive_budget_bytes", "reserve_bytes", "recording_count", "recording_bytes", "replica_count", "replica_bytes", "migration_bandwidth_bps", "high_watermark_pct", "low_watermark_pct", "archival_window_start_minute", "archival_window_end_minute", "revision", "created_at", "updated_at", "health" ], additionalProperties: !1 -}, schema336 = { +}, schema340 = { type: "object", properties: { status: { @@ -93673,11 +102312,11 @@ var __contract_StorageTarget = validate309, schema337 = { additionalProperties: !1 }; -function validate309(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate309.evaluated; +function validate312(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate312.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate309.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -93685,7 +102324,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.uuid) return validate309.errors = [ { + if (void 0 === data.uuid) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93693,7 +102332,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uuid" } } ], !1; - if (void 0 === data.name) return validate309.errors = [ { + if (void 0 === data.name) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93701,7 +102340,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.root_path) return validate309.errors = [ { + if (void 0 === data.root_path) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93709,7 +102348,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "root_path" } } ], !1; - if (void 0 === data.endpoint) return validate309.errors = [ { + if (void 0 === data.endpoint) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93717,7 +102356,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "endpoint" } } ], !1; - if (void 0 === data.region) return validate309.errors = [ { + if (void 0 === data.region) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93725,7 +102364,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "region" } } ], !1; - if (void 0 === data.bucket) return validate309.errors = [ { + if (void 0 === data.bucket) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93733,7 +102372,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bucket" } } ], !1; - if (void 0 === data.credential_ref) return validate309.errors = [ { + if (void 0 === data.credential_ref) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93741,7 +102380,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "credential_ref" } } ], !1; - if (void 0 === data.target_type) return validate309.errors = [ { + if (void 0 === data.target_type) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93749,7 +102388,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "target_type" } } ], !1; - if (void 0 === data.archive_only) return validate309.errors = [ { + if (void 0 === data.archive_only) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93757,7 +102396,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archive_only" } } ], !1; - if (void 0 === data.enabled) return validate309.errors = [ { + if (void 0 === data.enabled) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93765,7 +102404,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.is_default) return validate309.errors = [ { + if (void 0 === data.is_default) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93773,7 +102412,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "is_default" } } ], !1; - if (void 0 === data.mount_required) return validate309.errors = [ { + if (void 0 === data.mount_required) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93781,7 +102420,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "mount_required" } } ], !1; - if (void 0 === data.mount_guard_path) return validate309.errors = [ { + if (void 0 === data.mount_guard_path) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93789,7 +102428,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "mount_guard_path" } } ], !1; - if (void 0 === data.storage_class) return validate309.errors = [ { + if (void 0 === data.storage_class) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93797,7 +102436,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "storage_class" } } ], !1; - if (void 0 === data.archive_budget_bytes) return validate309.errors = [ { + if (void 0 === data.archive_budget_bytes) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93805,7 +102444,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archive_budget_bytes" } } ], !1; - if (void 0 === data.reserve_bytes) return validate309.errors = [ { + if (void 0 === data.reserve_bytes) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93813,7 +102452,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "reserve_bytes" } } ], !1; - if (void 0 === data.recording_count) return validate309.errors = [ { + if (void 0 === data.recording_count) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93821,7 +102460,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_count" } } ], !1; - if (void 0 === data.recording_bytes) return validate309.errors = [ { + if (void 0 === data.recording_bytes) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93829,7 +102468,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_bytes" } } ], !1; - if (void 0 === data.replica_count) return validate309.errors = [ { + if (void 0 === data.replica_count) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93837,7 +102476,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "replica_count" } } ], !1; - if (void 0 === data.replica_bytes) return validate309.errors = [ { + if (void 0 === data.replica_bytes) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93845,7 +102484,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "replica_bytes" } } ], !1; - if (void 0 === data.migration_bandwidth_bps) return validate309.errors = [ { + if (void 0 === data.migration_bandwidth_bps) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93853,7 +102492,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "migration_bandwidth_bps" } } ], !1; - if (void 0 === data.high_watermark_pct) return validate309.errors = [ { + if (void 0 === data.high_watermark_pct) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93861,7 +102500,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "high_watermark_pct" } } ], !1; - if (void 0 === data.low_watermark_pct) return validate309.errors = [ { + if (void 0 === data.low_watermark_pct) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93869,7 +102508,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "low_watermark_pct" } } ], !1; - if (void 0 === data.archival_window_start_minute) return validate309.errors = [ { + if (void 0 === data.archival_window_start_minute) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93877,7 +102516,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archival_window_start_minute" } } ], !1; - if (void 0 === data.archival_window_end_minute) return validate309.errors = [ { + if (void 0 === data.archival_window_end_minute) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93885,7 +102524,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "archival_window_end_minute" } } ], !1; - if (void 0 === data.revision) return validate309.errors = [ { + if (void 0 === data.revision) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93893,7 +102532,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "revision" } } ], !1; - if (void 0 === data.created_at) return validate309.errors = [ { + if (void 0 === data.created_at) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93901,7 +102540,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "created_at" } } ], !1; - if (void 0 === data.updated_at) return validate309.errors = [ { + if (void 0 === data.updated_at) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93909,7 +102548,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "updated_at" } } ], !1; - if (void 0 === data.health) return validate309.errors = [ { + if (void 0 === data.health) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -93919,7 +102558,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema337.properties, key0)) return validate309.errors = [ { + for (let key0 in data) if (!func7.call(schema341.properties, key0)) return validate312.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -93931,7 +102570,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.uuid) { let data0 = data.uuid, _errs2 = errors; if (errors == errors) { - if ("string" != typeof data0) return validate309.errors = [ { + if ("string" != typeof data0) return validate312.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/type", keyword: "type", @@ -93939,7 +102578,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (!pattern4.test(data0)) return validate309.errors = [ { + if (!pattern4.test(data0)) return validate312.errors = [ { instancePath: instancePath + "/uuid", schemaPath: "#/$defs/CameraUuid/pattern", keyword: "pattern", @@ -93953,7 +102592,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.name) { let _errs5 = errors; - if ("string" != typeof data.name) return validate309.errors = [ { + if ("string" != typeof data.name) return validate312.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -93966,7 +102605,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.root_path) { let _errs7 = errors; - if ("string" != typeof data.root_path) return validate309.errors = [ { + if ("string" != typeof data.root_path) return validate312.errors = [ { instancePath: instancePath + "/root_path", schemaPath: "#/properties/root_path/type", keyword: "type", @@ -93979,7 +102618,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.endpoint) { let _errs9 = errors; - if ("string" != typeof data.endpoint) return validate309.errors = [ { + if ("string" != typeof data.endpoint) return validate312.errors = [ { instancePath: instancePath + "/endpoint", schemaPath: "#/properties/endpoint/type", keyword: "type", @@ -93992,7 +102631,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.region) { let _errs11 = errors; - if ("string" != typeof data.region) return validate309.errors = [ { + if ("string" != typeof data.region) return validate312.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/type", keyword: "type", @@ -94005,7 +102644,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.bucket) { let _errs13 = errors; - if ("string" != typeof data.bucket) return validate309.errors = [ { + if ("string" != typeof data.bucket) return validate312.errors = [ { instancePath: instancePath + "/bucket", schemaPath: "#/properties/bucket/type", keyword: "type", @@ -94018,7 +102657,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.credential_ref) { let _errs15 = errors; - if ("string" != typeof data.credential_ref) return validate309.errors = [ { + if ("string" != typeof data.credential_ref) return validate312.errors = [ { instancePath: instancePath + "/credential_ref", schemaPath: "#/properties/credential_ref/type", keyword: "type", @@ -94031,7 +102670,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.target_type) { let data7 = data.target_type, _errs17 = errors; - if ("string" != typeof data7) return validate309.errors = [ { + if ("string" != typeof data7) return validate312.errors = [ { instancePath: instancePath + "/target_type", schemaPath: "#/properties/target_type/type", keyword: "type", @@ -94039,12 +102678,12 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("filesystem" !== data7 && "s3" !== data7) return validate309.errors = [ { + if ("filesystem" !== data7 && "s3" !== data7) return validate312.errors = [ { instancePath: instancePath + "/target_type", schemaPath: "#/properties/target_type/enum", keyword: "enum", params: { - allowedValues: schema337.properties.target_type.enum + allowedValues: schema341.properties.target_type.enum } } ], !1; var valid8 = _errs17 === errors; @@ -94052,7 +102691,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.archive_only) { let _errs19 = errors; - if ("boolean" != typeof data.archive_only) return validate309.errors = [ { + if ("boolean" != typeof data.archive_only) return validate312.errors = [ { instancePath: instancePath + "/archive_only", schemaPath: "#/properties/archive_only/type", keyword: "type", @@ -94065,7 +102704,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.enabled) { let _errs21 = errors; - if ("boolean" != typeof data.enabled) return validate309.errors = [ { + if ("boolean" != typeof data.enabled) return validate312.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -94078,7 +102717,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.is_default) { let _errs23 = errors; - if ("boolean" != typeof data.is_default) return validate309.errors = [ { + if ("boolean" != typeof data.is_default) return validate312.errors = [ { instancePath: instancePath + "/is_default", schemaPath: "#/properties/is_default/type", keyword: "type", @@ -94091,7 +102730,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.mount_required) { let _errs25 = errors; - if ("boolean" != typeof data.mount_required) return validate309.errors = [ { + if ("boolean" != typeof data.mount_required) return validate312.errors = [ { instancePath: instancePath + "/mount_required", schemaPath: "#/properties/mount_required/type", keyword: "type", @@ -94136,7 +102775,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err2), errors++, validate309.errors = vErrors, !1; + return vErrors.push(err2), errors++, validate312.errors = vErrors, !1; } errors = _errs28, vErrors.length = _errs28; var valid13 = _errs27 === errors; @@ -94144,7 +102783,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data.storage_class) { let data13 = data.storage_class, _errs33 = errors; - if ("string" != typeof data13) return validate309.errors = [ { + if ("string" != typeof data13) return validate312.errors = [ { instancePath: instancePath + "/storage_class", schemaPath: "#/properties/storage_class/type", keyword: "type", @@ -94152,12 +102791,12 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("hot" !== data13 && "warm" !== data13 && "cold" !== data13) return validate309.errors = [ { + if ("hot" !== data13 && "warm" !== data13 && "cold" !== data13) return validate312.errors = [ { instancePath: instancePath + "/storage_class", schemaPath: "#/properties/storage_class/enum", keyword: "enum", params: { - allowedValues: schema337.properties.storage_class.enum + allowedValues: schema341.properties.storage_class.enum } } ], !1; var valid15 = _errs33 === errors; @@ -94165,7 +102804,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data.archive_budget_bytes) { let data14 = data.archive_budget_bytes, _errs35 = errors; - if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate309.errors = [ { + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate312.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/type", keyword: "type", @@ -94173,7 +102812,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs35 && "number" == typeof data14 && isFinite(data14) && (data14 < 0 || isNaN(data14))) return validate309.errors = [ { + if (errors === _errs35 && "number" == typeof data14 && isFinite(data14) && (data14 < 0 || isNaN(data14))) return validate312.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/minimum", keyword: "minimum", @@ -94187,7 +102826,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data.reserve_bytes) { let data15 = data.reserve_bytes, _errs37 = errors; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate309.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate312.errors = [ { instancePath: instancePath + "/reserve_bytes", schemaPath: "#/properties/reserve_bytes/type", keyword: "type", @@ -94195,7 +102834,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs37 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate309.errors = [ { + if (errors === _errs37 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate312.errors = [ { instancePath: instancePath + "/reserve_bytes", schemaPath: "#/properties/reserve_bytes/minimum", keyword: "minimum", @@ -94209,7 +102848,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data.recording_count) { let data16 = data.recording_count, _errs39 = errors; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate309.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate312.errors = [ { instancePath: instancePath + "/recording_count", schemaPath: "#/properties/recording_count/type", keyword: "type", @@ -94217,7 +102856,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs39 && "number" == typeof data16 && isFinite(data16) && (data16 < 0 || isNaN(data16))) return validate309.errors = [ { + if (errors === _errs39 && "number" == typeof data16 && isFinite(data16) && (data16 < 0 || isNaN(data16))) return validate312.errors = [ { instancePath: instancePath + "/recording_count", schemaPath: "#/properties/recording_count/minimum", keyword: "minimum", @@ -94231,7 +102870,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data.recording_bytes) { let data17 = data.recording_bytes, _errs41 = errors; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate309.errors = [ { + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate312.errors = [ { instancePath: instancePath + "/recording_bytes", schemaPath: "#/properties/recording_bytes/type", keyword: "type", @@ -94239,7 +102878,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs41 && "number" == typeof data17 && isFinite(data17) && (data17 < 0 || isNaN(data17))) return validate309.errors = [ { + if (errors === _errs41 && "number" == typeof data17 && isFinite(data17) && (data17 < 0 || isNaN(data17))) return validate312.errors = [ { instancePath: instancePath + "/recording_bytes", schemaPath: "#/properties/recording_bytes/minimum", keyword: "minimum", @@ -94253,7 +102892,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data.replica_count) { let data18 = data.replica_count, _errs43 = errors; - if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate309.errors = [ { + if ("number" != typeof data18 || data18 % 1 || isNaN(data18) || !isFinite(data18)) return validate312.errors = [ { instancePath: instancePath + "/replica_count", schemaPath: "#/properties/replica_count/type", keyword: "type", @@ -94261,7 +102900,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs43 && "number" == typeof data18 && isFinite(data18) && (data18 < 0 || isNaN(data18))) return validate309.errors = [ { + if (errors === _errs43 && "number" == typeof data18 && isFinite(data18) && (data18 < 0 || isNaN(data18))) return validate312.errors = [ { instancePath: instancePath + "/replica_count", schemaPath: "#/properties/replica_count/minimum", keyword: "minimum", @@ -94275,7 +102914,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data.replica_bytes) { let data19 = data.replica_bytes, _errs45 = errors; - if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate309.errors = [ { + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate312.errors = [ { instancePath: instancePath + "/replica_bytes", schemaPath: "#/properties/replica_bytes/type", keyword: "type", @@ -94283,7 +102922,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs45 && "number" == typeof data19 && isFinite(data19) && (data19 < 0 || isNaN(data19))) return validate309.errors = [ { + if (errors === _errs45 && "number" == typeof data19 && isFinite(data19) && (data19 < 0 || isNaN(data19))) return validate312.errors = [ { instancePath: instancePath + "/replica_bytes", schemaPath: "#/properties/replica_bytes/minimum", keyword: "minimum", @@ -94297,7 +102936,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data.migration_bandwidth_bps) { let data20 = data.migration_bandwidth_bps, _errs47 = errors; - if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate309.errors = [ { + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate312.errors = [ { instancePath: instancePath + "/migration_bandwidth_bps", schemaPath: "#/properties/migration_bandwidth_bps/type", keyword: "type", @@ -94305,7 +102944,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs47 && "number" == typeof data20 && isFinite(data20) && (data20 < 0 || isNaN(data20))) return validate309.errors = [ { + if (errors === _errs47 && "number" == typeof data20 && isFinite(data20) && (data20 < 0 || isNaN(data20))) return validate312.errors = [ { instancePath: instancePath + "/migration_bandwidth_bps", schemaPath: "#/properties/migration_bandwidth_bps/minimum", keyword: "minimum", @@ -94319,7 +102958,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data.high_watermark_pct) { let data21 = data.high_watermark_pct, _errs49 = errors; - if ("number" != typeof data21 || !isFinite(data21)) return validate309.errors = [ { + if ("number" != typeof data21 || !isFinite(data21)) return validate312.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/type", keyword: "type", @@ -94332,7 +102971,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data.low_watermark_pct) { let data22 = data.low_watermark_pct, _errs51 = errors; - if ("number" != typeof data22 || !isFinite(data22)) return validate309.errors = [ { + if ("number" != typeof data22 || !isFinite(data22)) return validate312.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/type", keyword: "type", @@ -94345,7 +102984,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data.archival_window_start_minute) { let data23 = data.archival_window_start_minute, _errs53 = errors; - if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate309.errors = [ { + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate312.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/type", keyword: "type", @@ -94354,7 +102993,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs53 && "number" == typeof data23 && isFinite(data23)) { - if (data23 > 1439 || isNaN(data23)) return validate309.errors = [ { + if (data23 > 1439 || isNaN(data23)) return validate312.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/maximum", keyword: "maximum", @@ -94363,7 +103002,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data23 < 0 || isNaN(data23)) return validate309.errors = [ { + if (data23 < 0 || isNaN(data23)) return validate312.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/minimum", keyword: "minimum", @@ -94378,7 +103017,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data.archival_window_end_minute) { let data24 = data.archival_window_end_minute, _errs55 = errors; - if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate309.errors = [ { + if ("number" != typeof data24 || data24 % 1 || isNaN(data24) || !isFinite(data24)) return validate312.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/type", keyword: "type", @@ -94387,7 +103026,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs55 && "number" == typeof data24 && isFinite(data24)) { - if (data24 > 1439 || isNaN(data24)) return validate309.errors = [ { + if (data24 > 1439 || isNaN(data24)) return validate312.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/maximum", keyword: "maximum", @@ -94396,7 +103035,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data24 < 0 || isNaN(data24)) return validate309.errors = [ { + if (data24 < 0 || isNaN(data24)) return validate312.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/minimum", keyword: "minimum", @@ -94411,7 +103050,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data.revision) { let data25 = data.revision, _errs57 = errors; - if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate309.errors = [ { + if ("number" != typeof data25 || data25 % 1 || isNaN(data25) || !isFinite(data25)) return validate312.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -94420,7 +103059,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (errors === _errs57 && "number" == typeof data25 && isFinite(data25)) { - if (data25 > 9007199254740991 || isNaN(data25)) return validate309.errors = [ { + if (data25 > 9007199254740991 || isNaN(data25)) return validate312.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -94429,7 +103068,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data25 < 1 || isNaN(data25)) return validate309.errors = [ { + if (data25 < 1 || isNaN(data25)) return validate312.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -94444,7 +103083,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data.created_at) { let data26 = data.created_at, _errs59 = errors; - if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate309.errors = [ { + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate312.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/type", keyword: "type", @@ -94452,7 +103091,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs59 && "number" == typeof data26 && isFinite(data26) && (data26 < 0 || isNaN(data26))) return validate309.errors = [ { + if (errors === _errs59 && "number" == typeof data26 && isFinite(data26) && (data26 < 0 || isNaN(data26))) return validate312.errors = [ { instancePath: instancePath + "/created_at", schemaPath: "#/properties/created_at/minimum", keyword: "minimum", @@ -94466,7 +103105,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid28) { if (void 0 !== data.updated_at) { let data27 = data.updated_at, _errs61 = errors; - if ("number" != typeof data27 || data27 % 1 || isNaN(data27) || !isFinite(data27)) return validate309.errors = [ { + if ("number" != typeof data27 || data27 % 1 || isNaN(data27) || !isFinite(data27)) return validate312.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/type", keyword: "type", @@ -94474,7 +103113,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs61 && "number" == typeof data27 && isFinite(data27) && (data27 < 0 || isNaN(data27))) return validate309.errors = [ { + if (errors === _errs61 && "number" == typeof data27 && isFinite(data27) && (data27 < 0 || isNaN(data27))) return validate312.errors = [ { instancePath: instancePath + "/updated_at", schemaPath: "#/properties/updated_at/minimum", keyword: "minimum", @@ -94488,7 +103127,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid29 && void 0 !== data.health) { let data28 = data.health; if (errors == errors) { - if (!data28 || "object" != typeof data28 || Array.isArray(data28)) return validate309.errors = [ { + if (!data28 || "object" != typeof data28 || Array.isArray(data28)) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/type", keyword: "type", @@ -94496,7 +103135,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data28.status) return validate309.errors = [ { + if (void 0 === data28.status) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94504,7 +103143,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status" } } ], !1; - if (void 0 === data28.pressure) return validate309.errors = [ { + if (void 0 === data28.pressure) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94512,7 +103151,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pressure" } } ], !1; - if (void 0 === data28.last_error) return validate309.errors = [ { + if (void 0 === data28.last_error) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94520,7 +103159,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_error" } } ], !1; - if (void 0 === data28.capacity_known) return validate309.errors = [ { + if (void 0 === data28.capacity_known) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94528,7 +103167,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capacity_known" } } ], !1; - if (void 0 === data28.duplicate_filesystem) return validate309.errors = [ { + if (void 0 === data28.duplicate_filesystem) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94536,7 +103175,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "duplicate_filesystem" } } ], !1; - if (void 0 === data28.capacity_bytes) return validate309.errors = [ { + if (void 0 === data28.capacity_bytes) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94544,7 +103183,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capacity_bytes" } } ], !1; - if (void 0 === data28.available_bytes) return validate309.errors = [ { + if (void 0 === data28.available_bytes) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94552,7 +103191,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "available_bytes" } } ], !1; - if (void 0 === data28.used_bytes) return validate309.errors = [ { + if (void 0 === data28.used_bytes) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94560,7 +103199,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "used_bytes" } } ], !1; - if (void 0 === data28.cleanup_target_bytes) return validate309.errors = [ { + if (void 0 === data28.cleanup_target_bytes) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94568,7 +103207,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cleanup_target_bytes" } } ], !1; - if (void 0 === data28.filesystem_device) return validate309.errors = [ { + if (void 0 === data28.filesystem_device) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94576,7 +103215,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "filesystem_device" } } ], !1; - if (void 0 === data28.used_pct) return validate309.errors = [ { + if (void 0 === data28.used_pct) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94584,7 +103223,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "used_pct" } } ], !1; - if (void 0 === data28.last_probe_at) return validate309.errors = [ { + if (void 0 === data28.last_probe_at) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94592,7 +103231,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_probe_at" } } ], !1; - if (void 0 === data28.last_success_at) return validate309.errors = [ { + if (void 0 === data28.last_success_at) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/required", keyword: "required", @@ -94602,7 +103241,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs66 = errors; - for (let key1 in data28) if (!func5.call(schema336.properties, key1)) return validate309.errors = [ { + for (let key1 in data28) if (!func7.call(schema340.properties, key1)) return validate312.errors = [ { instancePath: instancePath + "/health", schemaPath: "#/$defs/StorageTargetHealth/additionalProperties", keyword: "additionalProperties", @@ -94613,7 +103252,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (_errs66 === errors) { if (void 0 !== data28.status) { let _errs67 = errors; - if ("string" != typeof data28.status) return validate309.errors = [ { + if ("string" != typeof data28.status) return validate312.errors = [ { instancePath: instancePath + "/health/status", schemaPath: "#/$defs/StorageTargetHealth/properties/status/type", keyword: "type", @@ -94626,7 +103265,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data28.pressure) { let _errs69 = errors; - if ("string" != typeof data28.pressure) return validate309.errors = [ { + if ("string" != typeof data28.pressure) return validate312.errors = [ { instancePath: instancePath + "/health/pressure", schemaPath: "#/$defs/StorageTargetHealth/properties/pressure/type", keyword: "type", @@ -94639,7 +103278,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid33) { if (void 0 !== data28.last_error) { let _errs71 = errors; - if ("string" != typeof data28.last_error) return validate309.errors = [ { + if ("string" != typeof data28.last_error) return validate312.errors = [ { instancePath: instancePath + "/health/last_error", schemaPath: "#/$defs/StorageTargetHealth/properties/last_error/type", keyword: "type", @@ -94652,7 +103291,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data28.capacity_known) { let _errs73 = errors; - if ("boolean" != typeof data28.capacity_known) return validate309.errors = [ { + if ("boolean" != typeof data28.capacity_known) return validate312.errors = [ { instancePath: instancePath + "/health/capacity_known", schemaPath: "#/$defs/StorageTargetHealth/properties/capacity_known/type", keyword: "type", @@ -94665,7 +103304,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data28.duplicate_filesystem) { let _errs75 = errors; - if ("boolean" != typeof data28.duplicate_filesystem) return validate309.errors = [ { + if ("boolean" != typeof data28.duplicate_filesystem) return validate312.errors = [ { instancePath: instancePath + "/health/duplicate_filesystem", schemaPath: "#/$defs/StorageTargetHealth/properties/duplicate_filesystem/type", keyword: "type", @@ -94678,7 +103317,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid36) { if (void 0 !== data28.capacity_bytes) { let data34 = data28.capacity_bytes, _errs77 = errors; - if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate309.errors = [ { + if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate312.errors = [ { instancePath: instancePath + "/health/capacity_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/capacity_bytes/type", keyword: "type", @@ -94686,7 +103325,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs77 && "number" == typeof data34 && isFinite(data34) && (data34 < 0 || isNaN(data34))) return validate309.errors = [ { + if (errors === _errs77 && "number" == typeof data34 && isFinite(data34) && (data34 < 0 || isNaN(data34))) return validate312.errors = [ { instancePath: instancePath + "/health/capacity_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/capacity_bytes/minimum", keyword: "minimum", @@ -94700,7 +103339,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid37) { if (void 0 !== data28.available_bytes) { let data35 = data28.available_bytes, _errs79 = errors; - if ("number" != typeof data35 || data35 % 1 || isNaN(data35) || !isFinite(data35)) return validate309.errors = [ { + if ("number" != typeof data35 || data35 % 1 || isNaN(data35) || !isFinite(data35)) return validate312.errors = [ { instancePath: instancePath + "/health/available_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/available_bytes/type", keyword: "type", @@ -94708,7 +103347,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs79 && "number" == typeof data35 && isFinite(data35) && (data35 < 0 || isNaN(data35))) return validate309.errors = [ { + if (errors === _errs79 && "number" == typeof data35 && isFinite(data35) && (data35 < 0 || isNaN(data35))) return validate312.errors = [ { instancePath: instancePath + "/health/available_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/available_bytes/minimum", keyword: "minimum", @@ -94722,7 +103361,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid38) { if (void 0 !== data28.used_bytes) { let data36 = data28.used_bytes, _errs81 = errors; - if ("number" != typeof data36 || data36 % 1 || isNaN(data36) || !isFinite(data36)) return validate309.errors = [ { + if ("number" != typeof data36 || data36 % 1 || isNaN(data36) || !isFinite(data36)) return validate312.errors = [ { instancePath: instancePath + "/health/used_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/used_bytes/type", keyword: "type", @@ -94730,7 +103369,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs81 && "number" == typeof data36 && isFinite(data36) && (data36 < 0 || isNaN(data36))) return validate309.errors = [ { + if (errors === _errs81 && "number" == typeof data36 && isFinite(data36) && (data36 < 0 || isNaN(data36))) return validate312.errors = [ { instancePath: instancePath + "/health/used_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/used_bytes/minimum", keyword: "minimum", @@ -94744,7 +103383,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid39) { if (void 0 !== data28.cleanup_target_bytes) { let data37 = data28.cleanup_target_bytes, _errs83 = errors; - if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate309.errors = [ { + if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate312.errors = [ { instancePath: instancePath + "/health/cleanup_target_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/cleanup_target_bytes/type", keyword: "type", @@ -94752,7 +103391,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs83 && "number" == typeof data37 && isFinite(data37) && (data37 < 0 || isNaN(data37))) return validate309.errors = [ { + if (errors === _errs83 && "number" == typeof data37 && isFinite(data37) && (data37 < 0 || isNaN(data37))) return validate312.errors = [ { instancePath: instancePath + "/health/cleanup_target_bytes", schemaPath: "#/$defs/StorageTargetHealth/properties/cleanup_target_bytes/minimum", keyword: "minimum", @@ -94766,7 +103405,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (valid40) { if (void 0 !== data28.filesystem_device) { let data38 = data28.filesystem_device, _errs85 = errors; - if ("number" != typeof data38 || data38 % 1 || isNaN(data38) || !isFinite(data38)) return validate309.errors = [ { + if ("number" != typeof data38 || data38 % 1 || isNaN(data38) || !isFinite(data38)) return validate312.errors = [ { instancePath: instancePath + "/health/filesystem_device", schemaPath: "#/$defs/StorageTargetHealth/properties/filesystem_device/type", keyword: "type", @@ -94774,7 +103413,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs85 && "number" == typeof data38 && isFinite(data38) && (data38 < 0 || isNaN(data38))) return validate309.errors = [ { + if (errors === _errs85 && "number" == typeof data38 && isFinite(data38) && (data38 < 0 || isNaN(data38))) return validate312.errors = [ { instancePath: instancePath + "/health/filesystem_device", schemaPath: "#/$defs/StorageTargetHealth/properties/filesystem_device/minimum", keyword: "minimum", @@ -94789,7 +103428,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data28.used_pct) { let data39 = data28.used_pct, _errs87 = errors; if (errors === _errs87) { - if ("number" != typeof data39 || !isFinite(data39)) return validate309.errors = [ { + if ("number" != typeof data39 || !isFinite(data39)) return validate312.errors = [ { instancePath: instancePath + "/health/used_pct", schemaPath: "#/$defs/StorageTargetHealth/properties/used_pct/type", keyword: "type", @@ -94797,7 +103436,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data39 < 0 || isNaN(data39)) return validate309.errors = [ { + if (data39 < 0 || isNaN(data39)) return validate312.errors = [ { instancePath: instancePath + "/health/used_pct", schemaPath: "#/$defs/StorageTargetHealth/properties/used_pct/minimum", keyword: "minimum", @@ -94856,7 +103495,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err6), errors++, validate309.errors = vErrors, !1; + return vErrors.push(err6), errors++, validate312.errors = vErrors, !1; } errors = _errs90, vErrors.length = _errs90; var valid43 = _errs89 === errors; @@ -94907,7 +103546,7 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err10), errors++, validate309.errors = vErrors, !1; + return vErrors.push(err10), errors++, validate312.errors = vErrors, !1; } errors = _errs96, vErrors.length = _errs96; } @@ -94956,16 +103595,16 @@ function validate309(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate309.errors = vErrors, 0 === errors; + return validate312.errors = vErrors, 0 === errors; } -validate309.evaluated = { +validate312.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageTargetCreate = validate312, schema341 = { +var __contract_StorageTargetCreate = validate315, schema345 = { type: "object", properties: { name: { @@ -95040,10 +103679,10 @@ var __contract_StorageTargetCreate = validate312, schema341 = { additionalProperties: !1 }; -function validate312(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate312.evaluated; +function validate315(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate315.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate312.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate315.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -95051,7 +103690,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate312.errors = [ { + if (void 0 === data.name) return validate315.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95059,7 +103698,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.root_path) return validate312.errors = [ { + if (void 0 === data.root_path) return validate315.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95069,7 +103708,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema341.properties, key0)) return validate312.errors = [ { + for (let key0 in data) if (!func7.call(schema345.properties, key0)) return validate315.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -95081,7 +103720,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate312.errors = [ { + if ("string" != typeof data0) return validate315.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -95089,7 +103728,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate312.errors = [ { + if (func1(data0) > 127) return validate315.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -95097,7 +103736,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate312.errors = [ { + if (func1(data0) < 1) return validate315.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -95111,7 +103750,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.root_path) { let _errs4 = 0; - if ("string" != typeof data.root_path) return validate312.errors = [ { + if ("string" != typeof data.root_path) return validate315.errors = [ { instancePath: instancePath + "/root_path", schemaPath: "#/properties/root_path/type", keyword: "type", @@ -95124,7 +103763,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.endpoint) { let _errs6 = 0; - if ("string" != typeof data.endpoint) return validate312.errors = [ { + if ("string" != typeof data.endpoint) return validate315.errors = [ { instancePath: instancePath + "/endpoint", schemaPath: "#/properties/endpoint/type", keyword: "type", @@ -95137,7 +103776,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.region) { let _errs8 = 0; - if ("string" != typeof data.region) return validate312.errors = [ { + if ("string" != typeof data.region) return validate315.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/type", keyword: "type", @@ -95150,7 +103789,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.bucket) { let _errs10 = 0; - if ("string" != typeof data.bucket) return validate312.errors = [ { + if ("string" != typeof data.bucket) return validate315.errors = [ { instancePath: instancePath + "/bucket", schemaPath: "#/properties/bucket/type", keyword: "type", @@ -95163,7 +103802,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.credential_ref) { let _errs12 = 0; - if ("string" != typeof data.credential_ref) return validate312.errors = [ { + if ("string" != typeof data.credential_ref) return validate315.errors = [ { instancePath: instancePath + "/credential_ref", schemaPath: "#/properties/credential_ref/type", keyword: "type", @@ -95176,7 +103815,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.target_type) { let data6 = data.target_type, _errs14 = 0; - if ("string" != typeof data6) return validate312.errors = [ { + if ("string" != typeof data6) return validate315.errors = [ { instancePath: instancePath + "/target_type", schemaPath: "#/properties/target_type/type", keyword: "type", @@ -95184,12 +103823,12 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("filesystem" !== data6 && "s3" !== data6) return validate312.errors = [ { + if ("filesystem" !== data6 && "s3" !== data6) return validate315.errors = [ { instancePath: instancePath + "/target_type", schemaPath: "#/properties/target_type/enum", keyword: "enum", params: { - allowedValues: schema341.properties.target_type.enum + allowedValues: schema345.properties.target_type.enum } } ], !1; var valid6 = 0 === _errs14; @@ -95197,7 +103836,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.enabled) { let _errs16 = 0; - if ("boolean" != typeof data.enabled) return validate312.errors = [ { + if ("boolean" != typeof data.enabled) return validate315.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -95210,7 +103849,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.mount_required) { let _errs18 = 0; - if ("boolean" != typeof data.mount_required) return validate312.errors = [ { + if ("boolean" != typeof data.mount_required) return validate315.errors = [ { instancePath: instancePath + "/mount_required", schemaPath: "#/properties/mount_required/type", keyword: "type", @@ -95223,7 +103862,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.storage_class) { let data9 = data.storage_class, _errs20 = 0; - if ("string" != typeof data9) return validate312.errors = [ { + if ("string" != typeof data9) return validate315.errors = [ { instancePath: instancePath + "/storage_class", schemaPath: "#/properties/storage_class/type", keyword: "type", @@ -95231,12 +103870,12 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("hot" !== data9 && "warm" !== data9 && "cold" !== data9) return validate312.errors = [ { + if ("hot" !== data9 && "warm" !== data9 && "cold" !== data9) return validate315.errors = [ { instancePath: instancePath + "/storage_class", schemaPath: "#/properties/storage_class/enum", keyword: "enum", params: { - allowedValues: schema341.properties.storage_class.enum + allowedValues: schema345.properties.storage_class.enum } } ], !1; var valid9 = 0 === _errs20; @@ -95244,7 +103883,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.archive_budget_bytes) { let data10 = data.archive_budget_bytes, _errs22 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate312.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate315.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/type", keyword: "type", @@ -95253,7 +103892,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs22 && "number" == typeof data10 && isFinite(data10)) { - if (data10 > 9007199254740991 || isNaN(data10)) return validate312.errors = [ { + if (data10 > 9007199254740991 || isNaN(data10)) return validate315.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/maximum", keyword: "maximum", @@ -95262,7 +103901,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data10 < 0 || isNaN(data10)) return validate312.errors = [ { + if (data10 < 0 || isNaN(data10)) return validate315.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/minimum", keyword: "minimum", @@ -95277,7 +103916,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.reserve_bytes) { let data11 = data.reserve_bytes, _errs24 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate312.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate315.errors = [ { instancePath: instancePath + "/reserve_bytes", schemaPath: "#/properties/reserve_bytes/type", keyword: "type", @@ -95285,7 +103924,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs24 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate312.errors = [ { + if (0 === _errs24 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate315.errors = [ { instancePath: instancePath + "/reserve_bytes", schemaPath: "#/properties/reserve_bytes/minimum", keyword: "minimum", @@ -95299,7 +103938,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.migration_bandwidth_bps) { let data12 = data.migration_bandwidth_bps, _errs26 = 0; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate312.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate315.errors = [ { instancePath: instancePath + "/migration_bandwidth_bps", schemaPath: "#/properties/migration_bandwidth_bps/type", keyword: "type", @@ -95307,7 +103946,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs26 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate312.errors = [ { + if (0 === _errs26 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate315.errors = [ { instancePath: instancePath + "/migration_bandwidth_bps", schemaPath: "#/properties/migration_bandwidth_bps/minimum", keyword: "minimum", @@ -95322,7 +103961,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.high_watermark_pct) { let data13 = data.high_watermark_pct, _errs28 = 0; if (0 === _errs28) { - if ("number" != typeof data13 || !isFinite(data13)) return validate312.errors = [ { + if ("number" != typeof data13 || !isFinite(data13)) return validate315.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/type", keyword: "type", @@ -95330,7 +103969,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data13 >= 100 || isNaN(data13)) return validate312.errors = [ { + if (data13 >= 100 || isNaN(data13)) return validate315.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/exclusiveMaximum", keyword: "exclusiveMaximum", @@ -95339,7 +103978,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD limit: 100 } } ], !1; - if (data13 <= 0 || isNaN(data13)) return validate312.errors = [ { + if (data13 <= 0 || isNaN(data13)) return validate315.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -95355,7 +103994,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.low_watermark_pct) { let data14 = data.low_watermark_pct, _errs30 = 0; if (0 === _errs30) { - if ("number" != typeof data14 || !isFinite(data14)) return validate312.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate315.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/type", keyword: "type", @@ -95363,7 +104002,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate312.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate315.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/minimum", keyword: "minimum", @@ -95372,7 +104011,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD limit: 0 } } ], !1; - if (data14 >= 100 || isNaN(data14)) return validate312.errors = [ { + if (data14 >= 100 || isNaN(data14)) return validate315.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/exclusiveMaximum", keyword: "exclusiveMaximum", @@ -95387,7 +104026,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data.archival_window_start_minute) { let data15 = data.archival_window_start_minute, _errs32 = 0; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate312.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate315.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/type", keyword: "type", @@ -95396,7 +104035,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs32 && "number" == typeof data15 && isFinite(data15)) { - if (data15 > 1439 || isNaN(data15)) return validate312.errors = [ { + if (data15 > 1439 || isNaN(data15)) return validate315.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/maximum", keyword: "maximum", @@ -95405,7 +104044,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data15 < 0 || isNaN(data15)) return validate312.errors = [ { + if (data15 < 0 || isNaN(data15)) return validate315.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/minimum", keyword: "minimum", @@ -95419,7 +104058,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD } else valid15 = !0; if (valid15 && void 0 !== data.archival_window_end_minute) { let data16 = data.archival_window_end_minute, _errs34 = 0; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate312.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate315.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/type", keyword: "type", @@ -95428,7 +104067,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs34 && "number" == typeof data16 && isFinite(data16)) { - if (data16 > 1439 || isNaN(data16)) return validate312.errors = [ { + if (data16 > 1439 || isNaN(data16)) return validate315.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/maximum", keyword: "maximum", @@ -95437,7 +104076,7 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data16 < 0 || isNaN(data16)) return validate312.errors = [ { + if (data16 < 0 || isNaN(data16)) return validate315.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/minimum", keyword: "minimum", @@ -95465,22 +104104,22 @@ function validate312(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate312.errors = [], !0; + return validate315.errors = [], !0; } -validate312.evaluated = { +validate315.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageTargetHealth = validate308; +var __contract_StorageTargetHealth = validate311; -function validate308(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate308.evaluated; +function validate311(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate311.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate308.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -95488,7 +104127,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.status) return validate308.errors = [ { + if (void 0 === data.status) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95496,7 +104135,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status" } } ], !1; - if (void 0 === data.pressure) return validate308.errors = [ { + if (void 0 === data.pressure) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95504,7 +104143,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "pressure" } } ], !1; - if (void 0 === data.last_error) return validate308.errors = [ { + if (void 0 === data.last_error) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95512,7 +104151,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_error" } } ], !1; - if (void 0 === data.capacity_known) return validate308.errors = [ { + if (void 0 === data.capacity_known) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95520,7 +104159,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capacity_known" } } ], !1; - if (void 0 === data.duplicate_filesystem) return validate308.errors = [ { + if (void 0 === data.duplicate_filesystem) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95528,7 +104167,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "duplicate_filesystem" } } ], !1; - if (void 0 === data.capacity_bytes) return validate308.errors = [ { + if (void 0 === data.capacity_bytes) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95536,7 +104175,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capacity_bytes" } } ], !1; - if (void 0 === data.available_bytes) return validate308.errors = [ { + if (void 0 === data.available_bytes) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95544,7 +104183,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "available_bytes" } } ], !1; - if (void 0 === data.used_bytes) return validate308.errors = [ { + if (void 0 === data.used_bytes) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95552,7 +104191,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "used_bytes" } } ], !1; - if (void 0 === data.cleanup_target_bytes) return validate308.errors = [ { + if (void 0 === data.cleanup_target_bytes) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95560,7 +104199,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "cleanup_target_bytes" } } ], !1; - if (void 0 === data.filesystem_device) return validate308.errors = [ { + if (void 0 === data.filesystem_device) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95568,7 +104207,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "filesystem_device" } } ], !1; - if (void 0 === data.used_pct) return validate308.errors = [ { + if (void 0 === data.used_pct) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95576,7 +104215,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "used_pct" } } ], !1; - if (void 0 === data.last_probe_at) return validate308.errors = [ { + if (void 0 === data.last_probe_at) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95584,7 +104223,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_probe_at" } } ], !1; - if (void 0 === data.last_success_at) return validate308.errors = [ { + if (void 0 === data.last_success_at) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -95594,7 +104233,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if (!func5.call(schema336.properties, key0)) return validate308.errors = [ { + for (let key0 in data) if (!func7.call(schema340.properties, key0)) return validate311.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -95605,7 +104244,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (_errs1 === errors) { if (void 0 !== data.status) { let _errs2 = errors; - if ("string" != typeof data.status) return validate308.errors = [ { + if ("string" != typeof data.status) return validate311.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -95618,7 +104257,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.pressure) { let _errs4 = errors; - if ("string" != typeof data.pressure) return validate308.errors = [ { + if ("string" != typeof data.pressure) return validate311.errors = [ { instancePath: instancePath + "/pressure", schemaPath: "#/properties/pressure/type", keyword: "type", @@ -95631,7 +104270,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.last_error) { let _errs6 = errors; - if ("string" != typeof data.last_error) return validate308.errors = [ { + if ("string" != typeof data.last_error) return validate311.errors = [ { instancePath: instancePath + "/last_error", schemaPath: "#/properties/last_error/type", keyword: "type", @@ -95644,7 +104283,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.capacity_known) { let _errs8 = errors; - if ("boolean" != typeof data.capacity_known) return validate308.errors = [ { + if ("boolean" != typeof data.capacity_known) return validate311.errors = [ { instancePath: instancePath + "/capacity_known", schemaPath: "#/properties/capacity_known/type", keyword: "type", @@ -95657,7 +104296,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.duplicate_filesystem) { let _errs10 = errors; - if ("boolean" != typeof data.duplicate_filesystem) return validate308.errors = [ { + if ("boolean" != typeof data.duplicate_filesystem) return validate311.errors = [ { instancePath: instancePath + "/duplicate_filesystem", schemaPath: "#/properties/duplicate_filesystem/type", keyword: "type", @@ -95670,7 +104309,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.capacity_bytes) { let data5 = data.capacity_bytes, _errs12 = errors; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate308.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate311.errors = [ { instancePath: instancePath + "/capacity_bytes", schemaPath: "#/properties/capacity_bytes/type", keyword: "type", @@ -95678,7 +104317,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate308.errors = [ { + if (errors === _errs12 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate311.errors = [ { instancePath: instancePath + "/capacity_bytes", schemaPath: "#/properties/capacity_bytes/minimum", keyword: "minimum", @@ -95692,7 +104331,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.available_bytes) { let data6 = data.available_bytes, _errs14 = errors; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate308.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate311.errors = [ { instancePath: instancePath + "/available_bytes", schemaPath: "#/properties/available_bytes/type", keyword: "type", @@ -95700,7 +104339,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate308.errors = [ { + if (errors === _errs14 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate311.errors = [ { instancePath: instancePath + "/available_bytes", schemaPath: "#/properties/available_bytes/minimum", keyword: "minimum", @@ -95714,7 +104353,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.used_bytes) { let data7 = data.used_bytes, _errs16 = errors; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate308.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate311.errors = [ { instancePath: instancePath + "/used_bytes", schemaPath: "#/properties/used_bytes/type", keyword: "type", @@ -95722,7 +104361,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs16 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate308.errors = [ { + if (errors === _errs16 && "number" == typeof data7 && isFinite(data7) && (data7 < 0 || isNaN(data7))) return validate311.errors = [ { instancePath: instancePath + "/used_bytes", schemaPath: "#/properties/used_bytes/minimum", keyword: "minimum", @@ -95736,7 +104375,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.cleanup_target_bytes) { let data8 = data.cleanup_target_bytes, _errs18 = errors; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate308.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate311.errors = [ { instancePath: instancePath + "/cleanup_target_bytes", schemaPath: "#/properties/cleanup_target_bytes/type", keyword: "type", @@ -95744,7 +104383,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs18 && "number" == typeof data8 && isFinite(data8) && (data8 < 0 || isNaN(data8))) return validate308.errors = [ { + if (errors === _errs18 && "number" == typeof data8 && isFinite(data8) && (data8 < 0 || isNaN(data8))) return validate311.errors = [ { instancePath: instancePath + "/cleanup_target_bytes", schemaPath: "#/properties/cleanup_target_bytes/minimum", keyword: "minimum", @@ -95758,7 +104397,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.filesystem_device) { let data9 = data.filesystem_device, _errs20 = errors; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate308.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate311.errors = [ { instancePath: instancePath + "/filesystem_device", schemaPath: "#/properties/filesystem_device/type", keyword: "type", @@ -95766,7 +104405,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs20 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate308.errors = [ { + if (errors === _errs20 && "number" == typeof data9 && isFinite(data9) && (data9 < 0 || isNaN(data9))) return validate311.errors = [ { instancePath: instancePath + "/filesystem_device", schemaPath: "#/properties/filesystem_device/minimum", keyword: "minimum", @@ -95781,7 +104420,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.used_pct) { let data10 = data.used_pct, _errs22 = errors; if (errors === _errs22) { - if ("number" != typeof data10 || !isFinite(data10)) return validate308.errors = [ { + if ("number" != typeof data10 || !isFinite(data10)) return validate311.errors = [ { instancePath: instancePath + "/used_pct", schemaPath: "#/properties/used_pct/type", keyword: "type", @@ -95789,7 +104428,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data10 < 0 || isNaN(data10)) return validate308.errors = [ { + if (data10 < 0 || isNaN(data10)) return validate311.errors = [ { instancePath: instancePath + "/used_pct", schemaPath: "#/properties/used_pct/minimum", keyword: "minimum", @@ -95848,7 +104487,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err3), errors++, validate308.errors = vErrors, !1; + return vErrors.push(err3), errors++, validate311.errors = vErrors, !1; } errors = _errs25, vErrors.length = _errs25; var valid11 = _errs24 === errors; @@ -95899,7 +104538,7 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err7), errors++, validate308.errors = vErrors, !1; + return vErrors.push(err7), errors++, validate311.errors = vErrors, !1; } errors = _errs31, vErrors.length = _errs31; } @@ -95917,16 +104556,16 @@ function validate308(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate308.errors = vErrors, 0 === errors; + return validate311.errors = vErrors, 0 === errors; } -validate308.evaluated = { +validate311.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageTargetUpdate = validate313, schema342 = { +var __contract_StorageTargetUpdate = validate316, schema346 = { type: "object", properties: { name: { @@ -96006,10 +104645,10 @@ var __contract_StorageTargetUpdate = validate313, schema342 = { additionalProperties: !1 }; -function validate313(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate313.evaluated; +function validate316(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate316.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate313.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate316.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -96017,7 +104656,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.revision) return validate313.errors = [ { + if (void 0 === data.revision) return validate316.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -96027,7 +104666,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = 0; - for (let key0 in data) if (!func5.call(schema342.properties, key0)) return validate313.errors = [ { + for (let key0 in data) if (!func7.call(schema346.properties, key0)) return validate316.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -96039,7 +104678,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data0 = data.name, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate313.errors = [ { + if ("string" != typeof data0) return validate316.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -96047,7 +104686,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 127) return validate313.errors = [ { + if (func1(data0) > 127) return validate316.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -96055,7 +104694,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 127 } } ], !1; - if (func1(data0) < 1) return validate313.errors = [ { + if (func1(data0) < 1) return validate316.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -96069,7 +104708,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.root_path) { let _errs4 = 0; - if ("string" != typeof data.root_path) return validate313.errors = [ { + if ("string" != typeof data.root_path) return validate316.errors = [ { instancePath: instancePath + "/root_path", schemaPath: "#/properties/root_path/type", keyword: "type", @@ -96082,7 +104721,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.endpoint) { let _errs6 = 0; - if ("string" != typeof data.endpoint) return validate313.errors = [ { + if ("string" != typeof data.endpoint) return validate316.errors = [ { instancePath: instancePath + "/endpoint", schemaPath: "#/properties/endpoint/type", keyword: "type", @@ -96095,7 +104734,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.region) { let _errs8 = 0; - if ("string" != typeof data.region) return validate313.errors = [ { + if ("string" != typeof data.region) return validate316.errors = [ { instancePath: instancePath + "/region", schemaPath: "#/properties/region/type", keyword: "type", @@ -96108,7 +104747,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.bucket) { let _errs10 = 0; - if ("string" != typeof data.bucket) return validate313.errors = [ { + if ("string" != typeof data.bucket) return validate316.errors = [ { instancePath: instancePath + "/bucket", schemaPath: "#/properties/bucket/type", keyword: "type", @@ -96121,7 +104760,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.credential_ref) { let _errs12 = 0; - if ("string" != typeof data.credential_ref) return validate313.errors = [ { + if ("string" != typeof data.credential_ref) return validate316.errors = [ { instancePath: instancePath + "/credential_ref", schemaPath: "#/properties/credential_ref/type", keyword: "type", @@ -96134,7 +104773,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.target_type) { let data6 = data.target_type, _errs14 = 0; - if ("string" != typeof data6) return validate313.errors = [ { + if ("string" != typeof data6) return validate316.errors = [ { instancePath: instancePath + "/target_type", schemaPath: "#/properties/target_type/type", keyword: "type", @@ -96142,12 +104781,12 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("filesystem" !== data6 && "s3" !== data6) return validate313.errors = [ { + if ("filesystem" !== data6 && "s3" !== data6) return validate316.errors = [ { instancePath: instancePath + "/target_type", schemaPath: "#/properties/target_type/enum", keyword: "enum", params: { - allowedValues: schema342.properties.target_type.enum + allowedValues: schema346.properties.target_type.enum } } ], !1; var valid6 = 0 === _errs14; @@ -96155,7 +104794,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.enabled) { let _errs16 = 0; - if ("boolean" != typeof data.enabled) return validate313.errors = [ { + if ("boolean" != typeof data.enabled) return validate316.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -96168,7 +104807,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.mount_required) { let _errs18 = 0; - if ("boolean" != typeof data.mount_required) return validate313.errors = [ { + if ("boolean" != typeof data.mount_required) return validate316.errors = [ { instancePath: instancePath + "/mount_required", schemaPath: "#/properties/mount_required/type", keyword: "type", @@ -96181,7 +104820,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.storage_class) { let data9 = data.storage_class, _errs20 = 0; - if ("string" != typeof data9) return validate313.errors = [ { + if ("string" != typeof data9) return validate316.errors = [ { instancePath: instancePath + "/storage_class", schemaPath: "#/properties/storage_class/type", keyword: "type", @@ -96189,12 +104828,12 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("hot" !== data9 && "warm" !== data9 && "cold" !== data9) return validate313.errors = [ { + if ("hot" !== data9 && "warm" !== data9 && "cold" !== data9) return validate316.errors = [ { instancePath: instancePath + "/storage_class", schemaPath: "#/properties/storage_class/enum", keyword: "enum", params: { - allowedValues: schema342.properties.storage_class.enum + allowedValues: schema346.properties.storage_class.enum } } ], !1; var valid9 = 0 === _errs20; @@ -96202,7 +104841,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.archive_budget_bytes) { let data10 = data.archive_budget_bytes, _errs22 = 0; - if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate313.errors = [ { + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate316.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/type", keyword: "type", @@ -96211,7 +104850,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs22 && "number" == typeof data10 && isFinite(data10)) { - if (data10 > 9007199254740991 || isNaN(data10)) return validate313.errors = [ { + if (data10 > 9007199254740991 || isNaN(data10)) return validate316.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/maximum", keyword: "maximum", @@ -96220,7 +104859,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data10 < 0 || isNaN(data10)) return validate313.errors = [ { + if (data10 < 0 || isNaN(data10)) return validate316.errors = [ { instancePath: instancePath + "/archive_budget_bytes", schemaPath: "#/properties/archive_budget_bytes/minimum", keyword: "minimum", @@ -96235,7 +104874,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.reserve_bytes) { let data11 = data.reserve_bytes, _errs24 = 0; - if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate313.errors = [ { + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate316.errors = [ { instancePath: instancePath + "/reserve_bytes", schemaPath: "#/properties/reserve_bytes/type", keyword: "type", @@ -96243,7 +104882,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs24 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate313.errors = [ { + if (0 === _errs24 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate316.errors = [ { instancePath: instancePath + "/reserve_bytes", schemaPath: "#/properties/reserve_bytes/minimum", keyword: "minimum", @@ -96257,7 +104896,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.migration_bandwidth_bps) { let data12 = data.migration_bandwidth_bps, _errs26 = 0; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate313.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate316.errors = [ { instancePath: instancePath + "/migration_bandwidth_bps", schemaPath: "#/properties/migration_bandwidth_bps/type", keyword: "type", @@ -96265,7 +104904,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (0 === _errs26 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate313.errors = [ { + if (0 === _errs26 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate316.errors = [ { instancePath: instancePath + "/migration_bandwidth_bps", schemaPath: "#/properties/migration_bandwidth_bps/minimum", keyword: "minimum", @@ -96280,7 +104919,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.high_watermark_pct) { let data13 = data.high_watermark_pct, _errs28 = 0; if (0 === _errs28) { - if ("number" != typeof data13 || !isFinite(data13)) return validate313.errors = [ { + if ("number" != typeof data13 || !isFinite(data13)) return validate316.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/type", keyword: "type", @@ -96288,7 +104927,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data13 >= 100 || isNaN(data13)) return validate313.errors = [ { + if (data13 >= 100 || isNaN(data13)) return validate316.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/exclusiveMaximum", keyword: "exclusiveMaximum", @@ -96297,7 +104936,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 100 } } ], !1; - if (data13 <= 0 || isNaN(data13)) return validate313.errors = [ { + if (data13 <= 0 || isNaN(data13)) return validate316.errors = [ { instancePath: instancePath + "/high_watermark_pct", schemaPath: "#/properties/high_watermark_pct/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -96313,7 +104952,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.low_watermark_pct) { let data14 = data.low_watermark_pct, _errs30 = 0; if (0 === _errs30) { - if ("number" != typeof data14 || !isFinite(data14)) return validate313.errors = [ { + if ("number" != typeof data14 || !isFinite(data14)) return validate316.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/type", keyword: "type", @@ -96321,7 +104960,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data14 < 0 || isNaN(data14)) return validate313.errors = [ { + if (data14 < 0 || isNaN(data14)) return validate316.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/minimum", keyword: "minimum", @@ -96330,7 +104969,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 0 } } ], !1; - if (data14 >= 100 || isNaN(data14)) return validate313.errors = [ { + if (data14 >= 100 || isNaN(data14)) return validate316.errors = [ { instancePath: instancePath + "/low_watermark_pct", schemaPath: "#/properties/low_watermark_pct/exclusiveMaximum", keyword: "exclusiveMaximum", @@ -96345,7 +104984,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data.archival_window_start_minute) { let data15 = data.archival_window_start_minute, _errs32 = 0; - if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate313.errors = [ { + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate316.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/type", keyword: "type", @@ -96354,7 +104993,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs32 && "number" == typeof data15 && isFinite(data15)) { - if (data15 > 1439 || isNaN(data15)) return validate313.errors = [ { + if (data15 > 1439 || isNaN(data15)) return validate316.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/maximum", keyword: "maximum", @@ -96363,7 +105002,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data15 < 0 || isNaN(data15)) return validate313.errors = [ { + if (data15 < 0 || isNaN(data15)) return validate316.errors = [ { instancePath: instancePath + "/archival_window_start_minute", schemaPath: "#/properties/archival_window_start_minute/minimum", keyword: "minimum", @@ -96378,7 +105017,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data.archival_window_end_minute) { let data16 = data.archival_window_end_minute, _errs34 = 0; - if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate313.errors = [ { + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate316.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/type", keyword: "type", @@ -96387,7 +105026,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs34 && "number" == typeof data16 && isFinite(data16)) { - if (data16 > 1439 || isNaN(data16)) return validate313.errors = [ { + if (data16 > 1439 || isNaN(data16)) return validate316.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/maximum", keyword: "maximum", @@ -96396,7 +105035,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 1439 } } ], !1; - if (data16 < 0 || isNaN(data16)) return validate313.errors = [ { + if (data16 < 0 || isNaN(data16)) return validate316.errors = [ { instancePath: instancePath + "/archival_window_end_minute", schemaPath: "#/properties/archival_window_end_minute/minimum", keyword: "minimum", @@ -96410,7 +105049,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } else valid16 = !0; if (valid16 && void 0 !== data.revision) { let data17 = data.revision, _errs36 = 0; - if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate313.errors = [ { + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate316.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/type", keyword: "type", @@ -96419,7 +105058,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs36 && "number" == typeof data17 && isFinite(data17)) { - if (data17 > 9007199254740991 || isNaN(data17)) return validate313.errors = [ { + if (data17 > 9007199254740991 || isNaN(data17)) return validate316.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/maximum", keyword: "maximum", @@ -96428,7 +105067,7 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data17 < 1 || isNaN(data17)) return validate313.errors = [ { + if (data17 < 1 || isNaN(data17)) return validate316.errors = [ { instancePath: instancePath + "/revision", schemaPath: "#/properties/revision/minimum", keyword: "minimum", @@ -96457,22 +105096,22 @@ function validate313(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate313.errors = [], !0; + return validate316.errors = [], !0; } -validate313.evaluated = { +validate316.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StorageTargetsResponse = validate310; +var __contract_StorageTargetsResponse = validate313; -function validate310(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate310.evaluated; +function validate313(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate313.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate310.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate313.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -96480,7 +105119,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.targets) return validate310.errors = [ { + if (void 0 === data.targets) return validate313.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -96488,7 +105127,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "targets" } } ], !1; - if (void 0 === data.count) return validate310.errors = [ { + if (void 0 === data.count) return validate313.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -96498,7 +105137,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs1 = errors; - for (let key0 in data) if ("targets" !== key0 && "count" !== key0) return validate310.errors = [ { + for (let key0 in data) if ("targets" !== key0 && "count" !== key0) return validate313.errors = [ { instancePath: instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", @@ -96510,7 +105149,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.targets) { let data0 = data.targets, _errs2 = errors; if (errors === _errs2) { - if (!Array.isArray(data0)) return validate310.errors = [ { + if (!Array.isArray(data0)) return validate313.errors = [ { instancePath: instancePath + "/targets", schemaPath: "#/properties/targets/type", keyword: "type", @@ -96518,7 +105157,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data0.length > 128) return validate310.errors = [ { + if (data0.length > 128) return validate313.errors = [ { instancePath: instancePath + "/targets", schemaPath: "#/properties/targets/maxItems", keyword: "maxItems", @@ -96530,13 +105169,13 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let _errs4 = errors; - if (validate309(data0[i0], { + if (validate312(data0[i0], { instancePath: instancePath + "/targets/" + i0, parentData: data0, parentDataProperty: i0, rootData: rootData, dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate309.errors), errors = vErrors.length), _errs4 !== errors) break; + }) || (vErrors = vErrors.concat(validate312.errors), errors = vErrors.length), _errs4 !== errors) break; } } } @@ -96544,7 +105183,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD } else valid0 = !0; if (valid0 && void 0 !== data.count) { let data2 = data.count, _errs5 = errors; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate310.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate313.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -96552,7 +105191,7 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD type: "integer" } } ], !1; - if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate310.errors = [ { + if (errors === _errs5 && "number" == typeof data2 && isFinite(data2) && (data2 < 0 || isNaN(data2))) return validate313.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/minimum", keyword: "minimum", @@ -96565,10 +105204,10 @@ function validate310(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate310.errors = vErrors, 0 === errors; + return validate313.errors = vErrors, 0 === errors; } -validate310.evaluated = { +validate313.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 @@ -97766,14 +106405,123 @@ function validate24(data, {instancePath: instancePath = "", parentData: parentDa } ], !1; var valid56 = 0 === _errs111; } else valid56 = !0; - if (valid56 && void 0 !== data.error_message && "string" != typeof data.error_message) return validate24.errors = [ { - instancePath: instancePath + "/error_message", - schemaPath: "#/properties/error_message/type", - keyword: "type", - params: { - type: "string" + if (valid56) { + if (void 0 !== data.error_message) { + let _errs114 = 0; + if ("string" != typeof data.error_message) return validate24.errors = [ { + instancePath: instancePath + "/error_message", + schemaPath: "#/properties/error_message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid58 = 0 === _errs114; + } else valid58 = !0; + if (valid58) { + if (void 0 !== data.observation_evidence_enabled) { + let _errs116 = 0; + if ("boolean" != typeof data.observation_evidence_enabled) return validate24.errors = [ { + instancePath: instancePath + "/observation_evidence_enabled", + schemaPath: "#/properties/observation_evidence_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid59 = 0 === _errs116; + } else valid59 = !0; + if (valid59) { + if (void 0 !== data.observation_labels) { + let data56 = data.observation_labels, _errs118 = 0; + if (0 === _errs118) { + if ("string" != typeof data56) return validate24.errors = [ { + instancePath: instancePath + "/observation_labels", + schemaPath: "#/properties/observation_labels/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data56) > 511) return validate24.errors = [ { + instancePath: instancePath + "/observation_labels", + schemaPath: "#/properties/observation_labels/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + } ], !1; + } + var valid60 = 0 === _errs118; + } else valid60 = !0; + if (valid60) { + if (void 0 !== data.observation_max_age_days) { + let data57 = data.observation_max_age_days, _errs120 = 0; + if ("number" != typeof data57 || data57 % 1 || isNaN(data57) || !isFinite(data57)) return validate24.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs120 && "number" == typeof data57 && isFinite(data57)) { + if (data57 > 90 || isNaN(data57)) return validate24.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 90 + } + } ], !1; + if (data57 < 1 || isNaN(data57)) return validate24.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid61 = 0 === _errs120; + } else valid61 = !0; + if (valid61 && void 0 !== data.observation_max_bytes) { + let data58 = data.observation_max_bytes, _errs122 = 0; + if ("number" != typeof data58 || data58 % 1 || isNaN(data58) || !isFinite(data58)) return validate24.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (0 === _errs122 && "number" == typeof data58 && isFinite(data58)) { + if (data58 > 1099511627776 || isNaN(data58)) return validate24.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1099511627776 + } + } ], !1; + if (data58 < 67108864 || isNaN(data58)) return validate24.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 67108864 + } + } ], !1; + } + } + } + } } - } ], !1; + } } } } @@ -98355,7 +107103,7 @@ validate24.evaluated = { dynamicItems: !1 }; -var __contract_StreamCreateRequest = validate442, schema531 = { +var __contract_StreamCreateRequest = validate452, schema543 = { type: "object", properties: { url: { @@ -98579,6 +107327,27 @@ var __contract_StreamCreateRequest = validate442, schema531 = { set_privacy_mode: { type: "null", description: "A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form." + }, + observation_evidence_enabled: { + type: "boolean", + description: "INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + observation_labels: { + type: "string", + maxLength: 511, + description: "Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + observation_max_age_days: { + type: "integer", + minimum: 1, + maximum: 90, + description: "Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + observation_max_bytes: { + type: "integer", + minimum: 67108864, + maximum: 1099511627776, + description: "Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." } }, required: [ "name", "url" ], @@ -98595,10 +107364,10 @@ var __contract_StreamCreateRequest = validate442, schema531 = { } } } -}, pattern189 = new RegExp("^[A-Za-z0-9_.-]+$", "u"); +}, pattern196 = new RegExp("^[A-Za-z0-9_.-]+$", "u"); -function validate442(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate442.evaluated; +function validate452(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate452.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs1 = errors, _errs2 = errors; if (errors === _errs2) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.enable_disabled) { @@ -98624,14 +107393,14 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD let err4 = {}; vErrors.push(err4), errors++; } - if (_errs2 === errors) return validate442.errors = [ { + if (_errs2 === errors) return validate452.errors = [ { instancePath: instancePath, schemaPath: "#/not", keyword: "not", params: {} } ], !1; if (errors = _errs1, vErrors.length = _errs1, 0 === errors) { - if (!data || "object" != typeof data || Array.isArray(data)) return validate442.errors = [ { + if (!data || "object" != typeof data || Array.isArray(data)) return validate452.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -98639,7 +107408,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.name) return validate442.errors = [ { + if (void 0 === data.name) return validate452.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -98647,7 +107416,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data.url) return validate442.errors = [ { + if (void 0 === data.url) return validate452.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -98658,7 +107427,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.url) { let data2 = data.url, _errs7 = errors; if (errors === _errs7) { - if ("string" != typeof data2) return validate442.errors = [ { + if ("string" != typeof data2) return validate452.errors = [ { instancePath: instancePath + "/url", schemaPath: "#/properties/url/type", keyword: "type", @@ -98666,7 +107435,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) < 1) return validate442.errors = [ { + if (func1(data2) < 1) return validate452.errors = [ { instancePath: instancePath + "/url", schemaPath: "#/properties/url/minLength", keyword: "minLength", @@ -98680,12 +107449,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.detection_model) { let data3 = data.detection_model, _errs9 = errors; - if ("string" != typeof data3 && null !== data3) return validate442.errors = [ { + if ("string" != typeof data3 && null !== data3) return validate452.errors = [ { instancePath: instancePath + "/detection_model", schemaPath: "#/properties/detection_model/type", keyword: "type", params: { - type: schema531.properties.detection_model.type + type: schema543.properties.detection_model.type } } ], !1; var valid4 = _errs9 === errors; @@ -98693,12 +107462,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.detection_object_filter) { let data4 = data.detection_object_filter, _errs11 = errors; - if ("string" != typeof data4 && null !== data4) return validate442.errors = [ { + if ("string" != typeof data4 && null !== data4) return validate452.errors = [ { instancePath: instancePath + "/detection_object_filter", schemaPath: "#/properties/detection_object_filter/type", keyword: "type", params: { - type: schema531.properties.detection_object_filter.type + type: schema543.properties.detection_object_filter.type } } ], !1; var valid5 = _errs11 === errors; @@ -98706,12 +107475,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.detection_object_filter_list) { let data5 = data.detection_object_filter_list, _errs13 = errors; - if ("string" != typeof data5 && null !== data5) return validate442.errors = [ { + if ("string" != typeof data5 && null !== data5) return validate452.errors = [ { instancePath: instancePath + "/detection_object_filter_list", schemaPath: "#/properties/detection_object_filter_list/type", keyword: "type", params: { - type: schema531.properties.detection_object_filter_list.type + type: schema543.properties.detection_object_filter_list.type } } ], !1; var valid6 = _errs13 === errors; @@ -98719,12 +107488,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.codec) { let data6 = data.codec, _errs15 = errors; - if ("string" != typeof data6 && null !== data6) return validate442.errors = [ { + if ("string" != typeof data6 && null !== data6) return validate452.errors = [ { instancePath: instancePath + "/codec", schemaPath: "#/properties/codec/type", keyword: "type", params: { - type: schema531.properties.codec.type + type: schema543.properties.codec.type } } ], !1; var valid7 = _errs15 === errors; @@ -98732,12 +107501,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data.onvif_username) { let data7 = data.onvif_username, _errs17 = errors; - if ("string" != typeof data7 && null !== data7) return validate442.errors = [ { + if ("string" != typeof data7 && null !== data7) return validate452.errors = [ { instancePath: instancePath + "/onvif_username", schemaPath: "#/properties/onvif_username/type", keyword: "type", params: { - type: schema531.properties.onvif_username.type + type: schema543.properties.onvif_username.type } } ], !1; var valid8 = _errs17 === errors; @@ -98745,12 +107514,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data.onvif_password) { let data8 = data.onvif_password, _errs19 = errors; - if ("string" != typeof data8 && null !== data8) return validate442.errors = [ { + if ("string" != typeof data8 && null !== data8) return validate452.errors = [ { instancePath: instancePath + "/onvif_password", schemaPath: "#/properties/onvif_password/type", keyword: "type", params: { - type: schema531.properties.onvif_password.type + type: schema543.properties.onvif_password.type } } ], !1; var valid9 = _errs19 === errors; @@ -98758,12 +107527,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data.onvif_profile) { let data9 = data.onvif_profile, _errs21 = errors; - if ("string" != typeof data9 && null !== data9) return validate442.errors = [ { + if ("string" != typeof data9 && null !== data9) return validate452.errors = [ { instancePath: instancePath + "/onvif_profile", schemaPath: "#/properties/onvif_profile/type", keyword: "type", params: { - type: schema531.properties.onvif_profile.type + type: schema543.properties.onvif_profile.type } } ], !1; var valid10 = _errs21 === errors; @@ -98771,12 +107540,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data.enabled) { let data10 = data.enabled, _errs23 = errors; - if ("boolean" != typeof data10 && null !== data10) return validate442.errors = [ { + if ("boolean" != typeof data10 && null !== data10) return validate452.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", params: { - type: schema531.properties.enabled.type + type: schema543.properties.enabled.type } } ], !1; var valid11 = _errs23 === errors; @@ -98784,12 +107553,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid11) { if (void 0 !== data.streaming_enabled) { let data11 = data.streaming_enabled, _errs25 = errors; - if ("boolean" != typeof data11 && null !== data11) return validate442.errors = [ { + if ("boolean" != typeof data11 && null !== data11) return validate452.errors = [ { instancePath: instancePath + "/streaming_enabled", schemaPath: "#/properties/streaming_enabled/type", keyword: "type", params: { - type: schema531.properties.streaming_enabled.type + type: schema543.properties.streaming_enabled.type } } ], !1; var valid12 = _errs25 === errors; @@ -98797,12 +107566,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid12) { if (void 0 !== data.record) { let data12 = data.record, _errs27 = errors; - if ("boolean" != typeof data12 && null !== data12) return validate442.errors = [ { + if ("boolean" != typeof data12 && null !== data12) return validate452.errors = [ { instancePath: instancePath + "/record", schemaPath: "#/properties/record/type", keyword: "type", params: { - type: schema531.properties.record.type + type: schema543.properties.record.type } } ], !1; var valid13 = _errs27 === errors; @@ -98810,12 +107579,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data.detection_based_recording) { let data13 = data.detection_based_recording, _errs29 = errors; - if ("boolean" != typeof data13 && null !== data13) return validate442.errors = [ { + if ("boolean" != typeof data13 && null !== data13) return validate452.errors = [ { instancePath: instancePath + "/detection_based_recording", schemaPath: "#/properties/detection_based_recording/type", keyword: "type", params: { - type: schema531.properties.detection_based_recording.type + type: schema543.properties.detection_based_recording.type } } ], !1; var valid14 = _errs29 === errors; @@ -98823,12 +107592,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid14) { if (void 0 !== data.record_audio) { let data14 = data.record_audio, _errs31 = errors; - if ("boolean" != typeof data14 && null !== data14) return validate442.errors = [ { + if ("boolean" != typeof data14 && null !== data14) return validate452.errors = [ { instancePath: instancePath + "/record_audio", schemaPath: "#/properties/record_audio/type", keyword: "type", params: { - type: schema531.properties.record_audio.type + type: schema543.properties.record_audio.type } } ], !1; var valid15 = _errs31 === errors; @@ -98836,12 +107605,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid15) { if (void 0 !== data.audio_voice_enhancement) { let data15 = data.audio_voice_enhancement, _errs33 = errors; - if ("boolean" != typeof data15 && null !== data15) return validate442.errors = [ { + if ("boolean" != typeof data15 && null !== data15) return validate452.errors = [ { instancePath: instancePath + "/audio_voice_enhancement", schemaPath: "#/properties/audio_voice_enhancement/type", keyword: "type", params: { - type: schema531.properties.audio_voice_enhancement.type + type: schema543.properties.audio_voice_enhancement.type } } ], !1; var valid16 = _errs33 === errors; @@ -98849,12 +107618,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data.backchannel_enabled) { let data16 = data.backchannel_enabled, _errs35 = errors; - if ("boolean" != typeof data16 && null !== data16) return validate442.errors = [ { + if ("boolean" != typeof data16 && null !== data16) return validate452.errors = [ { instancePath: instancePath + "/backchannel_enabled", schemaPath: "#/properties/backchannel_enabled/type", keyword: "type", params: { - type: schema531.properties.backchannel_enabled.type + type: schema543.properties.backchannel_enabled.type } } ], !1; var valid17 = _errs35 === errors; @@ -98862,12 +107631,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data.ptz_enabled) { let data17 = data.ptz_enabled, _errs37 = errors; - if ("boolean" != typeof data17 && null !== data17) return validate442.errors = [ { + if ("boolean" != typeof data17 && null !== data17) return validate452.errors = [ { instancePath: instancePath + "/ptz_enabled", schemaPath: "#/properties/ptz_enabled/type", keyword: "type", params: { - type: schema531.properties.ptz_enabled.type + type: schema543.properties.ptz_enabled.type } } ], !1; var valid18 = _errs37 === errors; @@ -98875,12 +107644,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data.ptz_has_home) { let data18 = data.ptz_has_home, _errs39 = errors; - if ("boolean" != typeof data18 && null !== data18) return validate442.errors = [ { + if ("boolean" != typeof data18 && null !== data18) return validate452.errors = [ { instancePath: instancePath + "/ptz_has_home", schemaPath: "#/properties/ptz_has_home/type", keyword: "type", params: { - type: schema531.properties.ptz_has_home.type + type: schema543.properties.ptz_has_home.type } } ], !1; var valid19 = _errs39 === errors; @@ -98888,12 +107657,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data.record_on_schedule) { let data19 = data.record_on_schedule, _errs41 = errors; - if ("boolean" != typeof data19 && null !== data19) return validate442.errors = [ { + if ("boolean" != typeof data19 && null !== data19) return validate452.errors = [ { instancePath: instancePath + "/record_on_schedule", schemaPath: "#/properties/record_on_schedule/type", keyword: "type", params: { - type: schema531.properties.record_on_schedule.type + type: schema543.properties.record_on_schedule.type } } ], !1; var valid20 = _errs41 === errors; @@ -98901,12 +107670,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data.detection_record_on_schedule) { let data20 = data.detection_record_on_schedule, _errs43 = errors; - if ("boolean" != typeof data20 && null !== data20) return validate442.errors = [ { + if ("boolean" != typeof data20 && null !== data20) return validate452.errors = [ { instancePath: instancePath + "/detection_record_on_schedule", schemaPath: "#/properties/detection_record_on_schedule/type", keyword: "type", params: { - type: schema531.properties.detection_record_on_schedule.type + type: schema543.properties.detection_record_on_schedule.type } } ], !1; var valid21 = _errs43 === errors; @@ -98914,12 +107683,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data.isOnvif) { let data21 = data.isOnvif, _errs45 = errors; - if ("boolean" != typeof data21 && null !== data21) return validate442.errors = [ { + if ("boolean" != typeof data21 && null !== data21) return validate452.errors = [ { instancePath: instancePath + "/isOnvif", schemaPath: "#/properties/isOnvif/type", keyword: "type", params: { - type: schema531.properties.isOnvif.type + type: schema543.properties.isOnvif.type } } ], !1; var valid22 = _errs45 === errors; @@ -98927,12 +107696,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data.enable_disabled) { let data22 = data.enable_disabled, _errs47 = errors; - if ("boolean" != typeof data22 && null !== data22) return validate442.errors = [ { + if ("boolean" != typeof data22 && null !== data22) return validate452.errors = [ { instancePath: instancePath + "/enable_disabled", schemaPath: "#/properties/enable_disabled/type", keyword: "type", params: { - type: schema531.properties.enable_disabled.type + type: schema543.properties.enable_disabled.type } } ], !1; var valid23 = _errs47 === errors; @@ -98940,12 +107709,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data.priority) { let data23 = data.priority, _errs49 = errors; - if (("number" != typeof data23 || !isFinite(data23)) && null !== data23) return validate442.errors = [ { + if (("number" != typeof data23 || !isFinite(data23)) && null !== data23) return validate452.errors = [ { instancePath: instancePath + "/priority", schemaPath: "#/properties/priority/type", keyword: "type", params: { - type: schema531.properties.priority.type + type: schema543.properties.priority.type } } ], !1; var valid24 = _errs49 === errors; @@ -98953,12 +107722,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data.segment_duration) { let data24 = data.segment_duration, _errs51 = errors; - if (("number" != typeof data24 || !isFinite(data24)) && null !== data24) return validate442.errors = [ { + if (("number" != typeof data24 || !isFinite(data24)) && null !== data24) return validate452.errors = [ { instancePath: instancePath + "/segment_duration", schemaPath: "#/properties/segment_duration/type", keyword: "type", params: { - type: schema531.properties.segment_duration.type + type: schema543.properties.segment_duration.type } } ], !1; var valid25 = _errs51 === errors; @@ -98966,12 +107735,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data.detection_threshold) { let data25 = data.detection_threshold, _errs53 = errors; - if (("number" != typeof data25 || !isFinite(data25)) && null !== data25) return validate442.errors = [ { + if (("number" != typeof data25 || !isFinite(data25)) && null !== data25) return validate452.errors = [ { instancePath: instancePath + "/detection_threshold", schemaPath: "#/properties/detection_threshold/type", keyword: "type", params: { - type: schema531.properties.detection_threshold.type + type: schema543.properties.detection_threshold.type } } ], !1; var valid26 = _errs53 === errors; @@ -98979,12 +107748,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data.detection_interval) { let data26 = data.detection_interval, _errs55 = errors; - if (("number" != typeof data26 || !isFinite(data26)) && null !== data26) return validate442.errors = [ { + if (("number" != typeof data26 || !isFinite(data26)) && null !== data26) return validate452.errors = [ { instancePath: instancePath + "/detection_interval", schemaPath: "#/properties/detection_interval/type", keyword: "type", params: { - type: schema531.properties.detection_interval.type + type: schema543.properties.detection_interval.type } } ], !1; var valid27 = _errs55 === errors; @@ -98992,12 +107761,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data.pre_detection_buffer) { let data27 = data.pre_detection_buffer, _errs57 = errors; - if (("number" != typeof data27 || !isFinite(data27)) && null !== data27) return validate442.errors = [ { + if (("number" != typeof data27 || !isFinite(data27)) && null !== data27) return validate452.errors = [ { instancePath: instancePath + "/pre_detection_buffer", schemaPath: "#/properties/pre_detection_buffer/type", keyword: "type", params: { - type: schema531.properties.pre_detection_buffer.type + type: schema543.properties.pre_detection_buffer.type } } ], !1; var valid28 = _errs57 === errors; @@ -99005,12 +107774,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid28) { if (void 0 !== data.post_detection_buffer) { let data28 = data.post_detection_buffer, _errs59 = errors; - if (("number" != typeof data28 || !isFinite(data28)) && null !== data28) return validate442.errors = [ { + if (("number" != typeof data28 || !isFinite(data28)) && null !== data28) return validate452.errors = [ { instancePath: instancePath + "/post_detection_buffer", schemaPath: "#/properties/post_detection_buffer/type", keyword: "type", params: { - type: schema531.properties.post_detection_buffer.type + type: schema543.properties.post_detection_buffer.type } } ], !1; var valid29 = _errs59 === errors; @@ -99018,12 +107787,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid29) { if (void 0 !== data.max_storage_mb) { let data29 = data.max_storage_mb, _errs61 = errors; - if (("number" != typeof data29 || !isFinite(data29)) && null !== data29) return validate442.errors = [ { + if (("number" != typeof data29 || !isFinite(data29)) && null !== data29) return validate452.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/type", keyword: "type", params: { - type: schema531.properties.max_storage_mb.type + type: schema543.properties.max_storage_mb.type } } ], !1; var valid30 = _errs61 === errors; @@ -99031,12 +107800,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid30) { if (void 0 !== data.tier_critical_multiplier) { let data30 = data.tier_critical_multiplier, _errs63 = errors; - if (("number" != typeof data30 || !isFinite(data30)) && null !== data30) return validate442.errors = [ { + if (("number" != typeof data30 || !isFinite(data30)) && null !== data30) return validate452.errors = [ { instancePath: instancePath + "/tier_critical_multiplier", schemaPath: "#/properties/tier_critical_multiplier/type", keyword: "type", params: { - type: schema531.properties.tier_critical_multiplier.type + type: schema543.properties.tier_critical_multiplier.type } } ], !1; var valid31 = _errs63 === errors; @@ -99044,12 +107813,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid31) { if (void 0 !== data.tier_important_multiplier) { let data31 = data.tier_important_multiplier, _errs65 = errors; - if (("number" != typeof data31 || !isFinite(data31)) && null !== data31) return validate442.errors = [ { + if (("number" != typeof data31 || !isFinite(data31)) && null !== data31) return validate452.errors = [ { instancePath: instancePath + "/tier_important_multiplier", schemaPath: "#/properties/tier_important_multiplier/type", keyword: "type", params: { - type: schema531.properties.tier_important_multiplier.type + type: schema543.properties.tier_important_multiplier.type } } ], !1; var valid32 = _errs65 === errors; @@ -99057,12 +107826,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data.tier_ephemeral_multiplier) { let data32 = data.tier_ephemeral_multiplier, _errs67 = errors; - if (("number" != typeof data32 || !isFinite(data32)) && null !== data32) return validate442.errors = [ { + if (("number" != typeof data32 || !isFinite(data32)) && null !== data32) return validate452.errors = [ { instancePath: instancePath + "/tier_ephemeral_multiplier", schemaPath: "#/properties/tier_ephemeral_multiplier/type", keyword: "type", params: { - type: schema531.properties.tier_ephemeral_multiplier.type + type: schema543.properties.tier_ephemeral_multiplier.type } } ], !1; var valid33 = _errs67 === errors; @@ -99070,12 +107839,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid33) { if (void 0 !== data.storage_priority) { let data33 = data.storage_priority, _errs69 = errors; - if (("number" != typeof data33 || !isFinite(data33)) && null !== data33) return validate442.errors = [ { + if (("number" != typeof data33 || !isFinite(data33)) && null !== data33) return validate452.errors = [ { instancePath: instancePath + "/storage_priority", schemaPath: "#/properties/storage_priority/type", keyword: "type", params: { - type: schema531.properties.storage_priority.type + type: schema543.properties.storage_priority.type } } ], !1; var valid34 = _errs69 === errors; @@ -99083,12 +107852,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data.ptz_max_x) { let data34 = data.ptz_max_x, _errs71 = errors; - if (("number" != typeof data34 || !isFinite(data34)) && null !== data34) return validate442.errors = [ { + if (("number" != typeof data34 || !isFinite(data34)) && null !== data34) return validate452.errors = [ { instancePath: instancePath + "/ptz_max_x", schemaPath: "#/properties/ptz_max_x/type", keyword: "type", params: { - type: schema531.properties.ptz_max_x.type + type: schema543.properties.ptz_max_x.type } } ], !1; var valid35 = _errs71 === errors; @@ -99096,12 +107865,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data.ptz_max_y) { let data35 = data.ptz_max_y, _errs73 = errors; - if (("number" != typeof data35 || !isFinite(data35)) && null !== data35) return validate442.errors = [ { + if (("number" != typeof data35 || !isFinite(data35)) && null !== data35) return validate452.errors = [ { instancePath: instancePath + "/ptz_max_y", schemaPath: "#/properties/ptz_max_y/type", keyword: "type", params: { - type: schema531.properties.ptz_max_y.type + type: schema543.properties.ptz_max_y.type } } ], !1; var valid36 = _errs73 === errors; @@ -99109,12 +107878,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid36) { if (void 0 !== data.ptz_max_z) { let data36 = data.ptz_max_z, _errs75 = errors; - if (("number" != typeof data36 || !isFinite(data36)) && null !== data36) return validate442.errors = [ { + if (("number" != typeof data36 || !isFinite(data36)) && null !== data36) return validate452.errors = [ { instancePath: instancePath + "/ptz_max_z", schemaPath: "#/properties/ptz_max_z/type", keyword: "type", params: { - type: schema531.properties.ptz_max_z.type + type: schema543.properties.ptz_max_z.type } } ], !1; var valid37 = _errs75 === errors; @@ -99122,12 +107891,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid37) { if (void 0 !== data.protocol) { let data37 = data.protocol, _errs77 = errors; - if (("number" != typeof data37 || !isFinite(data37)) && null !== data37) return validate442.errors = [ { + if (("number" != typeof data37 || !isFinite(data37)) && null !== data37) return validate452.errors = [ { instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/type", keyword: "type", params: { - type: schema531.properties.protocol.type + type: schema543.properties.protocol.type } } ], !1; var valid38 = _errs77 === errors; @@ -99135,12 +107904,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid38) { if (void 0 !== data.onvif_port) { let data38 = data.onvif_port, _errs79 = errors; - if (("number" != typeof data38 || !isFinite(data38)) && null !== data38) return validate442.errors = [ { + if (("number" != typeof data38 || !isFinite(data38)) && null !== data38) return validate452.errors = [ { instancePath: instancePath + "/onvif_port", schemaPath: "#/properties/onvif_port/type", keyword: "type", params: { - type: schema531.properties.onvif_port.type + type: schema543.properties.onvif_port.type } } ], !1; var valid39 = _errs79 === errors; @@ -99148,16 +107917,16 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid39) { if (void 0 !== data.retention_days) { let data39 = data.retention_days, _errs81 = errors; - if (("number" != typeof data39 || !isFinite(data39)) && null !== data39) return validate442.errors = [ { + if (("number" != typeof data39 || !isFinite(data39)) && null !== data39) return validate452.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/type", keyword: "type", params: { - type: schema531.properties.retention_days.type + type: schema543.properties.retention_days.type } } ], !1; if (errors === _errs81 && "number" == typeof data39 && isFinite(data39)) { - if (data39 >= 366 || isNaN(data39)) return validate442.errors = [ { + if (data39 >= 366 || isNaN(data39)) return validate452.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/exclusiveMaximum", keyword: "exclusiveMaximum", @@ -99166,7 +107935,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD limit: 366 } } ], !1; - if (data39 <= -2 || isNaN(data39)) return validate442.errors = [ { + if (data39 <= -2 || isNaN(data39)) return validate452.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -99181,16 +107950,16 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid40) { if (void 0 !== data.detection_retention_days) { let data40 = data.detection_retention_days, _errs83 = errors; - if (("number" != typeof data40 || !isFinite(data40)) && null !== data40) return validate442.errors = [ { + if (("number" != typeof data40 || !isFinite(data40)) && null !== data40) return validate452.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/type", keyword: "type", params: { - type: schema531.properties.detection_retention_days.type + type: schema543.properties.detection_retention_days.type } } ], !1; if (errors === _errs83 && "number" == typeof data40 && isFinite(data40)) { - if (data40 >= 366 || isNaN(data40)) return validate442.errors = [ { + if (data40 >= 366 || isNaN(data40)) return validate452.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/exclusiveMaximum", keyword: "exclusiveMaximum", @@ -99199,7 +107968,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD limit: 366 } } ], !1; - if (data40 <= -2 || isNaN(data40)) return validate442.errors = [ { + if (data40 <= -2 || isNaN(data40)) return validate452.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/exclusiveMinimum", keyword: "exclusiveMinimum", @@ -99214,12 +107983,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid41) { if (void 0 !== data.tags) { let data41 = data.tags, _errs85 = errors; - if ("string" != typeof data41 && null !== data41) return validate442.errors = [ { + if ("string" != typeof data41 && null !== data41) return validate452.errors = [ { instancePath: instancePath + "/tags", schemaPath: "#/properties/tags/type", keyword: "type", params: { - type: schema531.properties.tags.type + type: schema543.properties.tags.type } } ], !1; var valid42 = _errs85 === errors; @@ -99227,12 +107996,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid42) { if (void 0 !== data.admin_url) { let data42 = data.admin_url, _errs87 = errors; - if ("string" != typeof data42 && null !== data42) return validate442.errors = [ { + if ("string" != typeof data42 && null !== data42) return validate452.errors = [ { instancePath: instancePath + "/admin_url", schemaPath: "#/properties/admin_url/type", keyword: "type", params: { - type: schema531.properties.admin_url.type + type: schema543.properties.admin_url.type } } ], !1; var valid43 = _errs87 === errors; @@ -99240,12 +108009,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid43) { if (void 0 !== data.motion_trigger_source) { let data43 = data.motion_trigger_source, _errs89 = errors; - if ("string" != typeof data43 && null !== data43) return validate442.errors = [ { + if ("string" != typeof data43 && null !== data43) return validate452.errors = [ { instancePath: instancePath + "/motion_trigger_source", schemaPath: "#/properties/motion_trigger_source/type", keyword: "type", params: { - type: schema531.properties.motion_trigger_source.type + type: schema543.properties.motion_trigger_source.type } } ], !1; var valid44 = _errs89 === errors; @@ -99253,12 +108022,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid44) { if (void 0 !== data.go2rtc_source_override) { let data44 = data.go2rtc_source_override, _errs91 = errors; - if ("string" != typeof data44 && null !== data44) return validate442.errors = [ { + if ("string" != typeof data44 && null !== data44) return validate452.errors = [ { instancePath: instancePath + "/go2rtc_source_override", schemaPath: "#/properties/go2rtc_source_override/type", keyword: "type", params: { - type: schema531.properties.go2rtc_source_override.type + type: schema543.properties.go2rtc_source_override.type } } ], !1; var valid45 = _errs91 === errors; @@ -99266,12 +108035,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid45) { if (void 0 !== data.sub_stream_url) { let data45 = data.sub_stream_url, _errs93 = errors; - if ("string" != typeof data45 && null !== data45) return validate442.errors = [ { + if ("string" != typeof data45 && null !== data45) return validate452.errors = [ { instancePath: instancePath + "/sub_stream_url", schemaPath: "#/properties/sub_stream_url/type", keyword: "type", params: { - type: schema531.properties.sub_stream_url.type + type: schema543.properties.sub_stream_url.type } } ], !1; var valid46 = _errs93 === errors; @@ -99279,12 +108048,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid46) { if (void 0 !== data.detection_url) { let data46 = data.detection_url, _errs95 = errors; - if ("string" != typeof data46 && null !== data46) return validate442.errors = [ { + if ("string" != typeof data46 && null !== data46) return validate452.errors = [ { instancePath: instancePath + "/detection_url", schemaPath: "#/properties/detection_url/type", keyword: "type", params: { - type: schema531.properties.detection_url.type + type: schema543.properties.detection_url.type } } ], !1; var valid47 = _errs95 === errors; @@ -99292,12 +108061,12 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid47) { if (void 0 !== data.publish_url) { let data47 = data.publish_url, _errs97 = errors; - if ("string" != typeof data47 && null !== data47) return validate442.errors = [ { + if ("string" != typeof data47 && null !== data47) return validate452.errors = [ { instancePath: instancePath + "/publish_url", schemaPath: "#/properties/publish_url/type", keyword: "type", params: { - type: schema531.properties.publish_url.type + type: schema543.properties.publish_url.type } } ], !1; var valid48 = _errs97 === errors; @@ -99305,7 +108074,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (valid48) { if (void 0 !== data.playback_transport) { let data48 = data.playback_transport, _errs99 = errors; - if ("string" != typeof data48) return validate442.errors = [ { + if ("string" != typeof data48) return validate452.errors = [ { instancePath: instancePath + "/playback_transport", schemaPath: "#/$defs/PlaybackTransport/type", keyword: "type", @@ -99313,7 +108082,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("auto" !== data48 && "webrtc_only" !== data48 && "mse_only" !== data48 && "hls_only" !== data48 && "webrtc_then_mse" !== data48 && "mse_then_hls" !== data48) return validate442.errors = [ { + if ("auto" !== data48 && "webrtc_only" !== data48 && "mse_only" !== data48 && "hls_only" !== data48 && "webrtc_then_mse" !== data48 && "mse_then_hls" !== data48) return validate452.errors = [ { instancePath: instancePath + "/playback_transport", schemaPath: "#/$defs/PlaybackTransport/enum", keyword: "enum", @@ -99327,7 +108096,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.eptz_config) { let data49 = data.eptz_config, _errs102 = errors; if (errors === _errs102) { - if ("string" != typeof data49) return validate442.errors = [ { + if ("string" != typeof data49) return validate452.errors = [ { instancePath: instancePath + "/eptz_config", schemaPath: "#/properties/eptz_config/type", keyword: "type", @@ -99335,7 +108104,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data49) > 1023) return validate442.errors = [ { + if (func1(data49) > 1023) return validate452.errors = [ { instancePath: instancePath + "/eptz_config", schemaPath: "#/properties/eptz_config/maxLength", keyword: "maxLength", @@ -99418,7 +108187,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err10), errors++, validate442.errors = vErrors, !1; + return vErrors.push(err10), errors++, validate452.errors = vErrors, !1; } errors = _errs105, vErrors.length = _errs105; var valid52 = _errs104 === errors; @@ -99495,7 +108264,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err16), errors++, validate442.errors = vErrors, !1; + return vErrors.push(err16), errors++, validate452.errors = vErrors, !1; } errors = _errs114, vErrors.length = _errs114; var valid56 = _errs113 === errors; @@ -99504,7 +108273,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.name) { let data54 = data.name, _errs122 = errors; if (errors === _errs122) { - if ("string" != typeof data54) return validate442.errors = [ { + if ("string" != typeof data54) return validate452.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/type", keyword: "type", @@ -99512,7 +108281,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data54) > 255) return validate442.errors = [ { + if (func1(data54) > 255) return validate452.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/maxLength", keyword: "maxLength", @@ -99520,7 +108289,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD limit: 255 } } ], !1; - if (func1(data54) < 1) return validate442.errors = [ { + if (func1(data54) < 1) return validate452.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/minLength", keyword: "minLength", @@ -99528,7 +108297,7 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (!pattern189.test(data54)) return validate442.errors = [ { + if (!pattern196.test(data54)) return validate452.errors = [ { instancePath: instancePath + "/name", schemaPath: "#/properties/name/pattern", keyword: "pattern", @@ -99539,14 +108308,123 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD } var valid60 = _errs122 === errors; } else valid60 = !0; - if (valid60 && void 0 !== data.set_privacy_mode && null !== data.set_privacy_mode) return validate442.errors = [ { - instancePath: instancePath + "/set_privacy_mode", - schemaPath: "#/properties/set_privacy_mode/type", - keyword: "type", - params: { - type: "null" + if (valid60) { + if (void 0 !== data.set_privacy_mode) { + let _errs124 = errors; + if (null !== data.set_privacy_mode) return validate452.errors = [ { + instancePath: instancePath + "/set_privacy_mode", + schemaPath: "#/properties/set_privacy_mode/type", + keyword: "type", + params: { + type: "null" + } + } ], !1; + var valid61 = _errs124 === errors; + } else valid61 = !0; + if (valid61) { + if (void 0 !== data.observation_evidence_enabled) { + let _errs126 = errors; + if ("boolean" != typeof data.observation_evidence_enabled) return validate452.errors = [ { + instancePath: instancePath + "/observation_evidence_enabled", + schemaPath: "#/properties/observation_evidence_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid62 = _errs126 === errors; + } else valid62 = !0; + if (valid62) { + if (void 0 !== data.observation_labels) { + let data57 = data.observation_labels, _errs128 = errors; + if (errors === _errs128) { + if ("string" != typeof data57) return validate452.errors = [ { + instancePath: instancePath + "/observation_labels", + schemaPath: "#/properties/observation_labels/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data57) > 511) return validate452.errors = [ { + instancePath: instancePath + "/observation_labels", + schemaPath: "#/properties/observation_labels/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + } ], !1; + } + var valid63 = _errs128 === errors; + } else valid63 = !0; + if (valid63) { + if (void 0 !== data.observation_max_age_days) { + let data58 = data.observation_max_age_days, _errs130 = errors; + if ("number" != typeof data58 || data58 % 1 || isNaN(data58) || !isFinite(data58)) return validate452.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs130 && "number" == typeof data58 && isFinite(data58)) { + if (data58 > 90 || isNaN(data58)) return validate452.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 90 + } + } ], !1; + if (data58 < 1 || isNaN(data58)) return validate452.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid64 = _errs130 === errors; + } else valid64 = !0; + if (valid64 && void 0 !== data.observation_max_bytes) { + let data59 = data.observation_max_bytes, _errs132 = errors; + if ("number" != typeof data59 || data59 % 1 || isNaN(data59) || !isFinite(data59)) return validate452.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs132 && "number" == typeof data59 && isFinite(data59)) { + if (data59 > 1099511627776 || isNaN(data59)) return validate452.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1099511627776 + } + } ], !1; + if (data59 < 67108864 || isNaN(data59)) return validate452.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 67108864 + } + } ], !1; + } + } + } + } } - } ], !1; + } } } } @@ -99598,16 +108476,16 @@ function validate442(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate442.errors = vErrors, 0 === errors; + return validate452.errors = vErrors, 0 === errors; } -validate442.evaluated = { +validate452.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamCreated = validate443, schema535 = { +var __contract_StreamCreated = validate453, schema547 = { type: "object", properties: { success: { @@ -99632,10 +108510,10 @@ var __contract_StreamCreated = validate443, schema535 = { additionalProperties: !0 }; -function validate443(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate443.evaluated; +function validate453(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate453.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate443.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate453.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -99643,7 +108521,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate443.errors = [ { + if (void 0 === data.success) return validate453.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99651,7 +108529,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.camera_uuid) return validate443.errors = [ { + if (void 0 === data.camera_uuid) return validate453.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99659,7 +108537,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data.location_uuid) return validate443.errors = [ { + if (void 0 === data.location_uuid) return validate453.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99669,7 +108547,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.success) { let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate443.errors = [ { + if ("boolean" != typeof data0) return validate453.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -99677,7 +108555,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate443.errors = [ { + if (!0 !== data0) return validate453.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -99690,7 +108568,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.camera_uuid) { let _errs4 = 0; - if ("string" != typeof data.camera_uuid) return validate443.errors = [ { + if ("string" != typeof data.camera_uuid) return validate453.errors = [ { instancePath: instancePath + "/camera_uuid", schemaPath: "#/properties/camera_uuid/type", keyword: "type", @@ -99703,7 +108581,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.location_uuid) { let _errs6 = 0; - if ("string" != typeof data.location_uuid) return validate443.errors = [ { + if ("string" != typeof data.location_uuid) return validate453.errors = [ { instancePath: instancePath + "/location_uuid", schemaPath: "#/properties/location_uuid/type", keyword: "type", @@ -99716,7 +108594,7 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.onvif_status) { let data3 = data.onvif_status, _errs8 = 0; - if ("string" != typeof data3) return validate443.errors = [ { + if ("string" != typeof data3) return validate453.errors = [ { instancePath: instancePath + "/onvif_status", schemaPath: "#/properties/onvif_status/type", keyword: "type", @@ -99724,17 +108602,17 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("success" !== data3 && "error" !== data3) return validate443.errors = [ { + if ("success" !== data3 && "error" !== data3) return validate453.errors = [ { instancePath: instancePath + "/onvif_status", schemaPath: "#/properties/onvif_status/enum", keyword: "enum", params: { - allowedValues: schema535.properties.onvif_status.enum + allowedValues: schema547.properties.onvif_status.enum } } ], !1; var valid3 = 0 === _errs8; } else valid3 = !0; - if (valid3 && void 0 !== data.onvif_message && "string" != typeof data.onvif_message) return validate443.errors = [ { + if (valid3 && void 0 !== data.onvif_message && "string" != typeof data.onvif_message) return validate453.errors = [ { instancePath: instancePath + "/onvif_message", schemaPath: "#/properties/onvif_message/type", keyword: "type", @@ -99745,21 +108623,21 @@ function validate443(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate443.errors = [], !0; + return validate453.errors = [], !0; } -validate443.evaluated = { +validate453.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamDeleteAccepted = validate446; +var __contract_StreamDeleteAccepted = validate456; -function validate446(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate446.evaluated; +function validate456(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate456.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate446.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate456.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -99767,7 +108645,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate446.errors = [ { + if (void 0 === data.success) return validate456.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99775,7 +108653,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.permanent) return validate446.errors = [ { + if (void 0 === data.permanent) return validate456.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99783,7 +108661,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "permanent" } } ], !1; - if (void 0 === data.status) return validate446.errors = [ { + if (void 0 === data.status) return validate456.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99793,7 +108671,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.success) { let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate446.errors = [ { + if ("boolean" != typeof data0) return validate456.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -99801,7 +108679,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate446.errors = [ { + if (!0 !== data0) return validate456.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -99814,7 +108692,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.permanent) { let _errs4 = 0; - if ("boolean" != typeof data.permanent) return validate446.errors = [ { + if ("boolean" != typeof data.permanent) return validate456.errors = [ { instancePath: instancePath + "/permanent", schemaPath: "#/properties/permanent/type", keyword: "type", @@ -99826,7 +108704,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.status) { let data2 = data.status; - if ("string" != typeof data2) return validate446.errors = [ { + if ("string" != typeof data2) return validate456.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -99834,7 +108712,7 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("accepted" !== data2) return validate446.errors = [ { + if ("accepted" !== data2) return validate456.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/const", keyword: "const", @@ -99844,16 +108722,16 @@ function validate446(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate446.errors = [], !0; + return validate456.errors = [], !0; } -validate446.evaluated = { +validate456.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamDetectionEngine = validate460, schema554 = { +var __contract_StreamDetectionEngine = validate470, schema566 = { type: "object", properties: { key: { @@ -99899,12 +108777,12 @@ var __contract_StreamDetectionEngine = validate460, schema554 = { }, required: [ "key", "type", "model_path", "enabled", "threshold", "interval_seconds", "sort_order", "config", "managed_by_legacy_stream_fields" ], additionalProperties: !0 -}, pattern194 = new RegExp("^[a-z0-9_-]+$", "u"); +}, pattern201 = new RegExp("^[a-z0-9_-]+$", "u"); -function validate460(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate460.evaluated; +function validate470(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate470.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate460.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -99912,7 +108790,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.key) return validate460.errors = [ { + if (void 0 === data.key) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99920,7 +108798,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data.type) return validate460.errors = [ { + if (void 0 === data.type) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99928,7 +108806,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data.model_path) return validate460.errors = [ { + if (void 0 === data.model_path) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99936,7 +108814,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "model_path" } } ], !1; - if (void 0 === data.enabled) return validate460.errors = [ { + if (void 0 === data.enabled) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99944,7 +108822,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data.threshold) return validate460.errors = [ { + if (void 0 === data.threshold) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99952,7 +108830,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "threshold" } } ], !1; - if (void 0 === data.interval_seconds) return validate460.errors = [ { + if (void 0 === data.interval_seconds) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99960,7 +108838,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "interval_seconds" } } ], !1; - if (void 0 === data.sort_order) return validate460.errors = [ { + if (void 0 === data.sort_order) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99968,7 +108846,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sort_order" } } ], !1; - if (void 0 === data.config) return validate460.errors = [ { + if (void 0 === data.config) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99976,7 +108854,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "config" } } ], !1; - if (void 0 === data.managed_by_legacy_stream_fields) return validate460.errors = [ { + if (void 0 === data.managed_by_legacy_stream_fields) return validate470.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -99987,7 +108865,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.key) { let data0 = data.key, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate460.errors = [ { + if ("string" != typeof data0) return validate470.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", @@ -99995,7 +108873,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 63) return validate460.errors = [ { + if (func1(data0) > 63) return validate470.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/maxLength", keyword: "maxLength", @@ -100003,7 +108881,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data0) < 1) return validate460.errors = [ { + if (func1(data0) < 1) return validate470.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/minLength", keyword: "minLength", @@ -100011,7 +108889,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (!pattern194.test(data0)) return validate460.errors = [ { + if (!pattern201.test(data0)) return validate470.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/pattern", keyword: "pattern", @@ -100025,7 +108903,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.type) { let data1 = data.type, _errs4 = 0; - if ("string" != typeof data1) return validate460.errors = [ { + if ("string" != typeof data1) return validate470.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -100033,12 +108911,12 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("object" !== data1 && "motion" !== data1 && "onvif" !== data1 && "api" !== data1 && "external" !== data1) return validate460.errors = [ { + if ("object" !== data1 && "motion" !== data1 && "onvif" !== data1 && "api" !== data1 && "external" !== data1) return validate470.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/enum", keyword: "enum", params: { - allowedValues: schema554.properties.type.enum + allowedValues: schema566.properties.type.enum } } ], !1; var valid1 = 0 === _errs4; @@ -100047,7 +108925,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.model_path) { let data2 = data.model_path, _errs6 = 0; if (0 === _errs6) { - if ("string" != typeof data2) return validate460.errors = [ { + if ("string" != typeof data2) return validate470.errors = [ { instancePath: instancePath + "/model_path", schemaPath: "#/properties/model_path/type", keyword: "type", @@ -100055,7 +108933,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 511) return validate460.errors = [ { + if (func1(data2) > 511) return validate470.errors = [ { instancePath: instancePath + "/model_path", schemaPath: "#/properties/model_path/maxLength", keyword: "maxLength", @@ -100069,7 +108947,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.enabled) { let _errs8 = 0; - if ("boolean" != typeof data.enabled) return validate460.errors = [ { + if ("boolean" != typeof data.enabled) return validate470.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -100083,7 +108961,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.threshold) { let data4 = data.threshold, _errs10 = 0; if (0 === _errs10) { - if ("number" != typeof data4 || !isFinite(data4)) return validate460.errors = [ { + if ("number" != typeof data4 || !isFinite(data4)) return validate470.errors = [ { instancePath: instancePath + "/threshold", schemaPath: "#/properties/threshold/type", keyword: "type", @@ -100091,7 +108969,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data4 > 1 || isNaN(data4)) return validate460.errors = [ { + if (data4 > 1 || isNaN(data4)) return validate470.errors = [ { instancePath: instancePath + "/threshold", schemaPath: "#/properties/threshold/maximum", keyword: "maximum", @@ -100100,7 +108978,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data4 < 0 || isNaN(data4)) return validate460.errors = [ { + if (data4 < 0 || isNaN(data4)) return validate470.errors = [ { instancePath: instancePath + "/threshold", schemaPath: "#/properties/threshold/minimum", keyword: "minimum", @@ -100115,7 +108993,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.interval_seconds) { let data5 = data.interval_seconds, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate460.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate470.errors = [ { instancePath: instancePath + "/interval_seconds", schemaPath: "#/properties/interval_seconds/type", keyword: "type", @@ -100124,7 +109002,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs12 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 86400 || isNaN(data5)) return validate460.errors = [ { + if (data5 > 86400 || isNaN(data5)) return validate470.errors = [ { instancePath: instancePath + "/interval_seconds", schemaPath: "#/properties/interval_seconds/maximum", keyword: "maximum", @@ -100133,7 +109011,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data5 < 1 || isNaN(data5)) return validate460.errors = [ { + if (data5 < 1 || isNaN(data5)) return validate470.errors = [ { instancePath: instancePath + "/interval_seconds", schemaPath: "#/properties/interval_seconds/minimum", keyword: "minimum", @@ -100148,7 +109026,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.sort_order) { let data6 = data.sort_order, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate460.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate470.errors = [ { instancePath: instancePath + "/sort_order", schemaPath: "#/properties/sort_order/type", keyword: "type", @@ -100157,7 +109035,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs14 && "number" == typeof data6 && isFinite(data6)) { - if (data6 > 2147483647 || isNaN(data6)) return validate460.errors = [ { + if (data6 > 2147483647 || isNaN(data6)) return validate470.errors = [ { instancePath: instancePath + "/sort_order", schemaPath: "#/properties/sort_order/maximum", keyword: "maximum", @@ -100166,7 +109044,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data6 < -2147483648 || isNaN(data6)) return validate460.errors = [ { + if (data6 < -2147483648 || isNaN(data6)) return validate470.errors = [ { instancePath: instancePath + "/sort_order", schemaPath: "#/properties/sort_order/minimum", keyword: "minimum", @@ -100181,7 +109059,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data.config) { let data7 = data.config, _errs16 = 0; - if (0 === _errs16 && (!data7 || "object" != typeof data7 || Array.isArray(data7))) return validate460.errors = [ { + if (0 === _errs16 && (!data7 || "object" != typeof data7 || Array.isArray(data7))) return validate470.errors = [ { instancePath: instancePath + "/config", schemaPath: "#/properties/config/type", keyword: "type", @@ -100191,7 +109069,7 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid7 = 0 === _errs16; } else valid7 = !0; - if (valid7 && void 0 !== data.managed_by_legacy_stream_fields && "boolean" != typeof data.managed_by_legacy_stream_fields) return validate460.errors = [ { + if (valid7 && void 0 !== data.managed_by_legacy_stream_fields && "boolean" != typeof data.managed_by_legacy_stream_fields) return validate470.errors = [ { instancePath: instancePath + "/managed_by_legacy_stream_fields", schemaPath: "#/properties/managed_by_legacy_stream_fields/type", keyword: "type", @@ -100206,16 +109084,16 @@ function validate460(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate460.errors = [], !0; + return validate470.errors = [], !0; } -validate460.evaluated = { +validate470.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamDetectionEngineWrite = validate459, schema553 = { +var __contract_StreamDetectionEngineWrite = validate469, schema565 = { type: "object", properties: { key: { @@ -100260,10 +109138,10 @@ var __contract_StreamDetectionEngineWrite = validate459, schema553 = { additionalProperties: !0 }; -function validate459(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate459.evaluated; +function validate469(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate469.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate459.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate469.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -100271,7 +109149,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.key) return validate459.errors = [ { + if (void 0 === data.key) return validate469.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100279,7 +109157,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data.type) return validate459.errors = [ { + if (void 0 === data.type) return validate469.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100287,7 +109165,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data.threshold) return validate459.errors = [ { + if (void 0 === data.threshold) return validate469.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100295,7 +109173,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "threshold" } } ], !1; - if (void 0 === data.interval_seconds) return validate459.errors = [ { + if (void 0 === data.interval_seconds) return validate469.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100306,7 +109184,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.key) { let data0 = data.key, _errs2 = 0; if (0 === _errs2) { - if ("string" != typeof data0) return validate459.errors = [ { + if ("string" != typeof data0) return validate469.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/type", keyword: "type", @@ -100314,7 +109192,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data0) > 63) return validate459.errors = [ { + if (func1(data0) > 63) return validate469.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/maxLength", keyword: "maxLength", @@ -100322,7 +109200,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data0) < 1) return validate459.errors = [ { + if (func1(data0) < 1) return validate469.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/minLength", keyword: "minLength", @@ -100330,7 +109208,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (!pattern194.test(data0)) return validate459.errors = [ { + if (!pattern201.test(data0)) return validate469.errors = [ { instancePath: instancePath + "/key", schemaPath: "#/properties/key/pattern", keyword: "pattern", @@ -100344,7 +109222,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.type) { let data1 = data.type, _errs4 = 0; - if ("string" != typeof data1) return validate459.errors = [ { + if ("string" != typeof data1) return validate469.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/type", keyword: "type", @@ -100352,12 +109230,12 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("object" !== data1 && "motion" !== data1 && "onvif" !== data1 && "api" !== data1 && "external" !== data1) return validate459.errors = [ { + if ("object" !== data1 && "motion" !== data1 && "onvif" !== data1 && "api" !== data1 && "external" !== data1) return validate469.errors = [ { instancePath: instancePath + "/type", schemaPath: "#/properties/type/enum", keyword: "enum", params: { - allowedValues: schema553.properties.type.enum + allowedValues: schema565.properties.type.enum } } ], !1; var valid1 = 0 === _errs4; @@ -100366,7 +109244,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.model_path) { let data2 = data.model_path, _errs6 = 0; if (0 === _errs6) { - if ("string" != typeof data2) return validate459.errors = [ { + if ("string" != typeof data2) return validate469.errors = [ { instancePath: instancePath + "/model_path", schemaPath: "#/properties/model_path/type", keyword: "type", @@ -100374,7 +109252,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 511) return validate459.errors = [ { + if (func1(data2) > 511) return validate469.errors = [ { instancePath: instancePath + "/model_path", schemaPath: "#/properties/model_path/maxLength", keyword: "maxLength", @@ -100388,7 +109266,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.enabled) { let _errs8 = 0; - if ("boolean" != typeof data.enabled) return validate459.errors = [ { + if ("boolean" != typeof data.enabled) return validate469.errors = [ { instancePath: instancePath + "/enabled", schemaPath: "#/properties/enabled/type", keyword: "type", @@ -100402,7 +109280,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.threshold) { let data4 = data.threshold, _errs10 = 0; if (0 === _errs10) { - if ("number" != typeof data4 || !isFinite(data4)) return validate459.errors = [ { + if ("number" != typeof data4 || !isFinite(data4)) return validate469.errors = [ { instancePath: instancePath + "/threshold", schemaPath: "#/properties/threshold/type", keyword: "type", @@ -100410,7 +109288,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data4 > 1 || isNaN(data4)) return validate459.errors = [ { + if (data4 > 1 || isNaN(data4)) return validate469.errors = [ { instancePath: instancePath + "/threshold", schemaPath: "#/properties/threshold/maximum", keyword: "maximum", @@ -100419,7 +109297,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data4 < 0 || isNaN(data4)) return validate459.errors = [ { + if (data4 < 0 || isNaN(data4)) return validate469.errors = [ { instancePath: instancePath + "/threshold", schemaPath: "#/properties/threshold/minimum", keyword: "minimum", @@ -100434,7 +109312,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.interval_seconds) { let data5 = data.interval_seconds, _errs12 = 0; - if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate459.errors = [ { + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate469.errors = [ { instancePath: instancePath + "/interval_seconds", schemaPath: "#/properties/interval_seconds/type", keyword: "type", @@ -100443,7 +109321,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs12 && "number" == typeof data5 && isFinite(data5)) { - if (data5 > 86400 || isNaN(data5)) return validate459.errors = [ { + if (data5 > 86400 || isNaN(data5)) return validate469.errors = [ { instancePath: instancePath + "/interval_seconds", schemaPath: "#/properties/interval_seconds/maximum", keyword: "maximum", @@ -100452,7 +109330,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data5 < 1 || isNaN(data5)) return validate459.errors = [ { + if (data5 < 1 || isNaN(data5)) return validate469.errors = [ { instancePath: instancePath + "/interval_seconds", schemaPath: "#/properties/interval_seconds/minimum", keyword: "minimum", @@ -100467,7 +109345,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data.sort_order) { let data6 = data.sort_order, _errs14 = 0; - if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate459.errors = [ { + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate469.errors = [ { instancePath: instancePath + "/sort_order", schemaPath: "#/properties/sort_order/type", keyword: "type", @@ -100476,7 +109354,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs14 && "number" == typeof data6 && isFinite(data6)) { - if (data6 > 2147483647 || isNaN(data6)) return validate459.errors = [ { + if (data6 > 2147483647 || isNaN(data6)) return validate469.errors = [ { instancePath: instancePath + "/sort_order", schemaPath: "#/properties/sort_order/maximum", keyword: "maximum", @@ -100485,7 +109363,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data6 < -2147483648 || isNaN(data6)) return validate459.errors = [ { + if (data6 < -2147483648 || isNaN(data6)) return validate469.errors = [ { instancePath: instancePath + "/sort_order", schemaPath: "#/properties/sort_order/minimum", keyword: "minimum", @@ -100499,7 +109377,7 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD } else valid6 = !0; if (valid6 && void 0 !== data.config) { let data7 = data.config; - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate459.errors = [ { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate469.errors = [ { instancePath: instancePath + "/config", schemaPath: "#/properties/config/type", keyword: "type", @@ -100514,21 +109392,21 @@ function validate459(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate459.errors = [], !0; + return validate469.errors = [], !0; } -validate459.evaluated = { +validate469.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamDetectionEngines = validate462; +var __contract_StreamDetectionEngines = validate472; -function validate462(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate462.evaluated; +function validate472(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate472.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate462.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate472.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -100536,7 +109414,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.stream_name) return validate462.errors = [ { + if (void 0 === data.stream_name) return validate472.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100544,7 +109422,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data.engines) return validate462.errors = [ { + if (void 0 === data.engines) return validate472.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100552,7 +109430,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "engines" } } ], !1; - if (void 0 === data.count) return validate462.errors = [ { + if (void 0 === data.count) return validate472.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100560,7 +109438,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "count" } } ], !1; - if (void 0 === data.trigger_policy) return validate462.errors = [ { + if (void 0 === data.trigger_policy) return validate472.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100568,7 +109446,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "trigger_policy" } } ], !1; - if (void 0 === data.restart_required) return validate462.errors = [ { + if (void 0 === data.restart_required) return validate472.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100578,7 +109456,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.stream_name) { let _errs2 = 0; - if ("string" != typeof data.stream_name) return validate462.errors = [ { + if ("string" != typeof data.stream_name) return validate472.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -100592,7 +109470,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.engines) { let data1 = data.engines, _errs4 = 0; if (0 === _errs4) { - if (!Array.isArray(data1)) return validate462.errors = [ { + if (!Array.isArray(data1)) return validate472.errors = [ { instancePath: instancePath + "/engines", schemaPath: "#/properties/engines/type", keyword: "type", @@ -100604,7 +109482,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD let len0 = data1.length; for (let i0 = 0; i0 < len0; i0++) { let data2 = data1[i0], _errs6 = 0; - if (!data2 || "object" != typeof data2 || Array.isArray(data2)) return validate462.errors = [ { + if (!data2 || "object" != typeof data2 || Array.isArray(data2)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/type", keyword: "type", @@ -100612,7 +109490,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data2.key) return validate462.errors = [ { + if (void 0 === data2.key) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100620,7 +109498,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data2.type) return validate462.errors = [ { + if (void 0 === data2.type) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100628,7 +109506,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data2.model_path) return validate462.errors = [ { + if (void 0 === data2.model_path) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100636,7 +109514,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "model_path" } } ], !1; - if (void 0 === data2.enabled) return validate462.errors = [ { + if (void 0 === data2.enabled) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100644,7 +109522,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "enabled" } } ], !1; - if (void 0 === data2.threshold) return validate462.errors = [ { + if (void 0 === data2.threshold) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100652,7 +109530,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "threshold" } } ], !1; - if (void 0 === data2.interval_seconds) return validate462.errors = [ { + if (void 0 === data2.interval_seconds) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100660,7 +109538,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "interval_seconds" } } ], !1; - if (void 0 === data2.sort_order) return validate462.errors = [ { + if (void 0 === data2.sort_order) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100668,7 +109546,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "sort_order" } } ], !1; - if (void 0 === data2.config) return validate462.errors = [ { + if (void 0 === data2.config) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100676,7 +109554,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "config" } } ], !1; - if (void 0 === data2.managed_by_legacy_stream_fields) return validate462.errors = [ { + if (void 0 === data2.managed_by_legacy_stream_fields) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngine/required", keyword: "required", @@ -100687,7 +109565,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data2.key) { let data3 = data2.key, _errs10 = 0; if (0 === _errs10) { - if ("string" != typeof data3) return validate462.errors = [ { + if ("string" != typeof data3) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngine/properties/key/type", keyword: "type", @@ -100695,7 +109573,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data3) > 63) return validate462.errors = [ { + if (func1(data3) > 63) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngine/properties/key/maxLength", keyword: "maxLength", @@ -100703,7 +109581,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data3) < 1) return validate462.errors = [ { + if (func1(data3) < 1) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngine/properties/key/minLength", keyword: "minLength", @@ -100711,7 +109589,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (!pattern194.test(data3)) return validate462.errors = [ { + if (!pattern201.test(data3)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngine/properties/key/pattern", keyword: "pattern", @@ -100725,7 +109603,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data2.type) { let data4 = data2.type, _errs12 = 0; - if ("string" != typeof data4) return validate462.errors = [ { + if ("string" != typeof data4) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/type", schemaPath: "#/$defs/StreamDetectionEngine/properties/type/type", keyword: "type", @@ -100733,12 +109611,12 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("object" !== data4 && "motion" !== data4 && "onvif" !== data4 && "api" !== data4 && "external" !== data4) return validate462.errors = [ { + if ("object" !== data4 && "motion" !== data4 && "onvif" !== data4 && "api" !== data4 && "external" !== data4) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/type", schemaPath: "#/$defs/StreamDetectionEngine/properties/type/enum", keyword: "enum", params: { - allowedValues: schema554.properties.type.enum + allowedValues: schema566.properties.type.enum } } ], !1; var valid5 = 0 === _errs12; @@ -100747,7 +109625,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data2.model_path) { let data5 = data2.model_path, _errs14 = 0; if (0 === _errs14) { - if ("string" != typeof data5) return validate462.errors = [ { + if ("string" != typeof data5) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/model_path", schemaPath: "#/$defs/StreamDetectionEngine/properties/model_path/type", keyword: "type", @@ -100755,7 +109633,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data5) > 511) return validate462.errors = [ { + if (func1(data5) > 511) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/model_path", schemaPath: "#/$defs/StreamDetectionEngine/properties/model_path/maxLength", keyword: "maxLength", @@ -100769,7 +109647,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid6) { if (void 0 !== data2.enabled) { let _errs16 = 0; - if ("boolean" != typeof data2.enabled) return validate462.errors = [ { + if ("boolean" != typeof data2.enabled) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/enabled", schemaPath: "#/$defs/StreamDetectionEngine/properties/enabled/type", keyword: "type", @@ -100783,7 +109661,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data2.threshold) { let data7 = data2.threshold, _errs18 = 0; if (0 === _errs18) { - if ("number" != typeof data7 || !isFinite(data7)) return validate462.errors = [ { + if ("number" != typeof data7 || !isFinite(data7)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/threshold", schemaPath: "#/$defs/StreamDetectionEngine/properties/threshold/type", keyword: "type", @@ -100791,7 +109669,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data7 > 1 || isNaN(data7)) return validate462.errors = [ { + if (data7 > 1 || isNaN(data7)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/threshold", schemaPath: "#/$defs/StreamDetectionEngine/properties/threshold/maximum", keyword: "maximum", @@ -100800,7 +109678,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data7 < 0 || isNaN(data7)) return validate462.errors = [ { + if (data7 < 0 || isNaN(data7)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/threshold", schemaPath: "#/$defs/StreamDetectionEngine/properties/threshold/minimum", keyword: "minimum", @@ -100815,7 +109693,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data2.interval_seconds) { let data8 = data2.interval_seconds, _errs20 = 0; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate462.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/interval_seconds", schemaPath: "#/$defs/StreamDetectionEngine/properties/interval_seconds/type", keyword: "type", @@ -100824,7 +109702,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs20 && "number" == typeof data8 && isFinite(data8)) { - if (data8 > 86400 || isNaN(data8)) return validate462.errors = [ { + if (data8 > 86400 || isNaN(data8)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/interval_seconds", schemaPath: "#/$defs/StreamDetectionEngine/properties/interval_seconds/maximum", keyword: "maximum", @@ -100833,7 +109711,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data8 < 1 || isNaN(data8)) return validate462.errors = [ { + if (data8 < 1 || isNaN(data8)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/interval_seconds", schemaPath: "#/$defs/StreamDetectionEngine/properties/interval_seconds/minimum", keyword: "minimum", @@ -100848,7 +109726,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid9) { if (void 0 !== data2.sort_order) { let data9 = data2.sort_order, _errs22 = 0; - if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate462.errors = [ { + if ("number" != typeof data9 || data9 % 1 || isNaN(data9) || !isFinite(data9)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/sort_order", schemaPath: "#/$defs/StreamDetectionEngine/properties/sort_order/type", keyword: "type", @@ -100857,7 +109735,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs22 && "number" == typeof data9 && isFinite(data9)) { - if (data9 > 2147483647 || isNaN(data9)) return validate462.errors = [ { + if (data9 > 2147483647 || isNaN(data9)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/sort_order", schemaPath: "#/$defs/StreamDetectionEngine/properties/sort_order/maximum", keyword: "maximum", @@ -100866,7 +109744,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data9 < -2147483648 || isNaN(data9)) return validate462.errors = [ { + if (data9 < -2147483648 || isNaN(data9)) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/sort_order", schemaPath: "#/$defs/StreamDetectionEngine/properties/sort_order/minimum", keyword: "minimum", @@ -100881,7 +109759,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid10) { if (void 0 !== data2.config) { let data10 = data2.config, _errs24 = 0; - if (0 === _errs24 && (!data10 || "object" != typeof data10 || Array.isArray(data10))) return validate462.errors = [ { + if (0 === _errs24 && (!data10 || "object" != typeof data10 || Array.isArray(data10))) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/config", schemaPath: "#/$defs/StreamDetectionEngine/properties/config/type", keyword: "type", @@ -100891,7 +109769,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid11 = 0 === _errs24; } else valid11 = !0; - if (valid11 && void 0 !== data2.managed_by_legacy_stream_fields && "boolean" != typeof data2.managed_by_legacy_stream_fields) return validate462.errors = [ { + if (valid11 && void 0 !== data2.managed_by_legacy_stream_fields && "boolean" != typeof data2.managed_by_legacy_stream_fields) return validate472.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/managed_by_legacy_stream_fields", schemaPath: "#/$defs/StreamDetectionEngine/properties/managed_by_legacy_stream_fields/type", keyword: "type", @@ -100915,7 +109793,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.count) { let data12 = data.count, _errs29 = 0; - if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate462.errors = [ { + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate472.errors = [ { instancePath: instancePath + "/count", schemaPath: "#/properties/count/type", keyword: "type", @@ -100928,7 +109806,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD if (valid13) { if (void 0 !== data.trigger_policy) { let data13 = data.trigger_policy, _errs31 = 0; - if ("string" != typeof data13) return validate462.errors = [ { + if ("string" != typeof data13) return validate472.errors = [ { instancePath: instancePath + "/trigger_policy", schemaPath: "#/properties/trigger_policy/type", keyword: "type", @@ -100936,7 +109814,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("any_of" !== data13) return validate462.errors = [ { + if ("any_of" !== data13) return validate472.errors = [ { instancePath: instancePath + "/trigger_policy", schemaPath: "#/properties/trigger_policy/const", keyword: "const", @@ -100946,7 +109824,7 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid14 = 0 === _errs31; } else valid14 = !0; - if (valid14 && void 0 !== data.restart_required && "boolean" != typeof data.restart_required) return validate462.errors = [ { + if (valid14 && void 0 !== data.restart_required && "boolean" != typeof data.restart_required) return validate472.errors = [ { instancePath: instancePath + "/restart_required", schemaPath: "#/properties/restart_required/type", keyword: "type", @@ -100957,21 +109835,21 @@ function validate462(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate462.errors = [], !0; + return validate472.errors = [], !0; } -validate462.evaluated = { +validate472.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamDetectionEnginesRequest = validate461; +var __contract_StreamDetectionEnginesRequest = validate471; -function validate461(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate461.evaluated; +function validate471(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate471.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate461.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate471.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -100979,7 +109857,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.engines) return validate461.errors = [ { + if (void 0 === data.engines) return validate471.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -100989,7 +109867,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.engines) { let data0 = data.engines; - if (!Array.isArray(data0)) return validate461.errors = [ { + if (!Array.isArray(data0)) return validate471.errors = [ { instancePath: instancePath + "/engines", schemaPath: "#/properties/engines/type", keyword: "type", @@ -100997,7 +109875,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "array" } } ], !1; - if (data0.length > 7) return validate461.errors = [ { + if (data0.length > 7) return validate471.errors = [ { instancePath: instancePath + "/engines", schemaPath: "#/properties/engines/maxItems", keyword: "maxItems", @@ -101009,7 +109887,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD let len0 = data0.length; for (let i0 = 0; i0 < len0; i0++) { let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate461.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngineWrite/type", keyword: "type", @@ -101017,7 +109895,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.key) return validate461.errors = [ { + if (void 0 === data1.key) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngineWrite/required", keyword: "required", @@ -101025,7 +109903,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data1.type) return validate461.errors = [ { + if (void 0 === data1.type) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngineWrite/required", keyword: "required", @@ -101033,7 +109911,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "type" } } ], !1; - if (void 0 === data1.threshold) return validate461.errors = [ { + if (void 0 === data1.threshold) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngineWrite/required", keyword: "required", @@ -101041,7 +109919,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "threshold" } } ], !1; - if (void 0 === data1.interval_seconds) return validate461.errors = [ { + if (void 0 === data1.interval_seconds) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0, schemaPath: "#/$defs/StreamDetectionEngineWrite/required", keyword: "required", @@ -101052,7 +109930,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.key) { let data2 = data1.key, _errs8 = 0; if (0 === _errs8) { - if ("string" != typeof data2) return validate461.errors = [ { + if ("string" != typeof data2) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/key/type", keyword: "type", @@ -101060,7 +109938,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data2) > 63) return validate461.errors = [ { + if (func1(data2) > 63) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/key/maxLength", keyword: "maxLength", @@ -101068,7 +109946,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD limit: 63 } } ], !1; - if (func1(data2) < 1) return validate461.errors = [ { + if (func1(data2) < 1) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/key/minLength", keyword: "minLength", @@ -101076,7 +109954,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (!pattern194.test(data2)) return validate461.errors = [ { + if (!pattern201.test(data2)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/key", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/key/pattern", keyword: "pattern", @@ -101090,7 +109968,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data1.type) { let data3 = data1.type, _errs10 = 0; - if ("string" != typeof data3) return validate461.errors = [ { + if ("string" != typeof data3) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/type", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/type/type", keyword: "type", @@ -101098,12 +109976,12 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("object" !== data3 && "motion" !== data3 && "onvif" !== data3 && "api" !== data3 && "external" !== data3) return validate461.errors = [ { + if ("object" !== data3 && "motion" !== data3 && "onvif" !== data3 && "api" !== data3 && "external" !== data3) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/type", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/type/enum", keyword: "enum", params: { - allowedValues: schema553.properties.type.enum + allowedValues: schema565.properties.type.enum } } ], !1; var valid4 = 0 === _errs10; @@ -101112,7 +109990,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.model_path) { let data4 = data1.model_path, _errs12 = 0; if (0 === _errs12) { - if ("string" != typeof data4) return validate461.errors = [ { + if ("string" != typeof data4) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/model_path", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/model_path/type", keyword: "type", @@ -101120,7 +109998,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if (func1(data4) > 511) return validate461.errors = [ { + if (func1(data4) > 511) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/model_path", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/model_path/maxLength", keyword: "maxLength", @@ -101134,7 +110012,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data1.enabled) { let _errs14 = 0; - if ("boolean" != typeof data1.enabled) return validate461.errors = [ { + if ("boolean" != typeof data1.enabled) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/enabled", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/enabled/type", keyword: "type", @@ -101148,7 +110026,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data1.threshold) { let data6 = data1.threshold, _errs16 = 0; if (0 === _errs16) { - if ("number" != typeof data6 || !isFinite(data6)) return validate461.errors = [ { + if ("number" != typeof data6 || !isFinite(data6)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/threshold", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/threshold/type", keyword: "type", @@ -101156,7 +110034,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD type: "number" } } ], !1; - if (data6 > 1 || isNaN(data6)) return validate461.errors = [ { + if (data6 > 1 || isNaN(data6)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/threshold", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/threshold/maximum", keyword: "maximum", @@ -101165,7 +110043,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD limit: 1 } } ], !1; - if (data6 < 0 || isNaN(data6)) return validate461.errors = [ { + if (data6 < 0 || isNaN(data6)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/threshold", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/threshold/minimum", keyword: "minimum", @@ -101180,7 +110058,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data1.interval_seconds) { let data7 = data1.interval_seconds, _errs18 = 0; - if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate461.errors = [ { + if ("number" != typeof data7 || data7 % 1 || isNaN(data7) || !isFinite(data7)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/interval_seconds", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/interval_seconds/type", keyword: "type", @@ -101189,7 +110067,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs18 && "number" == typeof data7 && isFinite(data7)) { - if (data7 > 86400 || isNaN(data7)) return validate461.errors = [ { + if (data7 > 86400 || isNaN(data7)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/interval_seconds", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/interval_seconds/maximum", keyword: "maximum", @@ -101198,7 +110076,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD limit: 86400 } } ], !1; - if (data7 < 1 || isNaN(data7)) return validate461.errors = [ { + if (data7 < 1 || isNaN(data7)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/interval_seconds", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/interval_seconds/minimum", keyword: "minimum", @@ -101213,7 +110091,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data1.sort_order) { let data8 = data1.sort_order, _errs20 = 0; - if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate461.errors = [ { + if ("number" != typeof data8 || data8 % 1 || isNaN(data8) || !isFinite(data8)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/sort_order", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/sort_order/type", keyword: "type", @@ -101222,7 +110100,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs20 && "number" == typeof data8 && isFinite(data8)) { - if (data8 > 2147483647 || isNaN(data8)) return validate461.errors = [ { + if (data8 > 2147483647 || isNaN(data8)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/sort_order", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/sort_order/maximum", keyword: "maximum", @@ -101231,7 +110109,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD limit: 2147483647 } } ], !1; - if (data8 < -2147483648 || isNaN(data8)) return validate461.errors = [ { + if (data8 < -2147483648 || isNaN(data8)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/sort_order", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/sort_order/minimum", keyword: "minimum", @@ -101245,7 +110123,7 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD } else valid9 = !0; if (valid9 && void 0 !== data1.config) { let data9 = data1.config; - if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate461.errors = [ { + if (!data9 || "object" != typeof data9 || Array.isArray(data9)) return validate471.errors = [ { instancePath: instancePath + "/engines/" + i0 + "/config", schemaPath: "#/$defs/StreamDetectionEngineWrite/properties/config/type", keyword: "type", @@ -101264,10 +110142,10 @@ function validate461(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate461.errors = [], !0; + return validate471.errors = [], !0; } -validate461.evaluated = { +validate471.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 @@ -101384,12 +110262,12 @@ validate32.evaluated = { dynamicItems: !1 }; -var __contract_StreamHealth = validate279; +var __contract_StreamHealth = validate282; -function validate279(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate279.evaluated; +function validate282(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate282.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate279.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -101397,7 +110275,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.healthy) return validate279.errors = [ { + if (void 0 === data.healthy) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101405,7 +110283,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "healthy" } } ], !1; - if (void 0 === data.uptime) return validate279.errors = [ { + if (void 0 === data.uptime) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101413,7 +110291,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uptime" } } ], !1; - if (void 0 === data.totalRequests) return validate279.errors = [ { + if (void 0 === data.totalRequests) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101421,7 +110299,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "totalRequests" } } ], !1; - if (void 0 === data.failedRequests) return validate279.errors = [ { + if (void 0 === data.failedRequests) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101429,7 +110307,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "failedRequests" } } ], !1; - if (void 0 === data.timestamp) return validate279.errors = [ { + if (void 0 === data.timestamp) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101437,7 +110315,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timestamp" } } ], !1; - if (void 0 === data.status) return validate279.errors = [ { + if (void 0 === data.status) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101445,7 +110323,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status" } } ], !1; - if (void 0 === data.streams) return validate279.errors = [ { + if (void 0 === data.streams) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101453,7 +110331,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "streams" } } ], !1; - if (void 0 === data.storage) return validate279.errors = [ { + if (void 0 === data.storage) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101461,7 +110339,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "storage" } } ], !1; - if (void 0 === data.streams_detail) return validate279.errors = [ { + if (void 0 === data.streams_detail) return validate282.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -101471,7 +110349,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.healthy) { let _errs2 = 0; - if ("boolean" != typeof data.healthy) return validate279.errors = [ { + if ("boolean" != typeof data.healthy) return validate282.errors = [ { instancePath: instancePath + "/healthy", schemaPath: "#/properties/healthy/type", keyword: "type", @@ -101484,7 +110362,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.status) { let _errs4 = 0; - if ("string" != typeof data.status) return validate279.errors = [ { + if ("string" != typeof data.status) return validate282.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -101497,7 +110375,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.uptime) { let data2 = data.uptime, _errs6 = 0; - if ("number" != typeof data2 || !isFinite(data2)) return validate279.errors = [ { + if ("number" != typeof data2 || !isFinite(data2)) return validate282.errors = [ { instancePath: instancePath + "/uptime", schemaPath: "#/properties/uptime/type", keyword: "type", @@ -101510,7 +110388,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.totalRequests) { let data3 = data.totalRequests, _errs8 = 0; - if ("number" != typeof data3 || !isFinite(data3)) return validate279.errors = [ { + if ("number" != typeof data3 || !isFinite(data3)) return validate282.errors = [ { instancePath: instancePath + "/totalRequests", schemaPath: "#/properties/totalRequests/type", keyword: "type", @@ -101523,7 +110401,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.failedRequests) { let data4 = data.failedRequests, _errs10 = 0; - if ("number" != typeof data4 || !isFinite(data4)) return validate279.errors = [ { + if ("number" != typeof data4 || !isFinite(data4)) return validate282.errors = [ { instancePath: instancePath + "/failedRequests", schemaPath: "#/properties/failedRequests/type", keyword: "type", @@ -101536,7 +110414,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.timestamp) { let _errs12 = 0; - if ("string" != typeof data.timestamp) return validate279.errors = [ { + if ("string" != typeof data.timestamp) return validate282.errors = [ { instancePath: instancePath + "/timestamp", schemaPath: "#/properties/timestamp/type", keyword: "type", @@ -101550,7 +110428,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.streams) { let data6 = data.streams, _errs14 = 0; if (0 === _errs14) { - if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate279.errors = [ { + if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate282.errors = [ { instancePath: instancePath + "/streams", schemaPath: "#/properties/streams/type", keyword: "type", @@ -101558,7 +110436,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data6.total) return validate279.errors = [ { + if (void 0 === data6.total) return validate282.errors = [ { instancePath: instancePath + "/streams", schemaPath: "#/properties/streams/required", keyword: "required", @@ -101566,7 +110444,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "total" } } ], !1; - if (void 0 === data6.up) return validate279.errors = [ { + if (void 0 === data6.up) return validate282.errors = [ { instancePath: instancePath + "/streams", schemaPath: "#/properties/streams/required", keyword: "required", @@ -101574,7 +110452,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "up" } } ], !1; - if (void 0 === data6.degraded) return validate279.errors = [ { + if (void 0 === data6.degraded) return validate282.errors = [ { instancePath: instancePath + "/streams", schemaPath: "#/properties/streams/required", keyword: "required", @@ -101582,7 +110460,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "degraded" } } ], !1; - if (void 0 === data6.down) return validate279.errors = [ { + if (void 0 === data6.down) return validate282.errors = [ { instancePath: instancePath + "/streams", schemaPath: "#/properties/streams/required", keyword: "required", @@ -101592,7 +110470,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data6.total) { let data7 = data6.total, _errs17 = 0; - if ("number" != typeof data7 || !isFinite(data7)) return validate279.errors = [ { + if ("number" != typeof data7 || !isFinite(data7)) return validate282.errors = [ { instancePath: instancePath + "/streams/total", schemaPath: "#/properties/streams/properties/total/type", keyword: "type", @@ -101605,7 +110483,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid7) { if (void 0 !== data6.up) { let data8 = data6.up, _errs19 = 0; - if ("number" != typeof data8 || !isFinite(data8)) return validate279.errors = [ { + if ("number" != typeof data8 || !isFinite(data8)) return validate282.errors = [ { instancePath: instancePath + "/streams/up", schemaPath: "#/properties/streams/properties/up/type", keyword: "type", @@ -101618,7 +110496,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid8) { if (void 0 !== data6.degraded) { let data9 = data6.degraded, _errs21 = 0; - if ("number" != typeof data9 || !isFinite(data9)) return validate279.errors = [ { + if ("number" != typeof data9 || !isFinite(data9)) return validate282.errors = [ { instancePath: instancePath + "/streams/degraded", schemaPath: "#/properties/streams/properties/degraded/type", keyword: "type", @@ -101630,7 +110508,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } else valid9 = !0; if (valid9 && void 0 !== data6.down) { let data10 = data6.down; - if ("number" != typeof data10 || !isFinite(data10)) return validate279.errors = [ { + if ("number" != typeof data10 || !isFinite(data10)) return validate282.errors = [ { instancePath: instancePath + "/streams/down", schemaPath: "#/properties/streams/properties/down/type", keyword: "type", @@ -101648,7 +110526,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.storage) { let data11 = data.storage, _errs25 = 0; if (0 === _errs25) { - if (!data11 || "object" != typeof data11 || Array.isArray(data11)) return validate279.errors = [ { + if (!data11 || "object" != typeof data11 || Array.isArray(data11)) return validate282.errors = [ { instancePath: instancePath + "/storage", schemaPath: "#/properties/storage/type", keyword: "type", @@ -101656,7 +110534,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data11.used_bytes) return validate279.errors = [ { + if (void 0 === data11.used_bytes) return validate282.errors = [ { instancePath: instancePath + "/storage", schemaPath: "#/properties/storage/required", keyword: "required", @@ -101664,7 +110542,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "used_bytes" } } ], !1; - if (void 0 === data11.available_bytes) return validate279.errors = [ { + if (void 0 === data11.available_bytes) return validate282.errors = [ { instancePath: instancePath + "/storage", schemaPath: "#/properties/storage/required", keyword: "required", @@ -101674,7 +110552,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data11.used_bytes) { let data12 = data11.used_bytes, _errs28 = 0; - if ("number" != typeof data12 || !isFinite(data12)) return validate279.errors = [ { + if ("number" != typeof data12 || !isFinite(data12)) return validate282.errors = [ { instancePath: instancePath + "/storage/used_bytes", schemaPath: "#/properties/storage/properties/used_bytes/type", keyword: "type", @@ -101686,7 +110564,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } else valid12 = !0; if (valid12 && void 0 !== data11.available_bytes) { let data13 = data11.available_bytes; - if ("number" != typeof data13 || !isFinite(data13)) return validate279.errors = [ { + if ("number" != typeof data13 || !isFinite(data13)) return validate282.errors = [ { instancePath: instancePath + "/storage/available_bytes", schemaPath: "#/properties/storage/properties/available_bytes/type", keyword: "type", @@ -101700,7 +110578,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } else valid11 = !0; if (valid11 && void 0 !== data.streams_detail) { let data14 = data.streams_detail; - if (!Array.isArray(data14)) return validate279.errors = [ { + if (!Array.isArray(data14)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail", schemaPath: "#/properties/streams_detail/type", keyword: "type", @@ -101713,7 +110591,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data15 = data14[i0], _errs34 = 0; if (0 === _errs34) { - if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate279.errors = [ { + if (!data15 || "object" != typeof data15 || Array.isArray(data15)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/type", keyword: "type", @@ -101721,7 +110599,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data15.name) return validate279.errors = [ { + if (void 0 === data15.name) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101729,7 +110607,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "name" } } ], !1; - if (void 0 === data15.status) return validate279.errors = [ { + if (void 0 === data15.status) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101737,7 +110615,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status" } } ], !1; - if (void 0 === data15.observed_fps) return validate279.errors = [ { + if (void 0 === data15.observed_fps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101745,7 +110623,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "observed_fps" } } ], !1; - if (void 0 === data15.current_fps) return validate279.errors = [ { + if (void 0 === data15.current_fps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101753,7 +110631,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "current_fps" } } ], !1; - if (void 0 === data15.expected_fps) return validate279.errors = [ { + if (void 0 === data15.expected_fps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101761,7 +110639,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "expected_fps" } } ], !1; - if (void 0 === data15.fps) return validate279.errors = [ { + if (void 0 === data15.fps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101769,7 +110647,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "fps" } } ], !1; - if (void 0 === data15.configured_fps) return validate279.errors = [ { + if (void 0 === data15.configured_fps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101777,7 +110655,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "configured_fps" } } ], !1; - if (void 0 === data15.bitrate_bps) return validate279.errors = [ { + if (void 0 === data15.bitrate_bps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101785,7 +110663,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "bitrate_bps" } } ], !1; - if (void 0 === data15.uptime_seconds) return validate279.errors = [ { + if (void 0 === data15.uptime_seconds) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101793,7 +110671,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uptime_seconds" } } ], !1; - if (void 0 === data15.reconnects) return validate279.errors = [ { + if (void 0 === data15.reconnects) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101801,7 +110679,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "reconnects" } } ], !1; - if (void 0 === data15.last_packet_ts) return validate279.errors = [ { + if (void 0 === data15.last_packet_ts) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101809,7 +110687,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_packet_ts" } } ], !1; - if (void 0 === data15.last_frame_ts) return validate279.errors = [ { + if (void 0 === data15.last_frame_ts) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101817,7 +110695,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_frame_ts" } } ], !1; - if (void 0 === data15.last_completed_segment_ts) return validate279.errors = [ { + if (void 0 === data15.last_completed_segment_ts) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101825,7 +110703,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "last_completed_segment_ts" } } ], !1; - if (void 0 === data15.frames_total) return validate279.errors = [ { + if (void 0 === data15.frames_total) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101833,7 +110711,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "frames_total" } } ], !1; - if (void 0 === data15.frames_dropped) return validate279.errors = [ { + if (void 0 === data15.frames_dropped) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101841,7 +110719,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "frames_dropped" } } ], !1; - if (void 0 === data15.connection_latency_ms) return validate279.errors = [ { + if (void 0 === data15.connection_latency_ms) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101849,7 +110727,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "connection_latency_ms" } } ], !1; - if (void 0 === data15.source_connected) return validate279.errors = [ { + if (void 0 === data15.source_connected) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101857,7 +110735,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "source_connected" } } ], !1; - if (void 0 === data15.video_advancing) return validate279.errors = [ { + if (void 0 === data15.video_advancing) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101865,7 +110743,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "video_advancing" } } ], !1; - if (void 0 === data15.recorder_writing) return validate279.errors = [ { + if (void 0 === data15.recorder_writing) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101873,7 +110751,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recorder_writing" } } ], !1; - if (void 0 === data15.detector_operating) return validate279.errors = [ { + if (void 0 === data15.detector_operating) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101881,7 +110759,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "detector_operating" } } ], !1; - if (void 0 === data15.recorder_session_active) return validate279.errors = [ { + if (void 0 === data15.recorder_session_active) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101889,7 +110767,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recorder_session_active" } } ], !1; - if (void 0 === data15.recording_active) return validate279.errors = [ { + if (void 0 === data15.recording_active) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101897,7 +110775,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_active" } } ], !1; - if (void 0 === data15.recording_gaps) return validate279.errors = [ { + if (void 0 === data15.recording_gaps) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -101907,7 +110785,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data15.name) { let _errs37 = 0; - if ("string" != typeof data15.name) return validate279.errors = [ { + if ("string" != typeof data15.name) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/name", schemaPath: "#/properties/streams_detail/items/properties/name/type", keyword: "type", @@ -101920,7 +110798,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid16) { if (void 0 !== data15.status) { let _errs39 = 0; - if ("string" != typeof data15.status) return validate279.errors = [ { + if ("string" != typeof data15.status) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/status", schemaPath: "#/properties/streams_detail/items/properties/status/type", keyword: "type", @@ -101933,7 +110811,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid17) { if (void 0 !== data15.observed_fps) { let data18 = data15.observed_fps, _errs41 = 0; - if ("number" != typeof data18 || !isFinite(data18)) return validate279.errors = [ { + if ("number" != typeof data18 || !isFinite(data18)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/observed_fps", schemaPath: "#/properties/streams_detail/items/properties/observed_fps/type", keyword: "type", @@ -101946,7 +110824,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid18) { if (void 0 !== data15.current_fps) { let data19 = data15.current_fps, _errs43 = 0; - if ("number" != typeof data19 || !isFinite(data19)) return validate279.errors = [ { + if ("number" != typeof data19 || !isFinite(data19)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/current_fps", schemaPath: "#/properties/streams_detail/items/properties/current_fps/type", keyword: "type", @@ -101959,7 +110837,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid19) { if (void 0 !== data15.expected_fps) { let data20 = data15.expected_fps, _errs45 = 0; - if ("number" != typeof data20 || !isFinite(data20)) return validate279.errors = [ { + if ("number" != typeof data20 || !isFinite(data20)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/expected_fps", schemaPath: "#/properties/streams_detail/items/properties/expected_fps/type", keyword: "type", @@ -101972,7 +110850,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid20) { if (void 0 !== data15.fps) { let data21 = data15.fps, _errs47 = 0; - if ("number" != typeof data21 || !isFinite(data21)) return validate279.errors = [ { + if ("number" != typeof data21 || !isFinite(data21)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/fps", schemaPath: "#/properties/streams_detail/items/properties/fps/type", keyword: "type", @@ -101985,7 +110863,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid21) { if (void 0 !== data15.configured_fps) { let data22 = data15.configured_fps, _errs49 = 0; - if ("number" != typeof data22 || !isFinite(data22)) return validate279.errors = [ { + if ("number" != typeof data22 || !isFinite(data22)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/configured_fps", schemaPath: "#/properties/streams_detail/items/properties/configured_fps/type", keyword: "type", @@ -101998,7 +110876,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid22) { if (void 0 !== data15.bitrate_bps) { let data23 = data15.bitrate_bps, _errs51 = 0; - if ("number" != typeof data23 || !isFinite(data23)) return validate279.errors = [ { + if ("number" != typeof data23 || !isFinite(data23)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/bitrate_bps", schemaPath: "#/properties/streams_detail/items/properties/bitrate_bps/type", keyword: "type", @@ -102011,7 +110889,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid23) { if (void 0 !== data15.uptime_seconds) { let data24 = data15.uptime_seconds, _errs53 = 0; - if ("number" != typeof data24 || !isFinite(data24)) return validate279.errors = [ { + if ("number" != typeof data24 || !isFinite(data24)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/uptime_seconds", schemaPath: "#/properties/streams_detail/items/properties/uptime_seconds/type", keyword: "type", @@ -102024,7 +110902,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid24) { if (void 0 !== data15.reconnects) { let data25 = data15.reconnects, _errs55 = 0; - if ("number" != typeof data25 || !isFinite(data25)) return validate279.errors = [ { + if ("number" != typeof data25 || !isFinite(data25)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/reconnects", schemaPath: "#/properties/streams_detail/items/properties/reconnects/type", keyword: "type", @@ -102037,7 +110915,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid25) { if (void 0 !== data15.last_packet_ts) { let data26 = data15.last_packet_ts, _errs57 = 0; - if ("number" != typeof data26 || !isFinite(data26)) return validate279.errors = [ { + if ("number" != typeof data26 || !isFinite(data26)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/last_packet_ts", schemaPath: "#/properties/streams_detail/items/properties/last_packet_ts/type", keyword: "type", @@ -102050,7 +110928,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid26) { if (void 0 !== data15.last_frame_ts) { let data27 = data15.last_frame_ts, _errs59 = 0; - if ("number" != typeof data27 || !isFinite(data27)) return validate279.errors = [ { + if ("number" != typeof data27 || !isFinite(data27)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/last_frame_ts", schemaPath: "#/properties/streams_detail/items/properties/last_frame_ts/type", keyword: "type", @@ -102063,7 +110941,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid27) { if (void 0 !== data15.last_completed_segment_ts) { let data28 = data15.last_completed_segment_ts, _errs61 = 0; - if ("number" != typeof data28 || !isFinite(data28)) return validate279.errors = [ { + if ("number" != typeof data28 || !isFinite(data28)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/last_completed_segment_ts", schemaPath: "#/properties/streams_detail/items/properties/last_completed_segment_ts/type", keyword: "type", @@ -102076,7 +110954,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid28) { if (void 0 !== data15.frames_total) { let data29 = data15.frames_total, _errs63 = 0; - if ("number" != typeof data29 || !isFinite(data29)) return validate279.errors = [ { + if ("number" != typeof data29 || !isFinite(data29)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/frames_total", schemaPath: "#/properties/streams_detail/items/properties/frames_total/type", keyword: "type", @@ -102089,7 +110967,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid29) { if (void 0 !== data15.frames_dropped) { let data30 = data15.frames_dropped, _errs65 = 0; - if ("number" != typeof data30 || !isFinite(data30)) return validate279.errors = [ { + if ("number" != typeof data30 || !isFinite(data30)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/frames_dropped", schemaPath: "#/properties/streams_detail/items/properties/frames_dropped/type", keyword: "type", @@ -102102,7 +110980,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid30) { if (void 0 !== data15.connection_latency_ms) { let data31 = data15.connection_latency_ms, _errs67 = 0; - if ("number" != typeof data31 || !isFinite(data31)) return validate279.errors = [ { + if ("number" != typeof data31 || !isFinite(data31)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/connection_latency_ms", schemaPath: "#/properties/streams_detail/items/properties/connection_latency_ms/type", keyword: "type", @@ -102115,7 +110993,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid31) { if (void 0 !== data15.source_connected) { let _errs69 = 0; - if ("boolean" != typeof data15.source_connected) return validate279.errors = [ { + if ("boolean" != typeof data15.source_connected) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/source_connected", schemaPath: "#/properties/streams_detail/items/properties/source_connected/type", keyword: "type", @@ -102128,7 +111006,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid32) { if (void 0 !== data15.video_advancing) { let _errs71 = 0; - if ("boolean" != typeof data15.video_advancing) return validate279.errors = [ { + if ("boolean" != typeof data15.video_advancing) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/video_advancing", schemaPath: "#/properties/streams_detail/items/properties/video_advancing/type", keyword: "type", @@ -102141,7 +111019,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid33) { if (void 0 !== data15.recorder_writing) { let _errs73 = 0; - if ("boolean" != typeof data15.recorder_writing) return validate279.errors = [ { + if ("boolean" != typeof data15.recorder_writing) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/recorder_writing", schemaPath: "#/properties/streams_detail/items/properties/recorder_writing/type", keyword: "type", @@ -102154,7 +111032,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid34) { if (void 0 !== data15.detector_operating) { let _errs75 = 0; - if ("boolean" != typeof data15.detector_operating) return validate279.errors = [ { + if ("boolean" != typeof data15.detector_operating) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/detector_operating", schemaPath: "#/properties/streams_detail/items/properties/detector_operating/type", keyword: "type", @@ -102167,7 +111045,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid35) { if (void 0 !== data15.recorder_session_active) { let _errs77 = 0; - if ("boolean" != typeof data15.recorder_session_active) return validate279.errors = [ { + if ("boolean" != typeof data15.recorder_session_active) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/recorder_session_active", schemaPath: "#/properties/streams_detail/items/properties/recorder_session_active/type", keyword: "type", @@ -102180,7 +111058,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid36) { if (void 0 !== data15.recording_active) { let _errs79 = 0; - if ("boolean" != typeof data15.recording_active) return validate279.errors = [ { + if ("boolean" != typeof data15.recording_active) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/recording_active", schemaPath: "#/properties/streams_detail/items/properties/recording_active/type", keyword: "type", @@ -102193,7 +111071,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (valid37) { if (void 0 !== data15.recording_gaps) { let data38 = data15.recording_gaps, _errs81 = 0; - if ("number" != typeof data38 || !isFinite(data38)) return validate279.errors = [ { + if ("number" != typeof data38 || !isFinite(data38)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/recording_gaps", schemaPath: "#/properties/streams_detail/items/properties/recording_gaps/type", keyword: "type", @@ -102207,7 +111085,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data15.sparkline_fps) { let data39 = data15.sparkline_fps, _errs83 = 0; if (0 === _errs83) { - if (!Array.isArray(data39)) return validate279.errors = [ { + if (!Array.isArray(data39)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_fps", schemaPath: "#/properties/streams_detail/items/properties/sparkline_fps/type", keyword: "type", @@ -102219,7 +111097,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD let len1 = data39.length; for (let i1 = 0; i1 < len1; i1++) { let data40 = data39[i1], _errs85 = 0; - if ("number" != typeof data40 || !isFinite(data40)) return validate279.errors = [ { + if ("number" != typeof data40 || !isFinite(data40)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_fps/" + i1, schemaPath: "#/properties/streams_detail/items/properties/sparkline_fps/items/type", keyword: "type", @@ -102235,7 +111113,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } else valid39 = !0; if (valid39 && void 0 !== data15.sparkline_bitrate) { let data41 = data15.sparkline_bitrate; - if (!Array.isArray(data41)) return validate279.errors = [ { + if (!Array.isArray(data41)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_bitrate", schemaPath: "#/properties/streams_detail/items/properties/sparkline_bitrate/type", keyword: "type", @@ -102247,7 +111125,7 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD let len2 = data41.length; for (let i2 = 0; i2 < len2; i2++) { let data42 = data41[i2], _errs89 = 0; - if ("number" != typeof data42 || !isFinite(data42)) return validate279.errors = [ { + if ("number" != typeof data42 || !isFinite(data42)) return validate282.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_bitrate/" + i2, schemaPath: "#/properties/streams_detail/items/properties/sparkline_bitrate/items/type", keyword: "type", @@ -102294,21 +111172,21 @@ function validate279(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate279.errors = [], !0; + return validate282.errors = [], !0; } -validate279.evaluated = { +validate282.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamHealthHistory = validate281; +var __contract_StreamHealthHistory = validate284; -function validate281(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate281.evaluated; +function validate284(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate284.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate281.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -102316,7 +111194,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.healthy) return validate281.errors = [ { + if (void 0 === data.healthy) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102324,7 +111202,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "healthy" } } ], !1; - if (void 0 === data.uptime) return validate281.errors = [ { + if (void 0 === data.uptime) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102332,7 +111210,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "uptime" } } ], !1; - if (void 0 === data.totalRequests) return validate281.errors = [ { + if (void 0 === data.totalRequests) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102340,7 +111218,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "totalRequests" } } ], !1; - if (void 0 === data.failedRequests) return validate281.errors = [ { + if (void 0 === data.failedRequests) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102348,7 +111226,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "failedRequests" } } ], !1; - if (void 0 === data.timestamp) return validate281.errors = [ { + if (void 0 === data.timestamp) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102356,7 +111234,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "timestamp" } } ], !1; - if (void 0 === data.streams_detail) return validate281.errors = [ { + if (void 0 === data.streams_detail) return validate284.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102366,7 +111244,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.healthy) { let _errs2 = 0; - if ("boolean" != typeof data.healthy) return validate281.errors = [ { + if ("boolean" != typeof data.healthy) return validate284.errors = [ { instancePath: instancePath + "/healthy", schemaPath: "#/properties/healthy/type", keyword: "type", @@ -102379,7 +111257,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.uptime) { let data1 = data.uptime, _errs4 = 0; - if ("number" != typeof data1 || !isFinite(data1)) return validate281.errors = [ { + if ("number" != typeof data1 || !isFinite(data1)) return validate284.errors = [ { instancePath: instancePath + "/uptime", schemaPath: "#/properties/uptime/type", keyword: "type", @@ -102392,7 +111270,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.totalRequests) { let data2 = data.totalRequests, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate281.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate284.errors = [ { instancePath: instancePath + "/totalRequests", schemaPath: "#/properties/totalRequests/type", keyword: "type", @@ -102405,7 +111283,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.failedRequests) { let data3 = data.failedRequests, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate281.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate284.errors = [ { instancePath: instancePath + "/failedRequests", schemaPath: "#/properties/failedRequests/type", keyword: "type", @@ -102418,7 +111296,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.timestamp) { let _errs10 = 0; - if ("string" != typeof data.timestamp) return validate281.errors = [ { + if ("string" != typeof data.timestamp) return validate284.errors = [ { instancePath: instancePath + "/timestamp", schemaPath: "#/properties/timestamp/type", keyword: "type", @@ -102430,7 +111308,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD } else valid4 = !0; if (valid4 && void 0 !== data.streams_detail) { let data5 = data.streams_detail; - if (!Array.isArray(data5)) return validate281.errors = [ { + if (!Array.isArray(data5)) return validate284.errors = [ { instancePath: instancePath + "/streams_detail", schemaPath: "#/properties/streams_detail/type", keyword: "type", @@ -102443,7 +111321,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data6 = data5[i0], _errs14 = 0; if (0 === _errs14) { - if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate281.errors = [ { + if (!data6 || "object" != typeof data6 || Array.isArray(data6)) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/type", keyword: "type", @@ -102451,7 +111329,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data6.name) return validate281.errors = [ { + if (void 0 === data6.name) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0, schemaPath: "#/properties/streams_detail/items/required", keyword: "required", @@ -102461,7 +111339,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data6.name) { let _errs17 = 0; - if ("string" != typeof data6.name) return validate281.errors = [ { + if ("string" != typeof data6.name) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/name", schemaPath: "#/properties/streams_detail/items/properties/name/type", keyword: "type", @@ -102475,7 +111353,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data6.sparkline_fps) { let data8 = data6.sparkline_fps, _errs19 = 0; if (0 === _errs19) { - if (!Array.isArray(data8)) return validate281.errors = [ { + if (!Array.isArray(data8)) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_fps", schemaPath: "#/properties/streams_detail/items/properties/sparkline_fps/type", keyword: "type", @@ -102487,7 +111365,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD let len1 = data8.length; for (let i1 = 0; i1 < len1; i1++) { let data9 = data8[i1], _errs21 = 0; - if ("number" != typeof data9 || !isFinite(data9)) return validate281.errors = [ { + if ("number" != typeof data9 || !isFinite(data9)) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_fps/" + i1, schemaPath: "#/properties/streams_detail/items/properties/sparkline_fps/items/type", keyword: "type", @@ -102503,7 +111381,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD } else valid8 = !0; if (valid8 && void 0 !== data6.sparkline_bitrate) { let data10 = data6.sparkline_bitrate; - if (!Array.isArray(data10)) return validate281.errors = [ { + if (!Array.isArray(data10)) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_bitrate", schemaPath: "#/properties/streams_detail/items/properties/sparkline_bitrate/type", keyword: "type", @@ -102515,7 +111393,7 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD let len2 = data10.length; for (let i2 = 0; i2 < len2; i2++) { let data11 = data10[i2], _errs25 = 0; - if ("number" != typeof data11 || !isFinite(data11)) return validate281.errors = [ { + if ("number" != typeof data11 || !isFinite(data11)) return validate284.errors = [ { instancePath: instancePath + "/streams_detail/" + i0 + "/sparkline_bitrate/" + i2, schemaPath: "#/properties/streams_detail/items/properties/sparkline_bitrate/items/type", keyword: "type", @@ -102537,10 +111415,10 @@ function validate281(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate281.errors = [], !0; + return validate284.errors = [], !0; } -validate281.evaluated = { +validate284.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 @@ -102634,12 +111512,12 @@ validate66.evaluated = { dynamicItems: !1 }; -var __contract_StreamProbeRequest = validate444; +var __contract_StreamProbeRequest = validate454; -function validate444(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate444.evaluated; +function validate454(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate454.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate444.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate454.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -102647,7 +111525,7 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.url) return validate444.errors = [ { + if (void 0 === data.url) return validate454.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102655,7 +111533,7 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "url" } } ], !1; - if (void 0 === data.protocol) return validate444.errors = [ { + if (void 0 === data.protocol) return validate454.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -102665,7 +111543,7 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.url) { let _errs2 = 0; - if ("string" != typeof data.url) return validate444.errors = [ { + if ("string" != typeof data.url) return validate454.errors = [ { instancePath: instancePath + "/url", schemaPath: "#/properties/url/type", keyword: "type", @@ -102678,7 +111556,7 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.protocol) { let data1 = data.protocol, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate444.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate454.errors = [ { instancePath: instancePath + "/protocol", schemaPath: "#/properties/protocol/type", keyword: "type", @@ -102691,7 +111569,7 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.onvif_username) { let _errs6 = 0; - if ("string" != typeof data.onvif_username) return validate444.errors = [ { + if ("string" != typeof data.onvif_username) return validate454.errors = [ { instancePath: instancePath + "/onvif_username", schemaPath: "#/properties/onvif_username/type", keyword: "type", @@ -102701,7 +111579,7 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid2 = 0 === _errs6; } else valid2 = !0; - if (valid2 && void 0 !== data.onvif_password && "string" != typeof data.onvif_password) return validate444.errors = [ { + if (valid2 && void 0 !== data.onvif_password && "string" != typeof data.onvif_password) return validate454.errors = [ { instancePath: instancePath + "/onvif_password", schemaPath: "#/properties/onvif_password/type", keyword: "type", @@ -102711,19 +111589,19 @@ function validate444(data, {instancePath: instancePath = "", parentData: parentD } ], !1; } } - return validate444.errors = [], !0; + return validate454.errors = [], !0; } -validate444.evaluated = { +validate454.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamProbeResponse = validate445; +var __contract_StreamProbeResponse = validate455; -function validate445(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate445.evaluated; +function validate455(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate455.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, passing0 = null, _errs1 = errors; if (errors === _errs1) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.success) { @@ -102984,18 +111862,18 @@ function validate445(data, {instancePath: instancePath = "", parentData: parentD passingSchemas: passing0 } }; - return vErrors.push(err20), errors++, validate445.errors = vErrors, !1; + return vErrors.push(err20), errors++, validate455.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate445.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate455.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate445.evaluated = { +validate455.evaluated = { dynamicProps: !0, dynamicItems: !1 }; -var __contract_StreamRecordingChanged = validate450, schema542 = { +var __contract_StreamRecordingChanged = validate460, schema554 = { type: "object", properties: { success: { @@ -103018,10 +111896,10 @@ var __contract_StreamRecordingChanged = validate450, schema542 = { additionalProperties: !0 }; -function validate450(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate450.evaluated; +function validate460(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate460.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate450.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate460.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -103029,7 +111907,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate450.errors = [ { + if (void 0 === data.success) return validate460.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103037,7 +111915,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.stream) return validate450.errors = [ { + if (void 0 === data.stream) return validate460.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103045,7 +111923,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream" } } ], !1; - if (void 0 === data.action) return validate450.errors = [ { + if (void 0 === data.action) return validate460.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103053,7 +111931,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "action" } } ], !1; - if (void 0 === data.state) return validate450.errors = [ { + if (void 0 === data.state) return validate460.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103063,7 +111941,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.success) { let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate450.errors = [ { + if ("boolean" != typeof data0) return validate460.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -103071,7 +111949,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate450.errors = [ { + if (!0 !== data0) return validate460.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -103084,7 +111962,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.stream) { let _errs4 = 0; - if ("string" != typeof data.stream) return validate450.errors = [ { + if ("string" != typeof data.stream) return validate460.errors = [ { instancePath: instancePath + "/stream", schemaPath: "#/properties/stream/type", keyword: "type", @@ -103097,7 +111975,7 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.action) { let data2 = data.action, _errs6 = 0; - if ("string" != typeof data2) return validate450.errors = [ { + if ("string" != typeof data2) return validate460.errors = [ { instancePath: instancePath + "/action", schemaPath: "#/properties/action/type", keyword: "type", @@ -103105,19 +111983,19 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("start" !== data2 && "stop" !== data2) return validate450.errors = [ { + if ("start" !== data2 && "stop" !== data2) return validate460.errors = [ { instancePath: instancePath + "/action", schemaPath: "#/properties/action/enum", keyword: "enum", params: { - allowedValues: schema542.properties.action.enum + allowedValues: schema554.properties.action.enum } } ], !1; var valid2 = 0 === _errs6; } else valid2 = !0; if (valid2 && void 0 !== data.state) { let data3 = data.state; - if ("string" != typeof data3) return validate450.errors = [ { + if ("string" != typeof data3) return validate460.errors = [ { instancePath: instancePath + "/state", schemaPath: "#/properties/state/type", keyword: "type", @@ -103125,27 +112003,27 @@ function validate450(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("starting" !== data3 && "idle" !== data3) return validate450.errors = [ { + if ("starting" !== data3 && "idle" !== data3) return validate460.errors = [ { instancePath: instancePath + "/state", schemaPath: "#/properties/state/enum", keyword: "enum", params: { - allowedValues: schema542.properties.state.enum + allowedValues: schema554.properties.state.enum } } ], !1; } } } - return validate450.errors = [], !0; + return validate460.errors = [], !0; } -validate450.evaluated = { +validate460.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamRecordingRequest = validate449, schema541 = { +var __contract_StreamRecordingRequest = validate459, schema553 = { type: "object", properties: { action: { @@ -103157,10 +112035,10 @@ var __contract_StreamRecordingRequest = validate449, schema541 = { additionalProperties: !0 }; -function validate449(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate449.evaluated; +function validate459(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate459.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate449.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate459.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -103168,7 +112046,7 @@ function validate449(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.action) return validate449.errors = [ { + if (void 0 === data.action) return validate459.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103178,7 +112056,7 @@ function validate449(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.action) { let data0 = data.action; - if ("string" != typeof data0) return validate449.errors = [ { + if ("string" != typeof data0) return validate459.errors = [ { instancePath: instancePath + "/action", schemaPath: "#/properties/action/type", keyword: "type", @@ -103186,25 +112064,25 @@ function validate449(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("start" !== data0 && "stop" !== data0) return validate449.errors = [ { + if ("start" !== data0 && "stop" !== data0) return validate459.errors = [ { instancePath: instancePath + "/action", schemaPath: "#/properties/action/enum", keyword: "enum", params: { - allowedValues: schema541.properties.action.enum + allowedValues: schema553.properties.action.enum } } ], !1; } - return validate449.errors = [], !0; + return validate459.errors = [], !0; } -validate449.evaluated = { +validate459.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamRecordingState = validate448, schema540 = { +var __contract_StreamRecordingState = validate458, schema552 = { type: "object", properties: { stream: { @@ -103235,10 +112113,10 @@ var __contract_StreamRecordingState = validate448, schema540 = { additionalProperties: !0 }; -function validate448(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate448.evaluated; +function validate458(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate458.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate448.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -103246,7 +112124,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.stream) return validate448.errors = [ { + if (void 0 === data.stream) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103254,7 +112132,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream" } } ], !1; - if (void 0 === data.state) return validate448.errors = [ { + if (void 0 === data.state) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103262,7 +112140,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "state" } } ], !1; - if (void 0 === data.recording_active) return validate448.errors = [ { + if (void 0 === data.recording_active) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103270,7 +112148,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_active" } } ], !1; - if (void 0 === data.capture_method) return validate448.errors = [ { + if (void 0 === data.capture_method) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103278,7 +112156,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "capture_method" } } ], !1; - if (void 0 === data.recording_id) return validate448.errors = [ { + if (void 0 === data.recording_id) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103286,7 +112164,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "recording_id" } } ], !1; - if (void 0 === data.manual_control_allowed) return validate448.errors = [ { + if (void 0 === data.manual_control_allowed) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103294,7 +112172,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "manual_control_allowed" } } ], !1; - if (void 0 === data.manual_control_reason) return validate448.errors = [ { + if (void 0 === data.manual_control_reason) return validate458.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103304,7 +112182,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.stream) { let _errs2 = 0; - if ("string" != typeof data.stream) return validate448.errors = [ { + if ("string" != typeof data.stream) return validate458.errors = [ { instancePath: instancePath + "/stream", schemaPath: "#/properties/stream/type", keyword: "type", @@ -103317,7 +112195,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.state) { let data1 = data.state, _errs4 = 0; - if ("string" != typeof data1) return validate448.errors = [ { + if ("string" != typeof data1) return validate458.errors = [ { instancePath: instancePath + "/state", schemaPath: "#/properties/state/type", keyword: "type", @@ -103325,12 +112203,12 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("starting" !== data1 && "recording" !== data1 && "idle" !== data1) return validate448.errors = [ { + if ("starting" !== data1 && "recording" !== data1 && "idle" !== data1) return validate458.errors = [ { instancePath: instancePath + "/state", schemaPath: "#/properties/state/enum", keyword: "enum", params: { - allowedValues: schema540.properties.state.enum + allowedValues: schema552.properties.state.enum } } ], !1; var valid1 = 0 === _errs4; @@ -103338,7 +112216,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.recording_active) { let _errs6 = 0; - if ("boolean" != typeof data.recording_active) return validate448.errors = [ { + if ("boolean" != typeof data.recording_active) return validate458.errors = [ { instancePath: instancePath + "/recording_active", schemaPath: "#/properties/recording_active/type", keyword: "type", @@ -103351,12 +112229,12 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.capture_method) { let data3 = data.capture_method, _errs8 = 0; - if ("string" != typeof data3 && null !== data3) return validate448.errors = [ { + if ("string" != typeof data3 && null !== data3) return validate458.errors = [ { instancePath: instancePath + "/capture_method", schemaPath: "#/properties/capture_method/type", keyword: "type", params: { - type: schema540.properties.capture_method.type + type: schema552.properties.capture_method.type } } ], !1; var valid3 = 0 === _errs8; @@ -103364,12 +112242,12 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data.recording_id) { let data4 = data.recording_id, _errs10 = 0; - if (("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) && null !== data4) return validate448.errors = [ { + if (("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) && null !== data4) return validate458.errors = [ { instancePath: instancePath + "/recording_id", schemaPath: "#/properties/recording_id/type", keyword: "type", params: { - type: schema540.properties.recording_id.type + type: schema552.properties.recording_id.type } } ], !1; var valid4 = 0 === _errs10; @@ -103377,7 +112255,7 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data.manual_control_allowed) { let _errs12 = 0; - if ("boolean" != typeof data.manual_control_allowed) return validate448.errors = [ { + if ("boolean" != typeof data.manual_control_allowed) return validate458.errors = [ { instancePath: instancePath + "/manual_control_allowed", schemaPath: "#/properties/manual_control_allowed/type", keyword: "type", @@ -103389,20 +112267,20 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD } else valid5 = !0; if (valid5 && void 0 !== data.manual_control_reason) { let data6 = data.manual_control_reason; - if ("string" != typeof data6 && null !== data6) return validate448.errors = [ { + if ("string" != typeof data6 && null !== data6) return validate458.errors = [ { instancePath: instancePath + "/manual_control_reason", schemaPath: "#/properties/manual_control_reason/type", keyword: "type", params: { - type: schema540.properties.manual_control_reason.type + type: schema552.properties.manual_control_reason.type } } ], !1; - if ("read_only" !== data6 && "continuous_recording" !== data6 && null !== data6) return validate448.errors = [ { + if ("read_only" !== data6 && "continuous_recording" !== data6 && null !== data6) return validate458.errors = [ { instancePath: instancePath + "/manual_control_reason", schemaPath: "#/properties/manual_control_reason/enum", keyword: "enum", params: { - allowedValues: schema540.properties.manual_control_reason.enum + allowedValues: schema552.properties.manual_control_reason.enum } } ], !1; } @@ -103411,21 +112289,21 @@ function validate448(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate448.errors = [], !0; + return validate458.errors = [], !0; } -validate448.evaluated = { +validate458.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamRefreshAccepted = validate447; +var __contract_StreamRefreshAccepted = validate457; -function validate447(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate447.evaluated; +function validate457(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate457.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate447.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate457.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -103433,7 +112311,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.success) return validate447.errors = [ { + if (void 0 === data.success) return validate457.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103441,7 +112319,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "success" } } ], !1; - if (void 0 === data.message) return validate447.errors = [ { + if (void 0 === data.message) return validate457.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103449,7 +112327,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "message" } } ], !1; - if (void 0 === data.stream) return validate447.errors = [ { + if (void 0 === data.stream) return validate457.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103459,7 +112337,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.success) { let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate447.errors = [ { + if ("boolean" != typeof data0) return validate457.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/type", keyword: "type", @@ -103467,7 +112345,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data0) return validate447.errors = [ { + if (!0 !== data0) return validate457.errors = [ { instancePath: instancePath + "/success", schemaPath: "#/properties/success/const", keyword: "const", @@ -103480,7 +112358,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.message) { let _errs4 = 0; - if ("string" != typeof data.message) return validate447.errors = [ { + if ("string" != typeof data.message) return validate457.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", @@ -103493,7 +112371,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.stream) { let _errs6 = 0; - if ("string" != typeof data.stream) return validate447.errors = [ { + if ("string" != typeof data.stream) return validate457.errors = [ { instancePath: instancePath + "/stream", schemaPath: "#/properties/stream/type", keyword: "type", @@ -103506,7 +112384,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.coalesced) { let _errs8 = 0; - if ("boolean" != typeof data.coalesced) return validate447.errors = [ { + if ("boolean" != typeof data.coalesced) return validate457.errors = [ { instancePath: instancePath + "/coalesced", schemaPath: "#/properties/coalesced/type", keyword: "type", @@ -103518,7 +112396,7 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD } else valid3 = !0; if (valid3 && void 0 !== data.retry_after_seconds) { let data4 = data.retry_after_seconds; - if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate447.errors = [ { + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate457.errors = [ { instancePath: instancePath + "/retry_after_seconds", schemaPath: "#/properties/retry_after_seconds/type", keyword: "type", @@ -103530,21 +112408,21 @@ function validate447(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate447.errors = [], !0; + return validate457.errors = [], !0; } -validate447.evaluated = { +validate457.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamRetention = validate458; +var __contract_StreamRetention = validate468; -function validate458(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate458.evaluated; +function validate468(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate468.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate458.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate468.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -103552,7 +112430,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.stream_name) return validate458.errors = [ { + if (void 0 === data.stream_name) return validate468.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103560,7 +112438,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "stream_name" } } ], !1; - if (void 0 === data.retention_days) return validate458.errors = [ { + if (void 0 === data.retention_days) return validate468.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103568,7 +112446,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "retention_days" } } ], !1; - if (void 0 === data.detection_retention_days) return validate458.errors = [ { + if (void 0 === data.detection_retention_days) return validate468.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103576,7 +112454,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "detection_retention_days" } } ], !1; - if (void 0 === data.max_storage_mb) return validate458.errors = [ { + if (void 0 === data.max_storage_mb) return validate468.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -103586,7 +112464,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.stream_name) { let _errs2 = 0; - if ("string" != typeof data.stream_name) return validate458.errors = [ { + if ("string" != typeof data.stream_name) return validate468.errors = [ { instancePath: instancePath + "/stream_name", schemaPath: "#/properties/stream_name/type", keyword: "type", @@ -103599,7 +112477,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.retention_days) { let data1 = data.retention_days, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate458.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate468.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/type", keyword: "type", @@ -103608,7 +112486,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs4 && "number" == typeof data1 && isFinite(data1)) { - if (data1 > 365 || isNaN(data1)) return validate458.errors = [ { + if (data1 > 365 || isNaN(data1)) return validate468.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/maximum", keyword: "maximum", @@ -103617,7 +112495,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD limit: 365 } } ], !1; - if (data1 < -1 || isNaN(data1)) return validate458.errors = [ { + if (data1 < -1 || isNaN(data1)) return validate468.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/minimum", keyword: "minimum", @@ -103632,7 +112510,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD if (valid1) { if (void 0 !== data.detection_retention_days) { let data2 = data.detection_retention_days, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate458.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate468.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/type", keyword: "type", @@ -103641,7 +112519,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs6 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 365 || isNaN(data2)) return validate458.errors = [ { + if (data2 > 365 || isNaN(data2)) return validate468.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/maximum", keyword: "maximum", @@ -103650,7 +112528,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD limit: 365 } } ], !1; - if (data2 < -1 || isNaN(data2)) return validate458.errors = [ { + if (data2 < -1 || isNaN(data2)) return validate468.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/minimum", keyword: "minimum", @@ -103665,7 +112543,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data.max_storage_mb) { let data3 = data.max_storage_mb, _errs8 = 0; - if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate458.errors = [ { + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate468.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/type", keyword: "type", @@ -103674,7 +112552,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs8 && "number" == typeof data3 && isFinite(data3)) { - if (data3 > 9007199254740991 || isNaN(data3)) return validate458.errors = [ { + if (data3 > 9007199254740991 || isNaN(data3)) return validate468.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/maximum", keyword: "maximum", @@ -103683,7 +112561,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data3 < 0 || isNaN(data3)) return validate458.errors = [ { + if (data3 < 0 || isNaN(data3)) return validate468.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/minimum", keyword: "minimum", @@ -103695,7 +112573,7 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD } var valid3 = 0 === _errs8; } else valid3 = !0; - if (valid3 && void 0 !== data.message && "string" != typeof data.message) return validate458.errors = [ { + if (valid3 && void 0 !== data.message && "string" != typeof data.message) return validate468.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", @@ -103706,21 +112584,21 @@ function validate458(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate458.errors = [], !0; + return validate468.errors = [], !0; } -validate458.evaluated = { +validate468.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamRetentionRequest = validate457; +var __contract_StreamRetentionRequest = validate467; -function validate457(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate457.evaluated; +function validate467(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate467.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate457.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate467.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -103730,7 +112608,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.retention_days) { let data0 = data.retention_days, _errs2 = 0; - if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate457.errors = [ { + if ("number" != typeof data0 || data0 % 1 || isNaN(data0) || !isFinite(data0)) return validate467.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/type", keyword: "type", @@ -103739,7 +112617,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs2 && "number" == typeof data0 && isFinite(data0)) { - if (data0 > 365 || isNaN(data0)) return validate457.errors = [ { + if (data0 > 365 || isNaN(data0)) return validate467.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/maximum", keyword: "maximum", @@ -103748,7 +112626,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD limit: 365 } } ], !1; - if (data0 < -1 || isNaN(data0)) return validate457.errors = [ { + if (data0 < -1 || isNaN(data0)) return validate467.errors = [ { instancePath: instancePath + "/retention_days", schemaPath: "#/properties/retention_days/minimum", keyword: "minimum", @@ -103763,7 +112641,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.detection_retention_days) { let data1 = data.detection_retention_days, _errs4 = 0; - if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate457.errors = [ { + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate467.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/type", keyword: "type", @@ -103772,7 +112650,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs4 && "number" == typeof data1 && isFinite(data1)) { - if (data1 > 365 || isNaN(data1)) return validate457.errors = [ { + if (data1 > 365 || isNaN(data1)) return validate467.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/maximum", keyword: "maximum", @@ -103781,7 +112659,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD limit: 365 } } ], !1; - if (data1 < -1 || isNaN(data1)) return validate457.errors = [ { + if (data1 < -1 || isNaN(data1)) return validate467.errors = [ { instancePath: instancePath + "/detection_retention_days", schemaPath: "#/properties/detection_retention_days/minimum", keyword: "minimum", @@ -103795,7 +112673,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD } else valid1 = !0; if (valid1 && void 0 !== data.max_storage_mb) { let data2 = data.max_storage_mb, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate457.errors = [ { + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate467.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/type", keyword: "type", @@ -103804,7 +112682,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; if (0 === _errs6 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 9007199254740991 || isNaN(data2)) return validate457.errors = [ { + if (data2 > 9007199254740991 || isNaN(data2)) return validate467.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/maximum", keyword: "maximum", @@ -103813,7 +112691,7 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD limit: 9007199254740991 } } ], !1; - if (data2 < 0 || isNaN(data2)) return validate457.errors = [ { + if (data2 < 0 || isNaN(data2)) return validate467.errors = [ { instancePath: instancePath + "/max_storage_mb", schemaPath: "#/properties/max_storage_mb/minimum", keyword: "minimum", @@ -103825,19 +112703,19 @@ function validate457(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate457.errors = [], !0; + return validate467.errors = [], !0; } -validate457.evaluated = { +validate467.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamSingleResponse = validate439; +var __contract_StreamSingleResponse = validate449; -function validate439(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate439.evaluated; +function validate449(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate449.evaluated; evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); let _errs0 = errors, valid0 = !1, _errs1 = errors; validate25(data, { @@ -103864,13 +112742,13 @@ function validate439(data, {instancePath: instancePath = "", parentData: parentD keyword: "anyOf", params: {} }; - return vErrors.push(err0), errors++, validate439.errors = vErrors, !1; + return vErrors.push(err0), errors++, validate449.errors = vErrors, !1; } - return errors = _errs0, vErrors.length = _errs0, validate439.errors = vErrors, evaluated0.props = props0, + return errors = _errs0, vErrors.length = _errs0, validate449.errors = vErrors, evaluated0.props = props0, 0 === errors; } -validate439.evaluated = { +validate449.evaluated = { dynamicProps: !0, dynamicItems: !1 }; @@ -104579,6 +113457,27 @@ var __contract_StreamUpdateRequest = validate63, schema60 = { set_privacy_mode: { type: "null", description: "A boolean selects the separate privacy-only form; null is ignored in the ordinary-update form." + }, + observation_evidence_enabled: { + type: "boolean", + description: "INT_01 per-camera opt-in for observation evidence for integrations (contract section 13). Off by default. Requires a bird-capable object detector for bird integrations. Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + observation_labels: { + type: "string", + maxLength: 511, + description: "Comma-separated detector labels eligible for observation evidence; empty means every object label. At most 16 labels, each 1..31 printable ASCII bytes after trimming surrounding whitespace; empty entries are rejected. Matching is ASCII case-insensitive on the original detector label (contract section 13). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + observation_max_age_days: { + type: "integer", + minimum: 1, + maximum: 90, + description: "Evidence media maximum age in days; must be an integer 1..90 (default 7). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." + }, + observation_max_bytes: { + type: "integer", + minimum: 67108864, + maximum: 1099511627776, + description: "Evidence media byte budget; must be an integer 67108864 (64 MiB)..1099511627776 (1 TiB) (default 2 GiB). Requires camera.configure (all stream writes do). Null or a wrong type is rejected with 400; omission preserves the current value. Takes effect without a restart." } }, required: [], @@ -105488,14 +114387,123 @@ function validate61(data, {instancePath: instancePath = "", parentData: parentDa } ], !1; var valid60 = _errs122 === errors; } else valid60 = !0; - if (valid60 && void 0 !== data.set_privacy_mode && null !== data.set_privacy_mode) return validate61.errors = [ { - instancePath: instancePath + "/set_privacy_mode", - schemaPath: "#/properties/set_privacy_mode/type", - keyword: "type", - params: { - type: "null" + if (valid60) { + if (void 0 !== data.set_privacy_mode) { + let _errs124 = errors; + if (null !== data.set_privacy_mode) return validate61.errors = [ { + instancePath: instancePath + "/set_privacy_mode", + schemaPath: "#/properties/set_privacy_mode/type", + keyword: "type", + params: { + type: "null" + } + } ], !1; + var valid61 = _errs124 === errors; + } else valid61 = !0; + if (valid61) { + if (void 0 !== data.observation_evidence_enabled) { + let _errs126 = errors; + if ("boolean" != typeof data.observation_evidence_enabled) return validate61.errors = [ { + instancePath: instancePath + "/observation_evidence_enabled", + schemaPath: "#/properties/observation_evidence_enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid62 = _errs126 === errors; + } else valid62 = !0; + if (valid62) { + if (void 0 !== data.observation_labels) { + let data57 = data.observation_labels, _errs128 = errors; + if (errors === _errs128) { + if ("string" != typeof data57) return validate61.errors = [ { + instancePath: instancePath + "/observation_labels", + schemaPath: "#/properties/observation_labels/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data57) > 511) return validate61.errors = [ { + instancePath: instancePath + "/observation_labels", + schemaPath: "#/properties/observation_labels/maxLength", + keyword: "maxLength", + params: { + limit: 511 + } + } ], !1; + } + var valid63 = _errs128 === errors; + } else valid63 = !0; + if (valid63) { + if (void 0 !== data.observation_max_age_days) { + let data58 = data.observation_max_age_days, _errs130 = errors; + if ("number" != typeof data58 || data58 % 1 || isNaN(data58) || !isFinite(data58)) return validate61.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs130 && "number" == typeof data58 && isFinite(data58)) { + if (data58 > 90 || isNaN(data58)) return validate61.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 90 + } + } ], !1; + if (data58 < 1 || isNaN(data58)) return validate61.errors = [ { + instancePath: instancePath + "/observation_max_age_days", + schemaPath: "#/properties/observation_max_age_days/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 1 + } + } ], !1; + } + var valid64 = _errs130 === errors; + } else valid64 = !0; + if (valid64 && void 0 !== data.observation_max_bytes) { + let data59 = data.observation_max_bytes, _errs132 = errors; + if ("number" != typeof data59 || data59 % 1 || isNaN(data59) || !isFinite(data59)) return validate61.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs132 && "number" == typeof data59 && isFinite(data59)) { + if (data59 > 1099511627776 || isNaN(data59)) return validate61.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1099511627776 + } + } ], !1; + if (data59 < 67108864 || isNaN(data59)) return validate61.errors = [ { + instancePath: instancePath + "/observation_max_bytes", + schemaPath: "#/properties/observation_max_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 67108864 + } + } ], !1; + } + } + } + } } - } ], !1; + } } } } @@ -105546,116 +114554,1206 @@ function validate61(data, {instancePath: instancePath = "", parentData: parentDa } } } - } - return validate61.errors = vErrors, 0 === errors; -} - -function validate63(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let vErrors = [], errors = 0, evaluated0 = validate63.evaluated; - evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); - let _errs0 = errors, valid0 = !1, _errs1 = errors; - validate61(data, { + } + return validate61.errors = vErrors, 0 === errors; +} + +function validate63(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate63.evaluated; + evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0); + let _errs0 = errors, valid0 = !1, _errs1 = errors; + validate61(data, { + instancePath: instancePath, + parentData: parentData, + parentDataProperty: parentDataProperty, + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate61.errors), errors = vErrors.length); + var _valid0 = _errs1 === errors; + if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; + let _errs2 = errors; + if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.set_privacy_mode) { + let err0 = { + instancePath: instancePath, + schemaPath: "#/$defs/StreamPrivacyUpdate/required", + keyword: "required", + params: { + missingProperty: "set_privacy_mode" + } + }; + vErrors.push(err0), errors++; + } else { + let _errs5 = errors; + for (let key0 in data) if ("set_privacy_mode" !== key0 && "name" !== key0) { + let err1 = { + instancePath: instancePath, + schemaPath: "#/$defs/StreamPrivacyUpdate/additionalProperties", + keyword: "additionalProperties", + params: { + additionalProperty: key0 + } + }; + vErrors.push(err1), errors++; + break; + } + if (_errs5 === errors) { + if (void 0 !== data.set_privacy_mode) { + let _errs6 = errors; + if ("boolean" != typeof data.set_privacy_mode) { + let err2 = { + instancePath: instancePath + "/set_privacy_mode", + schemaPath: "#/$defs/StreamPrivacyUpdate/properties/set_privacy_mode/type", + keyword: "type", + params: { + type: "boolean" + } + }; + vErrors.push(err2), errors++; + } + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2 && void 0 !== data.name && "string" != typeof data.name) { + let err3 = { + instancePath: instancePath + "/name", + schemaPath: "#/$defs/StreamPrivacyUpdate/properties/name/type", + keyword: "type", + params: { + type: "string" + } + }; + vErrors.push(err3), errors++; + } + } + } else { + let err4 = { + instancePath: instancePath, + schemaPath: "#/$defs/StreamPrivacyUpdate/type", + keyword: "type", + params: { + type: "object" + } + }; + vErrors.push(err4), errors++; + } + if (_valid0 = _errs2 === errors, valid0 = valid0 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), + !valid0) { + let err5 = { + instancePath: instancePath, + schemaPath: "#/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err5), errors++, validate63.errors = vErrors, !1; + } + return errors = _errs0, vErrors.length = _errs0, validate63.errors = vErrors, evaluated0.props = props0, + 0 === errors; +} + +validate61.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate63.evaluated = { + dynamicProps: !0, + dynamicItems: !1 +}; + +var __contract_StreamZones = validate463; + +function validate463(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate463.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate463.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.zones) return validate463.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "zones" + } + } ], !1; + if (void 0 !== data.zones) { + let data0 = data.zones; + if (!Array.isArray(data0)) return validate463.errors = [ { + instancePath: instancePath + "/zones", + schemaPath: "#/properties/zones/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.id) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "id" + } + } ], !1; + if (void 0 === data1.name) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "name" + } + } ], !1; + if (void 0 === data1.enabled) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "enabled" + } + } ], !1; + if (void 0 === data1.color) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "color" + } + } ], !1; + if (void 0 === data1.polygon) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "polygon" + } + } ], !1; + if (void 0 === data1.filter_classes) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "filter_classes" + } + } ], !1; + if (void 0 === data1.min_confidence) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "min_confidence" + } + } ], !1; + if (void 0 === data1.stream_name) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZone/required", + keyword: "required", + params: { + missingProperty: "stream_name" + } + } ], !1; + if (void 0 !== data1.id) { + let data2 = data1.id, _errs8 = 0; + if (0 === _errs8) { + if ("string" != typeof data2) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/id", + schemaPath: "#/$defs/DetectionZone/properties/id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 63) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/id", + schemaPath: "#/$defs/DetectionZone/properties/id/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + if (func1(data2) < 1) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/id", + schemaPath: "#/$defs/DetectionZone/properties/id/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data1.name) { + let data3 = data1.name, _errs10 = 0; + if (0 === _errs10) { + if ("string" != typeof data3) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/name", + schemaPath: "#/$defs/DetectionZone/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data3) > 63) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/name", + schemaPath: "#/$defs/DetectionZone/properties/name/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data1.enabled) { + let _errs12 = 0; + if ("boolean" != typeof data1.enabled) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/enabled", + schemaPath: "#/$defs/DetectionZone/properties/enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data1.color) { + let data5 = data1.color, _errs14 = 0; + if (0 === _errs14) { + if ("string" != typeof data5) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/color", + schemaPath: "#/$defs/DetectionZone/properties/color/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern197.test(data5)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/color", + schemaPath: "#/$defs/DetectionZone/properties/color/pattern", + keyword: "pattern", + params: { + pattern: "^#[0-9a-fA-F]{6}$" + } + } ], !1; + } + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data1.polygon) { + let data6 = data1.polygon, _errs16 = 0; + if (0 === _errs16) { + if (!Array.isArray(data6)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon", + schemaPath: "#/$defs/DetectionZone/properties/polygon/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data6.length > 32) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon", + schemaPath: "#/$defs/DetectionZone/properties/polygon/maxItems", + keyword: "maxItems", + params: { + limit: 32 + } + } ], !1; + if (data6.length < 3) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon", + schemaPath: "#/$defs/DetectionZone/properties/polygon/minItems", + keyword: "minItems", + params: { + limit: 3 + } + } ], !1; + { + let len1 = data6.length; + for (let i1 = 0; i1 < len1; i1++) { + let data7 = data6[i1], _errs18 = 0; + if (0 === _errs18) { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data7.x) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/required", + keyword: "required", + params: { + missingProperty: "x" + } + } ], !1; + if (void 0 === data7.y) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/required", + keyword: "required", + params: { + missingProperty: "y" + } + } ], !1; + if (void 0 !== data7.x) { + let data8 = data7.x, _errs21 = 0; + if (0 === _errs21) { + if ("number" != typeof data8 || !isFinite(data8)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/x/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data8 > 1 || isNaN(data8)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/x/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data8 < 0 || isNaN(data8)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/x/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid9 = 0 === _errs21; + } else valid9 = !0; + if (valid9 && void 0 !== data7.y) { + let data9 = data7.y; + if ("number" != typeof data9 || !isFinite(data9)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/y/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data9 > 1 || isNaN(data9)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/y/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data9 < 0 || isNaN(data9)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", + schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/y/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + if (0 !== _errs18) break; + } + } + } + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data1.filter_classes) { + let data10 = data1.filter_classes, _errs25 = 0; + if (0 === _errs25) { + if ("string" != typeof data10) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/filter_classes", + schemaPath: "#/$defs/DetectionZone/properties/filter_classes/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data10) > 255) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/filter_classes", + schemaPath: "#/$defs/DetectionZone/properties/filter_classes/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + } + var valid11 = 0 === _errs25; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data1.min_confidence) { + let data11 = data1.min_confidence, _errs27 = 0; + if (0 === _errs27) { + if ("number" != typeof data11 || !isFinite(data11)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/min_confidence", + schemaPath: "#/$defs/DetectionZone/properties/min_confidence/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data11 > 1 || isNaN(data11)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/min_confidence", + schemaPath: "#/$defs/DetectionZone/properties/min_confidence/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data11 < 0 || isNaN(data11)) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/min_confidence", + schemaPath: "#/$defs/DetectionZone/properties/min_confidence/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid12 = 0 === _errs27; + } else valid12 = !0; + if (valid12 && void 0 !== data1.stream_name && "string" != typeof data1.stream_name) return validate463.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/stream_name", + schemaPath: "#/$defs/DetectionZone/properties/stream_name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + } + } + } + } + if (0 !== _errs4) break; + } + } + } + return validate463.errors = [], !0; +} + +validate463.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_StreamZonesDeleted = validate466; + +function validate466(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate466.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate466.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate466.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.message) return validate466.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 !== data.success) { + let data0 = data.success, _errs2 = 0; + if ("boolean" != typeof data0) return validate466.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data0) return validate466.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + return valid0 && void 0 !== data.message && "string" != typeof data.message ? (validate466.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1) : (validate466.errors = [], !0); +} + +validate466.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_StreamZonesRequest = validate464; + +function validate464(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate464.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate464.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.zones) return validate464.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "zones" + } + } ], !1; + if (void 0 !== data.zones) { + let data0 = data.zones; + if (!Array.isArray(data0)) return validate464.errors = [ { + instancePath: instancePath + "/zones", + schemaPath: "#/properties/zones/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data0.length > 16) return validate464.errors = [ { + instancePath: instancePath + "/zones", + schemaPath: "#/properties/zones/maxItems", + keyword: "maxItems", + params: { + limit: 16 + } + } ], !1; + { + let len0 = data0.length; + for (let i0 = 0; i0 < len0; i0++) { + let data1 = data0[i0], _errs4 = 0; + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZoneWrite/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data1.id) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZoneWrite/required", + keyword: "required", + params: { + missingProperty: "id" + } + } ], !1; + if (void 0 === data1.name) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZoneWrite/required", + keyword: "required", + params: { + missingProperty: "name" + } + } ], !1; + if (void 0 === data1.polygon) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0, + schemaPath: "#/$defs/DetectionZoneWrite/required", + keyword: "required", + params: { + missingProperty: "polygon" + } + } ], !1; + if (void 0 !== data1.id) { + let data2 = data1.id, _errs8 = 0; + if (0 === _errs8) { + if ("string" != typeof data2) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/id", + schemaPath: "#/$defs/DetectionZoneWrite/properties/id/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data2) > 63) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/id", + schemaPath: "#/$defs/DetectionZoneWrite/properties/id/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + if (func1(data2) < 1) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/id", + schemaPath: "#/$defs/DetectionZoneWrite/properties/id/minLength", + keyword: "minLength", + params: { + limit: 1 + } + } ], !1; + } + var valid3 = 0 === _errs8; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data1.name) { + let data3 = data1.name, _errs10 = 0; + if (0 === _errs10) { + if ("string" != typeof data3) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/name", + schemaPath: "#/$defs/DetectionZoneWrite/properties/name/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data3) > 63) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/name", + schemaPath: "#/$defs/DetectionZoneWrite/properties/name/maxLength", + keyword: "maxLength", + params: { + limit: 63 + } + } ], !1; + } + var valid4 = 0 === _errs10; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data1.enabled) { + let _errs12 = 0; + if ("boolean" != typeof data1.enabled) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/enabled", + schemaPath: "#/$defs/DetectionZoneWrite/properties/enabled/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid5 = 0 === _errs12; + } else valid5 = !0; + if (valid5) { + if (void 0 !== data1.color) { + let data5 = data1.color, _errs14 = 0; + if (0 === _errs14) { + if ("string" != typeof data5) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/color", + schemaPath: "#/$defs/DetectionZoneWrite/properties/color/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (!pattern197.test(data5)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/color", + schemaPath: "#/$defs/DetectionZoneWrite/properties/color/pattern", + keyword: "pattern", + params: { + pattern: "^#[0-9a-fA-F]{6}$" + } + } ], !1; + } + var valid6 = 0 === _errs14; + } else valid6 = !0; + if (valid6) { + if (void 0 !== data1.polygon) { + let data6 = data1.polygon, _errs16 = 0; + if (0 === _errs16) { + if (!Array.isArray(data6)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/type", + keyword: "type", + params: { + type: "array" + } + } ], !1; + if (data6.length > 32) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/maxItems", + keyword: "maxItems", + params: { + limit: 32 + } + } ], !1; + if (data6.length < 3) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/minItems", + keyword: "minItems", + params: { + limit: 3 + } + } ], !1; + { + let len1 = data6.length; + for (let i1 = 0; i1 < len1; i1++) { + let data7 = data6[i1], _errs18 = 0; + if (0 === _errs18) { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data7.x) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/required", + keyword: "required", + params: { + missingProperty: "x" + } + } ], !1; + if (void 0 === data7.y) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/required", + keyword: "required", + params: { + missingProperty: "y" + } + } ], !1; + if (void 0 !== data7.x) { + let data8 = data7.x, _errs21 = 0; + if (0 === _errs21) { + if ("number" != typeof data8 || !isFinite(data8)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/x/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data8 > 1 || isNaN(data8)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/x/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data8 < 0 || isNaN(data8)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/x/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid9 = 0 === _errs21; + } else valid9 = !0; + if (valid9 && void 0 !== data7.y) { + let data9 = data7.y; + if ("number" != typeof data9 || !isFinite(data9)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/y/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data9 > 1 || isNaN(data9)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/y/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data9 < 0 || isNaN(data9)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", + schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/y/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + if (0 !== _errs18) break; + } + } + } + var valid7 = 0 === _errs16; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data1.filter_classes) { + let data10 = data1.filter_classes, _errs25 = 0; + if (0 === _errs25) { + if ("string" != typeof data10) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/filter_classes", + schemaPath: "#/$defs/DetectionZoneWrite/properties/filter_classes/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + if (func1(data10) > 255) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/filter_classes", + schemaPath: "#/$defs/DetectionZoneWrite/properties/filter_classes/maxLength", + keyword: "maxLength", + params: { + limit: 255 + } + } ], !1; + } + var valid11 = 0 === _errs25; + } else valid11 = !0; + if (valid11 && void 0 !== data1.min_confidence) { + let data11 = data1.min_confidence; + if ("number" != typeof data11 || !isFinite(data11)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/min_confidence", + schemaPath: "#/$defs/DetectionZoneWrite/properties/min_confidence/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data11 > 1 || isNaN(data11)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/min_confidence", + schemaPath: "#/$defs/DetectionZoneWrite/properties/min_confidence/maximum", + keyword: "maximum", + params: { + comparison: "<=", + limit: 1 + } + } ], !1; + if (data11 < 0 || isNaN(data11)) return validate464.errors = [ { + instancePath: instancePath + "/zones/" + i0 + "/min_confidence", + schemaPath: "#/$defs/DetectionZoneWrite/properties/min_confidence/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + } + } + if (0 !== _errs4) break; + } + } + } + return validate464.errors = [], !0; +} + +validate464.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_StreamZonesSaved = validate465; + +function validate465(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate465.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate465.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate465.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.message) return validate465.errors = [ { instancePath: instancePath, - parentData: parentData, - parentDataProperty: parentDataProperty, - rootData: rootData, - dynamicAnchors: dynamicAnchors - }) || (vErrors = vErrors.concat(validate61.errors), errors = vErrors.length); - var _valid0 = _errs1 === errors; - if (valid0 = valid0 || _valid0, _valid0) var props0 = !0; - let _errs2 = errors; - if (errors == errors) if (data && "object" == typeof data && !Array.isArray(data)) if (void 0 === data.set_privacy_mode) { - let err0 = { - instancePath: instancePath, - schemaPath: "#/$defs/StreamPrivacyUpdate/required", - keyword: "required", + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 === data.count) return validate465.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "count" + } + } ], !1; + if (void 0 !== data.success) { + let data0 = data.success, _errs2 = 0; + if ("boolean" != typeof data0) return validate465.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/type", + keyword: "type", params: { - missingProperty: "set_privacy_mode" + type: "boolean" } - }; - vErrors.push(err0), errors++; - } else { - let _errs5 = errors; - for (let key0 in data) if ("set_privacy_mode" !== key0 && "name" !== key0) { - let err1 = { - instancePath: instancePath, - schemaPath: "#/$defs/StreamPrivacyUpdate/additionalProperties", - keyword: "additionalProperties", + } ], !1; + if (!0 !== data0) return validate465.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.message) { + let _errs4 = 0; + if ("string" != typeof data.message) return validate465.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", params: { - additionalProperty: key0 + type: "string" } - }; - vErrors.push(err1), errors++; - break; - } - if (_errs5 === errors) { - if (void 0 !== data.set_privacy_mode) { - let _errs6 = errors; - if ("boolean" != typeof data.set_privacy_mode) { - let err2 = { - instancePath: instancePath + "/set_privacy_mode", - schemaPath: "#/$defs/StreamPrivacyUpdate/properties/set_privacy_mode/type", - keyword: "type", - params: { - type: "boolean" - } - }; - vErrors.push(err2), errors++; + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1 && void 0 !== data.count) { + let data2 = data.count, _errs6 = 0; + if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate465.errors = [ { + instancePath: instancePath + "/count", + schemaPath: "#/properties/count/type", + keyword: "type", + params: { + type: "integer" } - var valid2 = _errs6 === errors; - } else valid2 = !0; - if (valid2 && void 0 !== data.name && "string" != typeof data.name) { - let err3 = { - instancePath: instancePath + "/name", - schemaPath: "#/$defs/StreamPrivacyUpdate/properties/name/type", - keyword: "type", + } ], !1; + if (0 === _errs6 && "number" == typeof data2 && isFinite(data2)) { + if (data2 > 16 || isNaN(data2)) return validate465.errors = [ { + instancePath: instancePath + "/count", + schemaPath: "#/properties/count/maximum", + keyword: "maximum", params: { - type: "string" + comparison: "<=", + limit: 16 } - }; - vErrors.push(err3), errors++; + } ], !1; + if (data2 < 0 || isNaN(data2)) return validate465.errors = [ { + instancePath: instancePath + "/count", + schemaPath: "#/properties/count/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; } } - } else { - let err4 = { - instancePath: instancePath, - schemaPath: "#/$defs/StreamPrivacyUpdate/type", - keyword: "type", + } + return validate465.errors = [], !0; +} + +validate465.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_SuccessResponse = validate97; + +function validate97(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate97.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate97.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate97.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 !== data.success) { + let _errs2 = 0; + if (!0 !== data.success) return validate97.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", params: { - type: "object" + allowedValue: !0 } - }; - vErrors.push(err4), errors++; - } - if (_valid0 = _errs2 === errors, valid0 = valid0 || _valid0, _valid0 && !0 !== props0 && (props0 = !0), - !valid0) { - let err5 = { - instancePath: instancePath, - schemaPath: "#/anyOf", - keyword: "anyOf", + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.secret) return validate97.errors = [ { + instancePath: instancePath + "/secret", + schemaPath: "#/properties/secret/false schema", + keyword: "false schema", params: {} - }; - return vErrors.push(err5), errors++, validate63.errors = vErrors, !1; + } ], !1; + if (void 0 !== data.api_key) return validate97.errors = [ { + instancePath: instancePath + "/api_key", + schemaPath: "#/properties/api_key/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password) return validate97.errors = [ { + instancePath: instancePath + "/password", + schemaPath: "#/properties/password/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.password_hash) return validate97.errors = [ { + instancePath: instancePath + "/password_hash", + schemaPath: "#/properties/password_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.token_hash) return validate97.errors = [ { + instancePath: instancePath + "/token_hash", + schemaPath: "#/properties/token_hash/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_secret) return validate97.errors = [ { + instancePath: instancePath + "/totp_secret", + schemaPath: "#/properties/totp_secret/false schema", + keyword: "false schema", + params: {} + } ], !1; + if (void 0 !== data.totp_token) return validate97.errors = [ { + instancePath: instancePath + "/totp_token", + schemaPath: "#/properties/totp_token/false schema", + keyword: "false schema", + params: {} + } ], !1; } - return errors = _errs0, vErrors.length = _errs0, validate63.errors = vErrors, evaluated0.props = props0, - 0 === errors; + return validate97.errors = [], !0; } -validate61.evaluated = { +validate97.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 -}, validate63.evaluated = { - dynamicProps: !0, +}; + +var __contract_SystemActionAccepted = validate278; + +function validate278(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate278.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + !data || "object" != typeof data || Array.isArray(data)) return validate278.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.success) return validate278.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "success" + } + } ], !1; + if (void 0 === data.message) return validate278.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 !== data.success) { + let data0 = data.success, _errs2 = 0; + if ("boolean" != typeof data0) return validate278.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + if (!0 !== data0) return validate278.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + return valid0 && void 0 !== data.message && "string" != typeof data.message ? (validate278.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1) : (validate278.errors = [], !0); +} + +validate278.evaluated = { + props: !0, + dynamicProps: !1, dynamicItems: !1 }; -var __contract_StreamZones = validate453; +var __contract_SystemBackupCreated = validate279; -function validate453(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate453.evaluated; +function validate279(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate279.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate453.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate279.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -105663,1195 +115761,1463 @@ function validate453(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.zones) return validate453.errors = [ { + if (void 0 === data.success) return validate279.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", params: { - missingProperty: "zones" + missingProperty: "success" } } ], !1; - if (void 0 !== data.zones) { - let data0 = data.zones; - if (!Array.isArray(data0)) return validate453.errors = [ { - instancePath: instancePath + "/zones", - schemaPath: "#/properties/zones/type", + if (void 0 === data.message) return validate279.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "message" + } + } ], !1; + if (void 0 === data.backupUrl) return validate279.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "backupUrl" + } + } ], !1; + if (void 0 === data.filename) return validate279.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "filename" + } + } ], !1; + if (void 0 !== data.success) { + let data0 = data.success, _errs2 = 0; + if ("boolean" != typeof data0) return validate279.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/type", keyword: "type", params: { - type: "array" + type: "boolean" } } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/type", + if (!0 !== data0) return validate279.errors = [ { + instancePath: instancePath + "/success", + schemaPath: "#/properties/success/const", + keyword: "const", + params: { + allowedValue: !0 + } + } ], !1; + var valid0 = 0 === _errs2; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.message) { + let _errs4 = 0; + if ("string" != typeof data.message) return validate279.errors = [ { + instancePath: instancePath + "/message", + schemaPath: "#/properties/message/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid1 = 0 === _errs4; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.backupUrl) { + let _errs6 = 0; + if ("string" != typeof data.backupUrl) return validate279.errors = [ { + instancePath: instancePath + "/backupUrl", + schemaPath: "#/properties/backupUrl/type", keyword: "type", params: { - type: "object" - } - } ], !1; - if (void 0 === data1.id) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", - params: { - missingProperty: "id" - } - } ], !1; - if (void 0 === data1.name) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data1.enabled) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", - params: { - missingProperty: "enabled" - } - } ], !1; - if (void 0 === data1.color) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", - params: { - missingProperty: "color" - } - } ], !1; - if (void 0 === data1.polygon) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", - params: { - missingProperty: "polygon" - } - } ], !1; - if (void 0 === data1.filter_classes) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", - params: { - missingProperty: "filter_classes" + type: "string" } } ], !1; - if (void 0 === data1.min_confidence) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", + var valid2 = 0 === _errs6; + } else valid2 = !0; + if (valid2 && void 0 !== data.filename && "string" != typeof data.filename) return validate279.errors = [ { + instancePath: instancePath + "/filename", + schemaPath: "#/properties/filename/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + } + } + return validate279.errors = [], !0; +} + +validate279.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}; + +var __contract_SystemInformation = validate263; + +function validate264(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let vErrors = [], errors = 0, evaluated0 = validate264.evaluated; + if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), + 0 === errors) { + if (!data || "object" != typeof data || Array.isArray(data)) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data.active) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "active" + } + } ], !1; + if (void 0 === data.accepted) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "accepted" + } + } ], !1; + if (void 0 === data.dropped) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "dropped" + } + } ], !1; + if (void 0 === data.evidence) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "evidence" + } + } ], !1; + if (void 0 === data.history) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "history" + } + } ], !1; + if (void 0 === data.publications) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "publications" + } + } ], !1; + if (void 0 === data.budget_exhausted_cameras) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "budget_exhausted_cameras" + } + } ], !1; + if (void 0 === data.cameras) return validate264.errors = [ { + instancePath: instancePath, + schemaPath: "#/required", + keyword: "required", + params: { + missingProperty: "cameras" + } + } ], !1; + if (void 0 !== data.active) { + let _errs2 = errors; + if ("boolean" != typeof data.active) return validate264.errors = [ { + instancePath: instancePath + "/active", + schemaPath: "#/properties/active/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid0 = _errs2 === errors; + } else valid0 = !0; + if (valid0) { + if (void 0 !== data.accepted) { + let data1 = data.accepted, _errs4 = errors; + if ("number" != typeof data1 || data1 % 1 || isNaN(data1) || !isFinite(data1)) return validate264.errors = [ { + instancePath: instancePath + "/accepted", + schemaPath: "#/properties/accepted/type", + keyword: "type", params: { - missingProperty: "min_confidence" + type: "integer" } } ], !1; - if (void 0 === data1.stream_name) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZone/required", - keyword: "required", + if (errors === _errs4 && "number" == typeof data1 && isFinite(data1) && (data1 < 0 || isNaN(data1))) return validate264.errors = [ { + instancePath: instancePath + "/accepted", + schemaPath: "#/properties/accepted/minimum", + keyword: "minimum", params: { - missingProperty: "stream_name" + comparison: ">=", + limit: 0 } } ], !1; - if (void 0 !== data1.id) { - let data2 = data1.id, _errs8 = 0; - if (0 === _errs8) { - if ("string" != typeof data2) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/id", - schemaPath: "#/$defs/DetectionZone/properties/id/type", + var valid1 = _errs4 === errors; + } else valid1 = !0; + if (valid1) { + if (void 0 !== data.dropped) { + let data2 = data.dropped, _errs6 = errors; + if (errors === _errs6) { + if (!data2 || "object" != typeof data2 || Array.isArray(data2)) return validate264.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/type", keyword: "type", params: { - type: "string" + type: "object" } } ], !1; - if (func1(data2) > 63) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/id", - schemaPath: "#/$defs/DetectionZone/properties/id/maxLength", - keyword: "maxLength", + if (void 0 === data2.queue_full) return validate264.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", params: { - limit: 63 + missingProperty: "queue_full" } } ], !1; - if (func1(data2) < 1) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/id", - schemaPath: "#/$defs/DetectionZone/properties/id/minLength", - keyword: "minLength", + if (void 0 === data2.budget_exhausted) return validate264.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", params: { - limit: 1 + missingProperty: "budget_exhausted" } } ], !1; - } - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data1.name) { - let data3 = data1.name, _errs10 = 0; - if (0 === _errs10) { - if ("string" != typeof data3) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/name", - schemaPath: "#/$defs/DetectionZone/properties/name/type", + if (void 0 === data2.disabled) return validate264.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", + params: { + missingProperty: "disabled" + } + } ], !1; + if (void 0 === data2.untracked) return validate264.errors = [ { + instancePath: instancePath + "/dropped", + schemaPath: "#/properties/dropped/required", + keyword: "required", + params: { + missingProperty: "untracked" + } + } ], !1; + if (void 0 !== data2.queue_full) { + let data3 = data2.queue_full, _errs9 = errors; + if ("number" != typeof data3 || data3 % 1 || isNaN(data3) || !isFinite(data3)) return validate264.errors = [ { + instancePath: instancePath + "/dropped/queue_full", + schemaPath: "#/properties/dropped/properties/queue_full/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - if (func1(data3) > 63) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/name", - schemaPath: "#/$defs/DetectionZone/properties/name/maxLength", - keyword: "maxLength", + if (errors === _errs9 && "number" == typeof data3 && isFinite(data3) && (data3 < 0 || isNaN(data3))) return validate264.errors = [ { + instancePath: instancePath + "/dropped/queue_full", + schemaPath: "#/properties/dropped/properties/queue_full/minimum", + keyword: "minimum", params: { - limit: 63 + comparison: ">=", + limit: 0 } } ], !1; + var valid3 = _errs9 === errors; + } else valid3 = !0; + if (valid3) { + if (void 0 !== data2.budget_exhausted) { + let data4 = data2.budget_exhausted, _errs11 = errors; + if ("number" != typeof data4 || data4 % 1 || isNaN(data4) || !isFinite(data4)) return validate264.errors = [ { + instancePath: instancePath + "/dropped/budget_exhausted", + schemaPath: "#/properties/dropped/properties/budget_exhausted/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs11 && "number" == typeof data4 && isFinite(data4) && (data4 < 0 || isNaN(data4))) return validate264.errors = [ { + instancePath: instancePath + "/dropped/budget_exhausted", + schemaPath: "#/properties/dropped/properties/budget_exhausted/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid4 = _errs11 === errors; + } else valid4 = !0; + if (valid4) { + if (void 0 !== data2.disabled) { + let data5 = data2.disabled, _errs13 = errors; + if ("number" != typeof data5 || data5 % 1 || isNaN(data5) || !isFinite(data5)) return validate264.errors = [ { + instancePath: instancePath + "/dropped/disabled", + schemaPath: "#/properties/dropped/properties/disabled/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs13 && "number" == typeof data5 && isFinite(data5) && (data5 < 0 || isNaN(data5))) return validate264.errors = [ { + instancePath: instancePath + "/dropped/disabled", + schemaPath: "#/properties/dropped/properties/disabled/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid5 = _errs13 === errors; + } else valid5 = !0; + if (valid5 && void 0 !== data2.untracked) { + let data6 = data2.untracked, _errs15 = errors; + if ("number" != typeof data6 || data6 % 1 || isNaN(data6) || !isFinite(data6)) return validate264.errors = [ { + instancePath: instancePath + "/dropped/untracked", + schemaPath: "#/properties/dropped/properties/untracked/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs15 && "number" == typeof data6 && isFinite(data6) && (data6 < 0 || isNaN(data6))) return validate264.errors = [ { + instancePath: instancePath + "/dropped/untracked", + schemaPath: "#/properties/dropped/properties/untracked/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } } - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data1.enabled) { - let _errs12 = 0; - if ("boolean" != typeof data1.enabled) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/enabled", - schemaPath: "#/$defs/DetectionZone/properties/enabled/type", + } + var valid2 = _errs6 === errors; + } else valid2 = !0; + if (valid2) { + if (void 0 !== data.evidence) { + let data7 = data.evidence, _errs17 = errors; + if (errors === _errs17) { + if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/type", keyword: "type", params: { - type: "boolean" + type: "object" } } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data1.color) { - let data5 = data1.color, _errs14 = 0; - if (0 === _errs14) { - if ("string" != typeof data5) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/color", - schemaPath: "#/$defs/DetectionZone/properties/color/type", + if (void 0 === data7.queue_allocated) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "queue_allocated" + } + } ], !1; + if (void 0 === data7.worker_running) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "worker_running" + } + } ], !1; + if (void 0 === data7.backlog_items) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "backlog_items" + } + } ], !1; + if (void 0 === data7.backlog_bytes) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "backlog_bytes" + } + } ], !1; + if (void 0 === data7.high_water_items) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "high_water_items" + } + } ], !1; + if (void 0 === data7.high_water_bytes) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "high_water_bytes" + } + } ], !1; + if (void 0 === data7.drop_ring_depth) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "drop_ring_depth" + } + } ], !1; + if (void 0 === data7.ready) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "ready" + } + } ], !1; + if (void 0 === data7.unavailable_submitted) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "unavailable_submitted" + } + } ], !1; + if (void 0 === data7.bytes_written) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "bytes_written" + } + } ], !1; + if (void 0 === data7.failures) return validate264.errors = [ { + instancePath: instancePath + "/evidence", + schemaPath: "#/properties/evidence/required", + keyword: "required", + params: { + missingProperty: "failures" + } + } ], !1; + if (void 0 !== data7.queue_allocated) { + let _errs20 = errors; + if ("boolean" != typeof data7.queue_allocated) return validate264.errors = [ { + instancePath: instancePath + "/evidence/queue_allocated", + schemaPath: "#/properties/evidence/properties/queue_allocated/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid8 = _errs20 === errors; + } else valid8 = !0; + if (valid8) { + if (void 0 !== data7.worker_running) { + let _errs22 = errors; + if ("boolean" != typeof data7.worker_running) return validate264.errors = [ { + instancePath: instancePath + "/evidence/worker_running", + schemaPath: "#/properties/evidence/properties/worker_running/type", keyword: "type", params: { - type: "string" - } - } ], !1; - if (!pattern190.test(data5)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/color", - schemaPath: "#/$defs/DetectionZone/properties/color/pattern", - keyword: "pattern", - params: { - pattern: "^#[0-9a-fA-F]{6}$" + type: "boolean" } } ], !1; - } - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data1.polygon) { - let data6 = data1.polygon, _errs16 = 0; - if (0 === _errs16) { - if (!Array.isArray(data6)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon", - schemaPath: "#/$defs/DetectionZone/properties/polygon/type", + var valid9 = _errs22 === errors; + } else valid9 = !0; + if (valid9) { + if (void 0 !== data7.backlog_items) { + let data10 = data7.backlog_items, _errs24 = errors; + if ("number" != typeof data10 || data10 % 1 || isNaN(data10) || !isFinite(data10)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/backlog_items", + schemaPath: "#/properties/evidence/properties/backlog_items/type", keyword: "type", params: { - type: "array" - } - } ], !1; - if (data6.length > 32) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon", - schemaPath: "#/$defs/DetectionZone/properties/polygon/maxItems", - keyword: "maxItems", - params: { - limit: 32 + type: "integer" } } ], !1; - if (data6.length < 3) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon", - schemaPath: "#/$defs/DetectionZone/properties/polygon/minItems", - keyword: "minItems", + if (errors === _errs24 && "number" == typeof data10 && isFinite(data10) && (data10 < 0 || isNaN(data10))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/backlog_items", + schemaPath: "#/properties/evidence/properties/backlog_items/minimum", + keyword: "minimum", params: { - limit: 3 + comparison: ">=", + limit: 0 } } ], !1; - { - let len1 = data6.length; - for (let i1 = 0; i1 < len1; i1++) { - let data7 = data6[i1], _errs18 = 0; - if (0 === _errs18) { - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/type", + var valid10 = _errs24 === errors; + } else valid10 = !0; + if (valid10) { + if (void 0 !== data7.backlog_bytes) { + let data11 = data7.backlog_bytes, _errs26 = errors; + if ("number" != typeof data11 || data11 % 1 || isNaN(data11) || !isFinite(data11)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/backlog_bytes", + schemaPath: "#/properties/evidence/properties/backlog_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs26 && "number" == typeof data11 && isFinite(data11) && (data11 < 0 || isNaN(data11))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/backlog_bytes", + schemaPath: "#/properties/evidence/properties/backlog_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid11 = _errs26 === errors; + } else valid11 = !0; + if (valid11) { + if (void 0 !== data7.high_water_items) { + let data12 = data7.high_water_items, _errs28 = errors; + if ("number" != typeof data12 || data12 % 1 || isNaN(data12) || !isFinite(data12)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/high_water_items", + schemaPath: "#/properties/evidence/properties/high_water_items/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs28 && "number" == typeof data12 && isFinite(data12) && (data12 < 0 || isNaN(data12))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/high_water_items", + schemaPath: "#/properties/evidence/properties/high_water_items/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid12 = _errs28 === errors; + } else valid12 = !0; + if (valid12) { + if (void 0 !== data7.high_water_bytes) { + let data13 = data7.high_water_bytes, _errs30 = errors; + if ("number" != typeof data13 || data13 % 1 || isNaN(data13) || !isFinite(data13)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/high_water_bytes", + schemaPath: "#/properties/evidence/properties/high_water_bytes/type", keyword: "type", params: { - type: "object" - } - } ], !1; - if (void 0 === data7.x) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/required", - keyword: "required", - params: { - missingProperty: "x" + type: "integer" } } ], !1; - if (void 0 === data7.y) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/required", - keyword: "required", + if (errors === _errs30 && "number" == typeof data13 && isFinite(data13) && (data13 < 0 || isNaN(data13))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/high_water_bytes", + schemaPath: "#/properties/evidence/properties/high_water_bytes/minimum", + keyword: "minimum", params: { - missingProperty: "y" + comparison: ">=", + limit: 0 } } ], !1; - if (void 0 !== data7.x) { - let data8 = data7.x, _errs21 = 0; - if (0 === _errs21) { - if ("number" != typeof data8 || !isFinite(data8)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/x/type", + var valid13 = _errs30 === errors; + } else valid13 = !0; + if (valid13) { + if (void 0 !== data7.drop_ring_depth) { + let data14 = data7.drop_ring_depth, _errs32 = errors; + if ("number" != typeof data14 || data14 % 1 || isNaN(data14) || !isFinite(data14)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/drop_ring_depth", + schemaPath: "#/properties/evidence/properties/drop_ring_depth/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs32 && "number" == typeof data14 && isFinite(data14) && (data14 < 0 || isNaN(data14))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/drop_ring_depth", + schemaPath: "#/properties/evidence/properties/drop_ring_depth/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid14 = _errs32 === errors; + } else valid14 = !0; + if (valid14) { + if (void 0 !== data7.ready) { + let data15 = data7.ready, _errs34 = errors; + if ("number" != typeof data15 || data15 % 1 || isNaN(data15) || !isFinite(data15)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/ready", + schemaPath: "#/properties/evidence/properties/ready/type", keyword: "type", params: { - type: "number" - } - } ], !1; - if (data8 > 1 || isNaN(data8)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/x/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 + type: "integer" } } ], !1; - if (data8 < 0 || isNaN(data8)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/x/minimum", + if (errors === _errs34 && "number" == typeof data15 && isFinite(data15) && (data15 < 0 || isNaN(data15))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/ready", + schemaPath: "#/properties/evidence/properties/ready/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 } } ], !1; + var valid15 = _errs34 === errors; + } else valid15 = !0; + if (valid15) { + if (void 0 !== data7.unavailable_submitted) { + let data16 = data7.unavailable_submitted, _errs36 = errors; + if ("number" != typeof data16 || data16 % 1 || isNaN(data16) || !isFinite(data16)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/unavailable_submitted", + schemaPath: "#/properties/evidence/properties/unavailable_submitted/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs36 && "number" == typeof data16 && isFinite(data16) && (data16 < 0 || isNaN(data16))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/unavailable_submitted", + schemaPath: "#/properties/evidence/properties/unavailable_submitted/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid16 = _errs36 === errors; + } else valid16 = !0; + if (valid16) { + if (void 0 !== data7.bytes_written) { + let data17 = data7.bytes_written, _errs38 = errors; + if ("number" != typeof data17 || data17 % 1 || isNaN(data17) || !isFinite(data17)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/bytes_written", + schemaPath: "#/properties/evidence/properties/bytes_written/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs38 && "number" == typeof data17 && isFinite(data17) && (data17 < 0 || isNaN(data17))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/bytes_written", + schemaPath: "#/properties/evidence/properties/bytes_written/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid17 = _errs38 === errors; + } else valid17 = !0; + if (valid17 && void 0 !== data7.failures) { + let data18 = data7.failures; + if (errors == errors) { + if (!data18 || "object" != typeof data18 || Array.isArray(data18)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data18.encode_failed) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "encode_failed" + } + } ], !1; + if (void 0 === data18.write_failed) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "write_failed" + } + } ], !1; + if (void 0 === data18.db_error) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "db_error" + } + } ], !1; + if (void 0 === data18.disabled) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "disabled" + } + } ], !1; + if (void 0 === data18.interrupted) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures", + schemaPath: "#/properties/evidence/properties/failures/required", + keyword: "required", + params: { + missingProperty: "interrupted" + } + } ], !1; + if (void 0 !== data18.encode_failed) { + let data19 = data18.encode_failed, _errs43 = errors; + if ("number" != typeof data19 || data19 % 1 || isNaN(data19) || !isFinite(data19)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/encode_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/encode_failed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs43 && "number" == typeof data19 && isFinite(data19) && (data19 < 0 || isNaN(data19))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/encode_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/encode_failed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid19 = _errs43 === errors; + } else valid19 = !0; + if (valid19) { + if (void 0 !== data18.write_failed) { + let data20 = data18.write_failed, _errs45 = errors; + if ("number" != typeof data20 || data20 % 1 || isNaN(data20) || !isFinite(data20)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/write_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/write_failed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs45 && "number" == typeof data20 && isFinite(data20) && (data20 < 0 || isNaN(data20))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/write_failed", + schemaPath: "#/properties/evidence/properties/failures/properties/write_failed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid20 = _errs45 === errors; + } else valid20 = !0; + if (valid20) { + if (void 0 !== data18.db_error) { + let data21 = data18.db_error, _errs47 = errors; + if ("number" != typeof data21 || data21 % 1 || isNaN(data21) || !isFinite(data21)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/db_error", + schemaPath: "#/properties/evidence/properties/failures/properties/db_error/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs47 && "number" == typeof data21 && isFinite(data21) && (data21 < 0 || isNaN(data21))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/db_error", + schemaPath: "#/properties/evidence/properties/failures/properties/db_error/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid21 = _errs47 === errors; + } else valid21 = !0; + if (valid21) { + if (void 0 !== data18.disabled) { + let data22 = data18.disabled, _errs49 = errors; + if ("number" != typeof data22 || data22 % 1 || isNaN(data22) || !isFinite(data22)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/disabled", + schemaPath: "#/properties/evidence/properties/failures/properties/disabled/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs49 && "number" == typeof data22 && isFinite(data22) && (data22 < 0 || isNaN(data22))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/disabled", + schemaPath: "#/properties/evidence/properties/failures/properties/disabled/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid22 = _errs49 === errors; + } else valid22 = !0; + if (valid22 && void 0 !== data18.interrupted) { + let data23 = data18.interrupted, _errs51 = errors; + if ("number" != typeof data23 || data23 % 1 || isNaN(data23) || !isFinite(data23)) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/interrupted", + schemaPath: "#/properties/evidence/properties/failures/properties/interrupted/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs51 && "number" == typeof data23 && isFinite(data23) && (data23 < 0 || isNaN(data23))) return validate264.errors = [ { + instancePath: instancePath + "/evidence/failures/interrupted", + schemaPath: "#/properties/evidence/properties/failures/properties/interrupted/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } + } + } + } + } } - var valid9 = 0 === _errs21; - } else valid9 = !0; - if (valid9 && void 0 !== data7.y) { - let data9 = data7.y; - if ("number" != typeof data9 || !isFinite(data9)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/y/type", + } + } + } + } + } + } + } + } + var valid7 = _errs17 === errors; + } else valid7 = !0; + if (valid7) { + if (void 0 !== data.history) { + let data24 = data.history, _errs53 = errors; + if (errors === _errs53) { + if (!data24 || "object" != typeof data24 || Array.isArray(data24)) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data24.available) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "available" + } + } ], !1; + if (void 0 === data24.observations) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "observations" + } + } ], !1; + if (void 0 === data24.oldest_seq) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "oldest_seq" + } + } ], !1; + if (void 0 === data24.head_seq) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "head_seq" + } + } ], !1; + if (void 0 === data24.oldest_observed_at) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "oldest_observed_at" + } + } ], !1; + if (void 0 === data24.epoch_regenerations) return validate264.errors = [ { + instancePath: instancePath + "/history", + schemaPath: "#/properties/history/required", + keyword: "required", + params: { + missingProperty: "epoch_regenerations" + } + } ], !1; + if (void 0 !== data24.available) { + let _errs56 = errors; + if ("boolean" != typeof data24.available) return validate264.errors = [ { + instancePath: instancePath + "/history/available", + schemaPath: "#/properties/history/properties/available/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid25 = _errs56 === errors; + } else valid25 = !0; + if (valid25) { + if (void 0 !== data24.observations) { + let data26 = data24.observations, _errs58 = errors; + if ("number" != typeof data26 || data26 % 1 || isNaN(data26) || !isFinite(data26)) return validate264.errors = [ { + instancePath: instancePath + "/history/observations", + schemaPath: "#/properties/history/properties/observations/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs58 && "number" == typeof data26 && isFinite(data26) && (data26 < 0 || isNaN(data26))) return validate264.errors = [ { + instancePath: instancePath + "/history/observations", + schemaPath: "#/properties/history/properties/observations/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid26 = _errs58 === errors; + } else valid26 = !0; + if (valid26) { + if (void 0 !== data24.oldest_seq) { + let data27 = data24.oldest_seq, _errs60 = errors; + if ("number" != typeof data27 || data27 % 1 || isNaN(data27) || !isFinite(data27)) return validate264.errors = [ { + instancePath: instancePath + "/history/oldest_seq", + schemaPath: "#/properties/history/properties/oldest_seq/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs60 && "number" == typeof data27 && isFinite(data27) && (data27 < 0 || isNaN(data27))) return validate264.errors = [ { + instancePath: instancePath + "/history/oldest_seq", + schemaPath: "#/properties/history/properties/oldest_seq/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid27 = _errs60 === errors; + } else valid27 = !0; + if (valid27) { + if (void 0 !== data24.head_seq) { + let data28 = data24.head_seq, _errs62 = errors; + if ("number" != typeof data28 || data28 % 1 || isNaN(data28) || !isFinite(data28)) return validate264.errors = [ { + instancePath: instancePath + "/history/head_seq", + schemaPath: "#/properties/history/properties/head_seq/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs62 && "number" == typeof data28 && isFinite(data28) && (data28 < 0 || isNaN(data28))) return validate264.errors = [ { + instancePath: instancePath + "/history/head_seq", + schemaPath: "#/properties/history/properties/head_seq/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid28 = _errs62 === errors; + } else valid28 = !0; + if (valid28) { + if (void 0 !== data24.oldest_observed_at) { + let data29 = data24.oldest_observed_at, _errs64 = errors, _errs65 = errors, valid30 = !1, _errs66 = errors; + if (null !== data29) { + let err0 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/properties/history/properties/oldest_observed_at/anyOf/0/type", keyword: "type", params: { - type: "number" - } - } ], !1; - if (data9 > 1 || isNaN(data9)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/y/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 + type: "null" } - } ], !1; - if (data9 < 0 || isNaN(data9)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", - schemaPath: "#/$defs/DetectionZone/properties/polygon/items/properties/y/minimum", - keyword: "minimum", + }; + vErrors.push(err0), errors++; + } + var _valid0 = _errs66 === errors; + valid30 = valid30 || _valid0; + let _errs68 = errors; + if (errors == errors && errors == errors) if ("string" == typeof data29) { + if (!formats2.validate(data29)) { + let err1 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/format", + keyword: "format", + params: { + format: "date-time" + } + }; + vErrors.push(err1), errors++; + } + } else { + let err2 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/$defs/ObservationTime/type", + keyword: "type", params: { - comparison: ">=", - limit: 0 + type: "string" } - } ], !1; + }; + vErrors.push(err2), errors++; + } + if (_valid0 = _errs68 === errors, valid30 = valid30 || _valid0, !valid30) { + let err3 = { + instancePath: instancePath + "/history/oldest_observed_at", + schemaPath: "#/properties/history/properties/oldest_observed_at/anyOf", + keyword: "anyOf", + params: {} + }; + return vErrors.push(err3), errors++, validate264.errors = vErrors, !1; } + errors = _errs65, vErrors.length = _errs65; + var valid29 = _errs64 === errors; + } else valid29 = !0; + if (valid29 && void 0 !== data24.epoch_regenerations) { + let data30 = data24.epoch_regenerations, _errs71 = errors; + if ("number" != typeof data30 || data30 % 1 || isNaN(data30) || !isFinite(data30)) return validate264.errors = [ { + instancePath: instancePath + "/history/epoch_regenerations", + schemaPath: "#/properties/history/properties/epoch_regenerations/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs71 && "number" == typeof data30 && isFinite(data30) && (data30 < 0 || isNaN(data30))) return validate264.errors = [ { + instancePath: instancePath + "/history/epoch_regenerations", + schemaPath: "#/properties/history/properties/epoch_regenerations/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; } - if (0 !== _errs18) break; } } } - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data1.filter_classes) { - let data10 = data1.filter_classes, _errs25 = 0; - if (0 === _errs25) { - if ("string" != typeof data10) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/filter_classes", - schemaPath: "#/$defs/DetectionZone/properties/filter_classes/type", + } + } + var valid24 = _errs53 === errors; + } else valid24 = !0; + if (valid24) { + if (void 0 !== data.publications) { + let data31 = data.publications, _errs73 = errors; + if (errors === _errs73) { + if (!data31 || "object" != typeof data31 || Array.isArray(data31)) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/type", + keyword: "type", + params: { + type: "object" + } + } ], !1; + if (void 0 === data31.pending) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "pending" + } + } ], !1; + if (void 0 === data31.failed) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "failed" + } + } ], !1; + if (void 0 === data31.expired) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "expired" + } + } ], !1; + if (void 0 === data31.relay_lag_seconds) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relay_lag_seconds" + } + } ], !1; + if (void 0 === data31.relay_running) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relay_running" + } + } ], !1; + if (void 0 === data31.relayed) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relayed" + } + } ], !1; + if (void 0 === data31.relayed_no_route) return validate264.errors = [ { + instancePath: instancePath + "/publications", + schemaPath: "#/properties/publications/required", + keyword: "required", + params: { + missingProperty: "relayed_no_route" + } + } ], !1; + if (void 0 !== data31.pending) { + let data32 = data31.pending, _errs76 = errors; + if ("number" != typeof data32 || data32 % 1 || isNaN(data32) || !isFinite(data32)) return validate264.errors = [ { + instancePath: instancePath + "/publications/pending", + schemaPath: "#/properties/publications/properties/pending/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs76 && "number" == typeof data32 && isFinite(data32) && (data32 < 0 || isNaN(data32))) return validate264.errors = [ { + instancePath: instancePath + "/publications/pending", + schemaPath: "#/properties/publications/properties/pending/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid34 = _errs76 === errors; + } else valid34 = !0; + if (valid34) { + if (void 0 !== data31.failed) { + let data33 = data31.failed, _errs78 = errors; + if ("number" != typeof data33 || data33 % 1 || isNaN(data33) || !isFinite(data33)) return validate264.errors = [ { + instancePath: instancePath + "/publications/failed", + schemaPath: "#/properties/publications/properties/failed/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - if (func1(data10) > 255) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/filter_classes", - schemaPath: "#/$defs/DetectionZone/properties/filter_classes/maxLength", - keyword: "maxLength", + if (errors === _errs78 && "number" == typeof data33 && isFinite(data33) && (data33 < 0 || isNaN(data33))) return validate264.errors = [ { + instancePath: instancePath + "/publications/failed", + schemaPath: "#/properties/publications/properties/failed/minimum", + keyword: "minimum", params: { - limit: 255 + comparison: ">=", + limit: 0 } } ], !1; - } - var valid11 = 0 === _errs25; - } else valid11 = !0; - if (valid11) { - if (void 0 !== data1.min_confidence) { - let data11 = data1.min_confidence, _errs27 = 0; - if (0 === _errs27) { - if ("number" != typeof data11 || !isFinite(data11)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/min_confidence", - schemaPath: "#/$defs/DetectionZone/properties/min_confidence/type", + var valid35 = _errs78 === errors; + } else valid35 = !0; + if (valid35) { + if (void 0 !== data31.expired) { + let data34 = data31.expired, _errs80 = errors; + if ("number" != typeof data34 || data34 % 1 || isNaN(data34) || !isFinite(data34)) return validate264.errors = [ { + instancePath: instancePath + "/publications/expired", + schemaPath: "#/properties/publications/properties/expired/type", keyword: "type", params: { - type: "number" - } - } ], !1; - if (data11 > 1 || isNaN(data11)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/min_confidence", - schemaPath: "#/$defs/DetectionZone/properties/min_confidence/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 + type: "integer" } } ], !1; - if (data11 < 0 || isNaN(data11)) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/min_confidence", - schemaPath: "#/$defs/DetectionZone/properties/min_confidence/minimum", + if (errors === _errs80 && "number" == typeof data34 && isFinite(data34) && (data34 < 0 || isNaN(data34))) return validate264.errors = [ { + instancePath: instancePath + "/publications/expired", + schemaPath: "#/properties/publications/properties/expired/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 } } ], !1; + var valid36 = _errs80 === errors; + } else valid36 = !0; + if (valid36) { + if (void 0 !== data31.relay_lag_seconds) { + let data35 = data31.relay_lag_seconds, _errs82 = errors; + if (errors === _errs82) { + if ("number" != typeof data35 || !isFinite(data35)) return validate264.errors = [ { + instancePath: instancePath + "/publications/relay_lag_seconds", + schemaPath: "#/properties/publications/properties/relay_lag_seconds/type", + keyword: "type", + params: { + type: "number" + } + } ], !1; + if (data35 < 0 || isNaN(data35)) return validate264.errors = [ { + instancePath: instancePath + "/publications/relay_lag_seconds", + schemaPath: "#/properties/publications/properties/relay_lag_seconds/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + var valid37 = _errs82 === errors; + } else valid37 = !0; + if (valid37) { + if (void 0 !== data31.relay_running) { + let _errs84 = errors; + if ("boolean" != typeof data31.relay_running) return validate264.errors = [ { + instancePath: instancePath + "/publications/relay_running", + schemaPath: "#/properties/publications/properties/relay_running/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; + var valid38 = _errs84 === errors; + } else valid38 = !0; + if (valid38) { + if (void 0 !== data31.relayed) { + let data37 = data31.relayed, _errs86 = errors; + if ("number" != typeof data37 || data37 % 1 || isNaN(data37) || !isFinite(data37)) return validate264.errors = [ { + instancePath: instancePath + "/publications/relayed", + schemaPath: "#/properties/publications/properties/relayed/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs86 && "number" == typeof data37 && isFinite(data37) && (data37 < 0 || isNaN(data37))) return validate264.errors = [ { + instancePath: instancePath + "/publications/relayed", + schemaPath: "#/properties/publications/properties/relayed/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid39 = _errs86 === errors; + } else valid39 = !0; + if (valid39 && void 0 !== data31.relayed_no_route) { + let data38 = data31.relayed_no_route, _errs88 = errors; + if ("number" != typeof data38 || data38 % 1 || isNaN(data38) || !isFinite(data38)) return validate264.errors = [ { + instancePath: instancePath + "/publications/relayed_no_route", + schemaPath: "#/properties/publications/properties/relayed_no_route/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs88 && "number" == typeof data38 && isFinite(data38) && (data38 < 0 || isNaN(data38))) return validate264.errors = [ { + instancePath: instancePath + "/publications/relayed_no_route", + schemaPath: "#/properties/publications/properties/relayed_no_route/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + } + } + } } - var valid12 = 0 === _errs27; - } else valid12 = !0; - if (valid12 && void 0 !== data1.stream_name && "string" != typeof data1.stream_name) return validate453.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/stream_name", - schemaPath: "#/$defs/DetectionZone/properties/stream_name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; + } } } - } - } - } - } - if (0 !== _errs4) break; - } - } - } - return validate453.errors = [], !0; -} - -validate453.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_StreamZonesDeleted = validate456; - -function validate456(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate456.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate456.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate456.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.message) return validate456.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "message" - } - } ], !1; - if (void 0 !== data.success) { - let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate456.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data0) return validate456.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - return valid0 && void 0 !== data.message && "string" != typeof data.message ? (validate456.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1) : (validate456.errors = [], !0); -} - -validate456.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_StreamZonesRequest = validate454; - -function validate454(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate454.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate454.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.zones) return validate454.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "zones" - } - } ], !1; - if (void 0 !== data.zones) { - let data0 = data.zones; - if (!Array.isArray(data0)) return validate454.errors = [ { - instancePath: instancePath + "/zones", - schemaPath: "#/properties/zones/type", - keyword: "type", - params: { - type: "array" - } - } ], !1; - if (data0.length > 16) return validate454.errors = [ { - instancePath: instancePath + "/zones", - schemaPath: "#/properties/zones/maxItems", - keyword: "maxItems", - params: { - limit: 16 - } - } ], !1; - { - let len0 = data0.length; - for (let i0 = 0; i0 < len0; i0++) { - let data1 = data0[i0], _errs4 = 0; - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZoneWrite/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data1.id) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZoneWrite/required", - keyword: "required", - params: { - missingProperty: "id" - } - } ], !1; - if (void 0 === data1.name) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZoneWrite/required", - keyword: "required", - params: { - missingProperty: "name" - } - } ], !1; - if (void 0 === data1.polygon) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0, - schemaPath: "#/$defs/DetectionZoneWrite/required", - keyword: "required", - params: { - missingProperty: "polygon" - } - } ], !1; - if (void 0 !== data1.id) { - let data2 = data1.id, _errs8 = 0; - if (0 === _errs8) { - if ("string" != typeof data2) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/id", - schemaPath: "#/$defs/DetectionZoneWrite/properties/id/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data2) > 63) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/id", - schemaPath: "#/$defs/DetectionZoneWrite/properties/id/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - if (func1(data2) < 1) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/id", - schemaPath: "#/$defs/DetectionZoneWrite/properties/id/minLength", - keyword: "minLength", - params: { - limit: 1 - } - } ], !1; - } - var valid3 = 0 === _errs8; - } else valid3 = !0; - if (valid3) { - if (void 0 !== data1.name) { - let data3 = data1.name, _errs10 = 0; - if (0 === _errs10) { - if ("string" != typeof data3) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/name", - schemaPath: "#/$defs/DetectionZoneWrite/properties/name/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data3) > 63) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/name", - schemaPath: "#/$defs/DetectionZoneWrite/properties/name/maxLength", - keyword: "maxLength", - params: { - limit: 63 - } - } ], !1; - } - var valid4 = 0 === _errs10; - } else valid4 = !0; - if (valid4) { - if (void 0 !== data1.enabled) { - let _errs12 = 0; - if ("boolean" != typeof data1.enabled) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/enabled", - schemaPath: "#/$defs/DetectionZoneWrite/properties/enabled/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - var valid5 = 0 === _errs12; - } else valid5 = !0; - if (valid5) { - if (void 0 !== data1.color) { - let data5 = data1.color, _errs14 = 0; - if (0 === _errs14) { - if ("string" != typeof data5) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/color", - schemaPath: "#/$defs/DetectionZoneWrite/properties/color/type", + var valid33 = _errs73 === errors; + } else valid33 = !0; + if (valid33) { + if (void 0 !== data.budget_exhausted_cameras) { + let data39 = data.budget_exhausted_cameras, _errs90 = errors; + if ("number" != typeof data39 || data39 % 1 || isNaN(data39) || !isFinite(data39)) return validate264.errors = [ { + instancePath: instancePath + "/budget_exhausted_cameras", + schemaPath: "#/properties/budget_exhausted_cameras/type", keyword: "type", params: { - type: "string" + type: "integer" } } ], !1; - if (!pattern190.test(data5)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/color", - schemaPath: "#/$defs/DetectionZoneWrite/properties/color/pattern", - keyword: "pattern", + if (errors === _errs90 && "number" == typeof data39 && isFinite(data39) && (data39 < 0 || isNaN(data39))) return validate264.errors = [ { + instancePath: instancePath + "/budget_exhausted_cameras", + schemaPath: "#/properties/budget_exhausted_cameras/minimum", + keyword: "minimum", params: { - pattern: "^#[0-9a-fA-F]{6}$" + comparison: ">=", + limit: 0 } } ], !1; - } - var valid6 = 0 === _errs14; - } else valid6 = !0; - if (valid6) { - if (void 0 !== data1.polygon) { - let data6 = data1.polygon, _errs16 = 0; - if (0 === _errs16) { - if (!Array.isArray(data6)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/type", + var valid41 = _errs90 === errors; + } else valid41 = !0; + if (valid41 && void 0 !== data.cameras) { + let data40 = data.cameras; + if (errors == errors) { + if (!Array.isArray(data40)) return validate264.errors = [ { + instancePath: instancePath + "/cameras", + schemaPath: "#/properties/cameras/type", keyword: "type", params: { type: "array" } } ], !1; - if (data6.length > 32) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/maxItems", - keyword: "maxItems", - params: { - limit: 32 - } - } ], !1; - if (data6.length < 3) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/minItems", - keyword: "minItems", - params: { - limit: 3 - } - } ], !1; { - let len1 = data6.length; - for (let i1 = 0; i1 < len1; i1++) { - let data7 = data6[i1], _errs18 = 0; - if (0 === _errs18) { - if (!data7 || "object" != typeof data7 || Array.isArray(data7)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/type", + let len0 = data40.length; + for (let i0 = 0; i0 < len0; i0++) { + let data41 = data40[i0], _errs94 = errors; + if (errors === _errs94) { + if (!data41 || "object" != typeof data41 || Array.isArray(data41)) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/type", keyword: "type", params: { type: "object" } } ], !1; - if (void 0 === data7.x) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/required", + if (void 0 === data41.camera_uuid) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", keyword: "required", params: { - missingProperty: "x" + missingProperty: "camera_uuid" } } ], !1; - if (void 0 === data7.y) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1, - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/required", + if (void 0 === data41.retained_bytes) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", keyword: "required", params: { - missingProperty: "y" + missingProperty: "retained_bytes" } } ], !1; - if (void 0 !== data7.x) { - let data8 = data7.x, _errs21 = 0; - if (0 === _errs21) { - if ("number" != typeof data8 || !isFinite(data8)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/x/type", + if (void 0 === data41.max_bytes) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "max_bytes" + } + } ], !1; + if (void 0 === data41.max_age_days) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "max_age_days" + } + } ], !1; + if (void 0 === data41.budget_exhausted) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0, + schemaPath: "#/properties/cameras/items/required", + keyword: "required", + params: { + missingProperty: "budget_exhausted" + } + } ], !1; + if (void 0 !== data41.camera_uuid) { + let data42 = data41.camera_uuid, _errs97 = errors; + if (errors === _errs97) { + if ("string" != typeof data42) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/camera_uuid", + schemaPath: "#/properties/cameras/items/properties/camera_uuid/type", keyword: "type", params: { - type: "number" + type: "string" } } ], !1; - if (data8 > 1 || isNaN(data8)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/x/maximum", - keyword: "maximum", + if (!pattern4.test(data42)) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/camera_uuid", + schemaPath: "#/properties/cameras/items/properties/camera_uuid/pattern", + keyword: "pattern", params: { - comparison: "<=", - limit: 1 + pattern: "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" } } ], !1; - if (data8 < 0 || isNaN(data8)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/x", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/x/minimum", + } + var valid44 = _errs97 === errors; + } else valid44 = !0; + if (valid44) { + if (void 0 !== data41.retained_bytes) { + let data43 = data41.retained_bytes, _errs99 = errors; + if ("number" != typeof data43 || data43 % 1 || isNaN(data43) || !isFinite(data43)) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/retained_bytes", + schemaPath: "#/properties/cameras/items/properties/retained_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs99 && "number" == typeof data43 && isFinite(data43) && (data43 < 0 || isNaN(data43))) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/retained_bytes", + schemaPath: "#/properties/cameras/items/properties/retained_bytes/minimum", keyword: "minimum", params: { comparison: ">=", limit: 0 } } ], !1; - } - var valid9 = 0 === _errs21; - } else valid9 = !0; - if (valid9 && void 0 !== data7.y) { - let data9 = data7.y; - if ("number" != typeof data9 || !isFinite(data9)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/y/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - if (data9 > 1 || isNaN(data9)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/y/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 - } - } ], !1; - if (data9 < 0 || isNaN(data9)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/polygon/" + i1 + "/y", - schemaPath: "#/$defs/DetectionZoneWrite/properties/polygon/items/properties/y/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 + var valid45 = _errs99 === errors; + } else valid45 = !0; + if (valid45) { + if (void 0 !== data41.max_bytes) { + let data44 = data41.max_bytes, _errs101 = errors; + if ("number" != typeof data44 || data44 % 1 || isNaN(data44) || !isFinite(data44)) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_bytes", + schemaPath: "#/properties/cameras/items/properties/max_bytes/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs101 && "number" == typeof data44 && isFinite(data44) && (data44 < 0 || isNaN(data44))) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_bytes", + schemaPath: "#/properties/cameras/items/properties/max_bytes/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid46 = _errs101 === errors; + } else valid46 = !0; + if (valid46) { + if (void 0 !== data41.max_age_days) { + let data45 = data41.max_age_days, _errs103 = errors; + if ("number" != typeof data45 || data45 % 1 || isNaN(data45) || !isFinite(data45)) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_age_days", + schemaPath: "#/properties/cameras/items/properties/max_age_days/type", + keyword: "type", + params: { + type: "integer" + } + } ], !1; + if (errors === _errs103 && "number" == typeof data45 && isFinite(data45) && (data45 < 0 || isNaN(data45))) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/max_age_days", + schemaPath: "#/properties/cameras/items/properties/max_age_days/minimum", + keyword: "minimum", + params: { + comparison: ">=", + limit: 0 + } + } ], !1; + var valid47 = _errs103 === errors; + } else valid47 = !0; + if (valid47 && void 0 !== data41.budget_exhausted && "boolean" != typeof data41.budget_exhausted) return validate264.errors = [ { + instancePath: instancePath + "/cameras/" + i0 + "/budget_exhausted", + schemaPath: "#/properties/cameras/items/properties/budget_exhausted/type", + keyword: "type", + params: { + type: "boolean" + } + } ], !1; } - } ], !1; + } } } - if (0 !== _errs18) break; + if (_errs94 !== errors) break; } } } - var valid7 = 0 === _errs16; - } else valid7 = !0; - if (valid7) { - if (void 0 !== data1.filter_classes) { - let data10 = data1.filter_classes, _errs25 = 0; - if (0 === _errs25) { - if ("string" != typeof data10) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/filter_classes", - schemaPath: "#/$defs/DetectionZoneWrite/properties/filter_classes/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - if (func1(data10) > 255) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/filter_classes", - schemaPath: "#/$defs/DetectionZoneWrite/properties/filter_classes/maxLength", - keyword: "maxLength", - params: { - limit: 255 - } - } ], !1; - } - var valid11 = 0 === _errs25; - } else valid11 = !0; - if (valid11 && void 0 !== data1.min_confidence) { - let data11 = data1.min_confidence; - if ("number" != typeof data11 || !isFinite(data11)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/min_confidence", - schemaPath: "#/$defs/DetectionZoneWrite/properties/min_confidence/type", - keyword: "type", - params: { - type: "number" - } - } ], !1; - if (data11 > 1 || isNaN(data11)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/min_confidence", - schemaPath: "#/$defs/DetectionZoneWrite/properties/min_confidence/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 1 - } - } ], !1; - if (data11 < 0 || isNaN(data11)) return validate454.errors = [ { - instancePath: instancePath + "/zones/" + i0 + "/min_confidence", - schemaPath: "#/$defs/DetectionZoneWrite/properties/min_confidence/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - } } } } } } - if (0 !== _errs4) break; - } - } - } - return validate454.errors = [], !0; -} - -validate454.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_StreamZonesSaved = validate455; - -function validate455(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate455.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate455.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate455.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.message) return validate455.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "message" - } - } ], !1; - if (void 0 === data.count) return validate455.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "count" - } - } ], !1; - if (void 0 !== data.success) { - let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate455.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data0) return validate455.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.message) { - let _errs4 = 0; - if ("string" != typeof data.message) return validate455.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1 && void 0 !== data.count) { - let data2 = data.count, _errs6 = 0; - if ("number" != typeof data2 || data2 % 1 || isNaN(data2) || !isFinite(data2)) return validate455.errors = [ { - instancePath: instancePath + "/count", - schemaPath: "#/properties/count/type", - keyword: "type", - params: { - type: "integer" - } - } ], !1; - if (0 === _errs6 && "number" == typeof data2 && isFinite(data2)) { - if (data2 > 16 || isNaN(data2)) return validate455.errors = [ { - instancePath: instancePath + "/count", - schemaPath: "#/properties/count/maximum", - keyword: "maximum", - params: { - comparison: "<=", - limit: 16 - } - } ], !1; - if (data2 < 0 || isNaN(data2)) return validate455.errors = [ { - instancePath: instancePath + "/count", - schemaPath: "#/properties/count/minimum", - keyword: "minimum", - params: { - comparison: ">=", - limit: 0 - } - } ], !1; - } - } - } - return validate455.errors = [], !0; -} - -validate455.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_SuccessResponse = validate97; - -function validate97(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate97.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate97.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate97.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 !== data.success) { - let _errs2 = 0; - if (!0 !== data.success) return validate97.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.secret) return validate97.errors = [ { - instancePath: instancePath + "/secret", - schemaPath: "#/properties/secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.api_key) return validate97.errors = [ { - instancePath: instancePath + "/api_key", - schemaPath: "#/properties/api_key/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password) return validate97.errors = [ { - instancePath: instancePath + "/password", - schemaPath: "#/properties/password/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.password_hash) return validate97.errors = [ { - instancePath: instancePath + "/password_hash", - schemaPath: "#/properties/password_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.token_hash) return validate97.errors = [ { - instancePath: instancePath + "/token_hash", - schemaPath: "#/properties/token_hash/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_secret) return validate97.errors = [ { - instancePath: instancePath + "/totp_secret", - schemaPath: "#/properties/totp_secret/false schema", - keyword: "false schema", - params: {} - } ], !1; - if (void 0 !== data.totp_token) return validate97.errors = [ { - instancePath: instancePath + "/totp_token", - schemaPath: "#/properties/totp_token/false schema", - keyword: "false schema", - params: {} - } ], !1; - } - return validate97.errors = [], !0; -} - -validate97.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_SystemActionAccepted = validate275; - -function validate275(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate275.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate275.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate275.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.message) return validate275.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "message" - } - } ], !1; - if (void 0 !== data.success) { - let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate275.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data0) return validate275.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 - } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - return valid0 && void 0 !== data.message && "string" != typeof data.message ? (validate275.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1) : (validate275.errors = [], !0); -} - -validate275.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_SystemBackupCreated = validate276; - -function validate276(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate276.evaluated; - if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate276.errors = [ { - instancePath: instancePath, - schemaPath: "#/type", - keyword: "type", - params: { - type: "object" - } - } ], !1; - if (void 0 === data.success) return validate276.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "success" - } - } ], !1; - if (void 0 === data.message) return validate276.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "message" - } - } ], !1; - if (void 0 === data.backupUrl) return validate276.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "backupUrl" - } - } ], !1; - if (void 0 === data.filename) return validate276.errors = [ { - instancePath: instancePath, - schemaPath: "#/required", - keyword: "required", - params: { - missingProperty: "filename" - } - } ], !1; - if (void 0 !== data.success) { - let data0 = data.success, _errs2 = 0; - if ("boolean" != typeof data0) return validate276.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/type", - keyword: "type", - params: { - type: "boolean" - } - } ], !1; - if (!0 !== data0) return validate276.errors = [ { - instancePath: instancePath + "/success", - schemaPath: "#/properties/success/const", - keyword: "const", - params: { - allowedValue: !0 } - } ], !1; - var valid0 = 0 === _errs2; - } else valid0 = !0; - if (valid0) { - if (void 0 !== data.message) { - let _errs4 = 0; - if ("string" != typeof data.message) return validate276.errors = [ { - instancePath: instancePath + "/message", - schemaPath: "#/properties/message/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid1 = 0 === _errs4; - } else valid1 = !0; - if (valid1) { - if (void 0 !== data.backupUrl) { - let _errs6 = 0; - if ("string" != typeof data.backupUrl) return validate276.errors = [ { - instancePath: instancePath + "/backupUrl", - schemaPath: "#/properties/backupUrl/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; - var valid2 = 0 === _errs6; - } else valid2 = !0; - if (valid2 && void 0 !== data.filename && "string" != typeof data.filename) return validate276.errors = [ { - instancePath: instancePath + "/filename", - schemaPath: "#/properties/filename/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; } } - return validate276.errors = [], !0; + return validate264.errors = vErrors, 0 === errors; } -validate276.evaluated = { - props: !0, - dynamicProps: !1, - dynamicItems: !1 -}; - -var __contract_SystemInformation = validate263; - function validate263(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { let vErrors = [], errors = 0, evaluated0 = validate263.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), @@ -108590,14 +118956,27 @@ function validate263(data, {instancePath: instancePath = "", parentData: parentD } var valid85 = _errs226 === errors; } else valid85 = !0; - if (valid85 && void 0 !== data.uptimeCapability && "string" != typeof data.uptimeCapability) return validate263.errors = [ { - instancePath: instancePath + "/uptimeCapability", - schemaPath: "#/properties/uptimeCapability/type", - keyword: "type", - params: { - type: "string" - } - } ], !1; + if (valid85) { + if (void 0 !== data.uptimeCapability) { + let _errs242 = errors; + if ("string" != typeof data.uptimeCapability) return validate263.errors = [ { + instancePath: instancePath + "/uptimeCapability", + schemaPath: "#/properties/uptimeCapability/type", + keyword: "type", + params: { + type: "string" + } + } ], !1; + var valid92 = _errs242 === errors; + } else valid92 = !0; + valid92 && void 0 !== data.observation_pipeline && (validate264(data.observation_pipeline, { + instancePath: instancePath + "/observation_pipeline", + parentData: data, + parentDataProperty: "observation_pipeline", + rootData: rootData, + dynamicAnchors: dynamicAnchors + }) || (vErrors = vErrors.concat(validate264.errors), errors = vErrors.length)); + } } } } @@ -108619,18 +118998,22 @@ function validate263(data, {instancePath: instancePath = "", parentData: parentD return validate263.errors = vErrors, 0 === errors; } -validate263.evaluated = { +validate264.evaluated = { + props: !0, + dynamicProps: !1, + dynamicItems: !1 +}, validate263.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_SystemLogs = validate264; +var __contract_SystemLogs = validate267; -function validate264(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate264.evaluated; +function validate267(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate267.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate264.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate267.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -108638,7 +119021,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.logs) return validate264.errors = [ { + if (void 0 === data.logs) return validate267.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -108646,7 +119029,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "logs" } } ], !1; - if (void 0 === data.file) return validate264.errors = [ { + if (void 0 === data.file) return validate267.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -108654,7 +119037,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "file" } } ], !1; - if (void 0 === data.level) return validate264.errors = [ { + if (void 0 === data.level) return validate267.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -108665,7 +119048,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.logs) { let data0 = data.logs, _errs2 = 0; if (0 === _errs2) { - if (!Array.isArray(data0)) return validate264.errors = [ { + if (!Array.isArray(data0)) return validate267.errors = [ { instancePath: instancePath + "/logs", schemaPath: "#/properties/logs/type", keyword: "type", @@ -108678,7 +119061,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data1 = data0[i0], _errs4 = 0; if (0 === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate264.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate267.errors = [ { instancePath: instancePath + "/logs/" + i0, schemaPath: "#/properties/logs/items/type", keyword: "type", @@ -108688,7 +119071,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data1.timestamp) { let _errs7 = 0; - if ("string" != typeof data1.timestamp) return validate264.errors = [ { + if ("string" != typeof data1.timestamp) return validate267.errors = [ { instancePath: instancePath + "/logs/" + i0 + "/timestamp", schemaPath: "#/properties/logs/items/properties/timestamp/type", keyword: "type", @@ -108701,7 +119084,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data1.level) { let _errs9 = 0; - if ("string" != typeof data1.level) return validate264.errors = [ { + if ("string" != typeof data1.level) return validate267.errors = [ { instancePath: instancePath + "/logs/" + i0 + "/level", schemaPath: "#/properties/logs/items/properties/level/type", keyword: "type", @@ -108711,7 +119094,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid3 = 0 === _errs9; } else valid3 = !0; - if (valid3 && void 0 !== data1.message && "string" != typeof data1.message) return validate264.errors = [ { + if (valid3 && void 0 !== data1.message && "string" != typeof data1.message) return validate267.errors = [ { instancePath: instancePath + "/logs/" + i0 + "/message", schemaPath: "#/properties/logs/items/properties/message/type", keyword: "type", @@ -108730,7 +119113,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.file) { let _errs13 = 0; - if ("string" != typeof data.file) return validate264.errors = [ { + if ("string" != typeof data.file) return validate267.errors = [ { instancePath: instancePath + "/file", schemaPath: "#/properties/file/type", keyword: "type", @@ -108740,7 +119123,7 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid5 = 0 === _errs13; } else valid5 = !0; - if (valid5 && void 0 !== data.level && "string" != typeof data.level) return validate264.errors = [ { + if (valid5 && void 0 !== data.level && "string" != typeof data.level) return validate267.errors = [ { instancePath: instancePath + "/level", schemaPath: "#/properties/level/type", keyword: "type", @@ -108749,10 +119132,10 @@ function validate264(data, {instancePath: instancePath = "", parentData: parentD } } ], !1; } - return validate264.errors = [], !0; + return validate267.errors = [], !0; } -validate264.evaluated = { +validate267.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 @@ -114306,12 +124689,12 @@ validate261.evaluated = { dynamicItems: !1 }; -var __contract_SystemStatus = validate274; +var __contract_SystemStatus = validate277; -function validate274(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate274.evaluated; +function validate277(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate277.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate274.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate277.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -114319,7 +124702,7 @@ function validate274(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.status) return validate274.errors = [ { + if (void 0 === data.status) return validate277.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -114327,7 +124710,7 @@ function validate274(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "status" } } ], !1; - if (void 0 === data.message) return validate274.errors = [ { + if (void 0 === data.message) return validate277.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -114337,7 +124720,7 @@ function validate274(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.status) { let data0 = data.status, _errs2 = 0; - if ("string" != typeof data0) return validate274.errors = [ { + if ("string" != typeof data0) return validate277.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/type", keyword: "type", @@ -114345,7 +124728,7 @@ function validate274(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("ok" !== data0) return validate274.errors = [ { + if ("ok" !== data0) return validate277.errors = [ { instancePath: instancePath + "/status", schemaPath: "#/properties/status/const", keyword: "const", @@ -114355,17 +124738,17 @@ function validate274(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid0 = 0 === _errs2; } else valid0 = !0; - return valid0 && void 0 !== data.message && "string" != typeof data.message ? (validate274.errors = [ { + return valid0 && void 0 !== data.message && "string" != typeof data.message ? (validate277.errors = [ { instancePath: instancePath + "/message", schemaPath: "#/properties/message/type", keyword: "type", params: { type: "string" } - } ], !1) : (validate274.errors = [], !0); + } ], !1) : (validate277.errors = [], !0); } -validate274.evaluated = { +validate277.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 @@ -115196,7 +125579,7 @@ validate130.evaluated = { dynamicItems: !1 }; -var __contract_WorkspacesResponse = validate277, schema299 = { +var __contract_WorkspacesResponse = validate280, schema303 = { type: "object", properties: { workspaces: { @@ -115241,10 +125624,10 @@ var __contract_WorkspacesResponse = validate277, schema299 = { additionalProperties: !0 }; -function validate277(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate277.evaluated; +function validate280(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate280.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate277.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate280.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -115252,7 +125635,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.workspaces) return validate277.errors = [ { + if (void 0 === data.workspaces) return validate280.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -115260,7 +125643,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "workspaces" } } ], !1; - if (void 0 === data.configurable) return validate277.errors = [ { + if (void 0 === data.configurable) return validate280.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -115268,7 +125651,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "configurable" } } ], !1; - if (void 0 === data.scope) return validate277.errors = [ { + if (void 0 === data.scope) return validate280.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -115279,7 +125662,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD if (void 0 !== data.workspaces) { let data0 = data.workspaces, _errs2 = 0; if (0 === _errs2) { - if (!Array.isArray(data0)) return validate277.errors = [ { + if (!Array.isArray(data0)) return validate280.errors = [ { instancePath: instancePath + "/workspaces", schemaPath: "#/properties/workspaces/type", keyword: "type", @@ -115292,7 +125675,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD for (let i0 = 0; i0 < len0; i0++) { let data1 = data0[i0], _errs4 = 0; if (0 === _errs4) { - if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate277.errors = [ { + if (!data1 || "object" != typeof data1 || Array.isArray(data1)) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/type", keyword: "type", @@ -115300,7 +125683,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data1.key) return validate277.errors = [ { + if (void 0 === data1.key) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/required", keyword: "required", @@ -115308,7 +125691,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "key" } } ], !1; - if (void 0 === data1.label) return validate277.errors = [ { + if (void 0 === data1.label) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/required", keyword: "required", @@ -115316,7 +125699,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "label" } } ], !1; - if (void 0 === data1.description) return validate277.errors = [ { + if (void 0 === data1.description) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/required", keyword: "required", @@ -115324,7 +125707,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "description" } } ], !1; - if (void 0 === data1.visible) return validate277.errors = [ { + if (void 0 === data1.visible) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/required", keyword: "required", @@ -115332,7 +125715,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "visible" } } ], !1; - if (void 0 === data1.configurable) return validate277.errors = [ { + if (void 0 === data1.configurable) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/required", keyword: "required", @@ -115340,7 +125723,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD missingProperty: "configurable" } } ], !1; - if (void 0 === data1.authorization_unchanged) return validate277.errors = [ { + if (void 0 === data1.authorization_unchanged) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0, schemaPath: "#/properties/workspaces/items/required", keyword: "required", @@ -115350,7 +125733,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data1.key) { let data2 = data1.key, _errs7 = 0; - if ("string" != typeof data2) return validate277.errors = [ { + if ("string" != typeof data2) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/key", schemaPath: "#/properties/workspaces/items/properties/key/type", keyword: "type", @@ -115358,12 +125741,12 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("live.navigator" !== data2 && "investigation" !== data2) return validate277.errors = [ { + if ("live.navigator" !== data2 && "investigation" !== data2) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/key", schemaPath: "#/properties/workspaces/items/properties/key/enum", keyword: "enum", params: { - allowedValues: schema299.properties.workspaces.items.properties.key.enum + allowedValues: schema303.properties.workspaces.items.properties.key.enum } } ], !1; var valid2 = 0 === _errs7; @@ -115371,7 +125754,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD if (valid2) { if (void 0 !== data1.label) { let _errs9 = 0; - if ("string" != typeof data1.label) return validate277.errors = [ { + if ("string" != typeof data1.label) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/label", schemaPath: "#/properties/workspaces/items/properties/label/type", keyword: "type", @@ -115384,7 +125767,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD if (valid3) { if (void 0 !== data1.description) { let _errs11 = 0; - if ("string" != typeof data1.description) return validate277.errors = [ { + if ("string" != typeof data1.description) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/description", schemaPath: "#/properties/workspaces/items/properties/description/type", keyword: "type", @@ -115397,7 +125780,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD if (valid4) { if (void 0 !== data1.visible) { let _errs13 = 0; - if ("boolean" != typeof data1.visible) return validate277.errors = [ { + if ("boolean" != typeof data1.visible) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/visible", schemaPath: "#/properties/workspaces/items/properties/visible/type", keyword: "type", @@ -115410,7 +125793,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD if (valid5) { if (void 0 !== data1.configurable) { let _errs15 = 0; - if ("boolean" != typeof data1.configurable) return validate277.errors = [ { + if ("boolean" != typeof data1.configurable) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/configurable", schemaPath: "#/properties/workspaces/items/properties/configurable/type", keyword: "type", @@ -115422,7 +125805,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD } else valid6 = !0; if (valid6 && void 0 !== data1.authorization_unchanged) { let data7 = data1.authorization_unchanged; - if ("boolean" != typeof data7) return validate277.errors = [ { + if ("boolean" != typeof data7) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/authorization_unchanged", schemaPath: "#/properties/workspaces/items/properties/authorization_unchanged/type", keyword: "type", @@ -115430,7 +125813,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD type: "boolean" } } ], !1; - if (!0 !== data7) return validate277.errors = [ { + if (!0 !== data7) return validate280.errors = [ { instancePath: instancePath + "/workspaces/" + i0 + "/authorization_unchanged", schemaPath: "#/properties/workspaces/items/properties/authorization_unchanged/const", keyword: "const", @@ -115453,7 +125836,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD if (valid0) { if (void 0 !== data.configurable) { let _errs19 = 0; - if ("boolean" != typeof data.configurable) return validate277.errors = [ { + if ("boolean" != typeof data.configurable) return validate280.errors = [ { instancePath: instancePath + "/configurable", schemaPath: "#/properties/configurable/type", keyword: "type", @@ -115465,7 +125848,7 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD } else valid8 = !0; if (valid8 && void 0 !== data.scope) { let data9 = data.scope; - if ("string" != typeof data9) return validate277.errors = [ { + if ("string" != typeof data9) return validate280.errors = [ { instancePath: instancePath + "/scope", schemaPath: "#/properties/scope/type", keyword: "type", @@ -115473,31 +125856,31 @@ function validate277(data, {instancePath: instancePath = "", parentData: parentD type: "string" } } ], !1; - if ("user" !== data9 && "installation" !== data9) return validate277.errors = [ { + if ("user" !== data9 && "installation" !== data9) return validate280.errors = [ { instancePath: instancePath + "/scope", schemaPath: "#/properties/scope/enum", keyword: "enum", params: { - allowedValues: schema299.properties.scope.enum + allowedValues: schema303.properties.scope.enum } } ], !1; } } - return validate277.errors = [], !0; + return validate280.errors = [], !0; } -validate277.evaluated = { +validate280.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 }; -var __contract_WorkspacesUpdate = validate278; +var __contract_WorkspacesUpdate = validate281; -function validate278(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { - let evaluated0 = validate278.evaluated; +function validate281(data, {instancePath: instancePath = "", parentData: parentData, parentDataProperty: parentDataProperty, rootData: rootData = data, dynamicAnchors: dynamicAnchors = {}} = {}) { + let evaluated0 = validate281.evaluated; if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), - !data || "object" != typeof data || Array.isArray(data)) return validate278.errors = [ { + !data || "object" != typeof data || Array.isArray(data)) return validate281.errors = [ { instancePath: instancePath, schemaPath: "#/type", keyword: "type", @@ -115505,7 +125888,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (void 0 === data.workspaces) return validate278.errors = [ { + if (void 0 === data.workspaces) return validate281.errors = [ { instancePath: instancePath, schemaPath: "#/required", keyword: "required", @@ -115515,7 +125898,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD } ], !1; if (void 0 !== data.workspaces) { let data0 = data.workspaces; - if (!data0 || "object" != typeof data0 || Array.isArray(data0)) return validate278.errors = [ { + if (!data0 || "object" != typeof data0 || Array.isArray(data0)) return validate281.errors = [ { instancePath: instancePath + "/workspaces", schemaPath: "#/properties/workspaces/type", keyword: "type", @@ -115523,7 +125906,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD type: "object" } } ], !1; - if (Object.keys(data0).length < 1) return validate278.errors = [ { + if (Object.keys(data0).length < 1) return validate281.errors = [ { instancePath: instancePath + "/workspaces", schemaPath: "#/properties/workspaces/minProperties", keyword: "minProperties", @@ -115533,7 +125916,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD } ], !1; { let _errs4 = 0; - for (let key0 in data0) if ("live.navigator" !== key0 && "investigation" !== key0) return validate278.errors = [ { + for (let key0 in data0) if ("live.navigator" !== key0 && "investigation" !== key0) return validate281.errors = [ { instancePath: instancePath + "/workspaces", schemaPath: "#/properties/workspaces/additionalProperties", keyword: "additionalProperties", @@ -115544,7 +125927,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD if (0 === _errs4) { if (void 0 !== data0["live.navigator"]) { let _errs5 = 0; - if ("boolean" != typeof data0["live.navigator"]) return validate278.errors = [ { + if ("boolean" != typeof data0["live.navigator"]) return validate281.errors = [ { instancePath: instancePath + "/workspaces/live.navigator", schemaPath: "#/properties/workspaces/properties/live.navigator/type", keyword: "type", @@ -115554,7 +125937,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD } ], !1; var valid1 = 0 === _errs5; } else valid1 = !0; - if (valid1 && void 0 !== data0.investigation && "boolean" != typeof data0.investigation) return validate278.errors = [ { + if (valid1 && void 0 !== data0.investigation && "boolean" != typeof data0.investigation) return validate281.errors = [ { instancePath: instancePath + "/workspaces/investigation", schemaPath: "#/properties/workspaces/properties/investigation/type", keyword: "type", @@ -115565,7 +125948,7 @@ function validate278(data, {instancePath: instancePath = "", parentData: parentD } } } - return validate278.errors = [], !0; + return validate281.errors = [], !0; } function validateAdminActionCatalog(value) { @@ -116800,6 +127183,14 @@ function validateImagingSettingsResponse(value) { })), valid; } +function validateIntegrationCapabilities(value) { + let valid = __contract_IntegrationCapabilities(value); + return validateIntegrationCapabilities.errors = valid ? null : (__contract_IntegrationCapabilities.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + function validateLegacyApiKeyCreated(value) { let valid = __contract_LegacyApiKeyCreated(value); return validateLegacyApiKeyCreated.errors = valid ? null : (__contract_LegacyApiKeyCreated.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ @@ -116936,6 +127327,86 @@ function validateMotionTriggered(value) { })), valid; } +function validateObservation(value) { + let valid = __contract_Observation(value); + return validateObservation.errors = valid ? null : (__contract_Observation.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationChangeEvent(value) { + let valid = __contract_ObservationChangeEvent(value); + return validateObservationChangeEvent.errors = valid ? null : (__contract_ObservationChangeEvent.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationClassification(value) { + let valid = __contract_ObservationClassification(value); + return validateObservationClassification.errors = valid ? null : (__contract_ObservationClassification.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationClassificationResult(value) { + let valid = __contract_ObservationClassificationResult(value); + return validateObservationClassificationResult.errors = valid ? null : (__contract_ObservationClassificationResult.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationClassificationWrite(value) { + let valid = __contract_ObservationClassificationWrite(value); + return validateObservationClassificationWrite.errors = valid ? null : (__contract_ObservationClassificationWrite.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationError(value) { + let valid = __contract_ObservationError(value); + return validateObservationError.errors = valid ? null : (__contract_ObservationError.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationEvidence(value) { + let valid = __contract_ObservationEvidence(value); + return validateObservationEvidence.errors = valid ? null : (__contract_ObservationEvidence.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationPage(value) { + let valid = __contract_ObservationPage(value); + return validateObservationPage.errors = valid ? null : (__contract_ObservationPage.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationPipelineDiagnostics(value) { + let valid = __contract_ObservationPipelineDiagnostics(value); + return validateObservationPipelineDiagnostics.errors = valid ? null : (__contract_ObservationPipelineDiagnostics.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + +function validateObservationTransform(value) { + let valid = __contract_ObservationTransform(value); + return validateObservationTransform.errors = valid ? null : (__contract_ObservationTransform.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ + keyword: keyword, + schemaPath: schemaPath + })), valid; +} + function validateOnvifAddRequest(value) { let valid = __contract_OnvifAddRequest(value); return validateOnvifAddRequest.errors = valid ? null : (__contract_OnvifAddRequest.errors || []).map(({keyword: keyword, schemaPath: schemaPath}) => ({ @@ -118016,7 +128487,7 @@ function validateWorkspacesUpdate(value) { })), valid; } -validate278.evaluated = { +validate281.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 @@ -118087,14 +128558,19 @@ validateIceServersResponse.errors = null, validateImagingModeLevel.errors = null validateImagingModeLevelUpdate.errors = null, validateImagingModeOptions.errors = null, validateImagingOptions.errors = null, validateImagingOptionsResponse.errors = null, validateImagingSettings.errors = null, validateImagingSettingsRequest.errors = null, -validateImagingSettingsResponse.errors = null, validateLegacyApiKeyCreated.errors = null, -validateLiveCameraSlot.errors = null, validateLiveLayout.errors = null, validateLiveLayoutCreate.errors = null, -validateLiveLayoutUpdate.errors = null, validateLiveLayoutsResponse.errors = null, -validateLiveStreamSummaryPage.errors = null, validateLoginConfig.errors = null, -validateLoginLockoutCleared.errors = null, validateLoginMfaChallenge.errors = null, +validateImagingSettingsResponse.errors = null, validateIntegrationCapabilities.errors = null, +validateLegacyApiKeyCreated.errors = null, validateLiveCameraSlot.errors = null, +validateLiveLayout.errors = null, validateLiveLayoutCreate.errors = null, validateLiveLayoutUpdate.errors = null, +validateLiveLayoutsResponse.errors = null, validateLiveStreamSummaryPage.errors = null, +validateLoginConfig.errors = null, validateLoginLockoutCleared.errors = null, validateLoginMfaChallenge.errors = null, validateLoginRequest.errors = null, validateLoginResponse.errors = null, validateLoginSuccess.errors = null, validateLogoutResponse.errors = null, validateLprStreamSummaryPage.errors = null, -validateMotionTrigger.errors = null, validateMotionTriggered.errors = null, validateOnvifAddRequest.errors = null, +validateMotionTrigger.errors = null, validateMotionTriggered.errors = null, validateObservation.errors = null, +validateObservationChangeEvent.errors = null, validateObservationClassification.errors = null, +validateObservationClassificationResult.errors = null, validateObservationClassificationWrite.errors = null, +validateObservationError.errors = null, validateObservationEvidence.errors = null, +validateObservationPage.errors = null, validateObservationPipelineDiagnostics.errors = null, +validateObservationTransform.errors = null, validateOnvifAddRequest.errors = null, validateOnvifAddResult.errors = null, validateOnvifClaimRequest.errors = null, validateOnvifDevice.errors = null, validateOnvifDevicesResponse.errors = null, validateOnvifDiscoveryRequest.errors = null, validateOnvifDiscoveryStatus.errors = null, validateOnvifProfile.errors = null, @@ -118159,4 +128635,4 @@ validateTotpSetupResponse.errors = null, validateTotpStatusResponse.errors = nul validateTrustedDevicesResponse.errors = null, validateWorkspacesResponse.errors = null, validateWorkspacesUpdate.errors = null; -export { validateAdminActionCatalog, validateAdminActionCatalogEntry, validateAdminAuthorizationRole, validateAdminPolicyGrant, validateAdminPolicyWrite, validateAdminRoleDeleted, validateAdminRoleWrite, validateAdminRoleWritten, validateAdminRoles, validateAdminSimulation, validateAdminSimulationRequest, validateAdminUser, validateAdminUserCreate, validateAdminUserDeleted, validateAdminUserPolicy, validateAdminUserUpdate, validateAdminUsers, validateAdminVersionRequest, validateApiError, validateApiTokenCreateRequest, validateApiTokenCreated, validateApiTokenList, validateApiTokenMetadata, validateAuditDecisionMode, validateAuditEvent, validateAuditEventsPage, validateAuditSettings, validateAuditSettingsWrite, validateAuthSessionsResponse, validateAuthVerifyResponse, validateCameraCollection, validateCameraCollectionCreate, validateCameraCollectionMembersRequest, validateCameraCollectionMembersResponse, validateCameraCollectionPreview, validateCameraCollectionUpdate, validateCameraCollectionsResponse, validateCameraFavorite, validateCameraFavoriteRemoval, validateCameraFavoritesResponse, validateCameraLocationAssignment, validateCameraLocationAssignmentRequest, validateCameraTagAssignmentsRequest, validateCameraTagAssignmentsResponse, validateCameraUuid, validateClientConfig, validateCredentialRevoked, validateDaynightRequest, validateDaynightResponse, validateDaynightUpdated, validateDetectionZone, validateDetectionZoneWrite, validateDeviceCommandResult, validateDeviceEmptyRequest, validateDeviceFloatRange, validateDeviceSuccess, validateEventCameraScope, validateEventCatalog, validateEventDestination, validateEventDestinationCreate, validateEventDestinationUpdate, validateEventDestinations, validateEventMutationAck, validateEventPredicate, validateEventRoute, validateEventRouteCreate, validateEventRoutePreview, validateEventRoutePreviewRequest, validateEventRouteUpdate, validateEventRoutes, validateEventSchedule, validateEvidenceBookmark, validateEvidenceBookmarkCreate, validateEvidenceBookmarkDelete, validateEvidenceBookmarkDeleted, validateEvidenceBookmarkFilters, validateEvidenceBookmarkResult, validateEvidenceBookmarkUpdate, validateEvidenceBookmarks, validateEvidenceDetection, validateEvidenceDetections, validateEvidenceFileCheck, validateEvidenceFileDeleted, validateEvidenceLprDeleted, validateEvidenceLprExportRequest, validateEvidenceLprRead, validateEvidenceLprReads, validateEvidenceLprSearchRequest, validateEvidenceModel, validateEvidenceModels, validateEvidencePreview, validateEvidencePreviewRecording, validateEvidenceProtectedCount, validateEvidenceRangeRequest, validateEvidenceSearch, validateEvidenceSearchRequest, validateEvidenceSearchResult, validateEvidenceSegment, validateEvidenceSegmentAt, validateEvidenceSegmentAtRequest, validateEvidenceSyncComplete, validateEvidenceThumbnailRequest, validateEvidenceThumbnailSample, validateEvidenceThumbnails, validateEvidenceTimeline, validateEvidenceTimelineTrack, validateFleetLocation, validateFleetLocationCreate, validateFleetLocationUpdate, validateFleetLocationsResponse, validateFleetMutationAck, validateFleetPreviewRequest, validateFleetPreviewResponse, validateFleetQueryRequest, validateFleetQueryResponse, validateFleetRevisionRequest, validateFleetSavedView, validateFleetSavedViewsResponse, validateFleetTag, validateFleetTagCreate, validateFleetTagMergeRequest, validateFleetTagMergeResult, validateFleetTagUpdate, validateFleetTagsResponse, validateFleetViewCreate, validateFleetViewUpdate, validateFloorPlan, validateFloorPlanCamera, validateFloorPlanCameraRequest, validateFloorPlanCreate, validateFloorPlanDeletion, validateFloorPlanUpdate, validateFloorPlansResponse, validateFloorSketch, validateFloorSketchArea, validateFloorSketchLabel, validateFloorSketchRect, validateFloorSketchWall, validateGo2rtcConfigurationInput, validateGo2rtcConfigurationValidation, validateGo2rtcEffectiveConfiguration, validateGo2rtcOverrideDiagnostics, validateHlsHealth, validateHostHealth, validateHostHealthIncidents, validateIceServersResponse, validateImagingModeLevel, validateImagingModeLevelUpdate, validateImagingModeOptions, validateImagingOptions, validateImagingOptionsResponse, validateImagingSettings, validateImagingSettingsRequest, validateImagingSettingsResponse, validateLegacyApiKeyCreated, validateLiveCameraSlot, validateLiveLayout, validateLiveLayoutCreate, validateLiveLayoutUpdate, validateLiveLayoutsResponse, validateLiveStreamSummaryPage, validateLoginConfig, validateLoginLockoutCleared, validateLoginMfaChallenge, validateLoginRequest, validateLoginResponse, validateLoginSuccess, validateLogoutResponse, validateLprStreamSummaryPage, validateMotionTrigger, validateMotionTriggered, validateOnvifAddRequest, validateOnvifAddResult, validateOnvifClaimRequest, validateOnvifDevice, validateOnvifDevicesResponse, validateOnvifDiscoveryRequest, validateOnvifDiscoveryStatus, validateOnvifProfile, validateOnvifProfilesResponse, validateOnvifTestRequest, validateOpenApiDocument, validatePasswordAdminReset, validatePasswordChangeRequest, validatePasswordChangedResponse, validatePasswordLockRequest, validatePasswordSelfChange, validatePlayerTelemetry, validateProcessLiveness, validatePtzAxesRequest, validatePtzCapabilities, validatePtzGotoPresetRequest, validatePtzPresetRequest, validatePtzPresetResult, validatePtzPresets, validateRecordingBatchProtectionRequest, validateRecordingBatchProtectionResult, validateRecordingBatchTagsRequest, validateRecordingBatchTagsResult, validateRecordingDeletionFilter, validateRecordingDeletionJobAccepted, validateRecordingDeletionProgress, validateRecordingDeletionRequest, validateRecordingDeletionResult, validateRecordingDetail, validateRecordingDetectionLabels, validateRecordingExportAccepted, validateRecordingExportRequest, validateRecordingExportStatus, validateRecordingPlaybackPreparing, validateRecordingPlaybackReady, validateRecordingProtectionRequest, validateRecordingProtectionResult, validateRecordingRetentionRequest, validateRecordingRetentionResult, validateRecordingTags, validateRecordingTagsById, validateRecordingTagsRequest, validateRecordingTimeline, validateRecordingTimelineByIds, validateRecordingsPage, validateSetupStatus, validateSetupUpdate, validateSetupUpdated, validateStorageArchiveDeletion, validateStorageArchiveResponse, validateStorageArchiveRetryRequest, validateStorageArchiveRetryResponse, validateStorageCleanupRequest, validateStorageCleanupResponse, validateStorageCompliancePolicy, validateStorageComplianceResponse, validateStorageComplianceTarget, validateStorageComplianceViolation, validateStorageHealthResponse, validateStorageMigration, validateStorageMigrationCreate, validateStorageMigrationsResponse, validateStoragePoliciesResponse, validateStoragePolicy, validateStoragePolicyApplyRequest, validateStoragePolicyApplyResponse, validateStoragePolicyConflict, validateStoragePolicyCreate, validateStoragePolicyPreview, validateStoragePolicyPreviewRequest, validateStoragePolicyPreviewSample, validateStoragePolicyUpdate, validateStoragePolicyWinner, validateStoragePool, validateStoragePoolCreate, validateStoragePoolMember, validateStoragePoolMemberRequest, validateStoragePoolUpdate, validateStoragePoolsResponse, validateStorageTarget, validateStorageTargetCreate, validateStorageTargetHealth, validateStorageTargetUpdate, validateStorageTargetsResponse, validateStreamCompatibilityList, validateStreamCreateRequest, validateStreamCreated, validateStreamDeleteAccepted, validateStreamDetectionEngine, validateStreamDetectionEngineWrite, validateStreamDetectionEngines, validateStreamDetectionEnginesRequest, validateStreamFullResponse, validateStreamHealth, validateStreamHealthHistory, validateStreamPrivacyUpdated, validateStreamProbeRequest, validateStreamProbeResponse, validateStreamRecordingChanged, validateStreamRecordingRequest, validateStreamRecordingState, validateStreamRefreshAccepted, validateStreamRetention, validateStreamRetentionRequest, validateStreamSingleResponse, validateStreamSummaryPage, validateStreamUpdateAccepted, validateStreamUpdateRequest, validateStreamZones, validateStreamZonesDeleted, validateStreamZonesRequest, validateStreamZonesSaved, validateSuccessResponse, validateSystemActionAccepted, validateSystemBackupCreated, validateSystemInformation, validateSystemLogs, validateSystemSettings, validateSystemSettingsSaved, validateSystemSettingsUpdate, validateSystemStatus, validateTotpChangedResponse, validateTotpCodeRequest, validateTotpLoginRequest, validateTotpLoginResponse, validateTotpSetupResponse, validateTotpStatusResponse, validateTrustedDevicesResponse, validateWorkspacesResponse, validateWorkspacesUpdate }; +export { validateAdminActionCatalog, validateAdminActionCatalogEntry, validateAdminAuthorizationRole, validateAdminPolicyGrant, validateAdminPolicyWrite, validateAdminRoleDeleted, validateAdminRoleWrite, validateAdminRoleWritten, validateAdminRoles, validateAdminSimulation, validateAdminSimulationRequest, validateAdminUser, validateAdminUserCreate, validateAdminUserDeleted, validateAdminUserPolicy, validateAdminUserUpdate, validateAdminUsers, validateAdminVersionRequest, validateApiError, validateApiTokenCreateRequest, validateApiTokenCreated, validateApiTokenList, validateApiTokenMetadata, validateAuditDecisionMode, validateAuditEvent, validateAuditEventsPage, validateAuditSettings, validateAuditSettingsWrite, validateAuthSessionsResponse, validateAuthVerifyResponse, validateCameraCollection, validateCameraCollectionCreate, validateCameraCollectionMembersRequest, validateCameraCollectionMembersResponse, validateCameraCollectionPreview, validateCameraCollectionUpdate, validateCameraCollectionsResponse, validateCameraFavorite, validateCameraFavoriteRemoval, validateCameraFavoritesResponse, validateCameraLocationAssignment, validateCameraLocationAssignmentRequest, validateCameraTagAssignmentsRequest, validateCameraTagAssignmentsResponse, validateCameraUuid, validateClientConfig, validateCredentialRevoked, validateDaynightRequest, validateDaynightResponse, validateDaynightUpdated, validateDetectionZone, validateDetectionZoneWrite, validateDeviceCommandResult, validateDeviceEmptyRequest, validateDeviceFloatRange, validateDeviceSuccess, validateEventCameraScope, validateEventCatalog, validateEventDestination, validateEventDestinationCreate, validateEventDestinationUpdate, validateEventDestinations, validateEventMutationAck, validateEventPredicate, validateEventRoute, validateEventRouteCreate, validateEventRoutePreview, validateEventRoutePreviewRequest, validateEventRouteUpdate, validateEventRoutes, validateEventSchedule, validateEvidenceBookmark, validateEvidenceBookmarkCreate, validateEvidenceBookmarkDelete, validateEvidenceBookmarkDeleted, validateEvidenceBookmarkFilters, validateEvidenceBookmarkResult, validateEvidenceBookmarkUpdate, validateEvidenceBookmarks, validateEvidenceDetection, validateEvidenceDetections, validateEvidenceFileCheck, validateEvidenceFileDeleted, validateEvidenceLprDeleted, validateEvidenceLprExportRequest, validateEvidenceLprRead, validateEvidenceLprReads, validateEvidenceLprSearchRequest, validateEvidenceModel, validateEvidenceModels, validateEvidencePreview, validateEvidencePreviewRecording, validateEvidenceProtectedCount, validateEvidenceRangeRequest, validateEvidenceSearch, validateEvidenceSearchRequest, validateEvidenceSearchResult, validateEvidenceSegment, validateEvidenceSegmentAt, validateEvidenceSegmentAtRequest, validateEvidenceSyncComplete, validateEvidenceThumbnailRequest, validateEvidenceThumbnailSample, validateEvidenceThumbnails, validateEvidenceTimeline, validateEvidenceTimelineTrack, validateFleetLocation, validateFleetLocationCreate, validateFleetLocationUpdate, validateFleetLocationsResponse, validateFleetMutationAck, validateFleetPreviewRequest, validateFleetPreviewResponse, validateFleetQueryRequest, validateFleetQueryResponse, validateFleetRevisionRequest, validateFleetSavedView, validateFleetSavedViewsResponse, validateFleetTag, validateFleetTagCreate, validateFleetTagMergeRequest, validateFleetTagMergeResult, validateFleetTagUpdate, validateFleetTagsResponse, validateFleetViewCreate, validateFleetViewUpdate, validateFloorPlan, validateFloorPlanCamera, validateFloorPlanCameraRequest, validateFloorPlanCreate, validateFloorPlanDeletion, validateFloorPlanUpdate, validateFloorPlansResponse, validateFloorSketch, validateFloorSketchArea, validateFloorSketchLabel, validateFloorSketchRect, validateFloorSketchWall, validateGo2rtcConfigurationInput, validateGo2rtcConfigurationValidation, validateGo2rtcEffectiveConfiguration, validateGo2rtcOverrideDiagnostics, validateHlsHealth, validateHostHealth, validateHostHealthIncidents, validateIceServersResponse, validateImagingModeLevel, validateImagingModeLevelUpdate, validateImagingModeOptions, validateImagingOptions, validateImagingOptionsResponse, validateImagingSettings, validateImagingSettingsRequest, validateImagingSettingsResponse, validateIntegrationCapabilities, validateLegacyApiKeyCreated, validateLiveCameraSlot, validateLiveLayout, validateLiveLayoutCreate, validateLiveLayoutUpdate, validateLiveLayoutsResponse, validateLiveStreamSummaryPage, validateLoginConfig, validateLoginLockoutCleared, validateLoginMfaChallenge, validateLoginRequest, validateLoginResponse, validateLoginSuccess, validateLogoutResponse, validateLprStreamSummaryPage, validateMotionTrigger, validateMotionTriggered, validateObservation, validateObservationChangeEvent, validateObservationClassification, validateObservationClassificationResult, validateObservationClassificationWrite, validateObservationError, validateObservationEvidence, validateObservationPage, validateObservationPipelineDiagnostics, validateObservationTransform, validateOnvifAddRequest, validateOnvifAddResult, validateOnvifClaimRequest, validateOnvifDevice, validateOnvifDevicesResponse, validateOnvifDiscoveryRequest, validateOnvifDiscoveryStatus, validateOnvifProfile, validateOnvifProfilesResponse, validateOnvifTestRequest, validateOpenApiDocument, validatePasswordAdminReset, validatePasswordChangeRequest, validatePasswordChangedResponse, validatePasswordLockRequest, validatePasswordSelfChange, validatePlayerTelemetry, validateProcessLiveness, validatePtzAxesRequest, validatePtzCapabilities, validatePtzGotoPresetRequest, validatePtzPresetRequest, validatePtzPresetResult, validatePtzPresets, validateRecordingBatchProtectionRequest, validateRecordingBatchProtectionResult, validateRecordingBatchTagsRequest, validateRecordingBatchTagsResult, validateRecordingDeletionFilter, validateRecordingDeletionJobAccepted, validateRecordingDeletionProgress, validateRecordingDeletionRequest, validateRecordingDeletionResult, validateRecordingDetail, validateRecordingDetectionLabels, validateRecordingExportAccepted, validateRecordingExportRequest, validateRecordingExportStatus, validateRecordingPlaybackPreparing, validateRecordingPlaybackReady, validateRecordingProtectionRequest, validateRecordingProtectionResult, validateRecordingRetentionRequest, validateRecordingRetentionResult, validateRecordingTags, validateRecordingTagsById, validateRecordingTagsRequest, validateRecordingTimeline, validateRecordingTimelineByIds, validateRecordingsPage, validateSetupStatus, validateSetupUpdate, validateSetupUpdated, validateStorageArchiveDeletion, validateStorageArchiveResponse, validateStorageArchiveRetryRequest, validateStorageArchiveRetryResponse, validateStorageCleanupRequest, validateStorageCleanupResponse, validateStorageCompliancePolicy, validateStorageComplianceResponse, validateStorageComplianceTarget, validateStorageComplianceViolation, validateStorageHealthResponse, validateStorageMigration, validateStorageMigrationCreate, validateStorageMigrationsResponse, validateStoragePoliciesResponse, validateStoragePolicy, validateStoragePolicyApplyRequest, validateStoragePolicyApplyResponse, validateStoragePolicyConflict, validateStoragePolicyCreate, validateStoragePolicyPreview, validateStoragePolicyPreviewRequest, validateStoragePolicyPreviewSample, validateStoragePolicyUpdate, validateStoragePolicyWinner, validateStoragePool, validateStoragePoolCreate, validateStoragePoolMember, validateStoragePoolMemberRequest, validateStoragePoolUpdate, validateStoragePoolsResponse, validateStorageTarget, validateStorageTargetCreate, validateStorageTargetHealth, validateStorageTargetUpdate, validateStorageTargetsResponse, validateStreamCompatibilityList, validateStreamCreateRequest, validateStreamCreated, validateStreamDeleteAccepted, validateStreamDetectionEngine, validateStreamDetectionEngineWrite, validateStreamDetectionEngines, validateStreamDetectionEnginesRequest, validateStreamFullResponse, validateStreamHealth, validateStreamHealthHistory, validateStreamPrivacyUpdated, validateStreamProbeRequest, validateStreamProbeResponse, validateStreamRecordingChanged, validateStreamRecordingRequest, validateStreamRecordingState, validateStreamRefreshAccepted, validateStreamRetention, validateStreamRetentionRequest, validateStreamSingleResponse, validateStreamSummaryPage, validateStreamUpdateAccepted, validateStreamUpdateRequest, validateStreamZones, validateStreamZonesDeleted, validateStreamZonesRequest, validateStreamZonesSaved, validateSuccessResponse, validateSystemActionAccepted, validateSystemBackupCreated, validateSystemInformation, validateSystemLogs, validateSystemSettings, validateSystemSettingsSaved, validateSystemSettingsUpdate, validateSystemStatus, validateTotpChangedResponse, validateTotpCodeRequest, validateTotpLoginRequest, validateTotpLoginResponse, validateTotpSetupResponse, validateTotpStatusResponse, validateTrustedDevicesResponse, validateWorkspacesResponse, validateWorkspacesUpdate }; diff --git a/web/js/components/preact/DetectionClassifications.tsx b/web/js/components/preact/DetectionClassifications.tsx new file mode 100644 index 000000000..f4f3960f1 --- /dev/null +++ b/web/js/components/preact/DetectionClassifications.tsx @@ -0,0 +1,127 @@ +/** + * DetectionClassifications Component + * INT_01 L15: minimal display of an external classification (for example a + * YA-WAMF species) next to the detector's own result. The server links each + * detection to its observation in GET /api/detection/results/{name} + * ("observation" member) and already prefers an owner decision over automated + * ones, so this renders one line per observation and never issues requests. + * The detector label and confidence stay visible; there is no taxonomy view. + */ + +import type { ComponentChild } from 'preact'; +import { useI18n } from '../../i18n.ts'; +import type { DetectionObservation } from '../../types/core.ts'; + +type Translate = (key: string, params?: Record) => string; + +/** The detection fields this view reads (core Detection or TimelineDetection). */ +export interface ClassifiableDetection { + label: string; + confidence: number; + observation?: DetectionObservation | null; +} + +export interface DetectionClassificationView { + observationId: string; + observationUrl: string; + detectorLabel: string; + detectorConfidence: number; + classifiedLabel: string | null; + classifiedConfidence: number | null; + classifier: string | null; + ownerConfirmed: boolean; +} + +const percent = (value: unknown): number => + typeof value === 'number' && Number.isFinite(value) ? Math.round(Math.min(1, Math.max(0, value)) * 100) : 0; + +/** Null unless the detection carries a well-formed observation link. */ +export function detectionClassificationView(detection: ClassifiableDetection): DetectionClassificationView | null { + const observation = detection?.observation; + if (!observation || typeof observation.id !== 'string' || !observation.id) return null; + const classification = observation.classification && typeof observation.classification.label === 'string' + ? observation.classification : null; + return { + observationId: observation.id, + observationUrl: `/api/observations/${encodeURIComponent(observation.id)}`, + detectorLabel: detection.label, + detectorConfidence: percent(detection.confidence), + classifiedLabel: classification ? classification.label : null, + classifiedConfidence: classification ? percent(classification.confidence) : null, + classifier: classification ? classification.classifier : null, + ownerConfirmed: classification ? classification.kind === 'owner_confirmed' : false, + }; +} + +/** Canvas overlay text: the detector result, then the classification if any. */ +export function detectionOverlayLabel(detection: ClassifiableDetection): string { + const original = `${detection.label} (${percent(detection.confidence)}%)`; + const view = detectionClassificationView(detection); + if (!view || view.classifiedLabel === null) return original; + return `${original} · ${view.classifiedLabel} (${view.classifiedConfidence}%)`; +} + +/** One view per linked observation, in detection order. */ +export function classifiedDetections(detections: readonly ClassifiableDetection[]): DetectionClassificationView[] { + const seen = new Set(); + const views: DetectionClassificationView[] = []; + for (const detection of detections || []) { + const view = detectionClassificationView(detection); + if (!view || seen.has(view.observationId)) continue; + seen.add(view.observationId); + views.push(view); + } + return views; +} + +/** Pure render (testable without a DOM); null when nothing is linked. */ +export function renderDetectionClassifications( + detections: readonly ClassifiableDetection[], t: Translate, +): ComponentChild { + const views = classifiedDetections(detections); + if (views.length === 0) return null; + return ( +
+
{t('detections.classification.title')}
+
    + {views.map((view) => ( +
  • + + {t('detections.classification.detector', { label: view.detectorLabel, confidence: view.detectorConfidence })} + + {view.classifiedLabel !== null ? ( + <> + + {t('detections.classification.classified', { label: view.classifiedLabel, confidence: view.classifiedConfidence ?? 0 })} + + + {t('detections.classification.source', { classifier: view.classifier ?? '' })} + + {view.ownerConfirmed && ( + + {t('detections.classification.ownerConfirmed')} + + )} + + ) : ( + {t('detections.classification.unclassified')} + )} + + {t('detections.classification.observationLink')} + +
  • + ))} +
+
+ ); +} + +export interface DetectionClassificationsProps { + detections: readonly ClassifiableDetection[]; +} + +export function DetectionClassifications({ detections }: DetectionClassificationsProps) { + const { t } = useI18n(); + return renderDetectionClassifications(detections, t); +} diff --git a/web/js/components/preact/StreamConfigModal.tsx b/web/js/components/preact/StreamConfigModal.tsx index 5640c0d6b..83e285bd8 100644 --- a/web/js/components/preact/StreamConfigModal.tsx +++ b/web/js/components/preact/StreamConfigModal.tsx @@ -1462,6 +1462,81 @@ export function StreamConfigModal({ )} + {/* INT_01: per-camera observation evidence for integrations (off by default). */} + onToggleSection('observation')} + > +
+

{t('streamsConfig.observationEvidenceHelp')}

+
+ {t('streamsConfig.observationEvidenceDetectorRequired')} +
+ +
+ + +

{t('streamsConfig.observationLabelsHelp')}

+
+
+
+ + +
+
+ + +
+
+

{t('streamsConfig.observationBudgetHelp')}

+
+
+ {/* Browser-side fisheye ePTZ (independent of mechanical ONVIF PTZ). */} { if (e && typeof e.preventDefault === 'function') e.preventDefault(); + const observationError = validateObservationForm(currentStream); + if (observationError) { + showStatusMessage(t(observationError), 'error', 5000); + return Promise.resolve(undefined); + } + const streamData = streamSavePayloadFromForm(currentStream, isEditing); // Use mutation to save stream. We return mutateAsync's promise so @@ -834,6 +851,7 @@ export function StreamsView() { retentionDays: -1, detectionRetentionDays: -1, maxStorageMb: 0, + ...DEFAULT_OBSERVATION_FORM_FIELDS, // Recording schedule recordOnSchedule: false, recordingSchedule: Array(168).fill(true), diff --git a/web/js/components/preact/SystemView.tsx b/web/js/components/preact/SystemView.tsx index 893a7cee4..59e31ab33 100644 --- a/web/js/components/preact/SystemView.tsx +++ b/web/js/components/preact/SystemView.tsx @@ -18,6 +18,7 @@ import { SystemControls } from './system/SystemControls.tsx'; import { SystemInfo } from './system/SystemInfo.tsx'; import { MemoryStorage } from './system/MemoryStorage.tsx'; import { StreamStorage } from './system/StreamStorage.tsx'; +import { ObservationPipeline } from './system/ObservationPipeline.tsx'; import { StorageHealth } from './system/StorageHealth.tsx'; import { NetworkInfo } from './system/NetworkInfo.tsx'; import { SystemHealth } from './system/SystemHealth.tsx'; @@ -339,6 +340,12 @@ export function SystemView() { + {systemInfo.observation_pipeline && ( +
+ +
+ )} +
diff --git a/web/js/components/preact/UI.tsx b/web/js/components/preact/UI.tsx index c32e1c02e..5b33aec3d 100644 --- a/web/js/components/preact/UI.tsx +++ b/web/js/components/preact/UI.tsx @@ -17,6 +17,8 @@ import utc from 'dayjs/plugin/utc'; import customParseFormat from 'dayjs/plugin/customParseFormat'; import { ConfirmDialog } from './common/ModalDialog.tsx'; import { loadRecordingPlayback } from '../../utils/recording-playback.ts'; +import { DetectionClassifications, detectionOverlayLabel } from './DetectionClassifications.tsx'; +import type { DetectionObservation } from '../../types/core.ts'; export { ConfirmDialog } from './common/ModalDialog.tsx'; @@ -44,6 +46,8 @@ interface DetectionResult { width: number; height: number; timestamp?: number; + /** INT_01 L15: server-linked observation summary, when one matches. */ + observation?: DetectionObservation; } type WebkitFullscreenDocument = Document & { @@ -628,7 +632,7 @@ export function VideoModal({ isOpen, onClose, videoUrl, title, downloadUrl }: Vi // Draw label background const fontSize = Math.round(Math.max(12, 14 * scale)); ctx.font = `${fontSize}px Arial`; - const labelText = `${detection.label} (${Math.round(detection.confidence * 100)}%)`; + const labelText = detectionOverlayLabel(detection); const labelWidth = ctx.measureText(labelText).width + 10; const labelHeight = fontSize + 8; ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; @@ -1026,6 +1030,9 @@ export function VideoModal({ isOpen, onClose, videoUrl, title, downloadUrl }: Vi }`}> {detectionStatus}
+ + {/* INT_01 L15: classifications linked to these detections */} + diff --git a/web/js/components/preact/settings/Go2rtcTab.tsx b/web/js/components/preact/settings/Go2rtcTab.tsx index 1151b6eb0..94961d28f 100644 --- a/web/js/components/preact/settings/Go2rtcTab.tsx +++ b/web/js/components/preact/settings/Go2rtcTab.tsx @@ -436,6 +436,7 @@ export function Go2rtcTab({ settings, setSettings, handleInputChange, canModifyS name="go2rtcExternalIp" class="p-2 border border-input rounded bg-background text-foreground w-full max-w-md disabled:opacity-60 disabled:cursor-not-allowed" value={settings.go2rtcExternalIp} + maxLength={255} onChange={handleInputChange} disabled={!canModifySettings} placeholder={t('settings.autoDetect')} diff --git a/web/js/components/preact/system/ObservationPipeline.tsx b/web/js/components/preact/system/ObservationPipeline.tsx new file mode 100644 index 000000000..c84bb4441 --- /dev/null +++ b/web/js/components/preact/system/ObservationPipeline.tsx @@ -0,0 +1,105 @@ +/** + * ObservationPipeline Component + * INT_01 L10: observation evidence and publication diagnostics from + * GET /api/system/info "observation_pipeline" (contract section 15). + */ + +import { useI18n } from '../../../i18n.ts'; +import type { FormatBytes, ObservationPipelineDiagnostics, SystemInfoData } from '../../../types/system.ts'; + +export interface ObservationPipelineProps { + systemInfo: SystemInfoData; + formatBytes: FormatBytes; +} + +const count = (value: unknown): number => + typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : 0; + +/** Narrow the untyped system-info member; null when absent or malformed. */ +export function readObservationPipeline(systemInfo: SystemInfoData | undefined): ObservationPipelineDiagnostics | null { + const value = systemInfo?.observation_pipeline; + if (!value || typeof value !== 'object' || typeof value.active !== 'boolean') return null; + return value; +} + +export function totalDropped(pipeline: ObservationPipelineDiagnostics): number { + const dropped = pipeline.dropped ?? {}; + return count(dropped.queue_full) + count(dropped.budget_exhausted) + count(dropped.disabled) + count(dropped.untracked); +} + +export function totalFailures(pipeline: ObservationPipelineDiagnostics): number { + const failures = pipeline.evidence?.failures ?? {}; + return Object.values(failures).reduce((sum, value) => sum + count(value), 0); +} + +/** Whole seconds; the label carries the unit. */ +export function formatLag(seconds: unknown): string { + return String(Math.round(count(seconds))); +} + +function Row({ label, value, warn = false }: { label: string; value: string; warn?: boolean }) { + return ( +
+ {label}: + {value} +
+ ); +} + +export function ObservationPipeline({ systemInfo, formatBytes }: ObservationPipelineProps) { + const { t } = useI18n(); + const pipeline = readObservationPipeline(systemInfo); + if (!pipeline) return null; + + const dropped = pipeline.dropped ?? {}; + const evidence = pipeline.evidence ?? {}; + const publications = pipeline.publications ?? {}; + const history = pipeline.history ?? {}; + const cameras = Array.isArray(pipeline.cameras) ? pipeline.cameras : []; + const retained = cameras.reduce((sum, camera) => sum + count(camera.retained_bytes), 0); + const droppedTotal = totalDropped(pipeline); + const failureTotal = totalFailures(pipeline); + const exhausted = count(pipeline.budget_exhausted_cameras); + + return ( +
+

{t('system.observations.title')}

+ {!pipeline.active ? ( +

{t('system.observations.inactive')}

+ ) : ( +
+ + 0} + /> + + + 0} /> + + 0} /> + + 0} + /> + = 60} + /> +
+ )} +
+ ); +} diff --git a/web/js/components/preact/timeline/TimelinePlayer.tsx b/web/js/components/preact/timeline/TimelinePlayer.tsx index 00526aeee..b33cbb713 100644 --- a/web/js/components/preact/timeline/TimelinePlayer.tsx +++ b/web/js/components/preact/timeline/TimelinePlayer.tsx @@ -17,6 +17,7 @@ import { showStatusMessage } from '../ToastContainer.tsx'; import { ConfirmDialog } from '../UI.tsx'; import { formatFilenameTimestamp, formatLocalDateTime, toUnixSeconds } from '../../../utils/date-utils.ts'; import { useI18n } from '../../../i18n.ts'; +import { DetectionClassifications, detectionOverlayLabel } from '../DetectionClassifications.tsx'; import { FisheyeEptzCanvas } from '../FisheyeEptzCanvas.tsx'; import { isEptzEnabled } from '../../../utils/eptz-config.ts'; import { resolveRecordedStreamSummary } from '../../../utils/stream-summaries.ts'; @@ -743,7 +744,7 @@ export function TimelinePlayer({ const fontSize = Math.round(Math.max(12, 14 * scale)); ctx.font = `${fontSize}px Arial`; - const labelText = `${detection.label} (${Math.round(detection.confidence * 100)}%)`; + const labelText = detectionOverlayLabel(detection); const labelWidth = ctx.measureText(labelText).width + 10; const labelHeight = fontSize + 8; ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; @@ -1104,6 +1105,9 @@ export function TimelinePlayer({ + {/* INT_01 L15: classifications linked to this segment's detections */} + + {/* Delete confirmation */} ]: FormNumber }; /** UI-owned fields only; full wire responses are mapped explicitly. */ -export type StreamFormState = StreamFormFields & StreamEptzFormFields & { +export type StreamFormState = StreamFormFields & StreamEptzFormFields & ObservationFormFields & { canControlPrivacy?: boolean; }; @@ -258,6 +259,7 @@ export interface ExpandedSections { eptz: boolean; ptz: boolean; advanced: boolean; + observation: boolean; go2rtcOverride: boolean; restream: boolean; } diff --git a/web/js/types/system.ts b/web/js/types/system.ts index d07b5c4f8..db08a72f0 100644 --- a/web/js/types/system.ts +++ b/web/js/types/system.ts @@ -86,6 +86,55 @@ export interface SystemInfoData { recordings?: { count?: number; size?: number }; streamStorage?: StreamStorageEntry[]; versions?: SystemVersions; + /** INT_01 L10 (core/observation_diagnostics.c). */ + observation_pipeline?: ObservationPipelineDiagnostics; +} + +/** GET /api/system/info "observation_pipeline": observation_diagnostics_to_json(). */ +export interface ObservationPipelineDiagnostics { + /** False when evidence is unused: counters are zero and no queue exists. */ + active: boolean; + accepted?: number; + dropped?: { queue_full?: number; budget_exhausted?: number; disabled?: number; untracked?: number }; + evidence?: { + queue_allocated?: boolean; + worker_running?: boolean; + backlog_items?: number; + backlog_bytes?: number; + high_water_items?: number; + high_water_bytes?: number; + drop_ring_depth?: number; + ready?: number; + unavailable_submitted?: number; + bytes_written?: number; + /** Counts by reason; OpenAPI allows additional members, so values are unknown. */ + failures?: Record; + }; + history?: { + available?: boolean; + observations?: number; + oldest_seq?: number; + head_seq?: number; + oldest_observed_at?: string | null; + epoch_regenerations?: number; + }; + publications?: { + pending?: number; + failed?: number; + expired?: number; + relay_lag_seconds?: number; + relay_running?: boolean; + relayed?: number; + relayed_no_route?: number; + }; + budget_exhausted_cameras?: number; + cameras?: Array<{ + camera_uuid: string; + retained_bytes?: number; + max_bytes?: number; + max_age_days?: number; + budget_exhausted?: boolean; + }>; } // --------------------------------------------------------------------------- diff --git a/web/js/types/timeline.ts b/web/js/types/timeline.ts index e91ddf82d..33aea7509 100644 --- a/web/js/types/timeline.ts +++ b/web/js/types/timeline.ts @@ -3,7 +3,7 @@ * src/web/api_handlers_detection.c, and the timelineState store in * components/preact/timeline/TimelinePage.tsx. */ -import type { StreamSummary } from './core.ts'; +import type { DetectionObservation, StreamSummary } from './core.ts'; /** Anything with a Unix-second span: segments and exact detection intervals. */ export interface TimelineRange { @@ -74,6 +74,7 @@ export interface TimelineDetection { height: number; timestamp: number; end_timestamp: number; + observation?: DetectionObservation; } export interface TimelineDetectionsResponse { diff --git a/web/js/utils/observation-evidence.ts b/web/js/utils/observation-evidence.ts new file mode 100644 index 000000000..9275a0f3c --- /dev/null +++ b/web/js/utils/observation-evidence.ts @@ -0,0 +1,96 @@ +/** + * INT_01 L9: per-camera observation evidence settings for integrations + * (docs/integrations/OBSERVATIONS_CONTRACT.md section 13). The server enforces + * the same bounds; the form checks them first so the user gets a clear message. + */ + +type FormNumber = number | string; + +export interface ObservationFormFields { + observationEvidenceEnabled: boolean; + /** Comma-separated detector labels; empty means every object label. */ + observationLabels: string; + observationMaxAgeDays: FormNumber; + /** The byte budget in MiB (the wire value is bytes). */ + observationMaxMib: FormNumber; +} + +export interface ObservationWireFields { + observation_evidence_enabled?: boolean; + observation_labels?: string; + observation_max_age_days?: number; + observation_max_bytes?: number; +} + +const MIB = 1024 * 1024; +export const OBSERVATION_MAX_AGE_DAYS = { min: 1, max: 90 } as const; +export const OBSERVATION_MAX_MIB = { min: 64, max: 1024 * 1024 } as const; +export const OBSERVATION_LABELS_MAX = 16; +export const OBSERVATION_LABEL_MAX_BYTES = 31; + +export const DEFAULT_OBSERVATION_FORM_FIELDS: Readonly = Object.freeze({ + observationEvidenceEnabled: false, + observationLabels: '', + observationMaxAgeDays: 7, + observationMaxMib: 2048, +}); + +const toInteger = (value: FormNumber): number => { + const parsed = typeof value === 'number' ? value : Number(String(value).trim()); + return Number.isInteger(parsed) ? parsed : Number.NaN; +}; + +/** Trimmed label entries, or null when the list is not acceptable to the server. */ +export function parseObservationLabels(value: string): string[] | null { + if (value.trim() === '') return []; + const labels = value.split(',').map(label => label.trim()); + if (labels.length > OBSERVATION_LABELS_MAX) return null; + for (const label of labels) { + if (label.length === 0 || label.length > OBSERVATION_LABEL_MAX_BYTES) return null; + if (!/^[\x20-\x7e]+$/.test(label)) return null; + } + return labels; +} + +/** Map stream details to form fields; missing values take the contract defaults. */ +export function observationFormFields(stream: ObservationWireFields): ObservationFormFields { + const bytes = stream.observation_max_bytes; + return { + observationEvidenceEnabled: stream.observation_evidence_enabled === true, + observationLabels: stream.observation_labels ?? '', + observationMaxAgeDays: stream.observation_max_age_days ?? DEFAULT_OBSERVATION_FORM_FIELDS.observationMaxAgeDays, + observationMaxMib: typeof bytes === 'number' && bytes > 0 + ? Math.round(bytes / MIB) + : DEFAULT_OBSERVATION_FORM_FIELDS.observationMaxMib, + }; +} + +/** The i18n key of the first invalid field, or null when the form is valid. */ +export function validateObservationForm(form: ObservationFormFields): string | null { + const days = toInteger(form.observationMaxAgeDays); + if (!(days >= OBSERVATION_MAX_AGE_DAYS.min && days <= OBSERVATION_MAX_AGE_DAYS.max)) { + return 'streamsConfig.observationMaxAgeInvalid'; + } + const mib = toInteger(form.observationMaxMib); + if (!(mib >= OBSERVATION_MAX_MIB.min && mib <= OBSERVATION_MAX_MIB.max)) { + return 'streamsConfig.observationMaxSizeInvalid'; + } + if (parseObservationLabels(form.observationLabels ?? '') === null) { + return 'streamsConfig.observationLabelsInvalid'; + } + return null; +} + +/** Wire fields for POST/PUT. Call validateObservationForm first. */ +export function observationPayloadFields(form: ObservationFormFields): Required { + const days = toInteger(form.observationMaxAgeDays); + const mib = toInteger(form.observationMaxMib); + const labels = parseObservationLabels(form.observationLabels ?? ''); + return { + observation_evidence_enabled: !!form.observationEvidenceEnabled, + observation_labels: labels ? labels.join(',') : (form.observationLabels ?? ''), + observation_max_age_days: Number.isNaN(days) + ? Number(DEFAULT_OBSERVATION_FORM_FIELDS.observationMaxAgeDays) : days, + observation_max_bytes: (Number.isNaN(mib) ? Number(DEFAULT_OBSERVATION_FORM_FIELDS.observationMaxMib) : mib) * MIB, + }; +} diff --git a/web/public/locales/ar.json b/web/public/locales/ar.json index b023e40fa..a6eb4de01 100644 --- a/web/public/locales/ar.json +++ b/web/public/locales/ar.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "سجّل الدخول بحساب مسموح له بعرض مرجع API.", "apiDocs.unavailable": "تعذر تحميل مرجع API. حاول مرة أخرى.", "apiDocs.partial": "يغطي هذا المرجع واجهات API محددة. غياب العمليات لا يعني رفض الوصول.", - "apiDocs.empty": "لا توجد عمليات API متاحة في هذا المرجع للجلسة الحالية." + "apiDocs.empty": "لا توجد عمليات API متاحة في هذا المرجع للجلسة الحالية.", + "system.observations.title": "مسار الملاحظات", + "system.observations.inactive": "غير مستخدم: لم تُنشئ أي كاميرا أدلة ملاحظة في هذا التشغيل.", + "system.observations.accepted": "الملاحظات المقبولة", + "system.observations.dropped": "المُسقطة (الطابور ممتلئ / الميزانية / معطّل / غير متتبَّع)", + "system.observations.backlog": "الأدلة المتراكمة", + "system.observations.highWater": "أعلى تراكم", + "system.observations.failures": "إخفاقات الأدلة", + "system.observations.retained": "الأدلة المحفوظة", + "system.observations.budgetExhausted": "الكاميرات التي تجاوزت ميزانية الأدلة", + "system.observations.oldestRecoverable": "أقدم مؤشر قابل للاسترداد", + "system.observations.publications": "المنشورات المعلقة / الفاشلة / المنتهية", + "system.observations.relayLag": "تأخر الترحيل (ثوانٍ)", + "detections.classification.title": "الاكتشافات المصنفة", + "detections.classification.detector": "الكاشف: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "عبر {classifier}", + "detections.classification.ownerConfirmed": "مؤكد من المالك", + "detections.classification.unclassified": "غير مصنف", + "detections.classification.observationLink": "الملاحظة" } diff --git a/web/public/locales/de.json b/web/public/locales/de.json index c90407409..6bb922ab2 100644 --- a/web/public/locales/de.json +++ b/web/public/locales/de.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Melde dich mit einem Konto an, das die API-Referenz ansehen darf.", "apiDocs.unavailable": "Die API-Referenz konnte nicht geladen werden. Versuche es erneut.", "apiDocs.partial": "Diese Referenz umfasst ausgewählte APIs. Fehlende Operationen bedeuten keine Zugriffsverweigerung.", - "apiDocs.empty": "Für die aktuelle Sitzung sind in dieser Referenz keine API-Operationen verfügbar." + "apiDocs.empty": "Für die aktuelle Sitzung sind in dieser Referenz keine API-Operationen verfügbar.", + "system.observations.title": "Beobachtungs-Pipeline", + "system.observations.inactive": "Nicht in Verwendung: Keine Kamera hat in diesem Lauf Beobachtungsnachweise erzeugt.", + "system.observations.accepted": "Akzeptierte Beobachtungen", + "system.observations.dropped": "Verworfen (Warteschlange voll / Budget / deaktiviert / nicht erfasst)", + "system.observations.backlog": "Nachweis-Rückstand", + "system.observations.highWater": "Höchster Rückstand", + "system.observations.failures": "Nachweisfehler", + "system.observations.retained": "Aufbewahrte Nachweise", + "system.observations.budgetExhausted": "Kameras über dem Nachweisbudget", + "system.observations.oldestRecoverable": "Ältester wiederherstellbarer Cursor", + "system.observations.publications": "Veröffentlichungen ausstehend / fehlgeschlagen / abgelaufen", + "system.observations.relayLag": "Relay-Verzögerung (Sekunden)", + "detections.classification.title": "Klassifizierte Erkennungen", + "detections.classification.detector": "Detektor: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "über {classifier}", + "detections.classification.ownerConfirmed": "Vom Eigentümer bestätigt", + "detections.classification.unclassified": "Nicht klassifiziert", + "detections.classification.observationLink": "Beobachtung" } diff --git a/web/public/locales/en.json b/web/public/locales/en.json index 491c40d3c..42e645683 100644 --- a/web/public/locales/en.json +++ b/web/public/locales/en.json @@ -1832,6 +1832,18 @@ "streamsConfig.retentionDaysHelp": "Days to keep regular recordings (0 = unlimited)", "streamsConfig.detectionRetentionDays": "Detection Retention Days", "streamsConfig.detectionRetentionDaysHelp": "Days to keep detection recordings (0 = unlimited)", + "streamsConfig.observationEvidence": "Observation evidence for integrations", + "streamsConfig.observationEvidenceHelp": "Keeps the frame each detection was made on, with its label and box, so integrations and automations can fetch it (for example YA-WAMF for birds). Works with any detector label. Off by default.", + "streamsConfig.observationEvidenceDetectorRequired": "Requires an object detector on this camera. Evidence is kept only for labels your detection model produces.", + "streamsConfig.observationEvidenceEnable": "Keep observation evidence for this camera", + "streamsConfig.observationLabels": "Eligible labels", + "streamsConfig.observationLabelsHelp": "Comma-separated detector labels, for example person, car, package or bird. Leave empty for every label. Up to 16 labels of 31 characters.", + "streamsConfig.observationMaxAgeDays": "Evidence max age (days)", + "streamsConfig.observationMaxSizeMib": "Evidence storage limit (MiB)", + "streamsConfig.observationBudgetHelp": "The oldest evidence images are removed when either limit is reached: 1 to 90 days, 64 MiB to 1 TiB. Changes apply without a restart.", + "streamsConfig.observationMaxAgeInvalid": "Evidence max age must be a whole number of days from 1 to 90.", + "streamsConfig.observationMaxSizeInvalid": "Evidence storage limit must be a whole number from 64 to 1048576 MiB.", + "streamsConfig.observationLabelsInvalid": "Eligible labels must be up to 16 comma-separated labels of 1 to 31 printable characters.", "streamsConfig.maxStorageMb": "Max Storage (MB)", "streamsConfig.maxStorageMbHelp": "Storage quota for this stream (0 = unlimited)", "streamsConfig.tierCriticalMultiplier": "Critical tier ×", @@ -2305,7 +2317,7 @@ "settings.stunServerHelp": "STUN server address for NAT traversal", "settings.externalIp": "External IP", "settings.autoDetect": "Auto-detect", - "settings.externalIpHelp": "Optional: External IP for NAT (leave empty for auto-detect)", + "settings.externalIpHelp": "Optional: the addresses clients use to reach this server for WebRTC, comma-separated (for example 100.101.102.103, 192.168.1.20, * for auto-detect). Add one for each way you connect: LAN, Tailscale/VPN, or public. Leave empty to auto-detect.", "settings.iceServers": "ICE Servers", "settings.iceServersPlaceholder": "Optional comma-separated ICE servers", "settings.iceServersHelp": "Optional: Custom ICE servers (comma-separated)", @@ -2980,5 +2992,24 @@ "apiDocs.empty": "No API operations are available in this reference for the current session.", "recordings.reverseSort": "Reverse sort order", "recordings.ascending": "Ascending ↑", - "recordings.descending": "Descending ↓" + "recordings.descending": "Descending ↓", + "system.observations.title": "Observation pipeline", + "system.observations.inactive": "Not in use: no camera has produced observation evidence in this run.", + "system.observations.accepted": "Accepted observations", + "system.observations.dropped": "Dropped (queue full / budget / disabled / untracked)", + "system.observations.backlog": "Evidence backlog", + "system.observations.highWater": "Backlog high-water mark", + "system.observations.failures": "Evidence failures", + "system.observations.retained": "Retained evidence", + "system.observations.budgetExhausted": "Cameras over evidence budget", + "system.observations.oldestRecoverable": "Oldest recoverable cursor", + "system.observations.publications": "Publications pending / failed / expired", + "system.observations.relayLag": "Relay lag (seconds)", + "detections.classification.title": "Classified detections", + "detections.classification.detector": "Detector: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "via {classifier}", + "detections.classification.ownerConfirmed": "Owner confirmed", + "detections.classification.unclassified": "Not classified", + "detections.classification.observationLink": "Observation" } diff --git a/web/public/locales/es.json b/web/public/locales/es.json index c34547ed3..d695450a8 100644 --- a/web/public/locales/es.json +++ b/web/public/locales/es.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Inicia sesión con una cuenta autorizada para ver la referencia de la API.", "apiDocs.unavailable": "No se pudo cargar la referencia de la API. Inténtalo de nuevo.", "apiDocs.partial": "Esta referencia cubre algunas API. Las operaciones ausentes no indican acceso denegado.", - "apiDocs.empty": "No hay operaciones de la API disponibles en esta referencia para la sesión actual." + "apiDocs.empty": "No hay operaciones de la API disponibles en esta referencia para la sesión actual.", + "system.observations.title": "Canal de observaciones", + "system.observations.inactive": "Sin uso: ninguna cámara ha generado evidencias de observación en esta ejecución.", + "system.observations.accepted": "Observaciones aceptadas", + "system.observations.dropped": "Descartadas (cola llena / presupuesto / desactivado / sin seguimiento)", + "system.observations.backlog": "Evidencias pendientes", + "system.observations.highWater": "Máximo de pendientes", + "system.observations.failures": "Fallos de evidencias", + "system.observations.retained": "Evidencias conservadas", + "system.observations.budgetExhausted": "Cámaras sobre el presupuesto de evidencias", + "system.observations.oldestRecoverable": "Cursor recuperable más antiguo", + "system.observations.publications": "Publicaciones pendientes / fallidas / caducadas", + "system.observations.relayLag": "Retraso del relé (segundos)", + "detections.classification.title": "Detecciones clasificadas", + "detections.classification.detector": "Detector: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "mediante {classifier}", + "detections.classification.ownerConfirmed": "Confirmado por el propietario", + "detections.classification.unclassified": "Sin clasificar", + "detections.classification.observationLink": "Observación" } diff --git a/web/public/locales/fil.json b/web/public/locales/fil.json index 346fe56b9..cb801aee1 100644 --- a/web/public/locales/fil.json +++ b/web/public/locales/fil.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Mag-sign in gamit ang account na pinapayagang tingnan ang sanggunian ng API.", "apiDocs.unavailable": "Hindi ma-load ang sanggunian ng API. Subukang muli.", "apiDocs.partial": "Piling API ang saklaw ng sangguniang ito. Ang mga nawawalang operasyon ay hindi nangangahulugang tinanggihan ang access.", - "apiDocs.empty": "Walang available na operasyon ng API sa sangguniang ito para sa kasalukuyang session." + "apiDocs.empty": "Walang available na operasyon ng API sa sangguniang ito para sa kasalukuyang session.", + "system.observations.title": "Pipeline ng obserbasyon", + "system.observations.inactive": "Hindi ginagamit: walang camera na gumawa ng ebidensya ng obserbasyon sa run na ito.", + "system.observations.accepted": "Mga tinanggap na obserbasyon", + "system.observations.dropped": "Itinapon (puno ang pila / badyet / naka-disable / hindi nasubaybayan)", + "system.observations.backlog": "Nakabinbing ebidensya", + "system.observations.highWater": "Pinakamataas na nakabinbin", + "system.observations.failures": "Mga pagkabigo ng ebidensya", + "system.observations.retained": "Naitabing ebidensya", + "system.observations.budgetExhausted": "Mga camera na lampas sa badyet ng ebidensya", + "system.observations.oldestRecoverable": "Pinakalumang mababawing cursor", + "system.observations.publications": "Mga publikasyong nakabinbin / nabigo / nag-expire", + "system.observations.relayLag": "Antala ng relay (segundo)", + "detections.classification.title": "Mga naka-classify na detection", + "detections.classification.detector": "Detector: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "sa pamamagitan ng {classifier}", + "detections.classification.ownerConfirmed": "Kinumpirma ng may-ari", + "detections.classification.unclassified": "Hindi naka-classify", + "detections.classification.observationLink": "Obserbasyon" } diff --git a/web/public/locales/fr.json b/web/public/locales/fr.json index e6ed01af8..087b8ca4c 100644 --- a/web/public/locales/fr.json +++ b/web/public/locales/fr.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Connectez-vous avec un compte autorisé à consulter la référence de l’API.", "apiDocs.unavailable": "Impossible de charger la référence de l’API. Réessayez.", "apiDocs.partial": "Cette référence couvre certaines API. Les opérations absentes ne signifient pas un accès refusé.", - "apiDocs.empty": "Aucune opération d’API n’est disponible dans cette référence pour la session actuelle." + "apiDocs.empty": "Aucune opération d’API n’est disponible dans cette référence pour la session actuelle.", + "system.observations.title": "Pipeline d'observations", + "system.observations.inactive": "Inutilisé : aucune caméra n'a produit de preuve d'observation pendant cette exécution.", + "system.observations.accepted": "Observations acceptées", + "system.observations.dropped": "Rejetées (file pleine / budget / désactivé / non suivi)", + "system.observations.backlog": "Preuves en attente", + "system.observations.highWater": "Pic d'attente", + "system.observations.failures": "Échecs de preuves", + "system.observations.retained": "Preuves conservées", + "system.observations.budgetExhausted": "Caméras au-delà du budget de preuves", + "system.observations.oldestRecoverable": "Plus ancien curseur récupérable", + "system.observations.publications": "Publications en attente / échouées / expirées", + "system.observations.relayLag": "Retard du relais (secondes)", + "detections.classification.title": "Détections classifiées", + "detections.classification.detector": "Détecteur : {label} ({confidence} %)", + "detections.classification.classified": "{label} ({confidence} %)", + "detections.classification.source": "via {classifier}", + "detections.classification.ownerConfirmed": "Confirmé par le propriétaire", + "detections.classification.unclassified": "Non classifié", + "detections.classification.observationLink": "Observation" } diff --git a/web/public/locales/it.json b/web/public/locales/it.json index 7913b5c40..28f124009 100644 --- a/web/public/locales/it.json +++ b/web/public/locales/it.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Accedi con un account autorizzato a visualizzare il riferimento API.", "apiDocs.unavailable": "Impossibile caricare il riferimento API. Riprova.", "apiDocs.partial": "Questo riferimento copre alcune API. Le operazioni assenti non indicano un accesso negato.", - "apiDocs.empty": "Nessuna operazione API disponibile in questo riferimento per la sessione attuale." + "apiDocs.empty": "Nessuna operazione API disponibile in questo riferimento per la sessione attuale.", + "system.observations.title": "Pipeline delle osservazioni", + "system.observations.inactive": "Non in uso: nessuna telecamera ha prodotto prove di osservazione in questa esecuzione.", + "system.observations.accepted": "Osservazioni accettate", + "system.observations.dropped": "Scartate (coda piena / budget / disattivato / non tracciato)", + "system.observations.backlog": "Prove in coda", + "system.observations.highWater": "Picco della coda", + "system.observations.failures": "Errori delle prove", + "system.observations.retained": "Prove conservate", + "system.observations.budgetExhausted": "Telecamere oltre il budget delle prove", + "system.observations.oldestRecoverable": "Cursore recuperabile più vecchio", + "system.observations.publications": "Pubblicazioni in attesa / non riuscite / scadute", + "system.observations.relayLag": "Ritardo del relay (secondi)", + "detections.classification.title": "Rilevamenti classificati", + "detections.classification.detector": "Rilevatore: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "tramite {classifier}", + "detections.classification.ownerConfirmed": "Confermato dal proprietario", + "detections.classification.unclassified": "Non classificato", + "detections.classification.observationLink": "Osservazione" } diff --git a/web/public/locales/ja.json b/web/public/locales/ja.json index 3923737b3..927afbe05 100644 --- a/web/public/locales/ja.json +++ b/web/public/locales/ja.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "API リファレンスを閲覧できるアカウントでログインしてください。", "apiDocs.unavailable": "API リファレンスを読み込めません。もう一度お試しください。", "apiDocs.partial": "このリファレンスは一部の API を対象としています。記載されていない操作がアクセス拒否を意味するわけではありません。", - "apiDocs.empty": "現在のセッションで利用可能な API 操作はこのリファレンスにありません。" + "apiDocs.empty": "現在のセッションで利用可能な API 操作はこのリファレンスにありません。", + "system.observations.title": "観測パイプライン", + "system.observations.inactive": "未使用: この実行ではどのカメラも観測エビデンスを生成していません。", + "system.observations.accepted": "受理された観測", + "system.observations.dropped": "破棄 (キュー満杯 / 予算 / 無効 / 未追跡)", + "system.observations.backlog": "エビデンスの待ち", + "system.observations.highWater": "待ちの最大値", + "system.observations.failures": "エビデンスの失敗", + "system.observations.retained": "保持中のエビデンス", + "system.observations.budgetExhausted": "エビデンス予算超過のカメラ", + "system.observations.oldestRecoverable": "復元可能な最古のカーソル", + "system.observations.publications": "公開 保留 / 失敗 / 期限切れ", + "system.observations.relayLag": "リレーの遅延 (秒)", + "detections.classification.title": "分類済みの検出", + "detections.classification.detector": "検出器: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "{classifier} による", + "detections.classification.ownerConfirmed": "所有者が確認済み", + "detections.classification.unclassified": "未分類", + "detections.classification.observationLink": "観測" } diff --git a/web/public/locales/ko.json b/web/public/locales/ko.json index 9f13c84e1..c74cd8617 100644 --- a/web/public/locales/ko.json +++ b/web/public/locales/ko.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "API 참조를 볼 수 있는 계정으로 로그인하세요.", "apiDocs.unavailable": "API 참조를 불러올 수 없습니다. 다시 시도하세요.", "apiDocs.partial": "이 참조는 일부 API만 다룹니다. 누락된 작업이 접근 거부를 의미하지는 않습니다.", - "apiDocs.empty": "현재 세션에 대해 이 참조에서 사용 가능한 API 작업이 없습니다." + "apiDocs.empty": "현재 세션에 대해 이 참조에서 사용 가능한 API 작업이 없습니다.", + "system.observations.title": "관찰 파이프라인", + "system.observations.inactive": "사용 안 함: 이번 실행에서 관찰 증거를 생성한 카메라가 없습니다.", + "system.observations.accepted": "수락된 관찰", + "system.observations.dropped": "삭제됨 (대기열 가득 참 / 예산 / 비활성 / 추적 안 됨)", + "system.observations.backlog": "증거 대기열", + "system.observations.highWater": "대기열 최고치", + "system.observations.failures": "증거 실패", + "system.observations.retained": "보관된 증거", + "system.observations.budgetExhausted": "증거 예산 초과 카메라", + "system.observations.oldestRecoverable": "복구 가능한 가장 오래된 커서", + "system.observations.publications": "게시 대기 / 실패 / 만료", + "system.observations.relayLag": "릴레이 지연 (초)", + "detections.classification.title": "분류된 감지", + "detections.classification.detector": "감지기: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "{classifier} 제공", + "detections.classification.ownerConfirmed": "소유자 확인됨", + "detections.classification.unclassified": "분류되지 않음", + "detections.classification.observationLink": "관측" } diff --git a/web/public/locales/nl.json b/web/public/locales/nl.json index 1c3833562..7ad357000 100644 --- a/web/public/locales/nl.json +++ b/web/public/locales/nl.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Log in met een account dat de API-referentie mag bekijken.", "apiDocs.unavailable": "De API-referentie kan niet worden geladen. Probeer het opnieuw.", "apiDocs.partial": "Deze referentie omvat geselecteerde API’s. Ontbrekende bewerkingen betekenen niet dat toegang is geweigerd.", - "apiDocs.empty": "Voor de huidige sessie zijn in deze referentie geen API-bewerkingen beschikbaar." + "apiDocs.empty": "Voor de huidige sessie zijn in deze referentie geen API-bewerkingen beschikbaar.", + "system.observations.title": "Observatiepijplijn", + "system.observations.inactive": "Niet in gebruik: geen camera heeft in deze run observatiebewijs gemaakt.", + "system.observations.accepted": "Geaccepteerde observaties", + "system.observations.dropped": "Verworpen (wachtrij vol / budget / uitgeschakeld / niet gevolgd)", + "system.observations.backlog": "Bewijsachterstand", + "system.observations.highWater": "Hoogste achterstand", + "system.observations.failures": "Bewijsfouten", + "system.observations.retained": "Bewaard bewijs", + "system.observations.budgetExhausted": "Camera's boven het bewijsbudget", + "system.observations.oldestRecoverable": "Oudste herstelbare cursor", + "system.observations.publications": "Publicaties in behandeling / mislukt / verlopen", + "system.observations.relayLag": "Relayvertraging (seconden)", + "detections.classification.title": "Geclassificeerde detecties", + "detections.classification.detector": "Detector: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "via {classifier}", + "detections.classification.ownerConfirmed": "Bevestigd door eigenaar", + "detections.classification.unclassified": "Niet geclassificeerd", + "detections.classification.observationLink": "Waarneming" } diff --git a/web/public/locales/pl.json b/web/public/locales/pl.json index dd0daef67..db91f4a28 100644 --- a/web/public/locales/pl.json +++ b/web/public/locales/pl.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Zaloguj się na konto z uprawnieniami do przeglądania dokumentacji API.", "apiDocs.unavailable": "Nie można załadować dokumentacji API. Spróbuj ponownie.", "apiDocs.partial": "Ta dokumentacja obejmuje wybrane API. Brak operacji nie oznacza odmowy dostępu.", - "apiDocs.empty": "W tej dokumentacji nie ma dostępnych operacji API dla bieżącej sesji." + "apiDocs.empty": "W tej dokumentacji nie ma dostępnych operacji API dla bieżącej sesji.", + "system.observations.title": "Potok obserwacji", + "system.observations.inactive": "Nieużywany: żadna kamera nie utworzyła dowodów obserwacji w tym uruchomieniu.", + "system.observations.accepted": "Zaakceptowane obserwacje", + "system.observations.dropped": "Odrzucone (kolejka pełna / budżet / wyłączone / nieśledzone)", + "system.observations.backlog": "Zaległe dowody", + "system.observations.highWater": "Szczyt zaległości", + "system.observations.failures": "Błędy dowodów", + "system.observations.retained": "Przechowywane dowody", + "system.observations.budgetExhausted": "Kamery powyżej budżetu dowodów", + "system.observations.oldestRecoverable": "Najstarszy odzyskiwalny kursor", + "system.observations.publications": "Publikacje oczekujące / nieudane / wygasłe", + "system.observations.relayLag": "Opóźnienie przekaźnika (sekundy)", + "detections.classification.title": "Sklasyfikowane wykrycia", + "detections.classification.detector": "Detektor: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "przez {classifier}", + "detections.classification.ownerConfirmed": "Potwierdzone przez właściciela", + "detections.classification.unclassified": "Niesklasyfikowane", + "detections.classification.observationLink": "Obserwacja" } diff --git a/web/public/locales/pt-BR.json b/web/public/locales/pt-BR.json index 6a1af351a..429531f80 100644 --- a/web/public/locales/pt-BR.json +++ b/web/public/locales/pt-BR.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Entre com uma conta autorizada a visualizar a referência da API.", "apiDocs.unavailable": "Não foi possível carregar a referência da API. Tente novamente.", "apiDocs.partial": "Esta referência abrange algumas APIs. Operações ausentes não indicam acesso negado.", - "apiDocs.empty": "Não há operações da API disponíveis nesta referência para a sessão atual." + "apiDocs.empty": "Não há operações da API disponíveis nesta referência para a sessão atual.", + "system.observations.title": "Pipeline de observações", + "system.observations.inactive": "Sem uso: nenhuma câmera gerou evidências de observação nesta execução.", + "system.observations.accepted": "Observações aceitas", + "system.observations.dropped": "Descartadas (fila cheia / orçamento / desativado / sem rastreio)", + "system.observations.backlog": "Evidências pendentes", + "system.observations.highWater": "Pico de pendências", + "system.observations.failures": "Falhas de evidências", + "system.observations.retained": "Evidências retidas", + "system.observations.budgetExhausted": "Câmeras acima do orçamento de evidências", + "system.observations.oldestRecoverable": "Cursor recuperável mais antigo", + "system.observations.publications": "Publicações pendentes / com falha / expiradas", + "system.observations.relayLag": "Atraso do relay (segundos)", + "detections.classification.title": "Detecções classificadas", + "detections.classification.detector": "Detector: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "via {classifier}", + "detections.classification.ownerConfirmed": "Confirmado pelo proprietário", + "detections.classification.unclassified": "Não classificado", + "detections.classification.observationLink": "Observação" } diff --git a/web/public/locales/pt-PT.json b/web/public/locales/pt-PT.json index 2104f4a13..c53557caf 100644 --- a/web/public/locales/pt-PT.json +++ b/web/public/locales/pt-PT.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Inicie sessão com uma conta autorizada a consultar a referência da API.", "apiDocs.unavailable": "Não foi possível carregar a referência da API. Tente novamente.", "apiDocs.partial": "Esta referência abrange algumas APIs. Operações ausentes não indicam acesso negado.", - "apiDocs.empty": "Não existem operações da API disponíveis nesta referência para a sessão atual." + "apiDocs.empty": "Não existem operações da API disponíveis nesta referência para a sessão atual.", + "system.observations.title": "Pipeline de observações", + "system.observations.inactive": "Sem utilização: nenhuma câmara gerou evidências de observação nesta execução.", + "system.observations.accepted": "Observações aceites", + "system.observations.dropped": "Descartadas (fila cheia / orçamento / desativado / sem registo)", + "system.observations.backlog": "Evidências pendentes", + "system.observations.highWater": "Pico de pendências", + "system.observations.failures": "Falhas de evidências", + "system.observations.retained": "Evidências retidas", + "system.observations.budgetExhausted": "Câmaras acima do orçamento de evidências", + "system.observations.oldestRecoverable": "Cursor recuperável mais antigo", + "system.observations.publications": "Publicações pendentes / falhadas / expiradas", + "system.observations.relayLag": "Atraso do relay (segundos)", + "detections.classification.title": "Deteções classificadas", + "detections.classification.detector": "Detetor: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "via {classifier}", + "detections.classification.ownerConfirmed": "Confirmado pelo proprietário", + "detections.classification.unclassified": "Não classificado", + "detections.classification.observationLink": "Observação" } diff --git a/web/public/locales/ru.json b/web/public/locales/ru.json index 245ba3930..64d5fe6e9 100644 --- a/web/public/locales/ru.json +++ b/web/public/locales/ru.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Войдите с учётной записью, которой разрешён просмотр справочника API.", "apiDocs.unavailable": "Не удалось загрузить справочник API. Попробуйте ещё раз.", "apiDocs.partial": "Этот справочник охватывает выбранные API. Отсутствие операций не означает отказ в доступе.", - "apiDocs.empty": "В этом справочнике нет доступных операций API для текущего сеанса." + "apiDocs.empty": "В этом справочнике нет доступных операций API для текущего сеанса.", + "system.observations.title": "Конвейер наблюдений", + "system.observations.inactive": "Не используется: ни одна камера не создала доказательств наблюдения в этом запуске.", + "system.observations.accepted": "Принятые наблюдения", + "system.observations.dropped": "Отброшенные (очередь полна / бюджет / отключено / не отслежено)", + "system.observations.backlog": "Очередь доказательств", + "system.observations.highWater": "Пик очереди", + "system.observations.failures": "Ошибки доказательств", + "system.observations.retained": "Сохранённые доказательства", + "system.observations.budgetExhausted": "Камеры сверх бюджета доказательств", + "system.observations.oldestRecoverable": "Самый старый восстановимый курсор", + "system.observations.publications": "Публикации ожидают / с ошибкой / истекли", + "system.observations.relayLag": "Задержка ретранслятора (секунды)", + "detections.classification.title": "Классифицированные обнаружения", + "detections.classification.detector": "Детектор: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "через {classifier}", + "detections.classification.ownerConfirmed": "Подтверждено владельцем", + "detections.classification.unclassified": "Не классифицировано", + "detections.classification.observationLink": "Наблюдение" } diff --git a/web/public/locales/tr.json b/web/public/locales/tr.json index 0f9bc54af..d1c707ec6 100644 --- a/web/public/locales/tr.json +++ b/web/public/locales/tr.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "API başvurusunu görüntüleme izni olan bir hesapla giriş yapın.", "apiDocs.unavailable": "API başvurusu yüklenemedi. Tekrar deneyin.", "apiDocs.partial": "Bu başvuru seçili API’leri kapsar. Eksik işlemler erişimin reddedildiği anlamına gelmez.", - "apiDocs.empty": "Mevcut oturum için bu başvuruda kullanılabilir API işlemi yok." + "apiDocs.empty": "Mevcut oturum için bu başvuruda kullanılabilir API işlemi yok.", + "system.observations.title": "Gözlem hattı", + "system.observations.inactive": "Kullanılmıyor: bu çalıştırmada hiçbir kamera gözlem kanıtı üretmedi.", + "system.observations.accepted": "Kabul edilen gözlemler", + "system.observations.dropped": "Atılan (kuyruk dolu / bütçe / devre dışı / izlenmeyen)", + "system.observations.backlog": "Kanıt birikimi", + "system.observations.highWater": "En yüksek birikim", + "system.observations.failures": "Kanıt hataları", + "system.observations.retained": "Saklanan kanıtlar", + "system.observations.budgetExhausted": "Kanıt bütçesini aşan kameralar", + "system.observations.oldestRecoverable": "Kurtarılabilir en eski imleç", + "system.observations.publications": "Yayınlar bekleyen / başarısız / süresi dolmuş", + "system.observations.relayLag": "Aktarma gecikmesi (saniye)", + "detections.classification.title": "Sınıflandırılmış algılamalar", + "detections.classification.detector": "Algılayıcı: {label} (%{confidence})", + "detections.classification.classified": "{label} (%{confidence})", + "detections.classification.source": "{classifier} aracılığıyla", + "detections.classification.ownerConfirmed": "Sahibi tarafından onaylandı", + "detections.classification.unclassified": "Sınıflandırılmamış", + "detections.classification.observationLink": "Gözlem" } diff --git a/web/public/locales/uk.json b/web/public/locales/uk.json index 74f4aeb1c..c8eac5cd4 100644 --- a/web/public/locales/uk.json +++ b/web/public/locales/uk.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "Увійдіть з обліковим записом, якому дозволено переглядати довідник API.", "apiDocs.unavailable": "Не вдалося завантажити довідник API. Спробуйте ще раз.", "apiDocs.partial": "Цей довідник охоплює вибрані API. Відсутність операцій не означає відмову в доступі.", - "apiDocs.empty": "У цьому довіднику немає доступних операцій API для поточного сеансу." + "apiDocs.empty": "У цьому довіднику немає доступних операцій API для поточного сеансу.", + "system.observations.title": "Конвеєр спостережень", + "system.observations.inactive": "Не використовується: жодна камера не створила доказів спостереження в цьому запуску.", + "system.observations.accepted": "Прийняті спостереження", + "system.observations.dropped": "Відкинуті (черга повна / бюджет / вимкнено / не відстежено)", + "system.observations.backlog": "Черга доказів", + "system.observations.highWater": "Пік черги", + "system.observations.failures": "Помилки доказів", + "system.observations.retained": "Збережені докази", + "system.observations.budgetExhausted": "Камери понад бюджет доказів", + "system.observations.oldestRecoverable": "Найстаріший відновлюваний курсор", + "system.observations.publications": "Публікації в очікуванні / невдалі / прострочені", + "system.observations.relayLag": "Затримка ретранслятора (секунди)", + "detections.classification.title": "Класифіковані виявлення", + "detections.classification.detector": "Детектор: {label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "через {classifier}", + "detections.classification.ownerConfirmed": "Підтверджено власником", + "detections.classification.unclassified": "Не класифіковано", + "detections.classification.observationLink": "Спостереження" } diff --git a/web/public/locales/zh-TW.json b/web/public/locales/zh-TW.json index 0ee63b3f7..6f6a1eed9 100644 --- a/web/public/locales/zh-TW.json +++ b/web/public/locales/zh-TW.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "請使用有權檢視 API 參考的帳戶登入。", "apiDocs.unavailable": "無法載入 API 參考。請重試。", "apiDocs.partial": "此參考僅涵蓋部分 API。缺少某項操作並不表示存取遭到拒絕。", - "apiDocs.empty": "目前工作階段在此參考中沒有可用的 API 操作。" + "apiDocs.empty": "目前工作階段在此參考中沒有可用的 API 操作。", + "system.observations.title": "觀測管線", + "system.observations.inactive": "未使用:本次執行中沒有攝影機產生觀測證據。", + "system.observations.accepted": "已接受的觀測", + "system.observations.dropped": "已捨棄(佇列已滿 / 預算 / 已停用 / 未追蹤)", + "system.observations.backlog": "證據積壓", + "system.observations.highWater": "積壓峰值", + "system.observations.failures": "證據失敗", + "system.observations.retained": "保留的證據", + "system.observations.budgetExhausted": "超出證據預算的攝影機", + "system.observations.oldestRecoverable": "最早可復原游標", + "system.observations.publications": "發布 待處理 / 失敗 / 已過期", + "system.observations.relayLag": "中繼延遲(秒)", + "detections.classification.title": "已分類的偵測", + "detections.classification.detector": "偵測器:{label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "來源:{classifier}", + "detections.classification.ownerConfirmed": "擁有者已確認", + "detections.classification.unclassified": "未分類", + "detections.classification.observationLink": "觀測" } diff --git a/web/public/locales/zh.json b/web/public/locales/zh.json index f09e2eee0..fb588934f 100644 --- a/web/public/locales/zh.json +++ b/web/public/locales/zh.json @@ -2725,5 +2725,24 @@ "apiDocs.denied": "请使用有权查看 API 参考的账户登录。", "apiDocs.unavailable": "无法加载 API 参考。请重试。", "apiDocs.partial": "此参考仅涵盖部分 API。缺少某项操作并不表示访问被拒绝。", - "apiDocs.empty": "当前会话在此参考中没有可用的 API 操作。" + "apiDocs.empty": "当前会话在此参考中没有可用的 API 操作。", + "system.observations.title": "观测管道", + "system.observations.inactive": "未使用:本次运行中没有摄像头生成观测证据。", + "system.observations.accepted": "已接受的观测", + "system.observations.dropped": "已丢弃(队列已满 / 预算 / 已禁用 / 未跟踪)", + "system.observations.backlog": "证据积压", + "system.observations.highWater": "积压峰值", + "system.observations.failures": "证据失败", + "system.observations.retained": "保留的证据", + "system.observations.budgetExhausted": "超出证据预算的摄像头", + "system.observations.oldestRecoverable": "最早可恢复游标", + "system.observations.publications": "发布 待处理 / 失败 / 已过期", + "system.observations.relayLag": "中继延迟(秒)", + "detections.classification.title": "已分类的检测", + "detections.classification.detector": "检测器:{label} ({confidence}%)", + "detections.classification.classified": "{label} ({confidence}%)", + "detections.classification.source": "来源:{classifier}", + "detections.classification.ownerConfirmed": "所有者已确认", + "detections.classification.unclassified": "未分类", + "detections.classification.observationLink": "观测" } diff --git a/web/tests/apiTokenPolicy.spec.js b/web/tests/apiTokenPolicy.spec.js index f6a4599dd..f5433528f 100644 --- a/web/tests/apiTokenPolicy.spec.js +++ b/web/tests/apiTokenPolicy.spec.js @@ -28,6 +28,8 @@ describe('scoped API token UI helpers', () => { { key: 'lpr.search', enforced: true }, { key: 'lpr.export', enforced: true }, { key: 'lpr.delete', enforced: true }, + // INT_01 P2 bit 19: enforced by the annotation PUT (L14). + { key: 'observations.annotate', enforced: true, category: 'Integrations' }, ]; test('offers the full enforced server catalog, including read and administration actions', () => { @@ -48,9 +50,19 @@ describe('scoped API token UI helpers', () => { 'lpr.search', 'lpr.export', 'lpr.delete', + 'observations.annotate', ]); }); + test('offers observations.annotate only while the server reports it enforced', () => { + expect(selectableTokenActions(actions).map((action) => action.key).slice(-1)) + .toEqual(['observations.annotate']); + const unenforced = actions.map((action) => (action.key === 'observations.annotate' + ? { ...action, enforced: false } : action)); + expect(selectableTokenActions(unenforced).map((action) => action.key)) + .not.toContain('observations.annotate'); + }); + test('requires explicit server enforcement instead of assuming old permissions are safe', () => { const reported = [ { key: 'recordings.export', enforced: true }, diff --git a/web/tests/detectionClassifications.spec.js b/web/tests/detectionClassifications.spec.js new file mode 100644 index 000000000..d7c97fc65 --- /dev/null +++ b/web/tests/detectionClassifications.spec.js @@ -0,0 +1,131 @@ +const fs = require('fs'); +const path = require('path'); +const { buildSync } = require('esbuild'); + +// INT_01 L15: the minimal classification display next to detections. +function loadJsx(relativePath) { + const output = buildSync({ + absWorkingDir: path.resolve(__dirname, '..'), + entryPoints: [relativePath], + bundle: true, + format: 'cjs', + platform: 'node', + alias: { + react: 'preact/compat', + 'react-dom': 'preact/compat', + 'react/jsx-runtime': 'preact/jsx-runtime', + }, + write: false, + logLevel: 'silent', + }).outputFiles[0].text; + const loaded = { exports: {} }; + Function('module', 'exports', 'require', output)(loaded, loaded.exports, require); + return loaded.exports; +} + +const { + classifiedDetections, + detectionClassificationView, + detectionOverlayLabel, + renderDetectionClassifications, +} = loadJsx('js/components/preact/DetectionClassifications.tsx'); + +const english = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../public/locales/en.json'), 'utf8')); +// The production t() template rule, against the real English catalog. +const t = (key, params = {}) => { + expect(english).toHaveProperty([key]); + return english[key].replace(/\{([^}]+)\}/g, (_, name) => String(params[name.trim()] ?? '')); +}; + +// Flatten a Preact vnode tree to its text, and collect link targets. +function textOf(node) { + if (node === null || node === undefined || typeof node === 'boolean') return ''; + if (typeof node === 'string' || typeof node === 'number') return String(node); + if (Array.isArray(node)) return node.map(textOf).join(''); + return textOf(node.props?.children); +} +function linksOf(node, out = []) { + if (!node || typeof node !== 'object') return out; + if (Array.isArray(node)) { node.forEach((child) => linksOf(child, out)); return out; } + if (node.type === 'a') out.push(node.props.href); + linksOf(node.props?.children, out); + return out; +} + +const OBSERVATION_ID = '44444444-4444-4444-8444-444444444444'; +const base = { label: 'bird', confidence: 0.82, x: 0.1, y: 0.2, width: 0.3, height: 0.4, + timestamp: 1794000195, end_timestamp: 1794000195 }; +const annotation = (overrides) => ({ + key: 'yawamf/species', writer_namespace: 'yawamf', classifier: 'yawamf', + model_version: 'convnext-v2-2026.10', label: 'Black-capped Chickadee', taxonomy_id: null, + confidence: 0.93, classified_at: '2026-11-08T14:03:15.002Z', + input_asset_id: '55555555-5555-4555-8555-555555555555', input_revision: 2, kind: 'automated', + annotation_revision: 1, created_at: '2026-11-08T14:03:15.100Z', updated_at: '2026-11-08T14:03:15.100Z', + ...overrides, +}); +const automated = { ...base, observation: { id: OBSERVATION_ID, owner_confirmed: false, + classification: annotation() } }; +const owner = { ...base, observation: { id: OBSERVATION_ID, owner_confirmed: true, + classification: annotation({ label: 'Carolina Chickadee', confidence: 1, kind: 'owner_confirmed', + model_version: null }) } }; + +describe('detection classification display (INT_01 L15)', () => { + test('a detection without an observation has no classification view', () => { + expect(detectionClassificationView(base)).toBeNull(); + expect(detectionClassificationView({ ...base, observation: null })).toBeNull(); + expect(detectionClassificationView({ ...base, observation: { owner_confirmed: false } })).toBeNull(); + expect(detectionOverlayLabel(base)).toBe('bird (82%)'); + expect(classifiedDetections([base])).toEqual([]); + }); + + test('keeps the detector label and confidence beside the classification', () => { + expect(detectionClassificationView(automated)).toEqual({ + observationId: OBSERVATION_ID, + observationUrl: `/api/observations/${OBSERVATION_ID}`, + detectorLabel: 'bird', + detectorConfidence: 82, + classifiedLabel: 'Black-capped Chickadee', + classifiedConfidence: 93, + classifier: 'yawamf', + ownerConfirmed: false, + }); + expect(detectionOverlayLabel(automated)).toBe('bird (82%) · Black-capped Chickadee (93%)'); + + const tree = renderDetectionClassifications([automated], t); + const text = textOf(tree); + expect(text).toContain('Detector: bird (82%)'); + expect(text).toContain('Black-capped Chickadee (93%)'); + expect(text).toContain('yawamf'); + expect(text).not.toContain('Owner confirmed'); + expect(linksOf(tree)).toEqual([`/api/observations/${OBSERVATION_ID}`]); + }); + + test('an owner decision shows the owner label with a badge, never the automated one', () => { + const view = detectionClassificationView(owner); + expect(view.ownerConfirmed).toBe(true); + expect(view.classifiedLabel).toBe('Carolina Chickadee'); + const text = textOf(renderDetectionClassifications([owner], t)); + expect(text).toContain('Detector: bird (82%)'); + expect(text).toContain('Carolina Chickadee (100%)'); + expect(text).toContain('Owner confirmed'); + expect(text).not.toContain('Black-capped Chickadee'); + }); + + test('a linked but unclassified observation still links back', () => { + const linked = { ...base, observation: { id: OBSERVATION_ID, owner_confirmed: false, classification: null } }; + const view = detectionClassificationView(linked); + expect(view.classifiedLabel).toBeNull(); + expect(detectionOverlayLabel(linked)).toBe('bird (82%)'); + const tree = renderDetectionClassifications([linked], t); + expect(textOf(tree)).toContain('Not classified'); + expect(linksOf(tree)).toEqual([`/api/observations/${OBSERVATION_ID}`]); + }); + + test('lists each observation once and renders nothing without links', () => { + const other = { ...automated, timestamp: base.timestamp + 1 }; + expect(classifiedDetections([automated, base, other, owner]).map((d) => d.observationId)) + .toEqual([OBSERVATION_ID]); + expect(renderDetectionClassifications([base], t)).toBeNull(); + expect(renderDetectionClassifications([], t)).toBeNull(); + }); +}); diff --git a/web/tests/observationEvidenceSettings.spec.js b/web/tests/observationEvidenceSettings.spec.js new file mode 100644 index 000000000..31a221a75 --- /dev/null +++ b/web/tests/observationEvidenceSettings.spec.js @@ -0,0 +1,95 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { + DEFAULT_OBSERVATION_FORM_FIELDS, + observationFormFields, + observationPayloadFields, + validateObservationForm, +} from '../js/utils/observation-evidence.ts'; + +/* INT_01 L9: per-camera observation evidence opt-in (contract section 13). */ +describe('observation evidence settings', () => { + test('defaults to off, every label, 7 days and 2 GiB', () => { + expect(DEFAULT_OBSERVATION_FORM_FIELDS).toEqual({ + observationEvidenceEnabled: false, + observationLabels: '', + observationMaxAgeDays: 7, + observationMaxMib: 2048, + }); + // An older server that omits the fields maps to the same defaults. + expect(observationFormFields({})).toEqual(DEFAULT_OBSERVATION_FORM_FIELDS); + expect(observationPayloadFields(DEFAULT_OBSERVATION_FORM_FIELDS)).toEqual({ + observation_evidence_enabled: false, + observation_labels: '', + observation_max_age_days: 7, + observation_max_bytes: 2147483648, + }); + }); + + test('an enabled toggle and budgets persist through the save payload', () => { + const form = observationFormFields({ + observation_evidence_enabled: true, + observation_labels: 'bird,squirrel', + observation_max_age_days: 14, + observation_max_bytes: 134217728, + }); + expect(form).toEqual({ + observationEvidenceEnabled: true, + observationLabels: 'bird,squirrel', + observationMaxAgeDays: 14, + observationMaxMib: 128, + }); + expect(observationPayloadFields({ ...form, observationLabels: ' bird , squirrel ' })).toEqual({ + observation_evidence_enabled: true, + observation_labels: 'bird,squirrel', + observation_max_age_days: 14, + observation_max_bytes: 134217728, + }); + // Edited numeric inputs hold strings. + expect(observationPayloadFields({ ...form, observationMaxAgeDays: '90', observationMaxMib: '1048576' })) + .toMatchObject({ observation_max_age_days: 90, observation_max_bytes: 1099511627776 }); + }); + + test('validates the server bounds before saving', () => { + expect(validateObservationForm(DEFAULT_OBSERVATION_FORM_FIELDS)).toBeNull(); + expect(validateObservationForm({ ...DEFAULT_OBSERVATION_FORM_FIELDS, observationMaxAgeDays: 0 })) + .toBe('streamsConfig.observationMaxAgeInvalid'); + expect(validateObservationForm({ ...DEFAULT_OBSERVATION_FORM_FIELDS, observationMaxAgeDays: 91 })) + .toBe('streamsConfig.observationMaxAgeInvalid'); + expect(validateObservationForm({ ...DEFAULT_OBSERVATION_FORM_FIELDS, observationMaxMib: 63 })) + .toBe('streamsConfig.observationMaxSizeInvalid'); + expect(validateObservationForm({ ...DEFAULT_OBSERVATION_FORM_FIELDS, observationMaxMib: 1048577 })) + .toBe('streamsConfig.observationMaxSizeInvalid'); + expect(validateObservationForm({ ...DEFAULT_OBSERVATION_FORM_FIELDS, observationLabels: 'bird,,cat' })) + .toBe('streamsConfig.observationLabelsInvalid'); + expect(validateObservationForm({ + ...DEFAULT_OBSERVATION_FORM_FIELDS, + observationLabels: Array.from({ length: 17 }, (_, i) => `l${i}`).join(','), + })).toBe('streamsConfig.observationLabelsInvalid'); + expect(validateObservationForm({ ...DEFAULT_OBSERVATION_FORM_FIELDS, observationLabels: 'x'.repeat(32) })) + .toBe('streamsConfig.observationLabelsInvalid'); + }); + + test('the stream editor wires the section, defaults and payload', () => { + const read = file => fs.readFileSync(path.resolve(__dirname, '../js/components/preact', file), 'utf8'); + const view = read('StreamsView.tsx'); + expect(view).toContain('...observationFormFields(stream)'); + expect(view).toContain('...observationPayloadFields(currentStream)'); + expect(view.match(/\.\.\.DEFAULT_OBSERVATION_FORM_FIELDS/g)?.length).toBeGreaterThanOrEqual(2); + const modal = read('StreamConfigModal.tsx'); + expect(modal).toContain("t('streamsConfig.observationEvidence')"); + expect(modal).toContain("t('streamsConfig.observationEvidenceDetectorRequired')"); + expect(modal).toContain('name="observationEvidenceEnabled"'); + expect(modal).toContain('checked={!!currentStream.observationEvidenceEnabled}'); + }); + + test('the English catalog carries the section strings', () => { + const en = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../public/locales/en.json'), 'utf8')); + for (const key of ['observationEvidence', 'observationEvidenceHelp', 'observationEvidenceDetectorRequired', + 'observationEvidenceEnable', 'observationLabels', 'observationLabelsHelp', 'observationMaxAgeDays', + 'observationMaxSizeMib', 'observationBudgetHelp', 'observationMaxAgeInvalid', + 'observationMaxSizeInvalid', 'observationLabelsInvalid']) { + expect(typeof en[`streamsConfig.${key}`]).toBe('string'); + } + }); +}); diff --git a/web/tests/observationPipeline.spec.js b/web/tests/observationPipeline.spec.js new file mode 100644 index 000000000..cd51838e0 --- /dev/null +++ b/web/tests/observationPipeline.spec.js @@ -0,0 +1,58 @@ +const path = require('path'); +const { buildSync } = require('esbuild'); + +function loadJsx(relativePath) { + const output = buildSync({ + absWorkingDir: path.resolve(__dirname, '..'), + entryPoints: [relativePath], + bundle: true, + format: 'cjs', + platform: 'node', + alias: { + react: 'preact/compat', + 'react-dom': 'preact/compat', + 'react/jsx-runtime': 'preact/jsx-runtime', + }, + write: false, + logLevel: 'silent', + }).outputFiles[0].text; + const loaded = { exports: {} }; + Function('module', 'exports', 'require', output)(loaded, loaded.exports, require); + return loaded.exports; +} + +const { + formatLag, + readObservationPipeline, + totalDropped, + totalFailures, +} = loadJsx('js/components/preact/system/ObservationPipeline.tsx'); + +describe('Observation pipeline diagnostics helpers (INT_01 L10)', () => { + test('ignores a missing or malformed observation_pipeline member', () => { + expect(readObservationPipeline(undefined)).toBeNull(); + expect(readObservationPipeline({})).toBeNull(); + expect(readObservationPipeline({ observation_pipeline: { accepted: 3 } })).toBeNull(); + const pipeline = { active: false, accepted: 0 }; + expect(readObservationPipeline({ observation_pipeline: pipeline })).toBe(pipeline); + }); + + test('sums drops by reason and evidence failures, treating junk as zero', () => { + const pipeline = { + active: true, + dropped: { queue_full: 3, budget_exhausted: 2, disabled: 1, untracked: 4 }, + evidence: { failures: { encode_failed: 1, write_failed: 2, db_error: -5, interrupted: 'x' } }, + }; + expect(totalDropped(pipeline)).toBe(10); + expect(totalFailures(pipeline)).toBe(3); + expect(totalDropped({ active: true })).toBe(0); + expect(totalFailures({ active: true })).toBe(0); + }); + + test('formats relay lag as whole seconds', () => { + expect(formatLag(undefined)).toBe('0'); + expect(formatLag(0.4)).toBe('0'); + expect(formatLag(59.6)).toBe('60'); + expect(formatLag(-3)).toBe('0'); + }); +});